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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 274f8c4fdb85670a259e32f6c378512e93a52619184714baea85372f77e54c9b
4
- data.tar.gz: 469563e881bb1678de111984c70cdd53c64abe241ab27b69ac206c262a10d078
3
+ metadata.gz: a2927188b4b24b36453f4bac48a82b9af7d2ddc33bb7b5219f533282da9e8370
4
+ data.tar.gz: 8d35707ba7d219950ab3ba8dda852e89687f16b360cf1f368ae02038ff3ff7cb
5
5
  SHA512:
6
- metadata.gz: 8d850e51292669d2763dfbf7f313fe49b24e99c1bce44a12322901a8dc9f164f1a09c96ff56c6b9fcd798fbc82f43aeed06ed6cf80ede7fb2ec517edbe49d631
7
- data.tar.gz: ef78109d3c19698308979c36ef43d3f8e163c5d053f45e093f56dd8da05f6a1c34b5463339a6631944718c561b01ded46845b35924a3d1b1454ed0379faa3310
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
@@ -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
- Summarize => policy.summarize?,
10
- Comment => policy.comment?
11
- }.compact.each do |action, active|
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
@@ -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: options.source_id, iids: iids) if graphql_query.present?
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
@@ -3,6 +3,7 @@ module Gitlab
3
3
  module Errors
4
4
  module Network
5
5
  InternalServerError = Class.new(StandardError)
6
+ UnexpectedResponse = Class.new(StandardError)
6
7
  end
7
8
  end
8
9
  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
- else
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
- return { results: {} } if parsed_response.nil?
22
- return { results: parsed_response.map(&:to_h) } if parsed_response.is_a?(Client::List)
23
- return { results: parsed_response.to_h } unless parsed_response.nodes?
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 headers(context) # rubocop:disable Lint/NestedMethodDefinition
51
- {
52
- 'Content-type' => 'application/json',
53
- 'PRIVATE-TOKEN' => context[:token]
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  module Triage
5
- VERSION = '1.14.3'
5
+ VERSION = '1.15.0'
6
6
  end
7
7
  end
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.14.3
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: 2020-11-06 00:00:00.000000000 Z
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