Commit a92d6b36 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 39a48637
# 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
......@@ -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
---
title: Add worker attributes to Sidekiq metrics
merge_request: 19491
author:
type: other
......@@ -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.
......
......@@ -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',
......
......@@ -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')
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -4,8 +4,12 @@ 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
class MetricsBasedStageEvent < StageEvent
# rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query)
query.joins(:metrics)
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
......
......@@ -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
......
......@@ -4,7 +4,7 @@ module Gitlab
module Analytics
module CycleAnalytics
module StageEvents
class ProductionStageEnd < SimpleStageEvent
class ProductionStageEnd < StageEvent
def self.name
PlanStageStart.name
end
......
......@@ -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
......
......@@ -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 ""
......
......@@ -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', () => {
......
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', () => {
......
# 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
......
# 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