gitlab-cloud-connector 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -3
  3. data/CONTRIBUTING.md +50 -0
  4. data/LICENSE +28 -0
  5. data/Makefile +33 -0
  6. data/README.md +11 -221
  7. data/Rakefile +1 -5
  8. data/config/add_ons/duo_enterprise.yml +2 -0
  9. data/config/add_ons/duo_pro.yml +2 -0
  10. data/config/backend_services/ai_gateway.yml +5 -0
  11. data/config/backend_services/duo_workflow_service.yml +5 -0
  12. data/config/backend_services/observability.yml +5 -0
  13. data/config/backend_services/security_gateway.yml +5 -0
  14. data/config/license_types/premium.yml +2 -0
  15. data/config/license_types/ultimate.yml +2 -0
  16. data/config/schemas/add_on_schema.json +15 -0
  17. data/config/schemas/backend_service_schema.json +26 -0
  18. data/config/schemas/license_type_schema.json +15 -0
  19. data/config/schemas/unit_primitive_schema.json +97 -0
  20. data/config/unit_primitives/ask_build.yml +15 -0
  21. data/config/unit_primitives/ask_commit.yml +15 -0
  22. data/config/unit_primitives/ask_epic.yml +15 -0
  23. data/config/unit_primitives/ask_issue.yml +15 -0
  24. data/config/unit_primitives/code_suggestions.yml +19 -0
  25. data/config/unit_primitives/complete_code.yml +19 -0
  26. data/config/unit_primitives/documentation_search.yml +19 -0
  27. data/config/unit_primitives/duo_chat.yml +19 -0
  28. data/config/unit_primitives/duo_workflow_execute_workflow.yml +13 -0
  29. data/config/unit_primitives/explain_code.yml +17 -0
  30. data/config/unit_primitives/explain_vulnerability.yml +17 -0
  31. data/config/unit_primitives/fix_code.yml +17 -0
  32. data/config/unit_primitives/generate_code.yml +19 -0
  33. data/config/unit_primitives/generate_commit_message.yml +17 -0
  34. data/config/unit_primitives/generate_issue_description.yml +15 -0
  35. data/config/unit_primitives/glab_ask_git_command.yml +17 -0
  36. data/config/unit_primitives/include_dependency_context.yml +15 -0
  37. data/config/unit_primitives/include_file_context.yml +17 -0
  38. data/config/unit_primitives/include_issue_context.yml +15 -0
  39. data/config/unit_primitives/include_merge_request_context.yml +15 -0
  40. data/config/unit_primitives/include_snippet_context.yml +17 -0
  41. data/config/unit_primitives/observability_all.yml +14 -0
  42. data/config/unit_primitives/refactor_code.yml +17 -0
  43. data/config/unit_primitives/resolve_vulnerability.yml +17 -0
  44. data/config/unit_primitives/security_scans.yml +13 -0
  45. data/config/unit_primitives/summarize_comments.yml +17 -0
  46. data/config/unit_primitives/summarize_review.yml +15 -0
  47. data/config/unit_primitives/troubleshoot_job.yml +17 -0
  48. data/config/unit_primitives/write_tests.yml +17 -0
  49. data/lib/cloud_connector.rb +10 -0
  50. data/lib/gitlab/cloud_connector/json_web_token.rb +59 -0
  51. data/lib/gitlab/cloud_connector/{backend_service.rb → version.rb} +1 -3
  52. metadata +85 -48
  53. data/.idea/.gitignore +0 -8
  54. data/.idea/gitlab-cloud-connector.iml +0 -127
  55. data/.idea/inspectionProfiles/Project_Default.xml +0 -9
  56. data/.idea/misc.xml +0 -4
  57. data/.idea/modules.xml +0 -8
  58. data/.idea/vcs.xml +0 -7
  59. data/CHANGELOG.md +0 -5
  60. data/Dangerfile +0 -5
  61. data/config/cloud_connector/backend_services/ai_gateway/unit_primitives/code_suggestions.yml +0 -19
  62. data/config/cloud_connector/backend_services/ai_gateway/unit_primitives/documentation_search.yml +0 -19
  63. data/config/cloud_connector/backend_services/ai_gateway/unit_primitives/duo_chat.yml +0 -19
  64. data/lib/gitlab/cloud_connector/auth/json_web_token.rb +0 -82
  65. data/lib/gitlab/cloud_connector/base_group.rb +0 -39
  66. data/lib/gitlab/cloud_connector/configuration.rb +0 -24
  67. data/lib/gitlab/cloud_connector/deliverable.rb +0 -31
  68. data/lib/gitlab/cloud_connector/delivered_by.rb +0 -10
  69. data/lib/gitlab/cloud_connector/purchasable_decorator.rb +0 -59
  70. data/lib/gitlab/cloud_connector/token_issuer.rb +0 -44
  71. data/lib/gitlab/cloud_connector/unit_primitives.rb +0 -195
  72. data/lib/gitlab/cloud_connector.rb +0 -68
  73. data/lib/gitlab/popen.rb +0 -68
  74. data/lib/utils/parse_helper.rb +0 -15
  75. data/lib/utils/strong_memoize.rb +0 -145
  76. data/sig/gitlab/cloud_connector.rbs +0 -0
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../utils/strong_memoize'
4
- require_relative '../../utils/parse_helper'
5
- require "active_support"
6
- require "active_support/core_ext/module/delegation"
7
-
8
- module Gitlab
9
- module CloudConnector
10
- class PurchasableDecorator
11
- include ::Utils::StrongMemoize
12
- include ::Utils::ParseHelper
13
-
14
- delegate_missing_to :unit_primitive_or_group
15
-
16
- def initialize(unit_primitive_or_group)
17
- @unit_primitive_or_group = unit_primitive_or_group
18
- end
19
-
20
- def available_for?(_resource)
21
- raise 'Not Implemented'
22
- end
23
-
24
- def free_access?
25
- !cut_off_date&.past?
26
- end
27
-
28
- def available?(resource, gitlab_version, gitlab_realm)
29
- return false unless gitlab_realms.include?(gitlab_realm)
30
-
31
- supported_gitlab_version?(gitlab_version) && (free_access? || available_for?(resource))
32
- end
33
-
34
- def supported_gitlab_version?(gitlab_version)
35
- min_gitlab_version = calculate_min_gitlab_version
36
-
37
- return true unless min_gitlab_version
38
- return false unless gitlab_version
39
- # Extract only major and minor version (ignore -ee, -pre and patch suffixes)
40
- gitlab_version = parse_version(gitlab_version)
41
-
42
- gitlab_version >= min_gitlab_version
43
- end
44
-
45
- def calculate_min_gitlab_version
46
- if free_access?
47
- min_gitlab_version_for_free_access
48
- else
49
- min_gitlab_version
50
- end
51
- end
52
-
53
- private
54
-
55
- attr_reader :unit_primitive_or_group
56
-
57
- end
58
- end
59
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'auth/json_web_token'
4
-
5
- module Gitlab
6
- module CloudConnector
7
- ###
8
- # Provides access information for GitLab services offered under Cloud Connector.
9
- #
10
- # Self-Managed GitLab instances sync with this service to get access to Cloud Connector
11
- # features based on billing status and other information.
12
- module TokenIssuer
13
-
14
- attr_reader :subject, :extra_claims
15
-
16
- def access_token(resource, gitlab_version: nil, gitlab_realm: nil, subject: nil, extra_claims: {})
17
- subject ||= ::Gitlab::CloudConnector.token_subject
18
- gitlab_version ||= ::Gitlab::CloudConnector.gitlab_version
19
- gitlab_realm ||= ::Gitlab::CloudConnector.gitlab_realm
20
-
21
- allowed_scopes = allowed_scopes(resource, gitlab_version, gitlab_realm)
22
-
23
- return nil if allowed_scopes.empty?
24
-
25
- ::Gitlab::CloudConnector::Auth::JsonWebToken.new(
26
- audience: allowed_backends,
27
- subject: subject,
28
- scopes: allowed_scopes,
29
- extra_claims: extra_claims
30
- )
31
- end
32
-
33
- private
34
-
35
- def allowed_backends
36
- unit_primitives.values.map{ |unit_primitive| "gitlab-#{unit_primitive.backend_service.dasherize}"}.uniq
37
- end
38
-
39
- def allowed_scopes(resource, gitlab_version, gitlab_realm)
40
- unit_primitives.values.select{ |unit_primitive| unit_primitive.available?(resource, gitlab_version, gitlab_realm) }.map(&:name)
41
- end
42
- end
43
- end
44
- end
@@ -1,195 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../utils/parse_helper'
4
-
5
- module Gitlab
6
- module CloudConnector
7
- class UnitPrimitives
8
- include ::Utils::ParseHelper
9
-
10
- attr_reader :path
11
- attr_reader :attributes
12
-
13
- VALID_UNIT_PRIMITIVE_NAME = %r{^#{/[a-z0-9]+/}(#{'_'} #{/[a-z0-9]+/})*$}x
14
- BACKEND_SERVICE_MATCHER = %r{\/backend_services\/(\w+)\/unit_primitives\/(?:\w+)\.yml}
15
-
16
- PARAMS = %i[
17
- name
18
- description
19
- backend_service
20
- cut_off_date
21
- introduced_by_url
22
- unit_primitive_issue_url
23
- milestone
24
- group
25
- feature_category
26
- maturity
27
- min_gitlab_version
28
- min_gitlab_version_for_free_access
29
- min_gitlab_version
30
- min_backend_version
31
- delivered_by
32
- bundled_with
33
- gitlab_realms
34
- ].freeze
35
-
36
- BACKEND_SERVICES = %i[
37
- ai_gateway
38
- ]
39
-
40
- InvalidUnitPrimitiveError = Class.new(Exception)
41
-
42
- PARAMS.each do |param|
43
- define_method(param) do
44
- ## parse time or create version
45
- attributes[param]
46
- end
47
- end
48
-
49
- BACKEND_SERVICES.each do |backend_service, _|
50
- define_method("#{backend_service}?") do
51
- attributes[:backend_service].to_sym == backend_service
52
- end
53
- end
54
-
55
- alias :add_ons :bundled_with
56
-
57
- def initialize(path, **opts)
58
- @path = path
59
- @attributes = {}
60
-
61
- PARAMS.each do |param|
62
- if param.end_with?('version')
63
- @attributes[param] = parse_version(opts[param]) if opts[param]
64
- elsif param.end_with?('date')
65
- @attributes[param] = parse_time(opts[param]).utc if opts[param]
66
- else
67
- @attributes[param] = opts[param]
68
- end
69
- end
70
-
71
- # read backend_service from the path
72
- @attributes[:backend_service] = BACKEND_SERVICE_MATCHER.match(path.to_s)[1]
73
- end
74
-
75
- def key
76
- name.to_sym
77
- end
78
-
79
- def validate!
80
- unless name.present?
81
- raise InvalidUnitPrimitiveError, "Unit primitive is missing name"
82
- end
83
-
84
- if backend_service.empty?
85
- raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' is missing `backend_service`. Ensure to update #{path}"
86
- end
87
-
88
- unless BACKEND_SERVICES.include?(backend_service.to_sym)
89
- raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' backend service '#{backend_service}' is invalid."
90
- end
91
-
92
- if delivered_by.empty?
93
- raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' is missing 'delivered_by'. If unit primitive is delivered as a stand-alone feature, set unit_primitive name."
94
- end
95
-
96
- if bundled_with.empty?
97
- raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' is missing 'bundled_with'."
98
- end
99
-
100
- if feature_category.empty?
101
- raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' is missing 'feature_category'."
102
- end
103
-
104
- unless VALID_UNIT_PRIMITIVE_NAME =~ name
105
- raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' is invalid"
106
- end
107
-
108
- if File.basename(path, ".yml") != name
109
- raise InvalidUnitPrimitiveError, "Unit primitive '#{name}' has an invalid path: '#{path}'. Ensure to update #{path}"
110
- end
111
- end
112
-
113
- def to_h
114
- attributes
115
- end
116
-
117
- class << self
118
- def paths
119
- @paths ||= [File.join(CloudConnector.root, 'config', 'cloud_connector', 'backend_services', '**', 'unit_primitives', '*.yml')]
120
- end
121
-
122
- def definitions
123
- # We lazily load all definitions
124
- @definitions ||= load_all!
125
- end
126
-
127
- def get(key)
128
- definitions[key.to_sym]
129
- end
130
-
131
- def reload!
132
- @definitions = load_all!
133
- end
134
-
135
- def has_definition?(key)
136
- definitions.has_key?(key.to_sym)
137
- end
138
-
139
- def register_hot_reloader!
140
- return unless Rails.env.development? || Rails.env.test?
141
- # Reload feature flags on change of this file or any `.yml`
142
- file_watcher = Rails.configuration.file_watcher.new(reload_files, reload_directories) do
143
- reload!
144
- end
145
-
146
- Rails.application.reloaders << file_watcher
147
- Rails.application.reloader.to_run { file_watcher.execute_if_updated }
148
-
149
- file_watcher
150
- end
151
-
152
- def load_all!
153
- paths.each_with_object({}) do |glob_path, definitions|
154
- load_all_from_path!(definitions, glob_path)
155
- end
156
- end
157
-
158
- def load_from_file(path)
159
- definition = File.read(path)
160
- definition = YAML.safe_load(definition, aliases: true)
161
- definition.deep_symbolize_keys!
162
-
163
- self.new(path, **definition).tap(&:validate!)
164
- rescue StandardError => e
165
- raise InvalidUnitPrimitiveError, "Invalid definition for `#{path}`: #{e.message}"
166
- end
167
-
168
- def load_all_from_path!(definitions, glob_path)
169
- Dir.glob(glob_path).each do |path|
170
- definition = load_from_file(path)
171
-
172
- if previous = definitions[definition.key]
173
- raise InvalidUnitPrimitiveError, "Feature flag '#{definition.key}' is already defined in '#{previous.path}'"
174
- end
175
-
176
- definitions[definition.key] = definition
177
- end
178
- end
179
-
180
- def reload_files
181
- []
182
- end
183
-
184
- def reload_directories
185
- paths.each_with_object({}) do |path, result|
186
- path = File.dirname(path)
187
- Dir.glob(path).each do |matching_dir|
188
- result[matching_dir] = 'yml'
189
- end
190
- end
191
- end
192
- end
193
- end
194
- end
195
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'cloud_connector/configuration'
4
- require_relative 'cloud_connector/token_issuer'
5
- require_relative 'cloud_connector/deliverable'
6
- require_relative 'cloud_connector/delivered_by'
7
- require_relative 'cloud_connector/backend_service'
8
- require_relative '../utils/strong_memoize'
9
-
10
- module Gitlab
11
- module CloudConnector
12
- extend Deliverable
13
- extend TokenIssuer
14
- extend ::Utils::StrongMemoize
15
- Time.zone = 'UTC'
16
-
17
- VERSION = "0.1.0"
18
-
19
- class << self
20
-
21
- alias :instance_access_token :access_token
22
- delegate :token_issuer, :token_expires_in, :token_subject, :gitlab_realm, :gitlab_version, to: :configuration
23
-
24
- def root
25
- File.expand_path '../../..', __FILE__
26
- end
27
-
28
- def configure
29
- yield configuration
30
- end
31
-
32
- def configuration
33
- strong_memoize(:configuration) do
34
- Configuration.new
35
- end
36
- end
37
-
38
- def unit_primitives
39
- strong_memoize(:unit_primitives) do
40
- unit_primitives = Gitlab::CloudConnector::UnitPrimitives.definitions
41
- unit_primitives.transform_values{ |up| configuration.purchasable_decorator_class.new(up) }
42
- end
43
- end
44
-
45
- def backend_services
46
- strong_memoize(:backend_services) do
47
- unit_primitives.values.group_by(&:backend_service).map do |name, unit_primitives|
48
- BackendService.new(name.to_sym, unit_primitives.index_by(&:key))
49
- end.index_by(&:name)
50
- end
51
- end
52
-
53
- def feature_categories
54
- strong_memoize(:feature_categories) do
55
- unit_primitives.values.map(&:feature_category).uniq
56
- end
57
- end
58
-
59
- def bundled_with
60
- strong_memoize(:bundled_with) do
61
- unit_primitives.values.flat_map(&:bundled_with).uniq
62
- end
63
- end
64
-
65
- alias :add_ons :bundled_with
66
- end
67
- end
68
- end
data/lib/gitlab/popen.rb DELETED
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'fileutils'
4
- require 'open3'
5
-
6
- module Gitlab
7
- module Popen
8
- extend self
9
-
10
- Result = Struct.new(:cmd, :stdout, :stderr, :status, :duration)
11
-
12
- # Returns [stdout + stderr, status]
13
- # status is either the exit code or the signal that killed the process
14
- def popen(cmd, path = nil, vars = {}, &block)
15
- result = popen_with_detail(cmd, path, vars, &block)
16
-
17
- # Process#waitpid returns Process::Status, which holds a 16-bit value.
18
- # The higher-order 8 bits hold the exit() code (`exitstatus`).
19
- # The lower-order bits holds whether the process was terminated.
20
- # If the process didn't exit normally, `exitstatus` will be `nil`,
21
- # but we still want a non-zero code, even if the value is
22
- # platform-dependent.
23
- status = result.status&.exitstatus || result.status.to_i
24
-
25
- ["#{result.stdout}#{result.stderr}", status]
26
- end
27
-
28
- # Returns Result
29
- def popen_with_detail(cmd, path = nil, vars = {})
30
- unless cmd.is_a?(Array)
31
- raise "System commands must be given as an array of strings"
32
- end
33
-
34
- if cmd.one? && cmd.first.match?(/\s/)
35
- raise "System commands must be split into an array of space-separated values"
36
- end
37
-
38
- path ||= Dir.pwd
39
- vars['PWD'] = path
40
- options = { chdir: path }
41
-
42
- unless File.directory?(path)
43
- FileUtils.mkdir_p(path)
44
- end
45
-
46
- cmd_stdout = ''
47
- cmd_stderr = ''
48
- cmd_status = nil
49
- start = Time.now
50
-
51
- Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
52
- # stderr and stdout pipes can block if stderr/stdout aren't drained: https://bugs.ruby-lang.org/issues/9082
53
- # Mimic what Ruby does with capture3: https://github.com/ruby/ruby/blob/1ec544695fa02d714180ef9c34e755027b6a2103/lib/open3.rb#L257-L273
54
- out_reader = Thread.new { stdout.read }
55
- err_reader = Thread.new { stderr.read }
56
-
57
- yield(stdin) if block_given?
58
- stdin.close
59
-
60
- cmd_stdout = out_reader.value
61
- cmd_stderr = err_reader.value
62
- cmd_status = wait_thr.value
63
- end
64
-
65
- Result.new(cmd, cmd_stdout, cmd_stderr, cmd_status, Time.now - start)
66
- end
67
- end
68
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Utils
4
- module ParseHelper
5
- VERSION_REGEX = /\A(\d+\.\d+)/
6
- def parse_time(time)
7
- Time.zone.parse(time).utc if time
8
- end
9
-
10
- def parse_version(version)
11
- version = VERSION_REGEX.match(version.to_s).to_s
12
- Gem::Version.new(version)
13
- end
14
- end
15
- end
@@ -1,145 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Utils
4
- module StrongMemoize
5
- # Instead of writing patterns like this:
6
- #
7
- # def trigger_from_token
8
- # return @trigger if defined?(@trigger)
9
- #
10
- # @trigger = Ci::Trigger.find_by_token(params[:token].to_s)
11
- # end
12
- #
13
- # We could write it like:
14
- #
15
- # include Utils::StrongMemoize
16
- #
17
- # def trigger_from_token
18
- # Ci::Trigger.find_by_token(params[:token].to_s)
19
- # end
20
- # strong_memoize_attr :trigger_from_token
21
- #
22
- # def enabled?
23
- # Feature.enabled?(:some_feature)
24
- # end
25
- # strong_memoize_attr :enabled?
26
- #
27
- def strong_memoize(name)
28
- key = ivar(name)
29
-
30
- if instance_variable_defined?(key)
31
- instance_variable_get(key)
32
- else
33
- instance_variable_set(key, yield)
34
- end
35
- end
36
-
37
- # Works the same way as "strong_memoize" but takes
38
- # a second argument - expire_in. This allows invalidate
39
- # the data after specified number of seconds
40
- def strong_memoize_with_expiration(name, expire_in)
41
- key = ivar(name)
42
- expiration_key = "#{key}_expired_at"
43
-
44
- if instance_variable_defined?(expiration_key)
45
- expire_at = instance_variable_get(expiration_key)
46
- clear_memoization(name) if Time.current > expire_at
47
- end
48
-
49
- if instance_variable_defined?(key)
50
- instance_variable_get(key)
51
- else
52
- value = instance_variable_set(key, yield)
53
- instance_variable_set(expiration_key, Time.current + expire_in)
54
- value
55
- end
56
- end
57
-
58
- def strong_memoize_with(name, *args)
59
- container = strong_memoize(name) { {} }
60
-
61
- if container.key?(args)
62
- container[args]
63
- else
64
- container[args] = yield
65
- end
66
- end
67
-
68
- def strong_memoized?(name)
69
- key = ivar(StrongMemoize.normalize_key(name))
70
- instance_variable_defined?(key)
71
- end
72
-
73
- def clear_memoization(name)
74
- key = ivar(StrongMemoize.normalize_key(name))
75
- remove_instance_variable(key) if instance_variable_defined?(key)
76
- end
77
-
78
- module StrongMemoizeClassMethods
79
- def strong_memoize_attr(method_name)
80
- member_name = StrongMemoize.normalize_key(method_name)
81
-
82
- StrongMemoize.send(:do_strong_memoize, self, method_name, member_name) # rubocop:disable GitlabSecurity/PublicSend
83
- end
84
- end
85
-
86
- def self.included(base)
87
- base.singleton_class.prepend(StrongMemoizeClassMethods)
88
- end
89
-
90
- private
91
-
92
- # Convert `"name"`/`:name` into `:@name`
93
- #
94
- # Depending on a type ensure that there's a single memory allocation
95
- def ivar(name)
96
- case name
97
- when Symbol
98
- name.to_s.prepend("@").to_sym
99
- when String
100
- :"@#{name}"
101
- else
102
- raise ArgumentError, "Invalid type of '#{name}'"
103
- end
104
- end
105
-
106
- class << self
107
- def normalize_key(key)
108
- return key unless key.end_with?('!', '?')
109
-
110
- # Replace invalid chars like `!` and `?` with allowed Unicode codeparts.
111
- key.to_s.tr('!?', "\uFF01\uFF1F")
112
- end
113
-
114
- private
115
-
116
- def do_strong_memoize(klass, method_name, member_name)
117
- method = klass.instance_method(method_name)
118
-
119
- unless method.arity == 0
120
- raise <<~ERROR
121
- Using `strong_memoize_attr` on methods with parameters is not supported.
122
-
123
- Use `strong_memoize_with` instead.
124
- See https://docs.gitlab.com/ee/development/utilities.html#strongmemoize
125
- ERROR
126
- end
127
-
128
- # Methods defined within a class method are already public by default, so we don't need to
129
- # explicitly make them public.
130
- scope = %i[private protected].find do |scope|
131
- klass.send(:"#{scope}_instance_methods") # rubocop:disable GitlabSecurity/PublicSend
132
- .include? method_name
133
- end
134
-
135
- klass.define_method(method_name) do |&block|
136
- strong_memoize(member_name) do
137
- method.bind_call(self, &block)
138
- end
139
- end
140
-
141
- klass.send(scope, method_name) if scope # rubocop:disable GitlabSecurity/PublicSend
142
- end
143
- end
144
- end
145
- end
File without changes