payping-gitlab-triage 0.1.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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +19 -0
  3. data/.gitignore +15 -0
  4. data/.gitlab/CODEOWNERS +2 -0
  5. data/.gitlab/changelog_config.yml +13 -0
  6. data/.gitlab/issue_templates/Default.md +13 -0
  7. data/.gitlab/merge_request_templates/Default.md +11 -0
  8. data/.gitlab/merge_request_templates/Release.md +13 -0
  9. data/.gitlab-ci.yml +146 -0
  10. data/.rubocop.yml +21 -0
  11. data/.rubocop_todo.yml +145 -0
  12. data/.ruby-version +1 -0
  13. data/.tool-versions +1 -0
  14. data/.yardopts +4 -0
  15. data/CONTRIBUTING.md +31 -0
  16. data/Dangerfile +5 -0
  17. data/Gemfile +15 -0
  18. data/Guardfile +70 -0
  19. data/LICENSE.md +25 -0
  20. data/README.md +1480 -0
  21. data/Rakefile +6 -0
  22. data/bin/gitlab-triage +19 -0
  23. data/gitlab-triage.gemspec +41 -0
  24. data/lib/gitlab/triage/action/base.rb +14 -0
  25. data/lib/gitlab/triage/action/comment.rb +104 -0
  26. data/lib/gitlab/triage/action/comment_on_summary.rb +83 -0
  27. data/lib/gitlab/triage/action/delete.rb +56 -0
  28. data/lib/gitlab/triage/action/issue.rb +64 -0
  29. data/lib/gitlab/triage/action/summarize.rb +82 -0
  30. data/lib/gitlab/triage/action.rb +36 -0
  31. data/lib/gitlab/triage/api_query_builders/base_query_param_builder.rb +27 -0
  32. data/lib/gitlab/triage/api_query_builders/date_query_param_builder.rb +42 -0
  33. data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +28 -0
  34. data/lib/gitlab/triage/api_query_builders/single_query_param_builder.rb +13 -0
  35. data/lib/gitlab/triage/command_builders/base_command_builder.rb +40 -0
  36. data/lib/gitlab/triage/command_builders/cc_command_builder.rb +19 -0
  37. data/lib/gitlab/triage/command_builders/comment_command_builder.rb +19 -0
  38. data/lib/gitlab/triage/command_builders/label_command_builder.rb +40 -0
  39. data/lib/gitlab/triage/command_builders/move_command_builder.rb +19 -0
  40. data/lib/gitlab/triage/command_builders/remove_label_command_builder.rb +15 -0
  41. data/lib/gitlab/triage/command_builders/status_command_builder.rb +23 -0
  42. data/lib/gitlab/triage/command_builders/text_content_builder.rb +138 -0
  43. data/lib/gitlab/triage/engine.rb +635 -0
  44. data/lib/gitlab/triage/entity_builders/issue_builder.rb +54 -0
  45. data/lib/gitlab/triage/entity_builders/summary_builder.rb +82 -0
  46. data/lib/gitlab/triage/errors/network.rb +11 -0
  47. data/lib/gitlab/triage/errors.rb +1 -0
  48. data/lib/gitlab/triage/expand_condition/expansion.rb +203 -0
  49. data/lib/gitlab/triage/expand_condition/list.rb +25 -0
  50. data/lib/gitlab/triage/expand_condition/sequence.rb +25 -0
  51. data/lib/gitlab/triage/expand_condition.rb +23 -0
  52. data/lib/gitlab/triage/filters/assignee_member_conditions_filter.rb +13 -0
  53. data/lib/gitlab/triage/filters/author_member_conditions_filter.rb +13 -0
  54. data/lib/gitlab/triage/filters/base_conditions_filter.rb +58 -0
  55. data/lib/gitlab/triage/filters/branch_date_filter.rb +73 -0
  56. data/lib/gitlab/triage/filters/branch_protected_filter.rb +26 -0
  57. data/lib/gitlab/triage/filters/discussions_conditions_filter.rb +58 -0
  58. data/lib/gitlab/triage/filters/issue_date_conditions_filter.rb +78 -0
  59. data/lib/gitlab/triage/filters/member_conditions_filter.rb +84 -0
  60. data/lib/gitlab/triage/filters/merge_request_date_conditions_filter.rb +13 -0
  61. data/lib/gitlab/triage/filters/name_conditions_filter.rb +26 -0
  62. data/lib/gitlab/triage/filters/no_additional_labels_conditions_filter.rb +30 -0
  63. data/lib/gitlab/triage/filters/ruby_conditions_filter.rb +33 -0
  64. data/lib/gitlab/triage/filters/votes_conditions_filter.rb +54 -0
  65. data/lib/gitlab/triage/graphql_network.rb +81 -0
  66. data/lib/gitlab/triage/graphql_queries/query_builder.rb +158 -0
  67. data/lib/gitlab/triage/graphql_queries/query_param_builders/base_param_builder.rb +30 -0
  68. data/lib/gitlab/triage/graphql_queries/query_param_builders/date_param_builder.rb +35 -0
  69. data/lib/gitlab/triage/graphql_queries/query_param_builders/labels_param_builder.rb +18 -0
  70. data/lib/gitlab/triage/limiters/base_limiter.rb +35 -0
  71. data/lib/gitlab/triage/limiters/date_field_limiter.rb +45 -0
  72. data/lib/gitlab/triage/network.rb +39 -0
  73. data/lib/gitlab/triage/network_adapters/base_adapter.rb +17 -0
  74. data/lib/gitlab/triage/network_adapters/graphql_adapter.rb +92 -0
  75. data/lib/gitlab/triage/network_adapters/httparty_adapter.rb +116 -0
  76. data/lib/gitlab/triage/network_adapters/test_adapter.rb +39 -0
  77. data/lib/gitlab/triage/option_parser.rb +105 -0
  78. data/lib/gitlab/triage/options.rb +30 -0
  79. data/lib/gitlab/triage/param_builders/date_param_builder.rb +64 -0
  80. data/lib/gitlab/triage/policies/base_policy.rb +80 -0
  81. data/lib/gitlab/triage/policies/rule_policy.rb +36 -0
  82. data/lib/gitlab/triage/policies/summary_policy.rb +29 -0
  83. data/lib/gitlab/triage/policies_resources/rule_resources.rb +11 -0
  84. data/lib/gitlab/triage/policies_resources/summary_resources.rb +11 -0
  85. data/lib/gitlab/triage/resource/base.rb +102 -0
  86. data/lib/gitlab/triage/resource/branch.rb +13 -0
  87. data/lib/gitlab/triage/resource/context.rb +47 -0
  88. data/lib/gitlab/triage/resource/epic.rb +20 -0
  89. data/lib/gitlab/triage/resource/instance_version.rb +35 -0
  90. data/lib/gitlab/triage/resource/issue.rb +52 -0
  91. data/lib/gitlab/triage/resource/label.rb +56 -0
  92. data/lib/gitlab/triage/resource/label_event.rb +48 -0
  93. data/lib/gitlab/triage/resource/linked_issue.rb +15 -0
  94. data/lib/gitlab/triage/resource/merge_request.rb +23 -0
  95. data/lib/gitlab/triage/resource/milestone.rb +98 -0
  96. data/lib/gitlab/triage/resource/shared/issuable.rb +119 -0
  97. data/lib/gitlab/triage/rest_api_network.rb +125 -0
  98. data/lib/gitlab/triage/retryable.rb +33 -0
  99. data/lib/gitlab/triage/ui.rb +23 -0
  100. data/lib/gitlab/triage/url_builders/url_builder.rb +54 -0
  101. data/lib/gitlab/triage/utils.rb +13 -0
  102. data/lib/gitlab/triage/validators/limiter_validator.rb +21 -0
  103. data/lib/gitlab/triage/validators/params_validator.rb +43 -0
  104. data/lib/gitlab/triage/version.rb +7 -0
  105. data/lib/gitlab/triage.rb +6 -0
  106. data/support/.gitlab-ci.example.yml +22 -0
  107. data/support/.triage-policies.example.yml +51 -0
  108. metadata +280 -0
@@ -0,0 +1,17 @@
1
+ require_relative '../version'
2
+
3
+ module Gitlab
4
+ module Triage
5
+ module NetworkAdapters
6
+ class BaseAdapter
7
+ USER_AGENT = "GitLab Triage #{Gitlab::Triage::VERSION}".freeze
8
+
9
+ attr_reader :options
10
+
11
+ def initialize(options)
12
+ @options = options
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,92 @@
1
+ require 'graphql/client'
2
+ require 'graphql/client/http'
3
+
4
+ require_relative 'base_adapter'
5
+ require_relative '../ui'
6
+ require_relative '../errors'
7
+
8
+ module Gitlab
9
+ module Triage
10
+ module NetworkAdapters
11
+ class GraphqlAdapter < BaseAdapter
12
+ Client = GraphQL::Client
13
+
14
+ def query(graphql_query, resource_path: [], variables: {})
15
+ response = client.query(graphql_query, variables: variables, context: { token: options.token })
16
+
17
+ raise_on_error!(response)
18
+
19
+ parsed_response = parse_response(response, resource_path)
20
+ headers = response.extensions.fetch('headers', {})
21
+
22
+ graphql_response = {
23
+ ratelimit_remaining: headers['ratelimit-remaining'].to_i,
24
+ ratelimit_reset_at: Time.at(headers['ratelimit-reset'].to_i)
25
+ }
26
+
27
+ return graphql_response.merge(results: {}) if parsed_response.nil?
28
+ return graphql_response.merge(results: parsed_response.map(&:to_h)) if parsed_response.is_a?(Client::List)
29
+ return graphql_response.merge(results: parsed_response.to_h) unless parsed_response.nodes?
30
+
31
+ graphql_response.merge(
32
+ more_pages: parsed_response.page_info.has_next_page,
33
+ end_cursor: parsed_response.page_info.end_cursor,
34
+ results: parsed_response.nodes.map(&:to_h)
35
+ )
36
+ end
37
+
38
+ delegate :parse, to: :client
39
+
40
+ private
41
+
42
+ def parse_response(response, resource_path)
43
+ resource_path.reduce(response.data) { |data, resource| data&.send(resource) }
44
+ end
45
+
46
+ def raise_on_error!(response)
47
+ return if response.errors.blank?
48
+
49
+ puts Gitlab::Triage::UI.debug response.inspect if options.debug
50
+
51
+ raise "There was an error: #{response.errors.messages.to_json}"
52
+ end
53
+
54
+ def http_client
55
+ Client::HTTP.new("#{options.host_url}/api/graphql") do
56
+ def execute(document:, operation_name: nil, variables: {}, context: {}) # rubocop:disable Lint/NestedMethodDefinition
57
+ body = {}
58
+ body['query'] = document.to_query_string
59
+ body['variables'] = variables if variables.any?
60
+ body['operationName'] = operation_name if operation_name
61
+
62
+ response = HTTParty.post(
63
+ uri,
64
+ body: body.to_json,
65
+ headers: {
66
+ 'User-Agent' => USER_AGENT,
67
+ 'Content-type' => 'application/json',
68
+ 'PRIVATE-TOKEN' => context[:token]
69
+ }
70
+ )
71
+
72
+ case response.code
73
+ when 200, 400
74
+ JSON.parse(response.body).merge('extensions' => { 'headers' => response.headers })
75
+ else
76
+ { 'errors' => [{ 'message' => "#{response.code} #{response.message}" }] }
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def schema
83
+ @schema ||= Client.load_schema(http_client)
84
+ end
85
+
86
+ def client
87
+ @client ||= Client.new(schema: schema, execute: http_client).tap { |client| client.allow_dynamic_queries = true }
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,116 @@
1
+ require 'httparty'
2
+
3
+ require_relative 'base_adapter'
4
+ require_relative '../ui'
5
+ require_relative '../errors'
6
+
7
+ module Gitlab
8
+ module Triage
9
+ module NetworkAdapters
10
+ class HttpartyAdapter < BaseAdapter
11
+ def get(token, url)
12
+ response = HTTParty.get(
13
+ url,
14
+ headers: {
15
+ 'User-Agent' => USER_AGENT,
16
+ 'Content-type' => 'application/json',
17
+ 'PRIVATE-TOKEN' => token
18
+ }
19
+ )
20
+
21
+ raise_on_unauthorized_error!(response)
22
+ raise_on_internal_server_error!(response)
23
+ raise_on_too_many_requests!(response)
24
+
25
+ {
26
+ more_pages: (response.headers["x-next-page"].to_s != ""),
27
+ next_page_url: next_page_url(url, response),
28
+ results: response.parsed_response,
29
+ ratelimit_remaining: response.headers["ratelimit-remaining"].to_i,
30
+ ratelimit_reset_at: Time.at(response.headers["ratelimit-reset"].to_i)
31
+ }
32
+ end
33
+
34
+ def post(token, url, body)
35
+ response = HTTParty.post(
36
+ url,
37
+ body: body.to_json,
38
+ headers: {
39
+ 'User-Agent' => "GitLab Triage #{Gitlab::Triage::VERSION}",
40
+ 'Content-type' => 'application/json',
41
+ 'PRIVATE-TOKEN' => token
42
+ }
43
+ )
44
+
45
+ raise_on_unauthorized_error!(response)
46
+ raise_on_internal_server_error!(response)
47
+ raise_on_too_many_requests!(response)
48
+
49
+ {
50
+ results: response.parsed_response,
51
+ ratelimit_remaining: response.headers["ratelimit-remaining"].to_i,
52
+ ratelimit_reset_at: Time.at(response.headers["ratelimit-reset"].to_i)
53
+ }
54
+ end
55
+
56
+ def delete(token, url)
57
+ response = HTTParty.delete(
58
+ url,
59
+ headers: {
60
+ 'User-Agent' => USER_AGENT,
61
+ 'PRIVATE-TOKEN' => token
62
+ }
63
+ )
64
+
65
+ raise_on_unauthorized_error!(response)
66
+ raise_on_internal_server_error!(response)
67
+ raise_on_too_many_requests!(response)
68
+
69
+ {
70
+ results: response.parsed_response,
71
+ ratelimit_remaining: response.headers["ratelimit-remaining"].to_i,
72
+ ratelimit_reset_at: Time.at(response.headers["ratelimit-reset"].to_i)
73
+ }
74
+ end
75
+
76
+ private
77
+
78
+ def raise_on_unauthorized_error!(response)
79
+ return unless response.response.is_a?(Net::HTTPUnauthorized)
80
+
81
+ puts Gitlab::Triage::UI.debug response.inspect if options.debug
82
+
83
+ raise 'The provided token is unauthorized!'
84
+ end
85
+
86
+ def raise_on_internal_server_error!(response)
87
+ return unless response.response.is_a?(Net::HTTPInternalServerError)
88
+
89
+ puts Gitlab::Triage::UI.debug response.inspect if options.debug
90
+
91
+ raise Errors::Network::InternalServerError, 'Internal server error encountered!'
92
+ end
93
+
94
+ def raise_on_too_many_requests!(response)
95
+ return unless response.response.is_a?(Net::HTTPTooManyRequests)
96
+
97
+ puts Gitlab::Triage::UI.debug response.inspect if options.debug
98
+
99
+ raise Errors::Network::TooManyRequests, 'Too many requests made!'
100
+ end
101
+
102
+ def next_page_url(url, response)
103
+ return unless response.headers['x-next-page'].present?
104
+
105
+ next_page = "&page=#{response.headers['x-next-page']}"
106
+
107
+ if url.include?('&page')
108
+ url.gsub(/&page=\d+/, next_page)
109
+ else
110
+ url + next_page
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,39 @@
1
+ require 'httparty'
2
+
3
+ require_relative 'base_adapter'
4
+
5
+ module Gitlab
6
+ module Triage
7
+ module NetworkAdapters
8
+ class TestAdapter < BaseAdapter
9
+ def get(_token, url)
10
+ results =
11
+ case url
12
+ when %r{\Ahttps://gitlab.com/v4/issues?per_page=100}
13
+ [
14
+ { id: 1, title: 'First issue' }
15
+ ]
16
+ else
17
+ []
18
+ end
19
+
20
+ {
21
+ more_pages: false,
22
+ next_page_url: nil,
23
+ results: results,
24
+ ratelimit_remaining: 600,
25
+ ratelimit_reset_at: Time.now
26
+ }
27
+ end
28
+
29
+ def post(token, url, body)
30
+ {
31
+ results: {},
32
+ ratelimit_remaining: 600,
33
+ ratelimit_reset_at: Time.now
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal
2
+
3
+ require 'optparse'
4
+ require 'fileutils'
5
+ require_relative 'options'
6
+
7
+ module Gitlab
8
+ module Triage
9
+ class OptionParser
10
+ class << self
11
+ # rubocop:disable Metrics/AbcSize
12
+ def parse(argv)
13
+ options = Options.new
14
+ options.host_url = 'https://gitlab.com'
15
+
16
+ parser = ::OptionParser.new do |opts|
17
+ opts.banner = "Usage: gitlab-triage [options]\n\n"
18
+
19
+ opts.on('-n', '--dry-run', "Don't actually update anything, just print") do |value|
20
+ options.dry_run = value
21
+ end
22
+
23
+ opts.on('-f', '--policies-file [string]', String, 'A valid policies YML file') do |value|
24
+ options.policies_files << value
25
+ end
26
+
27
+ opts.on('--all-projects', 'Process all projects the token has access to') do |value|
28
+ options.all = value
29
+ end
30
+
31
+ opts.on('-s', '--source [type]', [:projects, :groups], 'The source type between [ projects or groups ], default value: projects') do |value|
32
+ options.source = value
33
+ end
34
+
35
+ opts.on('-i', '--source-id [string]', String, 'Source ID or path') do |value|
36
+ options.source_id = value
37
+ end
38
+
39
+ opts.on('-p', '--project-id [string]', String, '[Deprecated] A project ID or path, please use `--source-id`') do |value|
40
+ puts Gitlab::Triage::UI.warn("The option `--project-id` has been deprecated, please use `--source-id` instead")
41
+ puts
42
+ options.source = 'projects'
43
+ options.source_id = value
44
+ end
45
+
46
+ opts.on('--resource-reference [string]', String, 'Resource short-reference, e.g. #42, !33, or &99') do |value|
47
+ options.resource_reference = value
48
+ end
49
+
50
+ opts.on('-t', '--token [string]', String, 'A valid API token') do |value|
51
+ options.token = value
52
+ end
53
+
54
+ opts.on('-H', '--host-url [string]', String, 'A valid host url') do |value|
55
+ options.host_url = value
56
+ end
57
+
58
+ opts.on('-r', '--require [string]', String, 'Require a file before performing') do |value|
59
+ options.require_files << value
60
+ end
61
+
62
+ opts.on('-d', '--debug', 'Print debug information') do |value|
63
+ options.debug = value
64
+ end
65
+
66
+ opts.on('-h', '--help', 'Print help message') do
67
+ $stdout.puts opts
68
+ exit # rubocop:disable Rails/Exit
69
+ end
70
+
71
+ opts.on('-v', '--version', 'Print version') do
72
+ require_relative 'version'
73
+ $stdout.puts Gitlab::Triage::VERSION
74
+ exit # rubocop:disable Rails/Exit
75
+ end
76
+
77
+ opts.on('--init', 'Initialize the project with a policy file') do
78
+ example_path =
79
+ File.expand_path('../../../support/.triage-policies.example.yml', __dir__)
80
+
81
+ FileUtils.cp(example_path, '.triage-policies.yml')
82
+ exit # rubocop:disable Rails/Exit
83
+ end
84
+
85
+ opts.on('--init-ci', 'Initialize the project with a .gitlab-ci.yml file') do
86
+ example_path =
87
+ File.expand_path('../../../support/.gitlab-ci.example.yml', __dir__)
88
+
89
+ FileUtils.cp(example_path, '.gitlab-ci.yml')
90
+ exit # rubocop:disable Rails/Exit
91
+ end
92
+ end
93
+
94
+ parser.parse!(argv)
95
+
96
+ options.source = nil if options.all
97
+ options.token ||= ''
98
+
99
+ options
100
+ end
101
+ # rubocop:enable Metrics/AbcSize
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,30 @@
1
+ module Gitlab
2
+ module Triage
3
+ Options = Struct.new(
4
+ :dry_run,
5
+ :policies_files,
6
+ :resources,
7
+ :all,
8
+ :source,
9
+ :source_id,
10
+ :resource_reference,
11
+ :token,
12
+ :debug,
13
+ :host_url,
14
+ :require_files,
15
+ :api_version
16
+ ) do
17
+ def initialize(*args)
18
+ super
19
+
20
+ # Defaults
21
+ self.host_url ||= 'https://gitlab.com'
22
+ self.api_version ||= 'v4'
23
+ self.all ||= false
24
+ self.source ||= 'projects'
25
+ self.require_files ||= []
26
+ self.policies_files ||= Set.new
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,64 @@
1
+ require_relative '../validators/params_validator'
2
+
3
+ module Gitlab
4
+ module Triage
5
+ module ParamBuilders
6
+ class DateParamBuilder
7
+ CONDITIONS = %w[older_than newer_than].freeze
8
+ TIME_BASED_INTERVALS = %w[minutes hours].freeze
9
+ DATE_BASED_INTERVALS = %w[days weeks months years].freeze
10
+ INTERVAL_TYPES = TIME_BASED_INTERVALS + DATE_BASED_INTERVALS
11
+
12
+ def initialize(allowed_attributes, condition_hash)
13
+ @allowed_attributes = allowed_attributes
14
+ @attribute = condition_hash[:attribute].to_s
15
+ @interval_condition = condition_hash[:condition].to_sym
16
+ @interval_type = condition_hash[:interval_type]
17
+ @interval = condition_hash[:interval]
18
+
19
+ validate_condition(condition_hash)
20
+ end
21
+
22
+ def param_content
23
+ if TIME_BASED_INTERVALS.include?(interval_type)
24
+ interval.public_send(interval_type).ago.to_datetime # rubocop:disable GitlabSecurity/PublicSend
25
+ else
26
+ interval.public_send(interval_type).ago.to_date # rubocop:disable GitlabSecurity/PublicSend
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :allowed_attributes, :attribute, :interval_condition, :interval_type, :interval
33
+
34
+ def validate_condition(condition)
35
+ ParamsValidator.new(filter_parameters, condition).validate!
36
+ end
37
+
38
+ def filter_parameters
39
+ [
40
+ {
41
+ name: :attribute,
42
+ type: String,
43
+ values: allowed_attributes
44
+ },
45
+ {
46
+ name: :condition,
47
+ type: String,
48
+ values: CONDITIONS
49
+ },
50
+ {
51
+ name: :interval_type,
52
+ type: String,
53
+ values: INTERVAL_TYPES
54
+ },
55
+ {
56
+ name: :interval,
57
+ type: Numeric
58
+ }
59
+ ]
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module Triage
5
+ module Policies
6
+ class BasePolicy
7
+ InvalidPolicyError = Class.new(StandardError)
8
+
9
+ attr_reader :type, :policy_spec, :resources, :network
10
+ attr_accessor :summary
11
+
12
+ def initialize(type, policy_spec, resources, network)
13
+ @type = type
14
+ @policy_spec = policy_spec
15
+ @resources = resources
16
+ @network = network
17
+ end
18
+
19
+ def validate!
20
+ raise InvalidPolicyError, 'Policies that comment_on_summary must include summarize action' if comment_on_summary? && !summarize?
21
+ end
22
+
23
+ def name
24
+ @name ||= (policy_spec[:name] || "#{type}-#{object_id}")
25
+ end
26
+
27
+ def source
28
+ case type
29
+ when 'epics'
30
+ 'groups'
31
+ else
32
+ 'projects'
33
+ end
34
+ end
35
+
36
+ def source_id_sym
37
+ case type
38
+ when 'epics'
39
+ :group_id
40
+ else
41
+ :project_id
42
+ end
43
+ end
44
+
45
+ def actions
46
+ @actions ||= policy_spec.fetch(:actions) { {} }
47
+ end
48
+
49
+ def summarize?
50
+ actions.key?(:summarize)
51
+ end
52
+
53
+ def comment_on_summary?
54
+ actions.key?(:comment_on_summary)
55
+ end
56
+
57
+ def comment?
58
+ # The actual keys are strings
59
+ (actions.keys.map(&:to_sym) - [:summarize, :comment_on_summary, :delete, :issue]).any?
60
+ end
61
+
62
+ def issue?
63
+ actions.key?(:issue)
64
+ end
65
+
66
+ def delete?
67
+ actions.key?(:delete) && actions[:delete]
68
+ end
69
+
70
+ def build_issue
71
+ raise NotImplementedError
72
+ end
73
+
74
+ def build_summary
75
+ raise NotImplementedError
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_policy'
4
+ require_relative '../entity_builders/issue_builder'
5
+ require_relative '../entity_builders/summary_builder'
6
+
7
+ module Gitlab
8
+ module Triage
9
+ module Policies
10
+ class RulePolicy < BasePolicy
11
+ # Build a summary from a single rule policy
12
+ def build_summary
13
+ action = actions.fetch(:summarize, {})
14
+
15
+ EntityBuilders::SummaryBuilder.new(
16
+ type: type,
17
+ policy_spec: policy_spec,
18
+ action: action,
19
+ resources: resources,
20
+ network: network)
21
+ end
22
+
23
+ def build_issue(resource)
24
+ action = actions.fetch(:issue, {})
25
+
26
+ EntityBuilders::IssueBuilder.new(
27
+ type: type,
28
+ policy_spec: policy_spec,
29
+ action: action,
30
+ resource: resource,
31
+ network: network)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_policy'
4
+ require_relative '../entity_builders/summary_builder'
5
+
6
+ module Gitlab
7
+ module Triage
8
+ module Policies
9
+ class SummaryPolicy < BasePolicy
10
+ # Build a summary from several rules policies
11
+ def build_summary
12
+ action = actions[:summarize]
13
+ issues = resources.map do |inner_policy_spec, inner_resources|
14
+ Policies::RulePolicy.new(
15
+ type, inner_policy_spec, inner_resources, network)
16
+ .build_summary
17
+ end
18
+
19
+ EntityBuilders::SummaryBuilder.new(
20
+ type: type,
21
+ action: action,
22
+ resources: issues.select(&:any_resources?),
23
+ network: network,
24
+ separator: "\n\n")
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module Gitlab
6
+ module Triage
7
+ module PoliciesResources
8
+ RuleResources = Class.new(SimpleDelegator)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module Gitlab
6
+ module Triage
7
+ module PoliciesResources
8
+ SummaryResources = Class.new(SimpleDelegator)
9
+ end
10
+ end
11
+ end