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
|
# frozen_string_literal: true
module Operations
class FeatureFlag < ApplicationRecord
include AfterCommitQueue
include AtomicInternalId
include IidRoutes
include Limitable
include Referable
self.table_name = 'operations_feature_flags'
self.limit_scope = :project
self.limit_name = 'project_feature_flags'
belongs_to :project
has_internal_id :iid, scope: :project
default_value_for :active, true
# scopes exists only for the first version
has_many :scopes, class_name: 'Operations::FeatureFlagScope'
# strategies exists only for the second version
has_many :strategies, class_name: 'Operations::FeatureFlags::Strategy'
has_many :feature_flag_issues
has_many :issues, through: :feature_flag_issues
has_one :default_scope, -> { where(environment_scope: '*') }, class_name: 'Operations::FeatureFlagScope'
validates :project, presence: true
validates :name,
presence: true,
length: 2..63,
format: {
with: Gitlab::Regex.feature_flag_regex,
message: Gitlab::Regex.feature_flag_regex_message
}
validates :name, uniqueness: { scope: :project_id }
validates :description, allow_blank: true, length: 0..255
validate :first_default_scope, on: :create, if: :has_scopes?
validate :version_associations
before_create :build_default_scope, if: -> { legacy_flag? && scopes.none? }
accepts_nested_attributes_for :scopes, allow_destroy: true
accepts_nested_attributes_for :strategies, allow_destroy: true
scope :ordered, -> { order(:name) }
scope :enabled, -> { where(active: true) }
scope :disabled, -> { where(active: false) }
scope :new_version_only, -> { where(version: :new_version_flag)}
enum version: {
legacy_flag: 1,
new_version_flag: 2
}
class << self
def preload_relations
preload(:scopes, strategies: :scopes)
end
def for_unleash_client(project, environment)
includes(strategies: [:scopes, :user_list])
.where(project: project)
.merge(Operations::FeatureFlags::Scope.on_environment(environment))
.reorder(:id)
.references(:operations_scopes)
end
def reference_prefix
'[feature_flag:'
end
def reference_pattern
@reference_pattern ||= %r{
#{Regexp.escape(reference_prefix)}(#{::Project.reference_pattern}\/)?(?<feature_flag>\d+)#{Regexp.escape(reference_postfix)}
}x
end
def link_reference_pattern
@link_reference_pattern ||= super("feature_flags", %r{(?<feature_flag>\d+)/edit})
end
def reference_postfix
']'
end
end
def to_reference(from = nil, full: false)
project
.to_reference_base(from, full: full)
.then { |reference_base| reference_base.present? ? "#{reference_base}/" : nil }
.then { |reference_base| "#{self.class.reference_prefix}#{reference_base}#{iid}#{self.class.reference_postfix}" }
end
def related_issues(current_user, preload:)
issues = ::Issue
.select('issues.*, operations_feature_flags_issues.id AS link_id')
.joins(:feature_flag_issues)
.where(operations_feature_flags_issues: { feature_flag_id: id })
.order('operations_feature_flags_issues.id ASC')
.includes(preload)
Ability.issues_readable_by_user(issues, current_user)
end
def execute_hooks(current_user)
run_after_commit do
feature_flag_data = Gitlab::DataBuilder::FeatureFlag.build(self, current_user)
project.execute_hooks(feature_flag_data, :feature_flag_hooks)
end
end
def hook_attrs
{
id: id,
name: name,
description: description,
active: active
}
end
private
def version_associations
if new_version_flag? && scopes.any?
errors.add(:version_associations, 'version 2 feature flags may not have scopes')
elsif legacy_flag? && strategies.any?
errors.add(:version_associations, 'version 1 feature flags may not have strategies')
end
end
def first_default_scope
unless scopes.first.environment_scope == '*'
errors.add(:default_scope, 'has to be the first element')
end
end
def build_default_scope
scopes.build(environment_scope: '*', active: self.active)
end
def has_scopes?
scopes.any?
end
end
end
|