gitlab-triage 1.14.1 → 1.17.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/.gitlab/merge_request_templates/Release.md +1 -1
- data/README.md +75 -4
- data/bin/gitlab-triage +5 -2
- data/lib/gitlab/triage/action.rb +8 -4
- data/lib/gitlab/triage/action/comment.rb +7 -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/api_query_builders/base_query_param_builder.rb +11 -2
- data/lib/gitlab/triage/api_query_builders/date_query_param_builder.rb +13 -50
- data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +10 -2
- data/lib/gitlab/triage/engine.rb +52 -11
- data/lib/gitlab/triage/errors/network.rb +2 -0
- data/lib/gitlab/triage/filters/discussions_conditions_filter.rb +1 -3
- data/lib/gitlab/triage/graphql_network.rb +28 -1
- data/lib/gitlab/triage/graphql_queries/query_builder.rb +72 -16
- data/lib/gitlab/triage/graphql_queries/query_param_builders/base_param_builder.rb +25 -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/network.rb +9 -3
- data/lib/gitlab/triage/network_adapters/graphql_adapter.rb +33 -10
- data/lib/gitlab/triage/network_adapters/httparty_adapter.rb +10 -0
- data/lib/gitlab/triage/option_parser.rb +2 -0
- data/lib/gitlab/triage/param_builders/date_param_builder.rb +58 -0
- data/lib/gitlab/triage/policies/base_policy.rb +30 -1
- data/lib/gitlab/triage/resource/context.rb +1 -0
- data/lib/gitlab/triage/resource/epic.rb +24 -0
- data/lib/gitlab/triage/retryable.rb +7 -5
- data/lib/gitlab/triage/utils.rb +13 -0
- data/lib/gitlab/triage/validators/params_validator.rb +1 -1
- data/lib/gitlab/triage/version.rb +1 -1
- metadata +10 -5
- data/lib/gitlab/triage/graphql_queries/threads_query.rb +0 -31
- data/lib/gitlab/triage/graphql_queries/user_notes_query.rb +0 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 68a42526c02ea2ea87e7f0ca3d24ac0fb5acbdd145fb30fc2bbd79e533ce9b65
|
4
|
+
data.tar.gz: 274ba6fb0d4040abc095207eb3349c3719e57e6e37d6087af1bedaed78e8b6b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be15fd2ef8f9e4d12116f83906905bd43a45f0ba81978f03504aa751e97ae367f92110072abb2b2c4ae2419fd01c9599d89db3469f96c871341291dfdcddc43a
|
7
|
+
data.tar.gz: e2a9669fd8373304b66d8470fe24fae0c55349c229accf9a523462ead4e63ea4f8ef3f3bc3c1d9a6765073a5d7226f8787f2feeb8a1c6c4491d6dcedbaa3af1f
|
@@ -32,4 +32,4 @@ with the latest commit from https://gitlab.com/gitlab-org/gitlab-triage/commits/
|
|
32
32
|
- Checklist after merging:
|
33
33
|
- [ ] [Update the release notes for the newly created tag](docs/release_process.md#how-to).
|
34
34
|
|
35
|
-
/label ~"Engineering Productivity" ~"ep::triage" ~"tooling::workflow"
|
35
|
+
/label ~"Engineering Productivity" ~"ep::triage" ~tooling ~"tooling::workflow"
|
data/README.md
CHANGED
@@ -38,6 +38,7 @@ The format of the file is [YAML](https://en.wikipedia.org/wiki/YAML).
|
|
38
38
|
project.
|
39
39
|
|
40
40
|
Select which resource to add the policy to:
|
41
|
+
- `epics`
|
41
42
|
- `issues`
|
42
43
|
- `merge_requests`
|
43
44
|
|
@@ -47,9 +48,28 @@ For example:
|
|
47
48
|
|
48
49
|
```yml
|
49
50
|
resource_rules:
|
51
|
+
epics:
|
52
|
+
rules:
|
53
|
+
- name: My epic policy
|
54
|
+
conditions:
|
55
|
+
date:
|
56
|
+
attribute: updated_at
|
57
|
+
condition: older_than
|
58
|
+
interval_type: days
|
59
|
+
interval: 5
|
60
|
+
state: opened
|
61
|
+
labels:
|
62
|
+
- None
|
63
|
+
actions:
|
64
|
+
labels:
|
65
|
+
- needs attention
|
66
|
+
mention:
|
67
|
+
- markglenfletcher
|
68
|
+
comment: |
|
69
|
+
{{author}} This epic is unlabelled after 5 days. It needs attention. Please take care of this before the end of #{2.days.from_now.strftime('%Y-%m-%d')}
|
50
70
|
issues:
|
51
71
|
rules:
|
52
|
-
- name: My policy
|
72
|
+
- name: My issue policy
|
53
73
|
conditions:
|
54
74
|
date:
|
55
75
|
attribute: updated_at
|
@@ -86,7 +106,7 @@ resource_rules:
|
|
86
106
|
/label ~"needs attention"
|
87
107
|
merge_requests:
|
88
108
|
rules:
|
89
|
-
- name: My policy
|
109
|
+
- name: My merge request policy
|
90
110
|
conditions:
|
91
111
|
state: opened
|
92
112
|
labels:
|
@@ -585,6 +605,7 @@ Available action types:
|
|
585
605
|
- [`comment` action](#comment-action)
|
586
606
|
- [`comment_type` action option](#comment-type-action-option)
|
587
607
|
- [`summarize` action](#summarize-action)
|
608
|
+
- [`comment_on_summary` action](#comment-on-summary-action)
|
588
609
|
|
589
610
|
##### Labels action
|
590
611
|
|
@@ -796,8 +817,8 @@ Accepts a hash of fields.
|
|
796
817
|
|
797
818
|
| Field | Type | Description | Required | Placeholders | Ruby expression | Default |
|
798
819
|
| ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
799
|
-
| `title` | string | The title of the generated issue | yes | yes |
|
800
|
-
| `destination` | integer or string | The project ID or path to create the generated issue in | no
|
820
|
+
| `title` | string | The title of the generated issue | yes | yes | yes | |
|
821
|
+
| `destination` | integer or string | The project ID or path to create the generated issue in | no | no | no | source project |
|
801
822
|
| `item` | string | Template representing each triaged resource | no | yes | yes | |
|
802
823
|
| `summary` | string | The description of the generated issue | no | Only `{{title}}`, `{{items}}`, `{{type}}` | yes | |
|
803
824
|
| `redact_confidential_resources` | boolean | Whether redact fields for confidential resources | no | no | no | true |
|
@@ -867,6 +888,56 @@ Which could generate an issue like:
|
|
867
888
|
/label ~"needs attention"
|
868
889
|
```
|
869
890
|
|
891
|
+
##### Comment on summary action
|
892
|
+
|
893
|
+
Generates one comment for each resource, attaching these comments to the summary
|
894
|
+
created by the [`summarize` action](#summarize-action).
|
895
|
+
|
896
|
+
The use case for this is wanting to create a summary with an overview, and then
|
897
|
+
a threaded discussion for each resource, with a header comment starting each
|
898
|
+
discussion.
|
899
|
+
|
900
|
+
Accepts a single string value: the template used to generate the comments. For
|
901
|
+
details of the syntax of this template, see the [comment action](#comment-action).
|
902
|
+
|
903
|
+
Since this action depends on the summary, it is invalid to supply a
|
904
|
+
`comment_on_summary` action without an accompanying `summarize` sibling action.
|
905
|
+
The `summarize` action will always be completed first.
|
906
|
+
|
907
|
+
Just like for [comment action](#comment-action), setting `comment_type` in the
|
908
|
+
`actions` set controls whether the comment must be resolved for merge requests.
|
909
|
+
See: [`comment_type` action option](#comment-type-action-option).
|
910
|
+
|
911
|
+
Example:
|
912
|
+
|
913
|
+
```yml
|
914
|
+
resource_rules:
|
915
|
+
issues:
|
916
|
+
rules:
|
917
|
+
- name: List of issues to discuss
|
918
|
+
limits:
|
919
|
+
most_recent: 15
|
920
|
+
actions:
|
921
|
+
comment_type: thread
|
922
|
+
comment_on_summary: |
|
923
|
+
# {{title}}
|
924
|
+
|
925
|
+
author: {{author}}
|
926
|
+
summarize:
|
927
|
+
title: |
|
928
|
+
#{resource[:type].capitalize} require labels
|
929
|
+
item: |
|
930
|
+
- [ ] [{{title}}]({{web_url}}) {{labels}}
|
931
|
+
summary: |
|
932
|
+
The following {{type}} require labels:
|
933
|
+
|
934
|
+
{{items}}
|
935
|
+
|
936
|
+
Please take care of them before the end of #{7.days.from_now.strftime('%Y-%m-%d')}
|
937
|
+
|
938
|
+
/label ~"needs attention"
|
939
|
+
```
|
940
|
+
|
870
941
|
### Summary policies
|
871
942
|
|
872
943
|
Summary policies are special policies that join multiple rule policies together
|
data/bin/gitlab-triage
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'yaml'
|
4
4
|
require_relative '../lib/gitlab/triage/option_parser'
|
5
5
|
require_relative '../lib/gitlab/triage/engine'
|
6
|
+
require_relative '../lib/gitlab/triage/ui'
|
6
7
|
|
7
8
|
options = Gitlab::Triage::OptionParser.parse(ARGV)
|
8
9
|
options.policies_files << '.triage-policies.yml' if options.policies_files.empty?
|
@@ -10,7 +11,9 @@ options.policies_files << '.triage-policies.yml' if options.policies_files.empty
|
|
10
11
|
options.policies_files.each do |policies_file|
|
11
12
|
policies = HashWithIndifferentAccess.new(YAML.load_file(policies_file))
|
12
13
|
|
13
|
-
Gitlab::Triage::Engine
|
14
|
+
policy_engine = Gitlab::Triage::Engine
|
14
15
|
.new(policies: policies, options: options)
|
15
|
-
|
16
|
+
|
17
|
+
puts Gitlab::Triage::UI.header("Executing policies from #{policies_file}.", char: '*')
|
18
|
+
policy_engine.perform
|
16
19
|
end
|
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
|
@@ -57,14 +57,17 @@ module Gitlab
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def build_post_url(resource)
|
60
|
-
|
61
|
-
post_url = UrlBuilders::UrlBuilder.new(
|
60
|
+
url_builder_opts = {
|
62
61
|
network_options: network.options,
|
63
|
-
|
62
|
+
source: policy.source,
|
63
|
+
source_id: resource[policy.source_id_sym],
|
64
64
|
resource_type: policy.type,
|
65
65
|
resource_id: resource['iid'],
|
66
66
|
sub_resource_type: sub_resource_type
|
67
|
-
|
67
|
+
}
|
68
|
+
|
69
|
+
# POST /(groups|projects)/:id/(epics|issues|merge_requests)/:iid/notes
|
70
|
+
post_url = UrlBuilders::UrlBuilder.new(url_builder_opts).build
|
68
71
|
|
69
72
|
puts Gitlab::Triage::UI.debug "post_url: #{post_url}" if network.options.debug
|
70
73
|
|
@@ -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
|
@@ -2,16 +2,25 @@ module Gitlab
|
|
2
2
|
module Triage
|
3
3
|
module APIQueryBuilders
|
4
4
|
class BaseQueryParamBuilder
|
5
|
-
attr_reader :param_name, :param_contents
|
5
|
+
attr_reader :param_name, :param_contents, :allowed_values
|
6
6
|
|
7
|
-
def initialize(param_name, param_contents)
|
7
|
+
def initialize(param_name, param_contents, allowed_values: nil)
|
8
8
|
@param_name = param_name
|
9
9
|
@param_contents = param_contents
|
10
|
+
@allowed_values = allowed_values
|
11
|
+
|
12
|
+
validate_allowed_values! if allowed_values
|
10
13
|
end
|
11
14
|
|
12
15
|
def build_param
|
13
16
|
"&#{param_name}=#{param_content.strip}"
|
14
17
|
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def validate_allowed_values!
|
22
|
+
ParamsValidator.new([{ name: param_name, type: String, values: allowed_values }], { param_name => param_contents }).validate!
|
23
|
+
end
|
15
24
|
end
|
16
25
|
end
|
17
26
|
end
|
@@ -1,57 +1,32 @@
|
|
1
|
-
require_relative '../
|
1
|
+
require_relative '../param_builders/date_param_builder'
|
2
|
+
require_relative 'base_query_param_builder'
|
2
3
|
|
3
4
|
module Gitlab
|
4
5
|
module Triage
|
5
6
|
module APIQueryBuilders
|
6
|
-
class DateQueryParamBuilder
|
7
|
+
class DateQueryParamBuilder < BaseQueryParamBuilder
|
7
8
|
ATTRIBUTES = %w[updated_at created_at].freeze
|
8
|
-
CONDITIONS = %w[older_than newer_than].freeze
|
9
|
-
INTERVAL_TYPES = %w[days weeks months years].freeze
|
10
|
-
|
11
|
-
def self.filter_parameters
|
12
|
-
[
|
13
|
-
{
|
14
|
-
name: :attribute,
|
15
|
-
type: String,
|
16
|
-
values: ATTRIBUTES
|
17
|
-
},
|
18
|
-
{
|
19
|
-
name: :condition,
|
20
|
-
type: String,
|
21
|
-
values: CONDITIONS
|
22
|
-
},
|
23
|
-
{
|
24
|
-
name: :interval_type,
|
25
|
-
type: String,
|
26
|
-
values: INTERVAL_TYPES
|
27
|
-
},
|
28
|
-
{
|
29
|
-
name: :interval,
|
30
|
-
type: Numeric
|
31
|
-
}
|
32
|
-
]
|
33
|
-
end
|
34
9
|
|
35
10
|
def self.applicable?(condition)
|
36
11
|
ATTRIBUTES.include?(condition[:attribute].to_s)
|
37
12
|
end
|
38
13
|
|
39
14
|
def initialize(condition_hash)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@interval = condition_hash[:interval]
|
44
|
-
validate_condition(condition_hash)
|
15
|
+
date_param_builder = ParamBuilders::DateParamBuilder.new(ATTRIBUTES, condition_hash)
|
16
|
+
|
17
|
+
super(build_param_name(condition_hash), date_param_builder.param_content)
|
45
18
|
end
|
46
19
|
|
47
|
-
def
|
48
|
-
|
20
|
+
def param_content
|
21
|
+
param_contents
|
49
22
|
end
|
50
23
|
|
51
|
-
|
52
|
-
|
24
|
+
private
|
25
|
+
|
26
|
+
def build_param_name(condition_hash)
|
27
|
+
prefix = condition_hash[:attribute].to_s.sub(/_at\z/, '')
|
53
28
|
suffix =
|
54
|
-
case
|
29
|
+
case condition_hash[:condition].to_sym
|
55
30
|
when :older_than
|
56
31
|
'before'
|
57
32
|
when :newer_than
|
@@ -60,18 +35,6 @@ module Gitlab
|
|
60
35
|
|
61
36
|
"#{prefix}_#{suffix}"
|
62
37
|
end
|
63
|
-
|
64
|
-
def param_content
|
65
|
-
interval.public_send(interval_type).ago.to_date # rubocop:disable GitlabSecurity/PublicSend
|
66
|
-
end
|
67
|
-
|
68
|
-
def build_param
|
69
|
-
"&#{param_name}=#{param_content.strip}"
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
attr_reader :condition_hash, :attribute, :interval_condition, :interval_type, :interval
|
75
38
|
end
|
76
39
|
end
|
77
40
|
end
|
@@ -6,14 +6,22 @@ module Gitlab
|
|
6
6
|
class MultiQueryParamBuilder < BaseQueryParamBuilder
|
7
7
|
attr_reader :separator
|
8
8
|
|
9
|
-
def initialize(param_name, param_contents, separator)
|
9
|
+
def initialize(param_name, param_contents, separator, allowed_values: nil)
|
10
10
|
@separator = separator
|
11
|
-
super(param_name, param_contents)
|
11
|
+
super(param_name, Array(param_contents), allowed_values: allowed_values)
|
12
12
|
end
|
13
13
|
|
14
14
|
def param_content
|
15
15
|
param_contents.map(&:strip).join(separator)
|
16
16
|
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def validate_allowed_values!
|
21
|
+
param_contents.each do |param|
|
22
|
+
ParamsValidator.new([{ name: param_name, type: String, values: allowed_values }], { param_name => param }).validate!
|
23
|
+
end
|
24
|
+
end
|
17
25
|
end
|
18
26
|
end
|
19
27
|
end
|