payping-gitlab-triage 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +19 -0
- data/.gitignore +15 -0
- data/.gitlab/CODEOWNERS +2 -0
- data/.gitlab/changelog_config.yml +13 -0
- data/.gitlab/issue_templates/Default.md +13 -0
- data/.gitlab/merge_request_templates/Default.md +11 -0
- data/.gitlab/merge_request_templates/Release.md +13 -0
- data/.gitlab-ci.yml +146 -0
- data/.rubocop.yml +21 -0
- data/.rubocop_todo.yml +145 -0
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/.yardopts +4 -0
- data/CONTRIBUTING.md +31 -0
- data/Dangerfile +5 -0
- data/Gemfile +15 -0
- data/Guardfile +70 -0
- data/LICENSE.md +25 -0
- data/README.md +1480 -0
- data/Rakefile +6 -0
- data/bin/gitlab-triage +19 -0
- data/gitlab-triage.gemspec +41 -0
- data/lib/gitlab/triage/action/base.rb +14 -0
- data/lib/gitlab/triage/action/comment.rb +104 -0
- data/lib/gitlab/triage/action/comment_on_summary.rb +83 -0
- data/lib/gitlab/triage/action/delete.rb +56 -0
- data/lib/gitlab/triage/action/issue.rb +64 -0
- data/lib/gitlab/triage/action/summarize.rb +82 -0
- data/lib/gitlab/triage/action.rb +36 -0
- data/lib/gitlab/triage/api_query_builders/base_query_param_builder.rb +27 -0
- data/lib/gitlab/triage/api_query_builders/date_query_param_builder.rb +42 -0
- data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +28 -0
- data/lib/gitlab/triage/api_query_builders/single_query_param_builder.rb +13 -0
- data/lib/gitlab/triage/command_builders/base_command_builder.rb +40 -0
- data/lib/gitlab/triage/command_builders/cc_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/comment_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/label_command_builder.rb +40 -0
- data/lib/gitlab/triage/command_builders/move_command_builder.rb +19 -0
- data/lib/gitlab/triage/command_builders/remove_label_command_builder.rb +15 -0
- data/lib/gitlab/triage/command_builders/status_command_builder.rb +23 -0
- data/lib/gitlab/triage/command_builders/text_content_builder.rb +138 -0
- data/lib/gitlab/triage/engine.rb +635 -0
- data/lib/gitlab/triage/entity_builders/issue_builder.rb +54 -0
- data/lib/gitlab/triage/entity_builders/summary_builder.rb +82 -0
- data/lib/gitlab/triage/errors/network.rb +11 -0
- data/lib/gitlab/triage/errors.rb +1 -0
- data/lib/gitlab/triage/expand_condition/expansion.rb +203 -0
- data/lib/gitlab/triage/expand_condition/list.rb +25 -0
- data/lib/gitlab/triage/expand_condition/sequence.rb +25 -0
- data/lib/gitlab/triage/expand_condition.rb +23 -0
- data/lib/gitlab/triage/filters/assignee_member_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/author_member_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/base_conditions_filter.rb +58 -0
- data/lib/gitlab/triage/filters/branch_date_filter.rb +73 -0
- data/lib/gitlab/triage/filters/branch_protected_filter.rb +26 -0
- data/lib/gitlab/triage/filters/discussions_conditions_filter.rb +58 -0
- data/lib/gitlab/triage/filters/issue_date_conditions_filter.rb +78 -0
- data/lib/gitlab/triage/filters/member_conditions_filter.rb +84 -0
- data/lib/gitlab/triage/filters/merge_request_date_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/name_conditions_filter.rb +26 -0
- data/lib/gitlab/triage/filters/no_additional_labels_conditions_filter.rb +30 -0
- data/lib/gitlab/triage/filters/ruby_conditions_filter.rb +33 -0
- data/lib/gitlab/triage/filters/votes_conditions_filter.rb +54 -0
- data/lib/gitlab/triage/graphql_network.rb +81 -0
- data/lib/gitlab/triage/graphql_queries/query_builder.rb +158 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/base_param_builder.rb +30 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/date_param_builder.rb +35 -0
- data/lib/gitlab/triage/graphql_queries/query_param_builders/labels_param_builder.rb +18 -0
- data/lib/gitlab/triage/limiters/base_limiter.rb +35 -0
- data/lib/gitlab/triage/limiters/date_field_limiter.rb +45 -0
- data/lib/gitlab/triage/network.rb +39 -0
- data/lib/gitlab/triage/network_adapters/base_adapter.rb +17 -0
- data/lib/gitlab/triage/network_adapters/graphql_adapter.rb +92 -0
- data/lib/gitlab/triage/network_adapters/httparty_adapter.rb +116 -0
- data/lib/gitlab/triage/network_adapters/test_adapter.rb +39 -0
- data/lib/gitlab/triage/option_parser.rb +105 -0
- data/lib/gitlab/triage/options.rb +30 -0
- data/lib/gitlab/triage/param_builders/date_param_builder.rb +64 -0
- data/lib/gitlab/triage/policies/base_policy.rb +80 -0
- data/lib/gitlab/triage/policies/rule_policy.rb +36 -0
- data/lib/gitlab/triage/policies/summary_policy.rb +29 -0
- data/lib/gitlab/triage/policies_resources/rule_resources.rb +11 -0
- data/lib/gitlab/triage/policies_resources/summary_resources.rb +11 -0
- data/lib/gitlab/triage/resource/base.rb +102 -0
- data/lib/gitlab/triage/resource/branch.rb +13 -0
- data/lib/gitlab/triage/resource/context.rb +47 -0
- data/lib/gitlab/triage/resource/epic.rb +20 -0
- data/lib/gitlab/triage/resource/instance_version.rb +35 -0
- data/lib/gitlab/triage/resource/issue.rb +52 -0
- data/lib/gitlab/triage/resource/label.rb +56 -0
- data/lib/gitlab/triage/resource/label_event.rb +48 -0
- data/lib/gitlab/triage/resource/linked_issue.rb +15 -0
- data/lib/gitlab/triage/resource/merge_request.rb +23 -0
- data/lib/gitlab/triage/resource/milestone.rb +98 -0
- data/lib/gitlab/triage/resource/shared/issuable.rb +119 -0
- data/lib/gitlab/triage/rest_api_network.rb +125 -0
- data/lib/gitlab/triage/retryable.rb +33 -0
- data/lib/gitlab/triage/ui.rb +23 -0
- data/lib/gitlab/triage/url_builders/url_builder.rb +54 -0
- data/lib/gitlab/triage/utils.rb +13 -0
- data/lib/gitlab/triage/validators/limiter_validator.rb +21 -0
- data/lib/gitlab/triage/validators/params_validator.rb +43 -0
- data/lib/gitlab/triage/version.rb +7 -0
- data/lib/gitlab/triage.rb +6 -0
- data/support/.gitlab-ci.example.yml +22 -0
- data/support/.triage-policies.example.yml +51 -0
- 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
|