gitlab-triage 1.14.3 → 1.15.0
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.
- 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
|