gitlab-triage 0.14.1 → 0.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 +69 -24
- data/bin/gitlab-triage +2 -74
- data/lib/gitlab/triage/action/base.rb +3 -3
- data/lib/gitlab/triage/action/comment.rb +5 -7
- data/lib/gitlab/triage/action/summarize.rb +4 -5
- data/lib/gitlab/triage/command_builders/base_command_builder.rb +1 -1
- data/lib/gitlab/triage/command_builders/text_content_builder.rb +54 -16
- data/lib/gitlab/triage/engine.rb +27 -34
- data/lib/gitlab/triage/entity_builders/issue_builder.rb +27 -17
- data/lib/gitlab/triage/filters/member_conditions_filter.rb +6 -8
- data/lib/gitlab/triage/filters/ruby_conditions_filter.rb +5 -3
- data/lib/gitlab/triage/network.rb +20 -8
- data/lib/gitlab/triage/network_adapters/base_adapter.rb +1 -1
- data/lib/gitlab/triage/option_parser.rb +72 -0
- data/lib/gitlab/triage/options.rb +21 -0
- data/lib/gitlab/triage/policies/base_policy.rb +21 -2
- data/lib/gitlab/triage/policies/rule_policy.rb +7 -1
- data/lib/gitlab/triage/policies/summary_policy.rb +25 -2
- data/lib/gitlab/triage/resource/base.rb +49 -10
- data/lib/gitlab/triage/resource/context.rb +13 -8
- data/lib/gitlab/triage/resource/instance_version.rb +3 -4
- data/lib/gitlab/triage/resource/issue.rb +14 -0
- data/lib/gitlab/triage/resource/label.rb +41 -0
- data/lib/gitlab/triage/resource/label_event.rb +45 -0
- data/lib/gitlab/triage/resource/merge_request.rb +14 -0
- data/lib/gitlab/triage/resource/milestone.rb +5 -5
- data/lib/gitlab/triage/resource/shared/issuable.rb +54 -0
- data/lib/gitlab/triage/url_builders/url_builder.rb +3 -2
- data/lib/gitlab/triage/version.rb +1 -1
- data/support/.triage-policies.example.yml +4 -4
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2282112f85b0f8af0edd3fd6c553848768e15c2b39402b1e9344564626086a9c
|
4
|
+
data.tar.gz: 55f84c356fd84e7cd35aea63d2f6c377a96a82b13f23322dece9d9eb077c7f98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c5989be48592d0f1d6110c2e0f752bcb3a492a20027845829a3b97c21b421d61548007f443f01e7b85c5136f47b081909b2f4470a7f142d4de7ec4abc4f121a
|
7
|
+
data.tar.gz: 994b0b296c5382f2c402b4a42c9ffac7678777051b8dd6ba28bd6e30360f297139d2ab8ec88edbf26ef919b436303ce62bdd0fddddb9358e35177aceb59971e4
|
data/README.md
CHANGED
@@ -66,9 +66,11 @@ resource_rules:
|
|
66
66
|
comment: |
|
67
67
|
{{author}} This issue 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')}
|
68
68
|
summarize:
|
69
|
-
title:
|
69
|
+
title: |
|
70
|
+
#{resource[:type].capitalize} require labels
|
70
71
|
item: |
|
71
72
|
- [ ] [{{title}}]({{web_url}}) {{labels}}
|
73
|
+
redact_confidential_resources: false
|
72
74
|
summary: |
|
73
75
|
The following issues require labels:
|
74
76
|
|
@@ -560,6 +562,8 @@ The following placeholders are supported:
|
|
560
562
|
- `downvotes`: the resources's downvotes count
|
561
563
|
- `title`: the resource's title
|
562
564
|
- `web_url`: the web URL pointing to the resource
|
565
|
+
- `type`: the type of the resources. For now, only `issues` and
|
566
|
+
`merge_requests` are supported.
|
563
567
|
|
564
568
|
If the resource doesn't respond to the placeholder, or if the field is `nil`,
|
565
569
|
the placeholder is not replaced.
|
@@ -611,42 +615,56 @@ Generates an issue summarizing what was triaged.
|
|
611
615
|
|
612
616
|
Accepts a hash of fields.
|
613
617
|
|
614
|
-
| Field | Type | Description | Required | Placeholders | Ruby expression |
|
615
|
-
| ---- | ---- | ---- | ---- | ---- | ---- |
|
616
|
-
| `title` | string | The title of the generated issue | yes |
|
617
|
-
| `item` | string | Template representing each triaged resource | no | yes | yes |
|
618
|
-
| `summary` | string | The description of the generated issue | no | Only `{{items}}
|
618
|
+
| Field | Type | Description | Required | Placeholders | Ruby expression | Default |
|
619
|
+
| ---- | ---- | ---- | ---- | ---- | ---- | ---- |
|
620
|
+
| `title` | string | The title of the generated issue | yes | yes | no | |
|
621
|
+
| `item` | string | Template representing each triaged resource | no | yes | yes | |
|
622
|
+
| `summary` | string | The description of the generated issue | no | Only `{{title}}`, `{{items}}`, `{{type}}` | yes | |
|
623
|
+
| `redact_confidential_resources` | boolean | Whether redact fields for confidential resources | no | no | no | true |
|
619
624
|
|
620
625
|
**Note:**: Both `item` and `summary` fields act like a
|
621
626
|
[comment action](#comment-action), therefore
|
622
627
|
[Ruby expression](#ruby-expression) is supported.
|
623
628
|
Placeholders work regularly for `item`, but for `summary` only
|
624
|
-
`{{items}}
|
629
|
+
`{{title}}`, `{{items}}`, `{{type}}` are supported because it's not tied to a
|
625
630
|
particular resource like the comment action.
|
626
631
|
|
632
|
+
**Note:**: `redact_confidential_resources` defaults to `true`, so fields on
|
633
|
+
confidential resources will be converted to `(confidential)` except for
|
634
|
+
`{{web_url}}`. Setting it to `false` will reveal the confidential fields.
|
635
|
+
This will be useful if the summary is confidential itself (not implemented
|
636
|
+
yet), or if we're posting to another private project (not implemented yet).
|
637
|
+
|
627
638
|
The following placeholders are supported for `summary`:
|
628
639
|
|
629
|
-
- `items`: Concatenated markdown separated by a newline for each `item`
|
630
640
|
- `title`: The title of the generated issue
|
641
|
+
- `items`: Concatenated markdown separated by a newline for each `item`
|
642
|
+
- `type`: The resource type for the summary. For now `issues` or
|
643
|
+
`merge_requests`
|
631
644
|
|
632
645
|
Example:
|
633
646
|
|
634
647
|
```yml
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
648
|
+
resource_rules:
|
649
|
+
issues:
|
650
|
+
rules:
|
651
|
+
- name: Issues require labels
|
652
|
+
limits:
|
653
|
+
most_recent: 15
|
654
|
+
actions:
|
655
|
+
summarize:
|
656
|
+
title: |
|
657
|
+
#{resource[:type].capitalize} require labels
|
658
|
+
item: |
|
659
|
+
- [ ] [{{title}}]({{web_url}}) {{labels}}
|
660
|
+
summary: |
|
661
|
+
The following {{type}} require labels:
|
644
662
|
|
645
|
-
|
663
|
+
{{items}}
|
646
664
|
|
647
|
-
|
665
|
+
Please take care of them before the end of #{7.days.from_now.strftime('%Y-%m-%d')}
|
648
666
|
|
649
|
-
|
667
|
+
/label ~"needs attention"
|
650
668
|
```
|
651
669
|
|
652
670
|
Which could generate an issue like:
|
@@ -688,9 +706,9 @@ resource_rules:
|
|
688
706
|
- name: Newest and oldest issues summary
|
689
707
|
actions:
|
690
708
|
summarize:
|
691
|
-
title: "Newest and oldest
|
709
|
+
title: "Newest and oldest {{type}} summary"
|
692
710
|
summary: |
|
693
|
-
Please triage the following
|
711
|
+
Please triage the following {{type}}:
|
694
712
|
|
695
713
|
{{items}}
|
696
714
|
|
@@ -707,7 +725,7 @@ resource_rules:
|
|
707
725
|
summarize:
|
708
726
|
item: "- [ ] [{{title}}]({{web_url}}) {{labels}}"
|
709
727
|
summary: |
|
710
|
-
Please triage the following new
|
728
|
+
Please triage the following new {{type}}:
|
711
729
|
|
712
730
|
{{items}}
|
713
731
|
- name: Old issues
|
@@ -719,7 +737,7 @@ resource_rules:
|
|
719
737
|
summarize:
|
720
738
|
item: "- [ ] [{{title}}]({{web_url}}) {{labels}}"
|
721
739
|
summary: |
|
722
|
-
Please triage the following old
|
740
|
+
Please triage the following old {{type}}:
|
723
741
|
|
724
742
|
{{items}}
|
725
743
|
```
|
@@ -759,6 +777,10 @@ Here's a list of currently available Ruby expression API:
|
|
759
777
|
| ---- | ---- | ---- |
|
760
778
|
| resource | Hash | The hash containing the raw data of the resource |
|
761
779
|
| milestone | Milestone | The milestone attached to the resource |
|
780
|
+
| labels | [Label] | A list of labels, having only names |
|
781
|
+
| labels_with_details | [Label] | A list of labels which has more information loaded from another API request |
|
782
|
+
| labels_chronologically | [Label] | Same as `labels_with_details` but sorted chronologically |
|
783
|
+
| label_events | [LabelEvent] | A list of label events on the resource |
|
762
784
|
| instance_version | InstanceVersion | The version for the GitLab instance we're triaging with |
|
763
785
|
|
764
786
|
##### Methods for `Milestone`
|
@@ -779,6 +801,29 @@ Here's a list of currently available Ruby expression API:
|
|
779
801
|
| succ | Milestone | The next active milestone beside this milestone |
|
780
802
|
| active? | Boolean | `true` if `state` is `active`; `false` otherwise |
|
781
803
|
|
804
|
+
##### Methods for `Label`
|
805
|
+
|
806
|
+
| Method | Return type | Description |
|
807
|
+
| ---- | ---- | ---- |
|
808
|
+
| id | Integer | The id of the label |
|
809
|
+
| project_id | Integer | The project id of the label if available |
|
810
|
+
| group_id | Integer | The group id of the label if available |
|
811
|
+
| name | String | The name of the label |
|
812
|
+
| description | String | The description of the label |
|
813
|
+
| color | String | The color of the label in RGB |
|
814
|
+
| priority | Integer | The priority of the label |
|
815
|
+
| added_at | Time | When the label was added to the resource |
|
816
|
+
|
817
|
+
##### Methods for `LabelEvent`
|
818
|
+
|
819
|
+
| Method | Return type | Description |
|
820
|
+
| ---- | ---- | ---- |
|
821
|
+
| id | Integer | The id of the label event |
|
822
|
+
| resource_type | String | The resource type of the event. Could be `Issue` or `MergeRequest` |
|
823
|
+
| resource_id | Integer | The id of the resource |
|
824
|
+
| action | String | The action of the event. Could be `add` or `remove` |
|
825
|
+
| created_at | Time | When the event happened |
|
826
|
+
|
782
827
|
##### Methods for `InstanceVersion`
|
783
828
|
|
784
829
|
| Method | Return type | Description |
|
data/bin/gitlab-triage
CHANGED
@@ -1,82 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'optparse'
|
4
3
|
require 'yaml'
|
4
|
+
require_relative '../lib/gitlab/triage/option_parser'
|
5
5
|
require_relative '../lib/gitlab/triage/engine'
|
6
6
|
|
7
|
-
|
8
|
-
:dry_run,
|
9
|
-
:policies_file,
|
10
|
-
:project_id,
|
11
|
-
:token,
|
12
|
-
:debug,
|
13
|
-
:host_url
|
14
|
-
)
|
15
|
-
|
16
|
-
class TriageOptionParser
|
17
|
-
class << self
|
18
|
-
def parse(argv)
|
19
|
-
options = Options.new
|
20
|
-
options.host_url = 'https://gitlab.com'
|
21
|
-
|
22
|
-
parser = OptionParser.new do |opts|
|
23
|
-
opts.banner = "Usage: #{__FILE__} [options]\n\n"
|
24
|
-
|
25
|
-
opts.on('-n', '--dry-run', "Don't actually update anything, just print") do |value|
|
26
|
-
options.dry_run = value
|
27
|
-
end
|
28
|
-
|
29
|
-
opts.on('-f', '--policies-file [string]', String, 'A valid policies YML file') do |value|
|
30
|
-
options.policies_file = value
|
31
|
-
end
|
32
|
-
|
33
|
-
opts.on('-p', '--project-id [string]', String, 'A project ID or path') do |value|
|
34
|
-
options.project_id = value
|
35
|
-
end
|
36
|
-
|
37
|
-
opts.on('-t', '--token [string]', String, 'A valid API token') do |value|
|
38
|
-
options.token = value
|
39
|
-
end
|
40
|
-
|
41
|
-
opts.on('-H', '--host-url [string]', String, 'A valid host url') do |value|
|
42
|
-
options.host_url = value
|
43
|
-
end
|
44
|
-
|
45
|
-
opts.on('-d', '--debug', 'Print debug information') do |value|
|
46
|
-
options.debug = value
|
47
|
-
end
|
48
|
-
|
49
|
-
opts.on('-h', '--help', 'Print help message') do
|
50
|
-
$stdout.puts opts
|
51
|
-
exit
|
52
|
-
end
|
53
|
-
|
54
|
-
opts.on('--init', 'Initialize the project with a policy file') do
|
55
|
-
example_path =
|
56
|
-
File.expand_path('../support/.triage-policies.example.yml', __dir__)
|
57
|
-
|
58
|
-
FileUtils.cp(example_path, '.triage-policies.yml')
|
59
|
-
exit
|
60
|
-
end
|
61
|
-
|
62
|
-
opts.on('--init-ci', 'Initialize the project with a .gitlab-ci.yml file') do
|
63
|
-
example_path =
|
64
|
-
File.expand_path('../support/.gitlab-ci.example.yml', __dir__)
|
65
|
-
|
66
|
-
FileUtils.cp(example_path, '.gitlab-ci.yml')
|
67
|
-
exit
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
parser.parse!(argv)
|
72
|
-
|
73
|
-
options
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
options = TriageOptionParser.parse(ARGV)
|
79
|
-
|
7
|
+
options = Gitlab::Triage::OptionParser.parse(ARGV)
|
80
8
|
policies_file = options.policies_file || '.triage-policies.yml'
|
81
9
|
policies = HashWithIndifferentAccess.new(YAML.load_file(policies_file))
|
82
10
|
|
@@ -2,11 +2,11 @@ module Gitlab
|
|
2
2
|
module Triage
|
3
3
|
module Action
|
4
4
|
class Base
|
5
|
-
attr_reader :policy, :
|
5
|
+
attr_reader :policy, :network
|
6
6
|
|
7
|
-
def initialize(policy:,
|
7
|
+
def initialize(policy:, network:)
|
8
8
|
@policy = policy
|
9
|
-
@
|
9
|
+
@network = network
|
10
10
|
end
|
11
11
|
end
|
12
12
|
end
|
@@ -39,7 +39,7 @@ module Gitlab
|
|
39
39
|
def build_comment(resource)
|
40
40
|
CommandBuilders::CommentCommandBuilder.new(
|
41
41
|
[
|
42
|
-
CommandBuilders::TextContentBuilder.new(policy.actions[:comment], resource: resource,
|
42
|
+
CommandBuilders::TextContentBuilder.new(policy.actions[:comment], resource: resource, network: network).build_command,
|
43
43
|
CommandBuilders::LabelCommandBuilder.new(policy.actions[:labels]).build_command,
|
44
44
|
CommandBuilders::RemoveLabelCommandBuilder.new(policy.actions[:remove_labels]).build_command,
|
45
45
|
CommandBuilders::CcCommandBuilder.new(policy.actions[:mention]).build_command,
|
@@ -49,8 +49,7 @@ module Gitlab
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def perform(resource, comment)
|
52
|
-
|
53
|
-
net[:token],
|
52
|
+
network.post_api(
|
54
53
|
build_post_url(resource),
|
55
54
|
body: comment)
|
56
55
|
end
|
@@ -58,15 +57,14 @@ module Gitlab
|
|
58
57
|
def build_post_url(resource)
|
59
58
|
# POST /projects/:id/issues/:issue_iid/notes
|
60
59
|
post_url = UrlBuilders::UrlBuilder.new(
|
61
|
-
|
62
|
-
|
63
|
-
source_id: net[:source_id],
|
60
|
+
network_options: network.options,
|
61
|
+
source_id: network.options.project_id,
|
64
62
|
resource_type: policy.type,
|
65
63
|
resource_id: resource['iid'],
|
66
64
|
sub_resource_type: 'notes'
|
67
65
|
).build
|
68
66
|
|
69
|
-
puts Gitlab::Triage::UI.debug "post_url: #{post_url}" if
|
67
|
+
puts Gitlab::Triage::UI.debug "post_url: #{post_url}" if network.options.debug
|
70
68
|
|
71
69
|
post_url
|
72
70
|
end
|
@@ -25,7 +25,7 @@ module Gitlab
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def perform
|
28
|
-
|
28
|
+
network.post_api(post_issue_url, post_issue_body)
|
29
29
|
end
|
30
30
|
|
31
31
|
def issue
|
@@ -36,13 +36,12 @@ module Gitlab
|
|
36
36
|
# POST /projects/:id/issues
|
37
37
|
# https://docs.gitlab.com/ee/api/issues.html#new-issue
|
38
38
|
post_url = UrlBuilders::UrlBuilder.new(
|
39
|
-
|
40
|
-
|
41
|
-
source_id: net[:source_id],
|
39
|
+
network_options: network.options,
|
40
|
+
source_id: network.options.project_id,
|
42
41
|
resource_type: 'issues'
|
43
42
|
).build
|
44
43
|
|
45
|
-
puts Gitlab::Triage::UI.debug "post_issue_url: #{post_url}" if
|
44
|
+
puts Gitlab::Triage::UI.debug "post_issue_url: #{post_url}" if network.options.debug
|
46
45
|
|
47
46
|
post_url
|
48
47
|
end
|
@@ -26,16 +26,19 @@ module Gitlab
|
|
26
26
|
downvotes: "{{downvotes}}",
|
27
27
|
title: "{{title}}",
|
28
28
|
web_url: "{{web_url}}",
|
29
|
+
type: "{{type}}",
|
29
30
|
items: "{{items}}"
|
30
31
|
}.freeze
|
31
32
|
PLACEHOLDER_REGEX = /{{([\w\.]+)}}/
|
32
33
|
|
33
|
-
attr_reader :resource, :
|
34
|
+
attr_reader :resource, :network
|
34
35
|
|
35
|
-
def initialize(
|
36
|
+
def initialize(
|
37
|
+
items, resource: nil, network: nil, redact_confidentials: true)
|
36
38
|
super(items)
|
37
|
-
@resource = resource
|
38
|
-
@
|
39
|
+
@resource = resource&.with_indifferent_access
|
40
|
+
@network = network
|
41
|
+
@redact_confidentials = redact_confidentials
|
39
42
|
end
|
40
43
|
|
41
44
|
private
|
@@ -53,30 +56,65 @@ module Gitlab
|
|
53
56
|
def eval_interpolation(item)
|
54
57
|
quoted_comment = "%Q{#{item}}"
|
55
58
|
|
56
|
-
Resource::Context.
|
59
|
+
Resource::Context.build(
|
60
|
+
resource,
|
61
|
+
network: network,
|
62
|
+
redact_confidentials: @redact_confidentials
|
63
|
+
).eval(quoted_comment)
|
57
64
|
end
|
58
65
|
|
59
66
|
def replace_placeholders(item)
|
60
|
-
SUPPORTED_PLACEHOLDERS.inject(item) do |comment, (placeholder,
|
67
|
+
SUPPORTED_PLACEHOLDERS.inject(item) do |comment, (placeholder, template)|
|
61
68
|
next comment unless comment.include?("{{#{placeholder}}}")
|
62
69
|
|
63
|
-
|
64
|
-
|
70
|
+
path = template[/.*#{PLACEHOLDER_REGEX}.*/, 1]
|
71
|
+
attributes = extract_attributes(path)
|
65
72
|
|
66
|
-
|
73
|
+
formatted_text = attributes.map do |attribute|
|
74
|
+
template.sub(PLACEHOLDER_REGEX, attribute.to_s)
|
75
|
+
end.join(', ')
|
67
76
|
|
68
|
-
comment.gsub("{{#{placeholder}}}",
|
77
|
+
comment.gsub("{{#{placeholder}}}", formatted_text)
|
69
78
|
end
|
70
79
|
end
|
71
80
|
|
72
|
-
def
|
73
|
-
|
81
|
+
def extract_attributes(path)
|
82
|
+
redact_attributes(path, resource_dig_and_map(path.split('.')))
|
83
|
+
end
|
84
|
+
|
85
|
+
# If we don't have to map arrays, we can simply do:
|
86
|
+
#
|
87
|
+
# resource.dig(*indices)
|
88
|
+
#
|
89
|
+
# Thus this method name. The only array here is `assignees`
|
90
|
+
def resource_dig_and_map(indices)
|
91
|
+
attributes = indices.inject(resource) do |result, index|
|
92
|
+
break unless result
|
93
|
+
|
94
|
+
case result
|
95
|
+
when Array
|
96
|
+
result.flat_map { |sub_resource| sub_resource[index] }
|
97
|
+
else
|
98
|
+
result[index]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
Array.wrap(attributes)
|
103
|
+
end
|
104
|
+
|
105
|
+
def redact_attributes(path, attributes)
|
106
|
+
return attributes unless redact_confidential_attributes?
|
74
107
|
|
75
|
-
|
76
|
-
|
108
|
+
case path
|
109
|
+
when 'web_url', 'items', 'type'
|
110
|
+
attributes # No need to redact them
|
111
|
+
else
|
112
|
+
[Resource::Base::CONFIDENTIAL_TEXT]
|
113
|
+
end
|
114
|
+
end
|
77
115
|
|
78
|
-
|
79
|
-
|
116
|
+
def redact_confidential_attributes?
|
117
|
+
@redact_confidentials && resource[:confidential]
|
80
118
|
end
|
81
119
|
end
|
82
120
|
end
|