diff options
28 files changed, 305 insertions, 174 deletions
diff --git a/app/services/groups/group_links/destroy_service.rb b/app/services/groups/group_links/destroy_service.rb new file mode 100644 index 00000000000..29aa8de4e68 --- /dev/null +++ b/app/services/groups/group_links/destroy_service.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Groups + module GroupLinks + class DestroyService < BaseService + def execute(one_or_more_links) + links = Array(one_or_more_links) + + GroupGroupLink.transaction do + GroupGroupLink.delete(links) + + groups_to_refresh = links.map(&:shared_with_group) + groups_to_refresh.uniq.each do |group| + group.refresh_members_authorized_projects + end + + Gitlab::AppLogger.info("GroupGroupLinks with ids: #{links.map(&:id)} have been deleted.") + rescue => ex + Gitlab::AppLogger.error(ex) + + raise + end + end + end + end +end diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb index 147b412b772..a43e6fd11d5 100644 --- a/app/workers/remove_expired_group_links_worker.rb +++ b/app/workers/remove_expired_group_links_worker.rb @@ -8,5 +8,9 @@ class RemoveExpiredGroupLinksWorker def perform ProjectGroupLink.expired.destroy_all # rubocop: disable DestroyAll + + GroupGroupLink.expired.find_in_batches do |link_batch| + Groups::GroupLinks::DestroyService.new(nil, nil).execute(link_batch) + end end end diff --git a/changelogs/unreleased/include-worker-attributes-in-sidekiq-metrics.yml b/changelogs/unreleased/include-worker-attributes-in-sidekiq-metrics.yml deleted file mode 100644 index ea7e229d0ea..00000000000 --- a/changelogs/unreleased/include-worker-attributes-in-sidekiq-metrics.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add worker attributes to Sidekiq metrics -merge_request: 19491 -author: -type: other diff --git a/doc/administration/geo/replication/database.md b/doc/administration/geo/replication/database.md index fa1b0f0e1d7..0919e62ab6c 100644 --- a/doc/administration/geo/replication/database.md +++ b/doc/administration/geo/replication/database.md @@ -425,6 +425,9 @@ data before running `pg_basebackup`. --host=<primary_node_ip> ``` + NOTE: **Note:** + Replication slot names must only contain lowercase letters, numbers, and the underscore character. + When prompted, enter the _plaintext_ password you set up for the `gitlab_replicator` user in the first step. diff --git a/jest.config.js b/jest.config.js index f5e589934e6..3f9dc3fe213 100644 --- a/jest.config.js +++ b/jest.config.js @@ -60,7 +60,7 @@ module.exports = { cacheDirectory: '<rootDir>/tmp/cache/jest', modulePathIgnorePatterns: ['<rootDir>/.yarn-cache/'], reporters, - setupFilesAfterEnv: ['<rootDir>/spec/frontend/test_setup.js'], + setupFilesAfterEnv: ['<rootDir>/spec/frontend/test_setup.js', 'jest-canvas-mock'], restoreMocks: true, transform: { '^.+\\.(gql|graphql)$': 'jest-transform-graphql', diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events.rb b/lib/gitlab/analytics/cycle_analytics/stage_events.rb index 58572446de6..f6e22044142 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events.rb @@ -47,27 +47,29 @@ module Gitlab ] }.freeze - def [](identifier) + def self.[](identifier) events.find { |e| e.identifier.to_s.eql?(identifier.to_s) } || raise(KeyError) end # hash for defining ActiveRecord enum: identifier => number - def to_enum - ENUM_MAPPING.each_with_object({}) { |(k, v), hash| hash[k.identifier] = v } + def self.to_enum + enum_mapping.each_with_object({}) { |(k, v), hash| hash[k.identifier] = v } end - # will be overridden in EE with custom events - def pairing_rules + def self.pairing_rules PAIRING_RULES end - # will be overridden in EE with custom events - def events + def self.events EVENTS end - module_function :[], :to_enum, :pairing_rules, :events + def self.enum_mapping + ENUM_MAPPING + end end end end end + +Gitlab::Analytics::CycleAnalytics::StageEvents.prepend_if_ee('::EE::Gitlab::Analytics::CycleAnalytics::StageEvents') diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb index 6af1b90bccc..9f0ca80ba50 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class CodeStageStart < SimpleStageEvent + class CodeStageStart < StageEvent def self.name s_("CycleAnalyticsEvent|Issue first mentioned in a commit") end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb index 8c9a80740a9..a159580b7bd 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class IssueCreated < SimpleStageEvent + class IssueCreated < StageEvent def self.name s_("CycleAnalyticsEvent|Issue created") end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb index fe7f2d85f8b..a3b7fa16daf 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class IssueFirstMentionedInCommit < SimpleStageEvent + class IssueFirstMentionedInCommit < MetricsBasedStageEvent def self.name s_("CycleAnalyticsEvent|Issue first mentioned in a commit") end @@ -20,12 +20,6 @@ module Gitlab def timestamp_projection issue_metrics_table[:first_mentioned_in_commit_at] end - - # rubocop: disable CodeReuse/ActiveRecord - def apply_query_customization(query) - query.joins(:metrics) - end - # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb index 77e4092b9ab..0ea98e82ecc 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class IssueStageEnd < SimpleStageEvent + class IssueStageEnd < MetricsBasedStageEvent def self.name PlanStageStart.name end @@ -26,7 +26,7 @@ module Gitlab # rubocop: disable CodeReuse/ActiveRecord def apply_query_customization(query) - query.joins(:metrics).where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) + super.where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb index 7059c425b8f..013e068e479 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class MergeRequestCreated < SimpleStageEvent + class MergeRequestCreated < StageEvent def self.name s_("CycleAnalyticsEvent|Merge request created") end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb index 3d7482eaaf0..654d0befbc3 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class MergeRequestFirstDeployedToProduction < SimpleStageEvent + class MergeRequestFirstDeployedToProduction < MetricsBasedStageEvent def self.name s_("CycleAnalyticsEvent|Merge request first deployed to production") end @@ -23,7 +23,7 @@ module Gitlab # rubocop: disable CodeReuse/ActiveRecord def apply_query_customization(query) - query.joins(:metrics).where(timestamp_projection.gteq(mr_table[:created_at])) + super.where(timestamp_projection.gteq(mr_table[:created_at])) end # rubocop: enable CodeReuse/ActiveRecord end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb index 36bb4d6fc8d..a0b1c12756f 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class MergeRequestLastBuildFinished < SimpleStageEvent + class MergeRequestLastBuildFinished < MetricsBasedStageEvent def self.name s_("CycleAnalyticsEvent|Merge request last build finish time") end @@ -20,12 +20,6 @@ module Gitlab def timestamp_projection mr_metrics_table[:latest_build_finished_at] end - - # rubocop: disable CodeReuse/ActiveRecord - def apply_query_customization(query) - query.joins(:metrics) - end - # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb index 468d9899cc7..da3b5cdfaa4 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class MergeRequestLastBuildStarted < SimpleStageEvent + class MergeRequestLastBuildStarted < MetricsBasedStageEvent def self.name s_("CycleAnalyticsEvent|Merge request last build start time") end @@ -20,12 +20,6 @@ module Gitlab def timestamp_projection mr_metrics_table[:latest_build_started_at] end - - # rubocop: disable CodeReuse/ActiveRecord - def apply_query_customization(query) - query.joins(:metrics) - end - # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb index 82ecaf1cd6b..e67a6f7eea6 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class MergeRequestMerged < SimpleStageEvent + class MergeRequestMerged < MetricsBasedStageEvent def self.name s_("CycleAnalyticsEvent|Merge request merged") end @@ -20,12 +20,6 @@ module Gitlab def timestamp_projection mr_metrics_table[:merged_at] end - - # rubocop: disable CodeReuse/ActiveRecord - def apply_query_customization(query) - query.joins(:metrics) - end - # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/metrics_based_stage_event.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/metrics_based_stage_event.rb new file mode 100644 index 00000000000..4ca8745abe4 --- /dev/null +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/metrics_based_stage_event.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Gitlab + module Analytics + module CycleAnalytics + module StageEvents + class MetricsBasedStageEvent < StageEvent + # rubocop: disable CodeReuse/ActiveRecord + def apply_query_customization(query) + query.joins(:metrics) + end + # rubocop: enable CodeReuse/ActiveRecord + end + end + end + end +end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb index 7ece7d62faa..37168a1fb0f 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class PlanStageStart < SimpleStageEvent + class PlanStageStart < MetricsBasedStageEvent def self.name s_("CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board") end @@ -26,8 +26,7 @@ module Gitlab # rubocop: disable CodeReuse/ActiveRecord def apply_query_customization(query) - query - .joins(:metrics) + super .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) .where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil)) end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/production_stage_end.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/production_stage_end.rb index 607371a32e8..b249f6874e7 100644 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/production_stage_end.rb +++ b/lib/gitlab/analytics/cycle_analytics/stage_events/production_stage_end.rb @@ -4,7 +4,7 @@ module Gitlab module Analytics module CycleAnalytics module StageEvents - class ProductionStageEnd < SimpleStageEvent + class ProductionStageEnd < StageEvent def self.name PlanStageStart.name end diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb deleted file mode 100644 index 253c489d822..00000000000 --- a/lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Analytics - module CycleAnalytics - module StageEvents - # Represents a simple event that usually refers to one database column and does not require additional user input - class SimpleStageEvent < StageEvent - end - end - end - end -end diff --git a/lib/gitlab/sidekiq_middleware/metrics.rb b/lib/gitlab/sidekiq_middleware/metrics.rb index 64e77a2d828..bd819843bd4 100644 --- a/lib/gitlab/sidekiq_middleware/metrics.rb +++ b/lib/gitlab/sidekiq_middleware/metrics.rb @@ -13,8 +13,8 @@ module Gitlab @metrics[:sidekiq_concurrency].set({}, Sidekiq.options[:concurrency].to_i) end - def call(worker, job, queue) - labels = create_labels(worker, queue) + def call(_worker, job, queue) + labels = create_labels(queue) queue_duration = ::Gitlab::InstrumentationHelper.queue_duration_for_job(job) @metrics[:sidekiq_jobs_queue_duration_seconds].observe(labels, queue_duration) if queue_duration @@ -62,20 +62,10 @@ module Gitlab } end - def create_labels(worker, queue) - labels = { queue: queue } - return labels unless worker.include? WorkerAttributes - - labels[:latency_sensitive] = true if worker.latency_sensitive_worker? - labels[:external_deps] = true if worker.worker_has_external_dependencies? - - feature_category = worker.get_feature_category - labels[:feat_cat] = feature_category if feature_category - - resource_boundary = worker.get_worker_resource_boundary - labels[:boundary] = resource_boundary if resource_boundary && resource_boundary != :unknown - - labels + def create_labels(queue) + { + queue: queue + } end def get_thread_cputime diff --git a/locale/gitlab.pot b/locale/gitlab.pot index dde99a8583a..05eb287274a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4995,15 +4995,30 @@ msgstr "" msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." msgstr "" +msgid "CycleAnalyticsEvent|Issue closed" +msgstr "" + msgid "CycleAnalyticsEvent|Issue created" msgstr "" +msgid "CycleAnalyticsEvent|Issue first added to a board" +msgstr "" + +msgid "CycleAnalyticsEvent|Issue first associated with a milestone" +msgstr "" + msgid "CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board" msgstr "" msgid "CycleAnalyticsEvent|Issue first mentioned in a commit" msgstr "" +msgid "CycleAnalyticsEvent|Issue last edited" +msgstr "" + +msgid "CycleAnalyticsEvent|Merge request closed" +msgstr "" + msgid "CycleAnalyticsEvent|Merge request created" msgstr "" @@ -5016,6 +5031,9 @@ msgstr "" msgid "CycleAnalyticsEvent|Merge request last build start time" msgstr "" +msgid "CycleAnalyticsEvent|Merge request last edited" +msgstr "" + msgid "CycleAnalyticsEvent|Merge request merged" msgstr "" diff --git a/package.json b/package.json index 183a978ca75..7deb8401f6d 100644 --- a/package.json +++ b/package.json @@ -174,6 +174,7 @@ "jasmine-diff": "^0.1.3", "jasmine-jquery": "^2.1.1", "jest": "^24.1.0", + "jest-canvas-mock": "^2.1.2", "jest-environment-jsdom": "^24.0.0", "jest-junit": "^6.3.0", "jest-util": "^24.0.0", diff --git a/spec/javascripts/monitoring/charts/time_series_spec.js b/spec/frontend/monitoring/charts/time_series_spec.js index 42feaca616b..03d63ef7c3e 100644 --- a/spec/javascripts/monitoring/charts/time_series_spec.js +++ b/spec/frontend/monitoring/charts/time_series_spec.js @@ -2,21 +2,34 @@ import { shallowMount } from '@vue/test-utils'; import { createStore } from '~/monitoring/stores'; import { GlLink } from '@gitlab/ui'; import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; -import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper'; +import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper'; import TimeSeries from '~/monitoring/components/charts/time_series.vue'; import * as types from '~/monitoring/stores/mutation_types'; import { TEST_HOST } from 'spec/test_constants'; -import MonitoringMock, { deploymentData, mockProjectPath } from '../mock_data'; +import MonitoringMock, { + deploymentData, + mockProjectPath, +} from '../../../javascripts/monitoring/mock_data'; +import * as iconUtils from '~/lib/utils/icon_utils'; + +const mockSvgPathContent = 'mockSvgPathContent'; +const mockSha = 'mockSha'; +const mockWidgets = 'mockWidgets'; +const projectPath = `${TEST_HOST}${mockProjectPath}`; +const commitUrl = `${projectPath}/commit/${mockSha}`; + +jest.mock('~/lib/utils/icon_utils', () => ({ + getSvgIconPathContent: jest.fn().mockImplementation( + () => + new Promise(resolve => { + resolve(mockSvgPathContent); + }), + ), +})); describe('Time series component', () => { - const mockSha = 'mockSha'; - const mockWidgets = 'mockWidgets'; - const mockSvgPathContent = 'mockSvgPathContent'; - const projectPath = `${TEST_HOST}${mockProjectPath}`; - const commitUrl = `${projectPath}/commit/${mockSha}`; let mockGraphData; let makeTimeSeriesChart; - let spriteSpy; let store; beforeEach(() => { @@ -27,6 +40,7 @@ describe('Time series component', () => { makeTimeSeriesChart = (graphData, type) => shallowMount(TimeSeries, { + attachToDocument: true, propsData: { graphData: { ...graphData, type }, deploymentData: store.state.monitoringDashboard.deploymentData, @@ -38,10 +52,6 @@ describe('Time series component', () => { sync: false, store, }); - - spriteSpy = spyOnDependency(TimeSeries, 'getSvgIconPathContent').and.callFake( - () => new Promise(resolve => resolve(mockSvgPathContent)), - ); }); describe('general functions', () => { @@ -147,7 +157,7 @@ describe('Time series component', () => { }); it('gets svg path content', () => { - expect(spriteSpy).toHaveBeenCalledWith(mockSvgName); + expect(iconUtils.getSvgIconPathContent).toHaveBeenCalledWith(mockSvgName); }); it('sets svg path content', () => { @@ -171,7 +181,7 @@ describe('Time series component', () => { const mockWidth = 233; beforeEach(() => { - spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({ + jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ width: mockWidth, })); timeSeriesChart.vm.onResize(); @@ -227,7 +237,7 @@ describe('Time series component', () => { option: mockOption, }); - expect(timeSeriesChart.vm.chartOptions).toEqual(jasmine.objectContaining(mockOption)); + expect(timeSeriesChart.vm.chartOptions).toEqual(expect.objectContaining(mockOption)); }); it('additional series', () => { diff --git a/spec/javascripts/monitoring/panel_type_spec.js b/spec/frontend/monitoring/panel_type_spec.js index e3781117eaf..1cf302c25da 100644 --- a/spec/javascripts/monitoring/panel_type_spec.js +++ b/spec/frontend/monitoring/panel_type_spec.js @@ -1,17 +1,31 @@ import { shallowMount } from '@vue/test-utils'; +import AxiosMockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import PanelType from '~/monitoring/components/panel_type.vue'; import EmptyChart from '~/monitoring/components/charts/empty_chart.vue'; import TimeSeriesChart from '~/monitoring/components/charts/time_series.vue'; import AnomalyChart from '~/monitoring/components/charts/anomaly.vue'; -import { graphDataPrometheusQueryRange } from './mock_data'; +import { graphDataPrometheusQueryRange } from '../../javascripts/monitoring/mock_data'; import { anomalyMockGraphData } from '../../frontend/monitoring/mock_data'; import { createStore } from '~/monitoring/stores'; +global.IS_EE = true; +global.URL.createObjectURL = jest.fn(() => {}); + describe('Panel Type component', () => { + let axiosMock; let store; let panelType; const dashboardWidth = 100; + beforeEach(() => { + axiosMock = new AxiosMockAdapter(axios); + }); + + afterEach(() => { + axiosMock.reset(); + }); + describe('When no graphData is available', () => { let glEmptyChart; // Deep clone object before modifying @@ -25,6 +39,7 @@ describe('Panel Type component', () => { dashboardWidth, graphData: graphDataNoResult, }, + sync: false, }); }); @@ -57,14 +72,14 @@ describe('Panel Type component', () => { graphData: graphDataPrometheusQueryRange, }; - beforeEach(done => { + beforeEach(() => { store = createStore(); panelType = shallowMount(PanelType, { propsData, - sync: false, store, + sync: false, + attachToDocument: true, }); - panelType.vm.$nextTick(done); }); describe('Time Series Chart panel type', () => { diff --git a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb index c9399f591da..0d8cff3a295 100644 --- a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb @@ -1,11 +1,8 @@ # frozen_string_literal: true require 'fast_spec_helper' -require 'rspec-parameterized' describe Gitlab::SidekiqMiddleware::Metrics do - using RSpec::Parameterized::TableSyntax - let(:middleware) { described_class.new } let(:concurrency_metric) { double('concurrency metric') } @@ -48,7 +45,7 @@ describe Gitlab::SidekiqMiddleware::Metrics do let(:job) { {} } let(:job_status) { :done } let(:labels) { { queue: :test } } - let(:labels_with_job_status) { labels.merge(job_status: job_status) } + let(:labels_with_job_status) { { queue: :test, job_status: job_status } } let(:thread_cputime_before) { 1 } let(:thread_cputime_after) { 2 } @@ -60,75 +57,52 @@ describe Gitlab::SidekiqMiddleware::Metrics do let(:queue_duration_for_job) { 0.01 } - where(:worker_has_attributes, :worker_is_latency_sensitive, :worker_has_external_dependencies, :worker_feature_category, :worker_resource_boundary, :labels) do - false | false | false | nil | nil | { queue: :test } - true | false | false | nil | nil | { queue: :test } - true | true | false | nil | nil | { queue: :test, latency_sensitive: true } - true | false | true | nil | nil | { queue: :test, external_deps: true } - true | false | false | :authentication | nil | { queue: :test, feat_cat: :authentication } - true | false | false | nil | :cpu | { queue: :test, boundary: :cpu } - true | false | false | nil | :memory | { queue: :test, boundary: :memory } - true | false | false | nil | :unknown | { queue: :test } - true | true | true | :authentication | :cpu | { queue: :test, latency_sensitive: true, external_deps: true, feat_cat: :authentication, boundary: :cpu } - end + before do + allow(middleware).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after) + allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after) + allow(Gitlab::InstrumentationHelper).to receive(:queue_duration_for_job).with(job).and_return(queue_duration_for_job) - with_them do - before do - allow(middleware).to receive(:get_thread_cputime).and_return(thread_cputime_before, thread_cputime_after) - allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(monotonic_time_before, monotonic_time_after) - allow(Gitlab::InstrumentationHelper).to receive(:queue_duration_for_job).with(job).and_return(queue_duration_for_job) - - # Attributes - allow(worker).to receive(:include?).with(WorkerAttributes).and_return(worker_has_attributes) - allow(worker).to receive(:latency_sensitive_worker?).and_return(worker_is_latency_sensitive) - allow(worker).to receive(:worker_has_external_dependencies?).and_return(worker_has_external_dependencies) - allow(worker).to receive(:get_worker_resource_boundary).and_return(worker_resource_boundary) - allow(worker).to receive(:get_feature_category).and_return(worker_feature_category) - - expect(running_jobs_metric).to receive(:increment).with(labels, 1) - expect(running_jobs_metric).to receive(:increment).with(labels, -1) - - expect(queue_duration_seconds).to receive(:observe).with(labels, queue_duration_for_job) if queue_duration_for_job - expect(user_execution_seconds_metric).to receive(:observe).with(labels_with_job_status, thread_cputime_duration) - expect(completion_seconds_metric).to receive(:observe).with(labels_with_job_status, monotonic_time_duration) - end + expect(running_jobs_metric).to receive(:increment).with(labels, 1) + expect(running_jobs_metric).to receive(:increment).with(labels, -1) - it 'yields block' do - expect { |b| middleware.call(worker, job, :test, &b) }.to yield_control.once - end + expect(queue_duration_seconds).to receive(:observe).with(labels, queue_duration_for_job) if queue_duration_for_job + expect(user_execution_seconds_metric).to receive(:observe).with(labels_with_job_status, thread_cputime_duration) + expect(completion_seconds_metric).to receive(:observe).with(labels_with_job_status, monotonic_time_duration) + end - it 'sets queue specific metrics' do - middleware.call(worker, job, :test) { nil } - end + it 'yields block' do + expect { |b| middleware.call(worker, job, :test, &b) }.to yield_control.once + end - context 'when job_duration is not available' do - let(:queue_duration_for_job) { nil } + it 'sets queue specific metrics' do + middleware.call(worker, job, :test) { nil } + end - it 'does not set the queue_duration_seconds histogram' do - expect(queue_duration_seconds).not_to receive(:observe) + context 'when job_duration is not available' do + let(:queue_duration_for_job) { nil } - middleware.call(worker, job, :test) { nil } - end + it 'does not set the queue_duration_seconds histogram' do + middleware.call(worker, job, :test) { nil } end + end - context 'when job is retried' do - let(:job) { { 'retry_count' => 1 } } + context 'when job is retried' do + let(:job) { { 'retry_count' => 1 } } - it 'sets sidekiq_jobs_retried_total metric' do - expect(retried_total_metric).to receive(:increment) + it 'sets sidekiq_jobs_retried_total metric' do + expect(retried_total_metric).to receive(:increment) - middleware.call(worker, job, :test) { nil } - end + middleware.call(worker, job, :test) { nil } end + end - context 'when error is raised' do - let(:job_status) { :fail } + context 'when error is raised' do + let(:job_status) { :fail } - it 'sets sidekiq_jobs_failed_total and reraises' do - expect(failed_total_metric).to receive(:increment).with(labels, 1) + it 'sets sidekiq_jobs_failed_total and reraises' do + expect(failed_total_metric).to receive(:increment).with(labels, 1) - expect { middleware.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed") - end + expect { middleware.call(worker, job, :test) { raise StandardError, "Failed" } }.to raise_error(StandardError, "Failed") end end end diff --git a/spec/services/groups/group_links/destroy_service_spec.rb b/spec/services/groups/group_links/destroy_service_spec.rb new file mode 100644 index 00000000000..6f49b6eda94 --- /dev/null +++ b/spec/services/groups/group_links/destroy_service_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Groups::GroupLinks::DestroyService, '#execute' do + let(:user) { create(:user) } + + let_it_be(:group) { create(:group, :private) } + let_it_be(:shared_group) { create(:group, :private) } + let_it_be(:project) { create(:project, group: shared_group) } + + subject { described_class.new(nil, nil) } + + context 'single link' do + let!(:link) { create(:group_group_link, shared_group: shared_group, shared_with_group: group) } + + it 'destroys link' do + expect { subject.execute(link) }.to change { GroupGroupLink.count }.from(1).to(0) + end + + it 'revokes project authorization' do + group.add_developer(user) + + expect { subject.execute(link) }.to( + change { Ability.allowed?(user, :read_project, project) }.from(true).to(false)) + end + end + + context 'multiple links' do + let_it_be(:another_group) { create(:group, :private) } + let_it_be(:another_shared_group) { create(:group, :private) } + + let!(:links) do + [ + create(:group_group_link, shared_group: shared_group, shared_with_group: group), + create(:group_group_link, shared_group: shared_group, shared_with_group: another_group), + create(:group_group_link, shared_group: another_shared_group, shared_with_group: group), + create(:group_group_link, shared_group: another_shared_group, shared_with_group: another_group) + ] + end + + it 'updates project authorization once per group' do + expect(GroupGroupLink).to receive(:delete) + expect(group).to receive(:refresh_members_authorized_projects).once + expect(another_group).to receive(:refresh_members_authorized_projects).once + + subject.execute(links) + end + + it 'rolls back changes when error happens' do + group.add_developer(user) + + expect(group).to receive(:refresh_members_authorized_projects).once.and_call_original + expect(another_group).to( + receive(:refresh_members_authorized_projects).and_raise('boom')) + + expect { subject.execute(links) }.to raise_error('boom') + + expect(GroupGroupLink.count).to eq(links.length) + expect(Ability.allowed?(user, :read_project, project)).to be_truthy + end + end +end diff --git a/spec/workers/remove_expired_group_links_worker_spec.rb b/spec/workers/remove_expired_group_links_worker_spec.rb index 10d9aa37dee..9557aa3086c 100644 --- a/spec/workers/remove_expired_group_links_worker_spec.rb +++ b/spec/workers/remove_expired_group_links_worker_spec.rb @@ -4,23 +4,54 @@ require 'spec_helper' describe RemoveExpiredGroupLinksWorker do describe '#perform' do - let!(:expired_project_group_link) { create(:project_group_link, expires_at: 1.hour.ago) } - let!(:project_group_link_expiring_in_future) { create(:project_group_link, expires_at: 10.days.from_now) } - let!(:non_expiring_project_group_link) { create(:project_group_link, expires_at: nil) } + context 'ProjectGroupLinks' do + let!(:expired_project_group_link) { create(:project_group_link, expires_at: 1.hour.ago) } + let!(:project_group_link_expiring_in_future) { create(:project_group_link, expires_at: 10.days.from_now) } + let!(:non_expiring_project_group_link) { create(:project_group_link, expires_at: nil) } - it 'removes expired group links' do - expect { subject.perform }.to change { ProjectGroupLink.count }.by(-1) - expect(ProjectGroupLink.find_by(id: expired_project_group_link.id)).to be_nil - end + it 'removes expired group links' do + expect { subject.perform }.to change { ProjectGroupLink.count }.by(-1) + expect(ProjectGroupLink.find_by(id: expired_project_group_link.id)).to be_nil + end + + it 'leaves group links that expire in the future' do + subject.perform + expect(project_group_link_expiring_in_future.reload).to be_present + end - it 'leaves group links that expire in the future' do - subject.perform - expect(project_group_link_expiring_in_future.reload).to be_present + it 'leaves group links that do not expire at all' do + subject.perform + expect(non_expiring_project_group_link.reload).to be_present + end end - it 'leaves group links that do not expire at all' do - subject.perform - expect(non_expiring_project_group_link.reload).to be_present + context 'GroupGroupLinks' do + let(:mock_destroy_service) { instance_double(Groups::GroupLinks::DestroyService) } + + before do + allow(Groups::GroupLinks::DestroyService).to( + receive(:new).and_return(mock_destroy_service)) + end + + context 'expired GroupGroupLink exists' do + before do + create(:group_group_link, expires_at: 1.hour.ago) + end + + it 'calls Groups::GroupLinks::DestroyService' do + expect(mock_destroy_service).to receive(:execute).once + + subject.perform + end + end + + context 'expired GroupGroupLink does not exist' do + it 'does not call Groups::GroupLinks::DestroyService' do + expect(mock_destroy_service).not_to receive(:execute) + + subject.perform + end + end end end end diff --git a/yarn.lock b/yarn.lock index 9beb499025c..515ac2b3549 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2991,7 +2991,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^0.5.3: +color-convert@^0.5.3, color-convert@~0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" integrity sha1-vbbGnOZg+t/+CwAHzER+G59ygr0= @@ -3468,6 +3468,11 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssfontparser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3" + integrity sha1-9AIvyPlwDGgCnVQghK+69CWj8+M= + cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": version "0.3.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797" @@ -6877,6 +6882,14 @@ jed@^1.1.1: resolved "https://registry.yarnpkg.com/jed/-/jed-1.1.1.tgz#7a549bbd9ffe1585b0cd0a191e203055bee574b4" integrity sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ= +jest-canvas-mock@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.1.2.tgz#0d16c9f91534f773fd132fc289f2e6b6db8faa28" + integrity sha512-1VI4PK4/X70yrSjYScYVkYJYbXYlZLKJkUrAlyHjQsfolv64aoFyIrmMDtqCjpYrpVvWYEcAGUaYv5DVJj00oQ== + dependencies: + cssfontparser "^1.2.1" + parse-color "^1.0.0" + jest-changed-files@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.8.0.tgz#7e7eb21cf687587a85e50f3d249d1327e15b157b" @@ -9123,6 +9136,13 @@ parse-asn1@^5.0.0: evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" +parse-color@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-color/-/parse-color-1.0.0.tgz#7b748b95a83f03f16a94f535e52d7f3d94658619" + integrity sha1-e3SLlag/A/FqlPU15S1/PZRlhhk= + dependencies: + color-convert "~0.5.0" + parse-entities@^1.0.2, parse-entities@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.0.tgz#9deac087661b2e36814153cb78d7e54a4c5fd6f4" |