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', () => {
invalidFeedback: '.invalid-feedback',
};
function selectDropdownOption(_wrapper, dropdown, index) {
_wrapper
function getDropdownOption(_wrapper, dropdown, index) {
return _wrapper
.find(dropdown)
.findAll('option')
.at(index)
.setSelected();
.at(index);
}
function selectDropdownOption(_wrapper, dropdown, index) {
getDropdownOption(_wrapper, dropdown, index).setSelected();
}
describe('Empty form', () => {
......@@ -97,21 +100,6 @@ describe('CustomStageForm', () => {
`<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', () => {
});
describe('Stop event', () => {
const index = 2;
const currAllowed = startEvents[index].allowedEndEvents;
beforeEach(() => {
wrapper = createComponent({}, false);
});
......@@ -192,48 +183,51 @@ describe('CustomStageForm', () => {
it('will update the list of stop events when a start event is changed', done => {
let stopOptions = wrapper.find(sel.stopEvent).findAll('option');
const selectedStartEventIndex = 1;
const selectedStartEvent = startEvents[selectedStartEventIndex];
expect(stopOptions.length).toEqual(1);
selectDropdownOption(wrapper, sel.startEvent, 1);
selectDropdownOption(wrapper, sel.startEvent, selectedStartEventIndex);
Vue.nextTick(() => {
stopOptions = wrapper.find(sel.stopEvent).findAll('option');
expect(stopOptions.length).toEqual(2);
stopOptions = wrapper.find(sel.stopEvent);
selectedStartEvent.allowedEndEvents.forEach(identifier => {
expect(stopOptions.html()).toContain(identifier);
});
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');
const index = 2;
const selectedStopEventIdentifer = startEvents[index].allowedEndEvents[0];
const selectedStopEvent = stopEvents.find(
ev => ev.identifier === selectedStopEventIdentifer,
);
const possibleEndEvents = 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).findAll('option');
stopOptions = wrapper.find(sel.stopEvent);
[
{ name: 'Select stop event', identifier: '' },
{
name: selectedStopEvent.name,
identifier: selectedStopEvent.identifier,
},
].forEach(({ name, identifier }, i) => {
expect(stopOptions.at(i).html()).toEqual(
`<option value="${identifier}">${name}</option>`,
);
possibleEndEvents.forEach(({ name, identifier }) => {
expect(stopOptions.html()).toContain(`<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);
[
{ name: 'Issue created', identifier: 'issue_created' },
{ name: 'Merge request closed', identifier: 'merge_request_closed' },
].forEach(({ name, identifier }) => {
Vue.nextTick(() => {
stopOptions = wrapper.find(sel.stopEvent);
excludedEndEvents.forEach(({ name, identifier }) => {
expect(wrapper.find(sel.stopEvent).html()).not.toHaveHtml(
`<option value="${identifier}">${name}</option>`,
);
......@@ -378,10 +372,7 @@ describe('CustomStageForm', () => {
describe('with all fields set', () => {
const startEventIndex = 2;
const firstStopEventIdentifier = startEvents[startEventIndex].allowedEndEvents[0];
const stopEventIndex = stopEvents.findIndex(
ev => ev.identifier === firstStopEventIdentifier,
);
const stopEventIndex = 1;
beforeEach(() => {
wrapper = createComponent({}, false);
......@@ -389,7 +380,7 @@ describe('CustomStageForm', () => {
selectDropdownOption(wrapper, sel.startEvent, startEventIndex);
return Vue.nextTick(() => {
selectDropdownOption(wrapper, sel.stopEvent, 1);
selectDropdownOption(wrapper, sel.stopEvent, stopEventIndex);
wrapper.find(sel.name).setValue('Cool stage');
});
});
......@@ -408,13 +399,15 @@ describe('CustomStageForm', () => {
it('`submit` event receives the latest data', () => {
expect(wrapper.emitted().submit).toBeUndefined();
const startEv = startEvents[startEventIndex];
const selectedStopEvent = getDropdownOption(wrapper, sel.stopEvent, stopEventIndex);
const res = [
{
name: 'Cool stage',
startEvent: startEvents[startEventIndex].identifier,
startEvent: startEv.identifier,
startEventLabel: null,
stopEvent: stopEvents[stopEventIndex].identifier,
stopEvent: selectedStopEvent.attributes('value'),
stopEventLabel: null,
},
];
......
......@@ -65,7 +65,13 @@ const { events: rawCustomStageEvents } = getJSONFixture('analytics/cycle_analyti
const camelCasedStageEvents = rawCustomStageEvents.map(deepCamelCase);
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
export const labelStartEvent = { ...customStageStartEvents[0], type: 'label' };
......
......@@ -11,7 +11,6 @@ import {
labelStartEvent,
labelStopEvent,
customStageStartEvents as startEvents,
customStageStopEvents as stopEvents,
} from './mock_data';
const labelEvents = [labelStartEvent, labelStopEvent].map(i => i.identifier);
......@@ -23,9 +22,11 @@ describe('Cycle analytics utils', () => {
});
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(
ev => {
expect(isStartEvent(ev)).toEqual(false);
});
},
);
});
});
......
......@@ -62,7 +62,7 @@ describe Gitlab::Analytics::CycleAnalytics::DataCollector do
shared_examples 'test various start and end event combinations' 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(:end_event_identifier) { :issue_first_mentioned_in_commit }
......@@ -76,6 +76,68 @@ describe Gitlab::Analytics::CycleAnalytics::DataCollector do
it_behaves_like 'custom cycle analytics stage'
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
context 'when `MergeRequest` based stage is given' do
......@@ -127,6 +189,36 @@ describe Gitlab::Analytics::CycleAnalytics::DataCollector do
it_behaves_like 'custom cycle analytics stage'
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
......
# 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
]
}.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
......
......@@ -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 ""
......
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