gitlab-triage 1.14.3 → 1.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +51 -0
- data/lib/gitlab/triage/action.rb +8 -4
- data/lib/gitlab/triage/action/comment_on_summary.rb +83 -0
- data/lib/gitlab/triage/action/summarize.rb +7 -1
- data/lib/gitlab/triage/engine.rb +16 -1
- data/lib/gitlab/triage/errors/network.rb +1 -0
- data/lib/gitlab/triage/graphql_network.rb +17 -0
- data/lib/gitlab/triage/network.rb +3 -1
- data/lib/gitlab/triage/network_adapters/graphql_adapter.rb +33 -10
- data/lib/gitlab/triage/policies/base_policy.rb +12 -1
- data/lib/gitlab/triage/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a2927188b4b24b36453f4bac48a82b9af7d2ddc33bb7b5219f533282da9e8370
|
4
|
+
data.tar.gz: 8d35707ba7d219950ab3ba8dda852e89687f16b360cf1f368ae02038ff3ff7cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12def4c673ceb025d1a286c9c10a53c410d037364865482b85500bff9215d6490e6d0fcc2737f859a8bbeeba5a302c5e2ff6cbddfc7bab1599e909c243240d67
|
7
|
+
data.tar.gz: b98bbed0aabc5cf72595f5dbb3b60ce8d5bb25ad4af19aff1de46c34642ac15c7dc367db39ad1092833a97a923ac73957768bf14b5ed06ccb8bfd518e5fdbdc2
|
data/README.md
CHANGED
@@ -585,6 +585,7 @@ Available action types:
|
|
585
585
|
- [`comment` action](#comment-action)
|
586
586
|
- [`comment_type` action option](#comment-type-action-option)
|
587
587
|
- [`summarize` action](#summarize-action)
|
588
|
+
- [`comment_on_summary` action](#comment-on-summary-action)
|
588
589
|
|
589
590
|
##### Labels action
|
590
591
|
|
@@ -867,6 +868,56 @@ Which could generate an issue like:
|
|
867
868
|
/label ~"needs attention"
|
868
869
|
```
|
869
870
|
|
871
|
+
##### Comment on summary action
|
872
|
+
|
873
|
+
Generates one comment for each resource, attaching these comments to the summary
|
874
|
+
created by the [`summarize` action](#summarize-action).
|
875
|
+
|
876
|
+
The use case for this is wanting to create a summary with an overview, and then
|
877
|
+
a threaded discussion for each resource, with a header comment starting each
|
878
|
+
discussion.
|
879
|
+
|
880
|
+
Accepts a single string value: the template used to generate the comments. For
|
881
|
+
details of the syntax of this template, see the [comment action](#comment-action).
|
882
|
+
|
883
|
+
Since this action depends on the summary, it is invalid to supply a
|
884
|
+
`comment_on_summary` action without an accompanying `summarize` sibling action.
|
885
|
+
The `summarize` action will always be completed first.
|
886
|
+
|
887
|
+
Just like for [comment action](#comment-action), setting `comment_type` in the
|
888
|
+
`actions` set controls whether the comment must be resolved for merge requests.
|
889
|
+
See: [`comment_type` action option](#comment-type-action-option).
|
890
|
+
|
891
|
+
Example:
|
892
|
+
|
893
|
+
```yml
|
894
|
+
resource_rules:
|
895
|
+
issues:
|
896
|
+
rules:
|
897
|
+
- name: List of issues to discuss
|
898
|
+
limits:
|
899
|
+
most_recent: 15
|
900
|
+
actions:
|
901
|
+
comment_type: thread
|
902
|
+
comment_on_summary: |
|
903
|
+
# {{title}}
|
904
|
+
|
905
|
+
author: {{author}}
|
906
|
+
summarize:
|
907
|
+
title: |
|
908
|
+
#{resource[:type].capitalize} require labels
|
909
|
+
item: |
|
910
|
+
- [ ] [{{title}}]({{web_url}}) {{labels}}
|
911
|
+
summary: |
|
912
|
+
The following {{type}} require labels:
|
913
|
+
|
914
|
+
{{items}}
|
915
|
+
|
916
|
+
Please take care of them before the end of #{7.days.from_now.strftime('%Y-%m-%d')}
|
917
|
+
|
918
|
+
/label ~"needs attention"
|
919
|
+
```
|
920
|
+
|
870
921
|
### Summary policies
|
871
922
|
|
872
923
|
Summary policies are special policies that join multiple rule policies together
|
data/lib/gitlab/triage/action.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
require_relative 'action/summarize'
|
2
2
|
require_relative 'action/comment'
|
3
|
+
require_relative 'action/comment_on_summary'
|
3
4
|
|
4
5
|
module Gitlab
|
5
6
|
module Triage
|
6
7
|
module Action
|
7
8
|
def self.process(policy:, **args)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
policy.validate!
|
10
|
+
|
11
|
+
[
|
12
|
+
[Summarize, policy.summarize?],
|
13
|
+
[Comment, policy.comment?],
|
14
|
+
[CommentOnSummary, policy.comment_on_summary?]
|
15
|
+
].each do |action, active|
|
12
16
|
act(action: action, policy: policy, **args) if active
|
13
17
|
end
|
14
18
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require_relative '../command_builders/text_content_builder'
|
5
|
+
require_relative '../command_builders/comment_command_builder'
|
6
|
+
require_relative '../command_builders/label_command_builder'
|
7
|
+
require_relative '../command_builders/remove_label_command_builder'
|
8
|
+
require_relative '../command_builders/cc_command_builder'
|
9
|
+
require_relative '../command_builders/status_command_builder'
|
10
|
+
require_relative '../command_builders/move_command_builder'
|
11
|
+
|
12
|
+
module Gitlab
|
13
|
+
module Triage
|
14
|
+
module Action
|
15
|
+
class CommentOnSummary < Base
|
16
|
+
class Dry < CommentOnSummary
|
17
|
+
def act
|
18
|
+
puts "The following comments would be posted for the rule **#{policy.name}**:\n\n"
|
19
|
+
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def perform(comment)
|
26
|
+
puts "# #{summary[:web_url]}\n```\n#{comment}\n```\n"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :summary
|
31
|
+
|
32
|
+
def initialize(policy:, network:)
|
33
|
+
super(policy: policy, network: network)
|
34
|
+
@summary = policy.summary
|
35
|
+
end
|
36
|
+
|
37
|
+
def act
|
38
|
+
policy.resources.each do |resource|
|
39
|
+
comment = build_comment(resource).strip
|
40
|
+
|
41
|
+
perform(comment) unless comment.empty?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def build_comment(resource)
|
48
|
+
CommandBuilders::TextContentBuilder.new(policy.actions[:comment_on_summary], resource: resource, network: network).build_command
|
49
|
+
end
|
50
|
+
|
51
|
+
def perform(comment)
|
52
|
+
network.post_api(build_post_url, body: comment)
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_post_url
|
56
|
+
# POST /projects/:id/issues/:issue_iid/notes
|
57
|
+
post_url = UrlBuilders::UrlBuilder.new(
|
58
|
+
network_options: network.options,
|
59
|
+
source_id: summary['project_id'],
|
60
|
+
resource_type: policy.type,
|
61
|
+
resource_id: summary['iid'],
|
62
|
+
sub_resource_type: sub_resource_type
|
63
|
+
).build
|
64
|
+
|
65
|
+
puts Gitlab::Triage::UI.debug "post_url: #{post_url}" if network.options.debug
|
66
|
+
|
67
|
+
post_url
|
68
|
+
end
|
69
|
+
|
70
|
+
def sub_resource_type
|
71
|
+
case type = policy.actions[:comment_type]
|
72
|
+
when 'comment', nil # nil is default
|
73
|
+
'notes'
|
74
|
+
when 'thread'
|
75
|
+
'discussions'
|
76
|
+
else
|
77
|
+
raise ArgumentError, "Unknown comment type: #{type}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -10,6 +10,12 @@ module Gitlab
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def perform
|
13
|
+
policy.summary = {
|
14
|
+
web_url: '[the-created-issue-url]',
|
15
|
+
project_id: 'some-id',
|
16
|
+
iid: 'some-iid'
|
17
|
+
}.with_indifferent_access
|
18
|
+
|
13
19
|
if group_summary_without_destination?
|
14
20
|
puts Gitlab::Triage::UI.warn("No issue will be created: No summary destination specified when source is 'groups'.")
|
15
21
|
return
|
@@ -35,7 +41,7 @@ module Gitlab
|
|
35
41
|
return
|
36
42
|
end
|
37
43
|
|
38
|
-
network.post_api(post_issue_url, post_issue_body)
|
44
|
+
policy.summary = network.post_api(post_issue_url, post_issue_body)
|
39
45
|
end
|
40
46
|
|
41
47
|
def issue
|
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -181,7 +181,7 @@ module Gitlab
|
|
181
181
|
iids = resources.pluck('iid').map(&:to_s)
|
182
182
|
|
183
183
|
graphql_query = build_graphql_query(resource_type, conditions)
|
184
|
-
graphql_resources = graphql_network.query(graphql_query, source:
|
184
|
+
graphql_resources = graphql_network.query(graphql_query, source: source_full_path, iids: iids) if graphql_query.present?
|
185
185
|
# In some filters/actions we want to know which resource type it is
|
186
186
|
attach_resource_type(resources, resource_type)
|
187
187
|
decorate_resources_with_graphql_data(resources, graphql_resources)
|
@@ -317,6 +317,21 @@ module Gitlab
|
|
317
317
|
Gitlab::Triage::GraphqlQueries::QueryBuilder
|
318
318
|
.new(options.source, resource_type, conditions)
|
319
319
|
end
|
320
|
+
|
321
|
+
def source_full_path
|
322
|
+
@source_full_path ||= fetch_source_full_path
|
323
|
+
end
|
324
|
+
|
325
|
+
def fetch_source_full_path
|
326
|
+
return options.source_id unless /\A\d+\z/.match?(options.source_id)
|
327
|
+
|
328
|
+
source_details = network.query_api(build_get_url(nil, {})).first
|
329
|
+
full_path = source_details['full_path'] || source_details['path_with_namespace']
|
330
|
+
|
331
|
+
raise ArgumentError, 'A source with given source_id was not found!' if full_path.blank?
|
332
|
+
|
333
|
+
full_path
|
334
|
+
end
|
320
335
|
end
|
321
336
|
end
|
322
337
|
end
|
@@ -11,6 +11,8 @@ module Gitlab
|
|
11
11
|
class GraphqlNetwork
|
12
12
|
attr_reader :options, :adapter
|
13
13
|
|
14
|
+
MINIMUM_RATE_LIMIT = 25
|
15
|
+
|
14
16
|
def initialize(adapter)
|
15
17
|
@adapter = adapter
|
16
18
|
@options = adapter.options
|
@@ -33,6 +35,9 @@ module Gitlab
|
|
33
35
|
variables: variables.merge(after: response.delete(:end_cursor))
|
34
36
|
)
|
35
37
|
|
38
|
+
rate_limit_debug(response) if options.debug
|
39
|
+
rate_limit_wait(response)
|
40
|
+
|
36
41
|
resources.concat(Array.wrap(response.delete(:results)))
|
37
42
|
end while response.delete(:more_pages)
|
38
43
|
|
@@ -49,6 +54,18 @@ module Gitlab
|
|
49
54
|
|
50
55
|
GlobalID.parse(global_id).model_id.to_i
|
51
56
|
end
|
57
|
+
|
58
|
+
def rate_limit_debug(response)
|
59
|
+
rate_limit_infos = "Rate limit remaining: #{response[:ratelimit_remaining]} (reset at #{response[:ratelimit_reset_at]})"
|
60
|
+
puts Gitlab::Triage::UI.debug "rate_limit_infos: #{rate_limit_infos}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def rate_limit_wait(response)
|
64
|
+
return unless response.delete(:ratelimit_remaining) < MINIMUM_RATE_LIMIT
|
65
|
+
|
66
|
+
puts Gitlab::Triage::UI.debug "Rate limit almost exceeded, sleeping for #{response[:ratelimit_reset_at] - Time.now} seconds" if options.debug
|
67
|
+
sleep(1) until Time.now >= response[:ratelimit_reset_at]
|
68
|
+
end
|
52
69
|
end
|
53
70
|
end
|
54
71
|
end
|
@@ -44,8 +44,10 @@ module Gitlab
|
|
44
44
|
case results
|
45
45
|
when Array
|
46
46
|
resources.concat(results)
|
47
|
-
|
47
|
+
when Hash
|
48
48
|
resources << results
|
49
|
+
else
|
50
|
+
raise Errors::Network::UnexpectedResponse, "Unexpected response: #{results.inspect}"
|
49
51
|
end
|
50
52
|
|
51
53
|
rate_limit_debug(response) if options.debug
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'httparty'
|
1
2
|
require 'graphql/client'
|
2
3
|
require 'graphql/client/http'
|
3
4
|
|
@@ -17,16 +18,22 @@ module Gitlab
|
|
17
18
|
raise_on_error!(response)
|
18
19
|
|
19
20
|
parsed_response = parse_response(response, resource_path)
|
21
|
+
headers = response.extensions.fetch('headers', {})
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
graphql_response = {
|
24
|
+
ratelimit_remaining: headers['ratelimit-remaining'].to_i,
|
25
|
+
ratelimit_reset_at: Time.at(headers['ratelimit-reset'].to_i)
|
26
|
+
}
|
27
|
+
|
28
|
+
return graphql_response.merge(results: {}) if parsed_response.nil?
|
29
|
+
return graphql_response.merge(results: parsed_response.map(&:to_h)) if parsed_response.is_a?(Client::List)
|
30
|
+
return graphql_response.merge(results: parsed_response.to_h) unless parsed_response.nodes?
|
24
31
|
|
25
|
-
|
32
|
+
graphql_response.merge(
|
26
33
|
more_pages: parsed_response.page_info.has_next_page,
|
27
34
|
end_cursor: parsed_response.page_info.end_cursor,
|
28
35
|
results: parsed_response.nodes.map(&:to_h)
|
29
|
-
|
36
|
+
)
|
30
37
|
end
|
31
38
|
|
32
39
|
delegate :parse, to: :client
|
@@ -47,11 +54,27 @@ module Gitlab
|
|
47
54
|
|
48
55
|
def http_client
|
49
56
|
Client::HTTP.new("#{options.host_url}/api/graphql") do
|
50
|
-
def
|
51
|
-
{
|
52
|
-
|
53
|
-
|
54
|
-
|
57
|
+
def execute(document:, operation_name: nil, variables: {}, context: {}) # rubocop:disable Lint/NestedMethodDefinition
|
58
|
+
body = {}
|
59
|
+
body['query'] = document.to_query_string
|
60
|
+
body['variables'] = variables if variables.any?
|
61
|
+
body['operationName'] = operation_name if operation_name
|
62
|
+
|
63
|
+
response = HTTParty.post(
|
64
|
+
uri,
|
65
|
+
body: body.to_json,
|
66
|
+
headers: {
|
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
|
55
78
|
end
|
56
79
|
end
|
57
80
|
end
|
@@ -4,7 +4,10 @@ module Gitlab
|
|
4
4
|
module Triage
|
5
5
|
module Policies
|
6
6
|
class BasePolicy
|
7
|
+
InvalidPolicyError = Class.new(StandardError)
|
8
|
+
|
7
9
|
attr_reader :type, :policy_spec, :resources, :network
|
10
|
+
attr_accessor :summary
|
8
11
|
|
9
12
|
def initialize(type, policy_spec, resources, network)
|
10
13
|
@type = type
|
@@ -13,6 +16,10 @@ module Gitlab
|
|
13
16
|
@network = network
|
14
17
|
end
|
15
18
|
|
19
|
+
def validate!
|
20
|
+
raise InvalidPolicyError, 'Policies that comment_on_summary must include summarize action' if comment_on_summary? && !summarize?
|
21
|
+
end
|
22
|
+
|
16
23
|
def name
|
17
24
|
@name ||= (policy_spec[:name] || "#{type}-#{object_id}")
|
18
25
|
end
|
@@ -25,9 +32,13 @@ module Gitlab
|
|
25
32
|
actions.key?(:summarize)
|
26
33
|
end
|
27
34
|
|
35
|
+
def comment_on_summary?
|
36
|
+
actions.key?(:comment_on_summary)
|
37
|
+
end
|
38
|
+
|
28
39
|
def comment?
|
29
40
|
# The actual keys are strings
|
30
|
-
(actions.keys.map(&:to_sym) - [:summarize]).any?
|
41
|
+
(actions.keys.map(&:to_sym) - [:summarize, :comment_on_summary]).any?
|
31
42
|
end
|
32
43
|
|
33
44
|
def build_issue
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-triage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitLab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -162,6 +162,7 @@ files:
|
|
162
162
|
- lib/gitlab/triage/action.rb
|
163
163
|
- lib/gitlab/triage/action/base.rb
|
164
164
|
- lib/gitlab/triage/action/comment.rb
|
165
|
+
- lib/gitlab/triage/action/comment_on_summary.rb
|
165
166
|
- lib/gitlab/triage/action/summarize.rb
|
166
167
|
- lib/gitlab/triage/api_query_builders/base_query_param_builder.rb
|
167
168
|
- lib/gitlab/triage/api_query_builders/date_query_param_builder.rb
|