Commit 4f01ac5b authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 213ce780
...@@ -124,8 +124,10 @@ export default { ...@@ -124,8 +124,10 @@ export default {
:diff-viewer-mode="diffViewerMode" :diff-viewer-mode="diffViewerMode"
:new-path="diffFile.new_path" :new-path="diffFile.new_path"
:new-sha="diffFile.diff_refs.head_sha" :new-sha="diffFile.diff_refs.head_sha"
:new-size="diffFile.new_size"
:old-path="diffFile.old_path" :old-path="diffFile.old_path"
:old-sha="diffFile.diff_refs.base_sha" :old-sha="diffFile.diff_refs.base_sha"
:old-size="diffFile.old_size"
:file-hash="diffFileHash" :file-hash="diffFileHash"
:project-path="projectPath" :project-path="projectPath"
:a-mode="diffFile.a_mode" :a-mode="diffFile.a_mode"
......
...@@ -26,8 +26,11 @@ export default (resolvers = {}, config = {}) => { ...@@ -26,8 +26,11 @@ export default (resolvers = {}, config = {}) => {
createUploadLink(httpOptions), createUploadLink(httpOptions),
new BatchHttpLink(httpOptions), new BatchHttpLink(httpOptions),
), ),
cache: new InMemoryCache({ ...config.cacheConfig, freezeResults: true }), cache: new InMemoryCache({
...config.cacheConfig,
freezeResults: config.assumeImmutableResults,
}),
resolvers, resolvers,
assumeImmutableResults: true, assumeImmutableResults: config.assumeImmutableResults,
}); });
}; };
...@@ -187,8 +187,11 @@ export default { ...@@ -187,8 +187,11 @@ export default {
firstDashboard() { firstDashboard() {
return this.allDashboards[0] || {}; return this.allDashboards[0] || {};
}, },
selectedDashboard() {
return this.allDashboards.find(d => d.path === this.currentDashboard) || this.firstDashboard;
},
selectedDashboardText() { selectedDashboardText() {
return this.currentDashboard || this.firstDashboard.display_name; return this.selectedDashboard.display_name;
}, },
showRearrangePanelsBtn() { showRearrangePanelsBtn() {
return !this.showEmptyState && this.rearrangePanelsAvailable; return !this.showEmptyState && this.rearrangePanelsAvailable;
...@@ -199,6 +202,14 @@ export default { ...@@ -199,6 +202,14 @@ export default {
alertWidgetAvailable() { alertWidgetAvailable() {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint; return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint;
}, },
hasHeaderButtons() {
return (
this.addingMetricsAvailable ||
this.showRearrangePanelsBtn ||
this.selectedDashboard.can_edit ||
this.externalDashboardUrl.length
);
},
}, },
created() { created() {
this.setEndpoints({ this.setEndpoints({
...@@ -390,7 +401,7 @@ export default { ...@@ -390,7 +401,7 @@ export default {
</template> </template>
<gl-form-group <gl-form-group
v-if="addingMetricsAvailable || showRearrangePanelsBtn || externalDashboardUrl.length" v-if="hasHeaderButtons"
label-for="prometheus-graphs-dropdown-buttons" label-for="prometheus-graphs-dropdown-buttons"
class="dropdown-buttons col-md d-md-flex col-lg d-lg-flex align-items-end" class="dropdown-buttons col-md d-md-flex col-lg d-lg-flex align-items-end"
> >
...@@ -437,6 +448,14 @@ export default { ...@@ -437,6 +448,14 @@ export default {
</div> </div>
</gl-modal> </gl-modal>
<gl-button
v-if="selectedDashboard.can_edit"
class="mt-1 js-edit-link"
:href="selectedDashboard.project_blob_path"
>
{{ __('Edit dashboard') }}
</gl-button>
<gl-button <gl-button
v-if="externalDashboardUrl.length" v-if="externalDashboardUrl.length"
class="mt-1 js-external-dashboard-link" class="mt-1 js-external-dashboard-link"
......
...@@ -6,8 +6,6 @@ import initSettingsPanels from '~/settings_panels'; ...@@ -6,8 +6,6 @@ import initSettingsPanels from '~/settings_panels';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
mountErrorTrackingForm(); mountErrorTrackingForm();
mountOperationSettings(); mountOperationSettings();
if (gon.features.gfmGrafanaIntegration) { mountGrafanaIntegration();
mountGrafanaIntegration();
}
initSettingsPanels(); initSettingsPanels();
}); });
...@@ -23,6 +23,11 @@ export default { ...@@ -23,6 +23,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
newSize: {
type: Number,
required: false,
default: 0,
},
oldPath: { oldPath: {
type: String, type: String,
required: true, required: true,
...@@ -31,6 +36,11 @@ export default { ...@@ -31,6 +36,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
oldSize: {
type: Number,
required: false,
default: 0,
},
projectPath: { projectPath: {
type: String, type: String,
required: false, required: false,
...@@ -85,6 +95,8 @@ export default { ...@@ -85,6 +95,8 @@ export default {
:diff-mode="diffMode" :diff-mode="diffMode"
:new-path="fullNewPath" :new-path="fullNewPath"
:old-path="fullOldPath" :old-path="fullOldPath"
:old-size="oldSize"
:new-size="newSize"
:project-path="projectPath" :project-path="projectPath"
:a-mode="aMode" :a-mode="aMode"
:b-mode="bMode" :b-mode="bMode"
......
...@@ -14,6 +14,16 @@ export default { ...@@ -14,6 +14,16 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
newSize: {
type: Number,
required: false,
default: 0,
},
oldSize: {
type: Number,
required: false,
default: 0,
},
}, },
}; };
</script> </script>
...@@ -22,12 +32,14 @@ export default { ...@@ -22,12 +32,14 @@ export default {
<div class="two-up view d-flex"> <div class="two-up view d-flex">
<image-viewer <image-viewer
:path="oldPath" :path="oldPath"
:file-size="oldSize"
:render-info="true" :render-info="true"
inner-css-classes="frame deleted" inner-css-classes="frame deleted"
class="wrap w-50" class="wrap w-50"
/> />
<image-viewer <image-viewer
:path="newPath" :path="newPath"
:file-size="newSize"
:render-info="true" :render-info="true"
:inner-css-classes="['frame', 'added']" :inner-css-classes="['frame', 'added']"
class="wrap w-50" class="wrap w-50"
......
...@@ -22,6 +22,16 @@ export default { ...@@ -22,6 +22,16 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
newSize: {
type: Number,
required: false,
default: 0,
},
oldSize: {
type: Number,
required: false,
default: 0,
},
}, },
data() { data() {
return { return {
......
...@@ -20,11 +20,11 @@ class ApplicationController < ActionController::Base ...@@ -20,11 +20,11 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user!, except: [:route_not_found] before_action :authenticate_user!, except: [:route_not_found]
before_action :enforce_terms!, if: :should_enforce_terms? before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket! before_action :validate_user_service_ticket!
before_action :check_password_expiration before_action :check_password_expiration, if: :html_request?
before_action :ldap_security_check before_action :ldap_security_check
before_action :sentry_context before_action :sentry_context
before_action :default_headers before_action :default_headers
before_action :add_gon_variables, unless: [:peek_request?, :json_request?] before_action :add_gon_variables, if: :html_request?
before_action :configure_permitted_parameters, if: :devise_controller? before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller? before_action :require_email, unless: :devise_controller?
before_action :active_user_check, unless: :devise_controller? before_action :active_user_check, unless: :devise_controller?
...@@ -455,8 +455,8 @@ class ApplicationController < ActionController::Base ...@@ -455,8 +455,8 @@ class ApplicationController < ActionController::Base
response.headers['Page-Title'] = URI.escape(page_title('GitLab')) response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end end
def peek_request? def html_request?
request.path.start_with?('/-/peek') request.format.html?
end end
def json_request? def json_request?
...@@ -466,7 +466,7 @@ class ApplicationController < ActionController::Base ...@@ -466,7 +466,7 @@ class ApplicationController < ActionController::Base
def should_enforce_terms? def should_enforce_terms?
return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms return false unless Gitlab::CurrentSettings.current_application_settings.enforce_terms
!(peek_request? || devise_controller?) html_request? && !devise_controller?
end end
def set_usage_stats_consent_flag def set_usage_stats_consent_flag
......
...@@ -4,15 +4,18 @@ module ConfirmEmailWarning ...@@ -4,15 +4,18 @@ module ConfirmEmailWarning
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
before_action :set_confirm_warning, if: -> { Feature.enabled?(:soft_email_confirmation) } before_action :set_confirm_warning, if: :show_confirm_warning?
end end
protected protected
def show_confirm_warning?
html_request? && request.get? && Feature.enabled?(:soft_email_confirmation)
end
def set_confirm_warning def set_confirm_warning
return unless current_user return unless current_user
return if current_user.confirmed? return if current_user.confirmed?
return if peek_request? || json_request? || !request.get?
email = current_user.unconfirmed_email || current_user.email email = current_user.unconfirmed_email || current_user.email
......
# frozen_string_literal: true # frozen_string_literal: true
module UploadsActions module UploadsActions
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
include SendFileUpload include SendFileUpload
UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze UPLOAD_MOUNTS = %w(avatar attachment file logo header_logo favicon).freeze
included do
prepend_before_action :set_request_format_from_path_extension
end
def create def create
uploader = UploadService.new(model, params[:file], uploader_class).execute uploader = UploadService.new(model, params[:file], uploader_class).execute
...@@ -64,6 +69,18 @@ module UploadsActions ...@@ -64,6 +69,18 @@ module UploadsActions
private private
# From ActionDispatch::Http::MimeNegotiation. We have an initializer that
# monkey-patches this method out (so that repository paths don't guess a
# format based on extension), but we do want this behaviour when serving
# uploads.
def set_request_format_from_path_extension
path = request.headers['action_dispatch.original_path'] || request.headers['PATH_INFO']
if match = path&.match(/\.(\w+)\z/)
request.format = match.captures.first
end
end
def uploader_class def uploader_class
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -4,8 +4,6 @@ class Projects::GrafanaApiController < Projects::ApplicationController ...@@ -4,8 +4,6 @@ class Projects::GrafanaApiController < Projects::ApplicationController
include RenderServiceResults include RenderServiceResults
include MetricsDashboard include MetricsDashboard
before_action :validate_feature_enabled!, only: [:metrics_dashboard]
def proxy def proxy
result = ::Grafana::ProxyService.new( result = ::Grafana::ProxyService.new(
project, project,
...@@ -26,10 +24,6 @@ class Projects::GrafanaApiController < Projects::ApplicationController ...@@ -26,10 +24,6 @@ class Projects::GrafanaApiController < Projects::ApplicationController
params.permit(:embedded, :grafana_url) params.permit(:embedded, :grafana_url)
end end
def validate_feature_enabled!
render_403 unless Feature.enabled?(:gfm_grafana_integration)
end
def query_params def query_params
params.permit(:query, :start, :end, :step) params.permit(:query, :start, :end, :step)
end end
......
...@@ -7,25 +7,6 @@ class ExpireBuildArtifactsWorker ...@@ -7,25 +7,6 @@ class ExpireBuildArtifactsWorker
feature_category :continuous_integration feature_category :continuous_integration
def perform def perform
if Feature.enabled?(:ci_new_expire_job_artifacts_service, default_enabled: true)
perform_efficient_artifacts_removal
else
perform_legacy_artifacts_removal
end
end
def perform_efficient_artifacts_removal
Ci::DestroyExpiredJobArtifactsService.new.execute Ci::DestroyExpiredJobArtifactsService.new.execute
end end
# rubocop: disable CodeReuse/ActiveRecord
def perform_legacy_artifacts_removal
Rails.logger.info 'Scheduling removal of build artifacts' # rubocop:disable Gitlab/RailsLogger
build_ids = Ci::Build.with_expired_artifacts.pluck(:id)
build_ids = build_ids.map { |build_id| [build_id] }
ExpireBuildInstanceArtifactsWorker.bulk_perform_async(build_ids)
end
# rubocop: enable CodeReuse/ActiveRecord
end end
---
title: Add edit button to metrics dashboard
merge_request: 19279
author:
type: added
---
title: Update SaaS trial header to include the tier Gold
merge_request: 19970
author:
type: changed
---
title: Re-add missing file sizes in 2-Up diff file viewer
merge_request: 19710
author:
type: fixed
---
title: Allow Grafana charts to be embedded in Gitlab Flavored Markdown
merge_request: 18486
author:
type: added
...@@ -18,8 +18,6 @@ module Banzai ...@@ -18,8 +18,6 @@ module Banzai
end end
def embed_params(node) def embed_params(node)
return unless Feature.enabled?(:gfm_grafana_integration)
query_params = Gitlab::Metrics::Dashboard::Url.parse_query(node['href']) query_params = Gitlab::Metrics::Dashboard::Url.parse_query(node['href'])
return unless [:panelId, :from, :to].all? do |param| return unless [:panelId, :from, :to].all? do |param|
query_params.include?(param) query_params.include?(param)
......
...@@ -42,9 +42,6 @@ module Gitlab ...@@ -42,9 +42,6 @@ module Gitlab
# Initialize gon.features with any flags that should be # Initialize gon.features with any flags that should be
# made globally available to the frontend # made globally available to the frontend
push_frontend_feature_flag(:suppress_ajax_navigation_errors, default_enabled: true) push_frontend_feature_flag(:suppress_ajax_navigation_errors, default_enabled: true)
# Flag controls a GFM feature used across many routes.
push_frontend_feature_flag(:gfm_grafana_integration)
end end
# Exposes the state of a feature flag to the frontend code. # Exposes the state of a feature flag to the frontend code.
......
...@@ -6022,6 +6022,9 @@ msgstr "" ...@@ -6022,6 +6022,9 @@ msgstr ""
msgid "Edit comment" msgid "Edit comment"
msgstr "" msgstr ""
msgid "Edit dashboard"
msgstr ""
msgid "Edit description" msgid "Edit description"
msgstr "" msgstr ""
...@@ -6442,6 +6445,9 @@ msgstr "" ...@@ -6442,6 +6445,9 @@ msgstr ""
msgid "Environments|No deployments yet" msgid "Environments|No deployments yet"
msgstr "" msgstr ""
msgid "Environments|No pods to display"
msgstr ""
msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file." msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
msgstr "" msgstr ""
...@@ -16145,7 +16151,7 @@ msgstr "" ...@@ -16145,7 +16151,7 @@ msgstr ""
msgid "Start a %{new_merge_request} with these changes" msgid "Start a %{new_merge_request} with these changes"
msgstr "" msgstr ""
msgid "Start a Free Trial" msgid "Start a Free Gold Trial"
msgstr "" msgstr ""
msgid "Start a new discussion..." msgid "Start a new discussion..."
......
...@@ -90,14 +90,6 @@ describe ApplicationController do ...@@ -90,14 +90,6 @@ describe ApplicationController do
let(:format) { :html } let(:format) { :html }
it_behaves_like 'setting gon variables' it_behaves_like 'setting gon variables'
context 'for peek requests' do
before do
request.path = '/-/peek'
end
it_behaves_like 'not setting gon variables'
end
end end
context 'with json format' do context 'with json format' do
...@@ -105,6 +97,12 @@ describe ApplicationController do ...@@ -105,6 +97,12 @@ describe ApplicationController do
it_behaves_like 'not setting gon variables' it_behaves_like 'not setting gon variables'
end end
context 'with atom format' do
let(:format) { :atom }
it_behaves_like 'not setting gon variables'
end
end end
describe 'session expiration' do describe 'session expiration' do
......
...@@ -164,17 +164,5 @@ describe Projects::GrafanaApiController do ...@@ -164,17 +164,5 @@ describe Projects::GrafanaApiController do
it_behaves_like 'error response', :bad_request it_behaves_like 'error response', :bad_request
end end
end end
context 'when grafana embeds are not enabled' do
before do
stub_feature_flags(gfm_grafana_integration: false)
end
it 'returns 403 immediately' do
get :metrics_dashboard, params: params
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end end
end end
...@@ -228,10 +228,10 @@ describe UploadsController do ...@@ -228,10 +228,10 @@ describe UploadsController do
user.block user.block
end end
it "redirects to the sign in page" do it "responds with status 401" do
get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" } get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path) expect(response).to have_gitlab_http_status(401)
end end
end end
...@@ -320,10 +320,10 @@ describe UploadsController do ...@@ -320,10 +320,10 @@ describe UploadsController do
end end
context "when not signed in" do context "when not signed in" do
it "redirects to the sign in page" do it "responds with status 401" do
get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" } get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path) expect(response).to have_gitlab_http_status(401)
end end
end end
...@@ -343,10 +343,10 @@ describe UploadsController do ...@@ -343,10 +343,10 @@ describe UploadsController do
project.add_maintainer(user) project.add_maintainer(user)
end end
it "redirects to the sign in page" do it "responds with status 401" do
get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" } get :show, params: { model: "project", mounted_as: "avatar", id: project.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path) expect(response).to have_gitlab_http_status(401)
end end
end end
...@@ -439,10 +439,10 @@ describe UploadsController do ...@@ -439,10 +439,10 @@ describe UploadsController do
user.block user.block
end end
it "redirects to the sign in page" do it "responds with status 401" do
get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" } get :show, params: { model: "group", mounted_as: "avatar", id: group.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path) expect(response).to have_gitlab_http_status(401)
end end
end end
...@@ -526,10 +526,10 @@ describe UploadsController do ...@@ -526,10 +526,10 @@ describe UploadsController do
end end
context "when not signed in" do context "when not signed in" do
it "redirects to the sign in page" do it "responds with status 401" do
get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" } get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path) expect(response).to have_gitlab_http_status(401)
end end
end end
...@@ -549,10 +549,10 @@ describe UploadsController do ...@@ -549,10 +549,10 @@ describe UploadsController do
project.add_maintainer(user) project.add_maintainer(user)
end end
it "redirects to the sign in page" do it "responds with status 401" do
get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" } get :show, params: { model: "note", mounted_as: "attachment", id: note.id, filename: "dk.png" }
expect(response).to redirect_to(new_user_session_path) expect(response).to have_gitlab_http_status(401)
end end
end end
......
...@@ -104,17 +104,7 @@ describe 'Projects > Settings > For a forked project', :js do ...@@ -104,17 +104,7 @@ describe 'Projects > Settings > For a forked project', :js do
end end
context 'grafana integration settings form' do context 'grafana integration settings form' do
it 'is not present when the feature flag is disabled' do it 'successfully fills and completes the form' do
stub_feature_flags(gfm_grafana_integration: false)
visit project_settings_operations_path(project)
wait_for_requests
expect(page).to have_no_css('.js-grafana-integration')
end
it 'is present when the feature flag is enabled' do
visit project_settings_operations_path(project) visit project_settings_operations_path(project)
wait_for_requests wait_for_requests
......
import { shallowMount } from '@vue/test-utils';
import ImageViewer from '~/vue_shared/components/content_viewer/viewers/image_viewer.vue';
import { GREEN_BOX_IMAGE_URL } from 'spec/test_constants';
describe('Image Viewer', () => {
const requiredProps = {
path: GREEN_BOX_IMAGE_URL,
renderInfo: true,
};
let wrapper;
let imageInfo;
function createElement({ props, includeRequired = true } = {}) {
const data = includeRequired ? { ...requiredProps, ...props } : { ...props };
wrapper = shallowMount(ImageViewer, {
propsData: data,
});
imageInfo = wrapper.find('.image-info');
}
describe('file sizes', () => {
it('should show the humanized file size when `renderInfo` is true and there is size info', () => {
createElement({ props: { fileSize: 1024 } });
expect(imageInfo.text()).toContain('1.00 KiB');
});
it('should not show the humanized file size when `renderInfo` is true and there is no size', () => {
const FILESIZE_RE = /\d+(\.\d+)?\s*([KMGTP]i)*B/;
createElement({ props: { fileSize: 0 } });
// It shouldn't show any filesize info
expect(imageInfo.text()).not.toMatch(FILESIZE_RE);
});
it('should not show any image information when `renderInfo` is false', () => {
createElement({ props: { renderInfo: false } });
expect(imageInfo.exists()).toBe(false);
});
});
});
...@@ -623,6 +623,49 @@ describe('Dashboard', () => { ...@@ -623,6 +623,49 @@ describe('Dashboard', () => {
}); });
}); });
describe('dashboard edit link', () => {
let wrapper;
const findEditLink = () => wrapper.find('.js-edit-link');
beforeEach(done => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
wrapper = shallowMount(DashboardComponent, {
localVue,
sync: false,
attachToDocument: true,
propsData: { ...propsData, hasMetrics: true },
store,
});
wrapper.vm.$store.commit(
`monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
dashboardGitResponse,
);
wrapper.vm.$nextTick(done);
});
afterEach(() => {
wrapper.destroy();
});
it('is not present for the default dashboard', () => {
expect(findEditLink().exists()).toBe(false);
});
it('is present for a custom dashboard, and links to its edit_path', done => {
const dashboard = dashboardGitResponse[1]; // non-default dashboard
const currentDashboard = dashboard.path;
wrapper.setProps({ currentDashboard });
wrapper.vm.$nextTick(() => {
expect(findEditLink().exists()).toBe(true);
expect(findEditLink().attributes('href')).toBe(dashboard.project_blob_path);
done();
});
});
});
describe('external dashboard link', () => { describe('external dashboard link', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
......
...@@ -931,14 +931,25 @@ export const metricsDashboardResponse = { ...@@ -931,14 +931,25 @@ export const metricsDashboardResponse = {
export const dashboardGitResponse = [ export const dashboardGitResponse = [
{ {
path: 'config/prometheus/common_metrics.yml',
display_name: 'Common Metrics',
default: true, default: true,
display_name: 'Default',
can_edit: false,
project_blob_path: null,
path: 'config/prometheus/common_metrics.yml',
}, },
{ {
path: '.gitlab/dashboards/super.yml', default: false,
display_name: 'Custom Dashboard 1', display_name: 'Custom Dashboard 1',
can_edit: true,
project_blob_path: `${mockProjectPath}/blob/master/dashboards/.gitlab/dashboards/dashboard_1.yml`,
path: '.gitlab/dashboards/dashboard_1.yml',
},
{
default: false, default: false,
display_name: 'Custom Dashboard 2',
can_edit: true,
project_blob_path: `${mockProjectPath}/blob/master/dashboards/.gitlab/dashboards/dashboard_2.yml`,
path: '.gitlab/dashboards/dashboard_2.yml',
}, },
]; ];
......
import Vue from 'vue'; import Vue from 'vue';
import diffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue'; import diffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants'; import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
describe('DiffViewer', () => { describe('DiffViewer', () => {
const requiredProps = {
diffMode: 'replaced',
diffViewerMode: 'image',
newPath: GREEN_BOX_IMAGE_URL,
newSha: 'ABC',
oldPath: RED_BOX_IMAGE_URL,
oldSha: 'DEF',
};
let vm; let vm;
function createComponent(props) { function createComponent(props) {
const DiffViewer = Vue.extend(diffViewer); const DiffViewer = Vue.extend(diffViewer);
vm = mountComponent(DiffViewer, props); vm = mountComponent(DiffViewer, props);
} }
...@@ -20,15 +30,11 @@ describe('DiffViewer', () => { ...@@ -20,15 +30,11 @@ describe('DiffViewer', () => {
relative_url_root: '', relative_url_root: '',
}; };
createComponent({ createComponent(
diffMode: 'replaced', Object.assign({}, requiredProps, {
diffViewerMode: 'image', projectPath: '',
newPath: GREEN_BOX_IMAGE_URL, }),
newSha: 'ABC', );
oldPath: RED_BOX_IMAGE_URL,
oldSha: 'DEF',
projectPath: '',
});
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe( expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(
...@@ -44,14 +50,13 @@ describe('DiffViewer', () => { ...@@ -44,14 +50,13 @@ describe('DiffViewer', () => {
}); });
it('renders fallback download diff display', done => { it('renders fallback download diff display', done => {
createComponent({ createComponent(
diffMode: 'replaced', Object.assign({}, requiredProps, {
diffViewerMode: 'added', diffViewerMode: 'added',
newPath: 'test.abc', newPath: 'test.abc',
newSha: 'ABC', oldPath: 'testold.abc',
oldPath: 'testold.abc', }),
oldSha: 'DEF', );
});
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.deleted .file-info').textContent.trim()).toContain( expect(vm.$el.querySelector('.deleted .file-info').textContent.trim()).toContain(
...@@ -72,29 +77,28 @@ describe('DiffViewer', () => { ...@@ -72,29 +77,28 @@ describe('DiffViewer', () => {
}); });
it('renders renamed component', () => { it('renders renamed component', () => {
createComponent({ createComponent(
diffMode: 'renamed', Object.assign({}, requiredProps, {
diffViewerMode: 'renamed', diffMode: 'renamed',
newPath: 'test.abc', diffViewerMode: 'renamed',
newSha: 'ABC', newPath: 'test.abc',
oldPath: 'testold.abc', oldPath: 'testold.abc',
oldSha: 'DEF', }),
}); );
expect(vm.$el.textContent).toContain('File moved'); expect(vm.$el.textContent).toContain('File moved');
}); });
it('renders mode changed component', () => { it('renders mode changed component', () => {
createComponent({ createComponent(
diffMode: 'mode_changed', Object.assign({}, requiredProps, {
diffViewerMode: 'image', diffMode: 'mode_changed',
newPath: 'test.abc', newPath: 'test.abc',
newSha: 'ABC', oldPath: 'testold.abc',
oldPath: 'testold.abc', aMode: '123',
oldSha: 'DEF', bMode: '321',
aMode: '123', }),
bMode: '321', );
});
expect(vm.$el.textContent).toContain('File mode changed from 123 to 321'); expect(vm.$el.textContent).toContain('File mode changed from 123 to 321');
}); });
......
...@@ -4,6 +4,11 @@ import mountComponent from 'spec/helpers/vue_mount_component_helper'; ...@@ -4,6 +4,11 @@ import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants'; import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
describe('ImageDiffViewer', () => { describe('ImageDiffViewer', () => {
const requiredProps = {
diffMode: 'replaced',
newPath: GREEN_BOX_IMAGE_URL,
oldPath: RED_BOX_IMAGE_URL,
};
let vm; let vm;
function createComponent(props) { function createComponent(props) {
...@@ -45,11 +50,7 @@ describe('ImageDiffViewer', () => { ...@@ -45,11 +50,7 @@ describe('ImageDiffViewer', () => {
}); });
it('renders image diff for replaced', done => { it('renders image diff for replaced', done => {
createComponent({ createComponent(requiredProps);
diffMode: 'replaced',
newPath: GREEN_BOX_IMAGE_URL,
oldPath: RED_BOX_IMAGE_URL,
});
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL); expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
...@@ -70,11 +71,12 @@ describe('ImageDiffViewer', () => { ...@@ -70,11 +71,12 @@ describe('ImageDiffViewer', () => {
}); });
it('renders image diff for new', done => { it('renders image diff for new', done => {
createComponent({ createComponent(
diffMode: 'new', Object.assign({}, requiredProps, {
newPath: GREEN_BOX_IMAGE_URL, diffMode: 'new',
oldPath: '', oldPath: '',
}); }),
);
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL); expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe(GREEN_BOX_IMAGE_URL);
...@@ -84,11 +86,12 @@ describe('ImageDiffViewer', () => { ...@@ -84,11 +86,12 @@ describe('ImageDiffViewer', () => {
}); });
it('renders image diff for deleted', done => { it('renders image diff for deleted', done => {
createComponent({ createComponent(
diffMode: 'deleted', Object.assign({}, requiredProps, {
newPath: '', diffMode: 'deleted',
oldPath: RED_BOX_IMAGE_URL, newPath: '',
}); }),
);
setTimeout(() => { setTimeout(() => {
expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(RED_BOX_IMAGE_URL); expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(RED_BOX_IMAGE_URL);
...@@ -119,11 +122,7 @@ describe('ImageDiffViewer', () => { ...@@ -119,11 +122,7 @@ describe('ImageDiffViewer', () => {
describe('swipeMode', () => { describe('swipeMode', () => {
beforeEach(done => { beforeEach(done => {
createComponent({ createComponent(requiredProps);
diffMode: 'replaced',
newPath: GREEN_BOX_IMAGE_URL,
oldPath: RED_BOX_IMAGE_URL,
});
setTimeout(() => { setTimeout(() => {
done(); done();
...@@ -142,11 +141,7 @@ describe('ImageDiffViewer', () => { ...@@ -142,11 +141,7 @@ describe('ImageDiffViewer', () => {
describe('onionSkin', () => { describe('onionSkin', () => {
beforeEach(done => { beforeEach(done => {
createComponent({ createComponent(requiredProps);
diffMode: 'replaced',
newPath: GREEN_BOX_IMAGE_URL,
oldPath: RED_BOX_IMAGE_URL,
});
setTimeout(() => { setTimeout(() => {
done(); done();
......
...@@ -18,16 +18,6 @@ describe Banzai::Filter::InlineGrafanaMetricsFilter do ...@@ -18,16 +18,6 @@ describe Banzai::Filter::InlineGrafanaMetricsFilter do
'&var-instance=All&panelId=14' '&var-instance=All&panelId=14'
end end
context 'when feature flag is disabled' do
before do
stub_feature_flags(gfm_grafana_integration: false)
end
it 'leaves the markdown unchanged' do
expect(unescape(doc.to_s)).to eq(input)
end
end
it 'appends a metrics charts placeholder with dashboard url after metrics links' do it 'appends a metrics charts placeholder with dashboard url after metrics links' do
node = doc.at_css('.js-render-metrics') node = doc.at_css('.js-render-metrics')
expect(node).to be_present expect(node).to be_present
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Loading a user avatar' do
let(:user) { create(:user, :with_avatar) }
context 'when logged in' do
# The exact query count will vary depending on the 2FA settings of the
# instance, group, and user. Removing those extra 2FA queries in this case
# may not be a good idea, so we just set up the ideal case.
before do
stub_application_setting(require_two_factor_authentication: true)
login_as(create(:user, :two_factor))
end
# One each for: current user, avatar user, and upload record
it 'only performs three SQL queries' do
get user.avatar_url # Skip queries on first application load
expect(response).to have_gitlab_http_status(200)
expect { get user.avatar_url }.not_to exceed_query_limit(3)
end
end
context 'when logged out' do
# One each for avatar user and upload record
it 'only performs two SQL queries' do
get user.avatar_url # Skip queries on first application load
expect(response).to have_gitlab_http_status(200)
expect { get user.avatar_url }.not_to exceed_query_limit(2)
end
end
end
...@@ -3,62 +3,11 @@ ...@@ -3,62 +3,11 @@
require 'spec_helper' require 'spec_helper'
describe ExpireBuildArtifactsWorker do describe ExpireBuildArtifactsWorker do
include RepoHelpers
let(:worker) { described_class.new } let(:worker) { described_class.new }
before do
Sidekiq::Worker.clear_all
end
describe '#perform' do describe '#perform' do
before do
stub_feature_flags(ci_new_expire_job_artifacts_service: false)
build
end
subject! do
Sidekiq::Testing.fake! { worker.perform }
end
context 'with expired artifacts' do
let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now - 7.days) }
it 'enqueues that build' do
expect(jobs_enqueued.size).to eq(1)
expect(jobs_enqueued[0]["args"]).to eq([build.id])
end
end
context 'with not yet expired artifacts' do
let(:build) { create(:ci_build, :artifacts, artifacts_expire_at: Time.now + 7.days) }
it 'does not enqueue that build' do
expect(jobs_enqueued.size).to eq(0)
end
end
context 'without expire date' do
let(:build) { create(:ci_build, :artifacts) }
it 'does not enqueue that build' do
expect(jobs_enqueued.size).to eq(0)
end
end
def jobs_enqueued
Sidekiq::Queues.jobs_by_worker['ExpireBuildInstanceArtifactsWorker']
end
end
describe '#perform with ci_new_expire_job_artifacts_service feature flag' do
before do
stub_feature_flags(ci_new_expire_job_artifacts_service: true)
end
it 'executes a service' do it 'executes a service' do
expect_any_instance_of(Ci::DestroyExpiredJobArtifactsService).to receive(:execute) expect_any_instance_of(Ci::DestroyExpiredJobArtifactsService).to receive(:execute)
expect(ExpireBuildInstanceArtifactsWorker).not_to receive(:bulk_perform_async)
worker.perform worker.perform
end end
......
...@@ -1052,7 +1052,7 @@ ...@@ -1052,7 +1052,7 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node@*", "@types/node@^10.11.7": "@types/node@*", "@types/node@>=6", "@types/node@^10.11.7":
version "10.12.9" version "10.12.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.9.tgz#a07bfa74331471e1dc22a47eb72026843f7b95c8" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.9.tgz#a07bfa74331471e1dc22a47eb72026843f7b95c8"
integrity sha512-eajkMXG812/w3w4a1OcBlaTwsFPO5F7fJ/amy+tieQxEMWBlbV1JGSjkFM+zkHNf81Cad+dfIRA+IBkvmvdAeA== integrity sha512-eajkMXG812/w3w4a1OcBlaTwsFPO5F7fJ/amy+tieQxEMWBlbV1JGSjkFM+zkHNf81Cad+dfIRA+IBkvmvdAeA==
...@@ -1310,6 +1310,14 @@ ...@@ -1310,6 +1310,14 @@
"@webassemblyjs/wast-parser" "1.8.5" "@webassemblyjs/wast-parser" "1.8.5"
"@xtuc/long" "4.2.2" "@xtuc/long" "4.2.2"
"@wry/context@^0.4.0":
version "0.4.4"
resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.4.4.tgz#e50f5fa1d6cfaabf2977d1fda5ae91717f8815f8"
integrity sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag==
dependencies:
"@types/node" ">=6"
tslib "^1.9.3"
"@wry/equality@^0.1.2": "@wry/equality@^0.1.2":
version "0.1.9" version "0.1.9"
resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.9.tgz#b13e18b7a8053c6858aa6c85b54911fb31e3a909" resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.9.tgz#b13e18b7a8053c6858aa6c85b54911fb31e3a909"
...@@ -1473,18 +1481,18 @@ anymatch@^3.0.1: ...@@ -1473,18 +1481,18 @@ anymatch@^3.0.1:
normalize-path "^3.0.0" normalize-path "^3.0.0"
picomatch "^2.0.4" picomatch "^2.0.4"
apollo-cache-inmemory@^1.5.1: apollo-cache-inmemory@^1.6.3:
version "1.5.1" version "1.6.3"
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.5.1.tgz#265d1ee67b0bf0aca9c37629d410bfae44e62953" resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.3.tgz#826861d20baca4abc45f7ca7a874105905b8525d"
integrity sha512-D3bdpPmWfaKQkWy8lfwUg+K8OBITo3sx0BHLs1B/9vIdOIZ7JNCKq3EUcAgAfInomJUdN0QG1yOfi8M8hxkN1g== integrity sha512-S4B/zQNSuYc0M/1Wq8dJDTIO9yRgU0ZwDGnmlqxGGmFombOZb9mLjylewSfQKmjNpciZ7iUIBbJ0mHlPJTzdXg==
dependencies: dependencies:
apollo-cache "^1.2.1" apollo-cache "^1.3.2"
apollo-utilities "^1.2.1" apollo-utilities "^1.3.2"
optimism "^0.6.9" optimism "^0.10.0"
ts-invariant "^0.2.1" ts-invariant "^0.4.0"
tslib "^1.9.3" tslib "^1.9.3"
apollo-cache@1.3.2, apollo-cache@^1.2.1: apollo-cache@1.3.2, apollo-cache@^1.3.2:
version "1.3.2" version "1.3.2"
resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.2.tgz#df4dce56240d6c95c613510d7e409f7214e6d26a" resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.2.tgz#df4dce56240d6c95c613510d7e409f7214e6d26a"
integrity sha512-+KA685AV5ETEJfjZuviRTEImGA11uNBp/MJGnaCvkgr+BYRrGLruVKBv6WvyFod27WEB2sp7SsG8cNBKANhGLg== integrity sha512-+KA685AV5ETEJfjZuviRTEImGA11uNBp/MJGnaCvkgr+BYRrGLruVKBv6WvyFod27WEB2sp7SsG8cNBKANhGLg==
...@@ -3475,7 +3483,7 @@ d3@^4.13.0: ...@@ -3475,7 +3483,7 @@ d3@^4.13.0:
d3-voronoi "1.1.2" d3-voronoi "1.1.2"
d3-zoom "1.7.1" d3-zoom "1.7.1"
d3@^5.12: d3@^5.12, d3@^5.7.0:
version "5.12.0" version "5.12.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.12.0.tgz#0ddeac879c28c882317cd439b495290acd59ab61" resolved "https://registry.yarnpkg.com/d3/-/d3-5.12.0.tgz#0ddeac879c28c882317cd439b495290acd59ab61"
integrity sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg== integrity sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==
...@@ -3512,43 +3520,6 @@ d3@^5.12: ...@@ -3512,43 +3520,6 @@ d3@^5.12:
d3-voronoi "1" d3-voronoi "1"
d3-zoom "1" d3-zoom "1"
d3@^5.7.0:
version "5.9.2"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.9.2.tgz#64e8a7e9c3d96d9e6e4999d2c8a2c829767e67f5"
integrity sha512-ydrPot6Lm3nTWH+gJ/Cxf3FcwuvesYQ5uk+j/kXEH/xbuYWYWTMAHTJQkyeuG8Y5WM5RSEYB41EctUrXQQytRQ==
dependencies:
d3-array "1"
d3-axis "1"
d3-brush "1"
d3-chord "1"
d3-collection "1"
d3-color "1"
d3-contour "1"
d3-dispatch "1"
d3-drag "1"
d3-dsv "1"
d3-ease "1"
d3-fetch "1"
d3-force "1"
d3-format "1"
d3-geo "1"
d3-hierarchy "1"
d3-interpolate "1"
d3-path "1"
d3-polygon "1"
d3-quadtree "1"
d3-random "1"
d3-scale "2"
d3-scale-chromatic "1"
d3-selection "1"
d3-shape "1"
d3-time "1"
d3-time-format "2"
d3-timer "1"
d3-transition "1"
d3-voronoi "1"
d3-zoom "1"
dagre-d3@dagrejs/dagre-d3: dagre-d3@dagrejs/dagre-d3:
version "0.6.4-pre" version "0.6.4-pre"
resolved "https://codeload.github.com/dagrejs/dagre-d3/tar.gz/e1a00e5cb518f5d2304a35647e024f31d178e55b" resolved "https://codeload.github.com/dagrejs/dagre-d3/tar.gz/e1a00e5cb518f5d2304a35647e024f31d178e55b"
...@@ -5682,11 +5653,6 @@ immediate@~3.0.5: ...@@ -5682,11 +5653,6 @@ immediate@~3.0.5:
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
immutable-tuple@^0.4.9:
version "0.4.9"
resolved "https://registry.yarnpkg.com/immutable-tuple/-/immutable-tuple-0.4.9.tgz#473ebdd6c169c461913a454bf87ef8f601a20ff0"
integrity sha512-LWbJPZnidF8eczu7XmcnLBsumuyRBkpwIRPCZxlojouhBo5jEBO4toj6n7hMy6IxHU/c+MqDSWkvaTpPlMQcyA==
import-fresh@^2.0.0: import-fresh@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
...@@ -8282,12 +8248,12 @@ opn@^5.5.0: ...@@ -8282,12 +8248,12 @@ opn@^5.5.0:
dependencies: dependencies:
is-wsl "^1.1.0" is-wsl "^1.1.0"
optimism@^0.6.9: optimism@^0.10.0:
version "0.6.9" version "0.10.3"
resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.6.9.tgz#19258ff8b3be0cea29ac35f06bff818e026e30bb" resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.10.3.tgz#163268fdc741dea2fb50f300bedda80356445fd7"
integrity sha512-xoQm2lvXbCA9Kd7SCx6y713Y7sZ6fUc5R6VYpoL5M6svKJbTuvtNopexK8sO8K4s0EOUYHuPN2+yAEsNyRggkQ== integrity sha512-9A5pqGoQk49H6Vhjb9kPgAeeECfUDF6aIICbMDL23kDLStBn1MWk3YvcZ4xWF9CsSf6XEgvRLkXy4xof/56vVw==
dependencies: dependencies:
immutable-tuple "^0.4.9" "@wry/context" "^0.4.0"
optimist@^0.6.1: optimist@^0.6.1:
version "0.6.1" version "0.6.1"
...@@ -11111,13 +11077,6 @@ tryer@^1.0.0: ...@@ -11111,13 +11077,6 @@ tryer@^1.0.0:
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7"
integrity sha1-Antp+oIyJeVRys4+8DsR9qs3wdc= integrity sha1-Antp+oIyJeVRys4+8DsR9qs3wdc=
ts-invariant@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.2.1.tgz#3d587f9d6e3bded97bf9ec17951dd9814d5a9d3f"
integrity sha512-Z/JSxzVmhTo50I+LKagEISFJW3pvPCqsMWLamCTX8Kr3N5aMrnGOqcflbe5hLUzwjvgPfnLzQtHZv0yWQ+FIHg==
dependencies:
tslib "^1.9.3"
ts-invariant@^0.3.2: ts-invariant@^0.3.2:
version "0.3.2" version "0.3.2"
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.2.tgz#89a2ffeb70879b777258df1df1c59383c35209b0" resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.2.tgz#89a2ffeb70879b777258df1df1c59383c35209b0"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment