1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
# frozen_string_literal: true
module Analytics
module CycleAnalytics
module Stage
extend ActiveSupport::Concern
include RelativePositioning
include Gitlab::Utils::StrongMemoize
included do
belongs_to :start_event_label, class_name: 'GroupLabel', optional: true
belongs_to :end_event_label, class_name: 'GroupLabel', optional: true
validates :name, presence: true
validates :name, exclusion: { in: Gitlab::Analytics::CycleAnalytics::DefaultStages.names }, if: :custom?
validates :start_event_identifier, presence: true
validates :end_event_identifier, presence: true
validates :start_event_label_id, presence: true, if: :start_event_label_based?
validates :end_event_label_id, presence: true, if: :end_event_label_based?
validate :validate_stage_event_pairs
validate :validate_labels
enum start_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum, _prefix: :start_event_identifier
enum end_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum, _prefix: :end_event_identifier
alias_attribute :custom_stage?, :custom
scope :default_stages, -> { where(custom: false) }
scope :ordered, -> { order(:relative_position, :id) }
scope :for_list, -> { includes(:start_event_label, :end_event_label).ordered }
end
def parent=(_)
raise NotImplementedError
end
def parent
raise NotImplementedError
end
def start_event
strong_memoize(:start_event) do
Gitlab::Analytics::CycleAnalytics::StageEvents[start_event_identifier].new(params_for_start_event)
end
end
def end_event
strong_memoize(:end_event) do
Gitlab::Analytics::CycleAnalytics::StageEvents[end_event_identifier].new(params_for_end_event)
end
end
def start_event_identifier
backward_compatible_identifier(:start_event_identifier) || super
end
def end_event_identifier
backward_compatible_identifier(:end_event_identifier) || super
end
def start_event_label_based?
start_event_identifier && start_event.label_based?
end
def end_event_label_based?
end_event_identifier && end_event.label_based?
end
def start_event_identifier=(identifier)
clear_memoization(:start_event)
super
end
def end_event_identifier=(identifier)
clear_memoization(:end_event)
super
end
def params_for_start_event
start_event_label.present? ? { label: start_event_label } : {}
end
def params_for_end_event
end_event_label.present? ? { label: end_event_label } : {}
end
def default_stage?
!custom
end
# The model class that is going to be queried, Issue or MergeRequest
def subject_class
start_event.object_type
end
def matches_with_stage_params?(stage_params)
default_stage? &&
start_event_identifier.to_s.eql?(stage_params[:start_event_identifier].to_s) &&
end_event_identifier.to_s.eql?(stage_params[:end_event_identifier].to_s)
end
def find_with_same_parent!(id)
parent.cycle_analytics_stages.find(id)
end
private
def validate_stage_event_pairs
return if start_event_identifier.nil? || end_event_identifier.nil?
unless pairing_rules.fetch(start_event.class, []).include?(end_event.class)
errors.add(:end_event, s_('CycleAnalytics|not allowed for the given start event'))
end
end
def pairing_rules
Gitlab::Analytics::CycleAnalytics::StageEvents.pairing_rules
end
def validate_labels
validate_label_within_group(:start_event_label_id, start_event_label_id) if start_event_label_id_changed?
validate_label_within_group(:end_event_label_id, end_event_label_id) if end_event_label_id_changed?
end
def validate_label_within_group(association_name, label_id)
return unless label_id
return unless group
unless label_available_for_group?(label_id)
errors.add(association_name, s_('CycleAnalyticsStage|is not available for the selected group'))
end
end
def label_available_for_group?(label_id)
LabelsFinder.new(nil, { group_id: group.id, include_ancestor_groups: true, only_group_labels: true })
.execute(skip_authorization: true)
.id_in(label_id)
.exists?
end
# Temporary, will be removed in 13.10
def backward_compatible_identifier(attribute_name)
removed_identifier = 6 # References IssueFirstMentionedInCommit removed on https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51975
replacement_identifier = :issue_first_mentioned_in_commit
# ActiveRecord returns nil if the column value is not part of the Enum definition
if self[attribute_name].nil? && read_attribute_before_type_cast(attribute_name) == removed_identifier
replacement_identifier
end
end
end
end
end
|