Commit 59ac8880 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '12196-additional-ca-events' into 'master'

Implement customizable CA events

See merge request gitlab-org/gitlab!16118
parents f2876fb0 f4d14663
# frozen_string_literal: true
module EE
module Gitlab
module Analytics
module CycleAnalytics
module StageEvents
extend ActiveSupport::Concern
prepended do
extend ::Gitlab::Utils::StrongMemoize
end
EE_ENUM_MAPPING = {
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueClosed => 3,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAddedToBoard => 4,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAssociatedWithMilestone => 5,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit => 6,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueLastEdited => 7,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestClosed => 105,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastEdited => 106
}.freeze
EE_EVENTS = EE_ENUM_MAPPING.keys.freeze
EE_PAIRING_RULES = {
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueCreated => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueClosed,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAddedToBoard,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAssociatedWithMilestone,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueLastEdited
],
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAddedToBoard => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueClosed,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAssociatedWithMilestone,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueLastEdited
],
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAssociatedWithMilestone => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueClosed,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAddedToBoard,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueLastEdited
],
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueClosed,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAssociatedWithMilestone,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAddedToBoard,
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueLastEdited
],
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueClosed => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::IssueLastEdited
],
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestClosed,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildStarted,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildFinished,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastEdited
],
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestClosed => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastEdited
],
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastEdited
],
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildStarted => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestClosed,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastEdited,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged
],
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastBuildFinished => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestClosed,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastEdited,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged
],
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged => [
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestClosed,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestFirstDeployedToProduction,
::Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastEdited
]
}.freeze
class_methods do
extend ::Gitlab::Utils::Override
override :events
def events
strong_memoize(:events) do
super + EE_EVENTS
end
end
override :pairing_rules
def pairing_rules
strong_memoize(:pairing_rules) do
# merging two hashes with array values
::Gitlab::Analytics::CycleAnalytics::StageEvents::PAIRING_RULES.merge(EE_PAIRING_RULES) do |klass, foss_events, ee_events|
foss_events + ee_events
end
end
end
override :enum_mapping
def enum_mapping
strong_memoize(:enum_mapping) do
super.merge(EE_ENUM_MAPPING)
end
end
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module StageEvents
class IssueClosed < StageEvent
def self.name
s_("CycleAnalyticsEvent|Issue closed")
end
def self.identifier
:issue_closed
end
def object_type
Issue
end
def timestamp_projection
issue_table[:closed_at]
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module StageEvents
class IssueFirstAddedToBoard < MetricsBasedStageEvent
def self.name
s_("CycleAnalyticsEvent|Issue first added to a board")
end
def self.identifier
:issue_first_added_to_board
end
def object_type
Issue
end
def timestamp_projection
issue_metrics_table[:first_added_to_board_at]
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module StageEvents
class IssueFirstAssociatedWithMilestone < MetricsBasedStageEvent
def self.name
s_("CycleAnalyticsEvent|Issue first associated with a milestone")
end
def self.identifier
:issue_first_associated_with_milestone
end
def object_type
Issue
end
def timestamp_projection
issue_metrics_table[:first_associated_with_milestone_at]
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module StageEvents
class IssueFirstMentionedInCommit < MetricsBasedStageEvent
def self.name
s_("CycleAnalyticsEvent|Issue first mentioned in a commit")
end
def self.identifier
:issue_first_mentioned_in_commit
end
def object_type
Issue
end
def timestamp_projection
issue_metrics_table[:first_mentioned_in_commit_at]
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module StageEvents
class IssueLastEdited < StageEvent
def self.name
s_("CycleAnalyticsEvent|Issue last edited")
end
def self.identifier
:issue_last_edited
end
def object_type
Issue
end
def timestamp_projection
issue_table[:last_edited_at]
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module StageEvents
class MergeRequestClosed < MetricsBasedStageEvent
def self.name
s_("CycleAnalyticsEvent|Merge request closed")
end
def self.identifier
:merge_request_closed
end
def object_type
MergeRequest
end
def timestamp_projection
mr_metrics_table[:latest_closed_at]
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
module StageEvents
class MergeRequestLastEdited < StageEvent
def self.name
s_("CycleAnalyticsEvent|Merge request last edited")
end
def self.identifier
:merge_request_last_edited
end
def object_type
MergeRequest
end
def timestamp_projection
mr_table[:last_edited_at]
end
end
end
end
end
end
...@@ -43,12 +43,15 @@ describe('CustomStageForm', () => { ...@@ -43,12 +43,15 @@ describe('CustomStageForm', () => {
invalidFeedback: '.invalid-feedback', invalidFeedback: '.invalid-feedback',
}; };
function selectDropdownOption(_wrapper, dropdown, index) { function getDropdownOption(_wrapper, dropdown, index) {
_wrapper return _wrapper
.find(dropdown) .find(dropdown)
.findAll('option') .findAll('option')
.at(index) .at(index);
.setSelected(); }
function selectDropdownOption(_wrapper, dropdown, index) {
getDropdownOption(_wrapper, dropdown, index).setSelected();
} }
describe('Empty form', () => { describe('Empty form', () => {
...@@ -97,21 +100,6 @@ describe('CustomStageForm', () => { ...@@ -97,21 +100,6 @@ describe('CustomStageForm', () => {
`<option value="${ev.identifier}">${ev.name}</option>`, `<option value="${ev.identifier}">${ev.name}</option>`,
); );
}); });
stopEvents.forEach(ev => {
expect(select.html()).not.toHaveHtml(
`<option value="${ev.identifier}">${ev.name}</option>`,
);
});
});
it('will exclude stop events for the dropdown', () => {
const select = wrapper.find(sel.startEvent);
stopEvents.forEach(ev => {
expect(select.html()).not.toHaveHtml(
`<option value="${ev.identifier}">${ev.name}</option>`,
);
});
}); });
}); });
...@@ -163,6 +151,9 @@ describe('CustomStageForm', () => { ...@@ -163,6 +151,9 @@ describe('CustomStageForm', () => {
}); });
describe('Stop event', () => { describe('Stop event', () => {
const index = 2;
const currAllowed = startEvents[index].allowedEndEvents;
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({}, false); wrapper = createComponent({}, false);
}); });
...@@ -192,48 +183,51 @@ describe('CustomStageForm', () => { ...@@ -192,48 +183,51 @@ describe('CustomStageForm', () => {
it('will update the list of stop events when a start event is changed', done => { it('will update the list of stop events when a start event is changed', done => {
let stopOptions = wrapper.find(sel.stopEvent).findAll('option'); let stopOptions = wrapper.find(sel.stopEvent).findAll('option');
const selectedStartEventIndex = 1;
const selectedStartEvent = startEvents[selectedStartEventIndex];
expect(stopOptions.length).toEqual(1); expect(stopOptions.length).toEqual(1);
selectDropdownOption(wrapper, sel.startEvent, 1); selectDropdownOption(wrapper, sel.startEvent, selectedStartEventIndex);
Vue.nextTick(() => { Vue.nextTick(() => {
stopOptions = wrapper.find(sel.stopEvent).findAll('option'); stopOptions = wrapper.find(sel.stopEvent);
expect(stopOptions.length).toEqual(2); selectedStartEvent.allowedEndEvents.forEach(identifier => {
expect(stopOptions.html()).toContain(identifier);
});
done(); done();
}); });
}); });
it('will only display valid stop events allowed for the selected start event', done => { it('will display all the valid stop events', done => {
let stopOptions = wrapper.find(sel.stopEvent).findAll('option'); let stopOptions = wrapper.find(sel.stopEvent).findAll('option');
const index = 2; const possibleEndEvents = stopEvents.filter(ev => currAllowed.includes(ev.identifier));
const selectedStopEventIdentifer = startEvents[index].allowedEndEvents[0];
const selectedStopEvent = stopEvents.find(
ev => ev.identifier === selectedStopEventIdentifer,
);
expect(stopOptions.at(0).html()).toEqual('<option value="">Select stop event</option>'); expect(stopOptions.at(0).html()).toEqual('<option value="">Select stop event</option>');
selectDropdownOption(wrapper, sel.startEvent, index); selectDropdownOption(wrapper, sel.startEvent, index);
Vue.nextTick(() => { Vue.nextTick(() => {
stopOptions = wrapper.find(sel.stopEvent).findAll('option'); stopOptions = wrapper.find(sel.stopEvent);
[ possibleEndEvents.forEach(({ name, identifier }) => {
{ name: 'Select stop event', identifier: '' }, expect(stopOptions.html()).toContain(`<option value="${identifier}">${name}</option>`);
{
name: selectedStopEvent.name,
identifier: selectedStopEvent.identifier,
},
].forEach(({ name, identifier }, i) => {
expect(stopOptions.at(i).html()).toEqual(
`<option value="${identifier}">${name}</option>`,
);
}); });
done();
});
});
it('will not display stop events that are not in the list of allowed stop events', done => {
let stopOptions = wrapper.find(sel.stopEvent).findAll('option');
const excludedEndEvents = stopEvents.filter(ev => !currAllowed.includes(ev.identifier));
expect(stopOptions.at(0).html()).toEqual('<option value="">Select stop event</option>');
selectDropdownOption(wrapper, sel.startEvent, index);
Vue.nextTick(() => {
stopOptions = wrapper.find(sel.stopEvent);
[ excludedEndEvents.forEach(({ name, identifier }) => {
{ name: 'Issue created', identifier: 'issue_created' },
{ name: 'Merge request closed', identifier: 'merge_request_closed' },
].forEach(({ name, identifier }) => {
expect(wrapper.find(sel.stopEvent).html()).not.toHaveHtml( expect(wrapper.find(sel.stopEvent).html()).not.toHaveHtml(
`<option value="${identifier}">${name}</option>`, `<option value="${identifier}">${name}</option>`,
); );
...@@ -378,10 +372,7 @@ describe('CustomStageForm', () => { ...@@ -378,10 +372,7 @@ describe('CustomStageForm', () => {
describe('with all fields set', () => { describe('with all fields set', () => {
const startEventIndex = 2; const startEventIndex = 2;
const firstStopEventIdentifier = startEvents[startEventIndex].allowedEndEvents[0]; const stopEventIndex = 1;
const stopEventIndex = stopEvents.findIndex(
ev => ev.identifier === firstStopEventIdentifier,
);
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({}, false); wrapper = createComponent({}, false);
...@@ -389,7 +380,7 @@ describe('CustomStageForm', () => { ...@@ -389,7 +380,7 @@ describe('CustomStageForm', () => {
selectDropdownOption(wrapper, sel.startEvent, startEventIndex); selectDropdownOption(wrapper, sel.startEvent, startEventIndex);
return Vue.nextTick(() => { return Vue.nextTick(() => {
selectDropdownOption(wrapper, sel.stopEvent, 1); selectDropdownOption(wrapper, sel.stopEvent, stopEventIndex);
wrapper.find(sel.name).setValue('Cool stage'); wrapper.find(sel.name).setValue('Cool stage');
}); });
}); });
...@@ -408,13 +399,15 @@ describe('CustomStageForm', () => { ...@@ -408,13 +399,15 @@ describe('CustomStageForm', () => {
it('`submit` event receives the latest data', () => { it('`submit` event receives the latest data', () => {
expect(wrapper.emitted().submit).toBeUndefined(); expect(wrapper.emitted().submit).toBeUndefined();
const startEv = startEvents[startEventIndex];
const selectedStopEvent = getDropdownOption(wrapper, sel.stopEvent, stopEventIndex);
const res = [ const res = [
{ {
name: 'Cool stage', name: 'Cool stage',
startEvent: startEvents[startEventIndex].identifier, startEvent: startEv.identifier,
startEventLabel: null, startEventLabel: null,
stopEvent: stopEvents[stopEventIndex].identifier, stopEvent: selectedStopEvent.attributes('value'),
stopEventLabel: null, stopEventLabel: null,
}, },
]; ];
......
...@@ -65,7 +65,13 @@ const { events: rawCustomStageEvents } = getJSONFixture('analytics/cycle_analyti ...@@ -65,7 +65,13 @@ const { events: rawCustomStageEvents } = getJSONFixture('analytics/cycle_analyti
const camelCasedStageEvents = rawCustomStageEvents.map(deepCamelCase); const camelCasedStageEvents = rawCustomStageEvents.map(deepCamelCase);
export const customStageStartEvents = camelCasedStageEvents.filter(ev => ev.canBeStartEvent); export const customStageStartEvents = camelCasedStageEvents.filter(ev => ev.canBeStartEvent);
export const customStageStopEvents = camelCasedStageEvents.filter(ev => !ev.canBeStartEvent);
// find get all the possible stop events
const allowedEndEventIds = new Set(customStageStartEvents.flatMap(e => e.allowedEndEvents));
export const customStageStopEvents = camelCasedStageEvents.filter(ev =>
allowedEndEventIds.has(ev.identifier),
);
// TODO: the shim below should be removed once we have label events seeding // TODO: the shim below should be removed once we have label events seeding
export const labelStartEvent = { ...customStageStartEvents[0], type: 'label' }; export const labelStartEvent = { ...customStageStartEvents[0], type: 'label' };
......
...@@ -11,7 +11,6 @@ import { ...@@ -11,7 +11,6 @@ import {
labelStartEvent, labelStartEvent,
labelStopEvent, labelStopEvent,
customStageStartEvents as startEvents, customStageStartEvents as startEvents,
customStageStopEvents as stopEvents,
} from './mock_data'; } from './mock_data';
const labelEvents = [labelStartEvent, labelStopEvent].map(i => i.identifier); const labelEvents = [labelStartEvent, labelStopEvent].map(i => i.identifier);
...@@ -23,9 +22,11 @@ describe('Cycle analytics utils', () => { ...@@ -23,9 +22,11 @@ describe('Cycle analytics utils', () => {
}); });
it('will return false for input that is not a start event', () => { it('will return false for input that is not a start event', () => {
[stopEvents[0], {}, [], null, undefined].forEach(ev => { [{ identifier: 'fake-event', canBeStartEvent: false }, {}, [], null, undefined].forEach(
expect(isStartEvent(ev)).toEqual(false); ev => {
}); expect(isStartEvent(ev)).toEqual(false);
},
);
}); });
}); });
......
...@@ -62,7 +62,7 @@ describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -62,7 +62,7 @@ describe Gitlab::Analytics::CycleAnalytics::DataCollector do
shared_examples 'test various start and end event combinations' do shared_examples 'test various start and end event combinations' do
context 'when `Issue` based stage is given' do context 'when `Issue` based stage is given' do
context 'between issue creation time and closing time' do context 'between issue creation time and issue first mentioned in commit time' do
let(:start_event_identifier) { :issue_created } let(:start_event_identifier) { :issue_created }
let(:end_event_identifier) { :issue_first_mentioned_in_commit } let(:end_event_identifier) { :issue_first_mentioned_in_commit }
...@@ -76,6 +76,68 @@ describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -76,6 +76,68 @@ describe Gitlab::Analytics::CycleAnalytics::DataCollector do
it_behaves_like 'custom cycle analytics stage' it_behaves_like 'custom cycle analytics stage'
end end
describe 'between issue creation time and closing time' do
let(:start_event_identifier) { :issue_created }
let(:end_event_identifier) { :issue_closed }
def create_data_for_start_event(example_class)
create(:issue, :opened, project: example_class.project)
end
def create_data_for_end_event(resource, example_class)
resource.close!
end
it_behaves_like 'custom cycle analytics stage'
end
describe 'between issue first mentioned in commit and first associated with milestone time' do
let(:start_event_identifier) { :issue_first_mentioned_in_commit }
let(:end_event_identifier) { :issue_first_associated_with_milestone }
def create_data_for_start_event(example_class)
issue = create(:issue, :opened, project: example_class.project)
issue.metrics.update!(first_mentioned_in_commit_at: Time.now)
issue
end
def create_data_for_end_event(resource, example_class)
resource.metrics.update!(first_associated_with_milestone_at: Time.now)
end
it_behaves_like 'custom cycle analytics stage'
end
describe 'between issue creation time and first added to board time' do
let(:start_event_identifier) { :issue_created }
let(:end_event_identifier) { :issue_first_added_to_board }
def create_data_for_start_event(example_class)
create(:issue, :opened, project: example_class.project)
end
def create_data_for_end_event(resource, example_class)
resource.metrics.update!(first_added_to_board_at: Time.now)
end
it_behaves_like 'custom cycle analytics stage'
end
describe 'between issue creation time and last edit time' do
let(:start_event_identifier) { :issue_created }
let(:end_event_identifier) { :issue_last_edited }
def create_data_for_start_event(example_class)
create(:issue, :opened, project: example_class.project)
end
def create_data_for_end_event(resource, example_class)
resource.update!(last_edited_at: Time.now)
end
it_behaves_like 'custom cycle analytics stage'
end
end end
context 'when `MergeRequest` based stage is given' do context 'when `MergeRequest` based stage is given' do
...@@ -127,6 +189,36 @@ describe Gitlab::Analytics::CycleAnalytics::DataCollector do ...@@ -127,6 +189,36 @@ describe Gitlab::Analytics::CycleAnalytics::DataCollector do
it_behaves_like 'custom cycle analytics stage' it_behaves_like 'custom cycle analytics stage'
end end
describe 'between merge request creation time and close time' do
let(:start_event_identifier) { :merge_request_created }
let(:end_event_identifier) { :merge_request_closed }
def create_data_for_start_event(example_class)
create(:merge_request, source_project: example_class.project, allow_broken: true)
end
def create_data_for_end_event(resource, example_class)
resource.metrics.update!(latest_closed_at: Time.now)
end
it_behaves_like 'custom cycle analytics stage'
end
describe 'between merge request creation time and last edit time' do
let(:start_event_identifier) { :merge_request_created }
let(:end_event_identifier) { :merge_request_last_edited }
def create_data_for_start_event(example_class)
create(:merge_request, source_project: example_class.project, allow_broken: true)
end
def create_data_for_end_event(resource, example_class)
resource.update!(last_edited_at: Time.now)
end
it_behaves_like 'custom cycle analytics stage'
end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueClosed do
it_behaves_like 'cycle analytics event'
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAddedToBoard do
it_behaves_like 'cycle analytics event'
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstAssociatedWithMilestone do
it_behaves_like 'cycle analytics event'
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueFirstMentionedInCommit do
it_behaves_like 'cycle analytics event'
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::StageEvents::IssueLastEdited do
it_behaves_like 'cycle analytics event'
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestClosed do
it_behaves_like 'cycle analytics event'
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestLastEdited do
it_behaves_like 'cycle analytics event'
end
...@@ -47,27 +47,29 @@ module Gitlab ...@@ -47,27 +47,29 @@ module Gitlab
] ]
}.freeze }.freeze
def [](identifier) def self.[](identifier)
events.find { |e| e.identifier.to_s.eql?(identifier.to_s) } || raise(KeyError) events.find { |e| e.identifier.to_s.eql?(identifier.to_s) } || raise(KeyError)
end end
# hash for defining ActiveRecord enum: identifier => number # hash for defining ActiveRecord enum: identifier => number
def to_enum def self.to_enum
ENUM_MAPPING.each_with_object({}) { |(k, v), hash| hash[k.identifier] = v } enum_mapping.each_with_object({}) { |(k, v), hash| hash[k.identifier] = v }
end end
# will be overridden in EE with custom events def self.pairing_rules
def pairing_rules
PAIRING_RULES PAIRING_RULES
end end
# will be overridden in EE with custom events def self.events
def events
EVENTS EVENTS
end end
module_function :[], :to_enum, :pairing_rules, :events def self.enum_mapping
ENUM_MAPPING
end
end end
end end
end end
end end
Gitlab::Analytics::CycleAnalytics::StageEvents.prepend_if_ee('::EE::Gitlab::Analytics::CycleAnalytics::StageEvents')
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class CodeStageStart < SimpleStageEvent class CodeStageStart < StageEvent
def self.name def self.name
s_("CycleAnalyticsEvent|Issue first mentioned in a commit") s_("CycleAnalyticsEvent|Issue first mentioned in a commit")
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class IssueCreated < SimpleStageEvent class IssueCreated < StageEvent
def self.name def self.name
s_("CycleAnalyticsEvent|Issue created") s_("CycleAnalyticsEvent|Issue created")
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class IssueFirstMentionedInCommit < SimpleStageEvent class IssueFirstMentionedInCommit < MetricsBasedStageEvent
def self.name def self.name
s_("CycleAnalyticsEvent|Issue first mentioned in a commit") s_("CycleAnalyticsEvent|Issue first mentioned in a commit")
end end
...@@ -20,12 +20,6 @@ module Gitlab ...@@ -20,12 +20,6 @@ module Gitlab
def timestamp_projection def timestamp_projection
issue_metrics_table[:first_mentioned_in_commit_at] issue_metrics_table[:first_mentioned_in_commit_at]
end end
# rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query)
query.joins(:metrics)
end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class IssueStageEnd < SimpleStageEvent class IssueStageEnd < MetricsBasedStageEvent
def self.name def self.name
PlanStageStart.name PlanStageStart.name
end end
...@@ -26,7 +26,7 @@ module Gitlab ...@@ -26,7 +26,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query) 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 end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class MergeRequestCreated < SimpleStageEvent class MergeRequestCreated < StageEvent
def self.name def self.name
s_("CycleAnalyticsEvent|Merge request created") s_("CycleAnalyticsEvent|Merge request created")
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class MergeRequestFirstDeployedToProduction < SimpleStageEvent class MergeRequestFirstDeployedToProduction < MetricsBasedStageEvent
def self.name def self.name
s_("CycleAnalyticsEvent|Merge request first deployed to production") s_("CycleAnalyticsEvent|Merge request first deployed to production")
end end
...@@ -23,7 +23,7 @@ module Gitlab ...@@ -23,7 +23,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query) 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 end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class MergeRequestLastBuildFinished < SimpleStageEvent class MergeRequestLastBuildFinished < MetricsBasedStageEvent
def self.name def self.name
s_("CycleAnalyticsEvent|Merge request last build finish time") s_("CycleAnalyticsEvent|Merge request last build finish time")
end end
...@@ -20,12 +20,6 @@ module Gitlab ...@@ -20,12 +20,6 @@ module Gitlab
def timestamp_projection def timestamp_projection
mr_metrics_table[:latest_build_finished_at] mr_metrics_table[:latest_build_finished_at]
end end
# rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query)
query.joins(:metrics)
end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class MergeRequestLastBuildStarted < SimpleStageEvent class MergeRequestLastBuildStarted < MetricsBasedStageEvent
def self.name def self.name
s_("CycleAnalyticsEvent|Merge request last build start time") s_("CycleAnalyticsEvent|Merge request last build start time")
end end
...@@ -20,12 +20,6 @@ module Gitlab ...@@ -20,12 +20,6 @@ module Gitlab
def timestamp_projection def timestamp_projection
mr_metrics_table[:latest_build_started_at] mr_metrics_table[:latest_build_started_at]
end end
# rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query)
query.joins(:metrics)
end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class MergeRequestMerged < SimpleStageEvent class MergeRequestMerged < MetricsBasedStageEvent
def self.name def self.name
s_("CycleAnalyticsEvent|Merge request merged") s_("CycleAnalyticsEvent|Merge request merged")
end end
...@@ -20,12 +20,6 @@ module Gitlab ...@@ -20,12 +20,6 @@ module Gitlab
def timestamp_projection def timestamp_projection
mr_metrics_table[:merged_at] mr_metrics_table[:merged_at]
end end
# rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query)
query.joins(:metrics)
end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
end end
......
...@@ -4,8 +4,12 @@ module Gitlab ...@@ -4,8 +4,12 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
# Represents a simple event that usually refers to one database column and does not require additional user input class MetricsBasedStageEvent < StageEvent
class SimpleStageEvent < StageEvent # rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query)
query.joins(:metrics)
end
# rubocop: enable CodeReuse/ActiveRecord
end end
end end
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class PlanStageStart < SimpleStageEvent class PlanStageStart < MetricsBasedStageEvent
def self.name def self.name
s_("CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board") s_("CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board")
end end
...@@ -26,8 +26,7 @@ module Gitlab ...@@ -26,8 +26,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def apply_query_customization(query) def apply_query_customization(query)
query super
.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))) .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)) .where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil))
end end
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module Analytics module Analytics
module CycleAnalytics module CycleAnalytics
module StageEvents module StageEvents
class ProductionStageEnd < SimpleStageEvent class ProductionStageEnd < StageEvent
def self.name def self.name
PlanStageStart.name PlanStageStart.name
end end
......
...@@ -4995,15 +4995,30 @@ msgstr "" ...@@ -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." msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr "" msgstr ""
msgid "CycleAnalyticsEvent|Issue closed"
msgstr ""
msgid "CycleAnalyticsEvent|Issue created" msgid "CycleAnalyticsEvent|Issue created"
msgstr "" 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" msgid "CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board"
msgstr "" msgstr ""
msgid "CycleAnalyticsEvent|Issue first mentioned in a commit" msgid "CycleAnalyticsEvent|Issue first mentioned in a commit"
msgstr "" msgstr ""
msgid "CycleAnalyticsEvent|Issue last edited"
msgstr ""
msgid "CycleAnalyticsEvent|Merge request closed"
msgstr ""
msgid "CycleAnalyticsEvent|Merge request created" msgid "CycleAnalyticsEvent|Merge request created"
msgstr "" msgstr ""
...@@ -5016,6 +5031,9 @@ msgstr "" ...@@ -5016,6 +5031,9 @@ msgstr ""
msgid "CycleAnalyticsEvent|Merge request last build start time" msgid "CycleAnalyticsEvent|Merge request last build start time"
msgstr "" msgstr ""
msgid "CycleAnalyticsEvent|Merge request last edited"
msgstr ""
msgid "CycleAnalyticsEvent|Merge request merged" msgid "CycleAnalyticsEvent|Merge request merged"
msgstr "" msgstr ""
......
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