gitlab-triage 1.20.0 → 1.21.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/.gitignore +1 -0
- data/.gitlab/changelog_config.yml +13 -0
- data/.gitlab/merge_request_templates/Release.md +9 -31
- data/.gitlab-ci.yml +7 -1
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/.yardopts +4 -0
- data/Dangerfile +5 -0
- data/Gemfile +7 -0
- data/README.md +65 -29
- data/gitlab-triage.gemspec +12 -3
- data/lib/gitlab/triage/command_builders/text_content_builder.rb +2 -1
- data/lib/gitlab/triage/engine.rb +186 -55
- data/lib/gitlab/triage/entity_builders/issue_builder.rb +2 -1
- data/lib/gitlab/triage/graphql_queries/query_builder.rb +47 -20
- data/lib/gitlab/triage/resource/epic.rb +0 -4
- data/lib/gitlab/triage/resource/issue.rb +0 -4
- data/lib/gitlab/triage/resource/merge_request.rb +0 -4
- data/lib/gitlab/triage/resource/shared/issuable.rb +1 -6
- data/lib/gitlab/triage/url_builders/url_builder.rb +2 -0
- data/lib/gitlab/triage/version.rb +1 -1
- data/support/.gitlab-ci.example.yml +2 -2
- metadata +14 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3390c4026f87e49c54807a88f711eaa87bce2e9e0093ca8c394b51f67986ccb4
|
4
|
+
data.tar.gz: 68a20611ec1d24730ca854985f77a991f85baba6dda5496619413b5dfdbdbc73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c435258d811e1c15c293924e609ebb75135a643f8020749f459206d11e37deb98ee4cc3e0fd365d74e0a6a547515af944f3e95b4b8f5887c82d49e5fc77b4c9
|
7
|
+
data.tar.gz: 21f85618ecdcf8d2f1e7b061f95682f1a29ce140553f1f5eb62f1baf121316bf6e84bd01d15fddf0b4bb4e789e70eb030f88cf36f5955d13c5b59033cee94f0d
|
data/.gitignore
CHANGED
@@ -0,0 +1,13 @@
|
|
1
|
+
---
|
2
|
+
# Settings for generating changelogs using the GitLab API. See
|
3
|
+
# https://docs.gitlab.com/ee/api/repositories.html#generate-changelog-data for
|
4
|
+
# more information.
|
5
|
+
categories:
|
6
|
+
added: Added
|
7
|
+
fixed: Fixed
|
8
|
+
changed: Changed
|
9
|
+
deprecated: Deprecated
|
10
|
+
removed: Removed
|
11
|
+
security: Security
|
12
|
+
performance: Performance
|
13
|
+
other: Other
|
@@ -1,35 +1,13 @@
|
|
1
|
-
<!-- Replace
|
2
|
-
|
3
|
-
|
1
|
+
<!-- Replace `<PREVIOUS_VERSION>` with the previous version number here, `<COMMIT_UPDATING_VERSION>` with the latest
|
2
|
+
commit from this merge request, and `<NEW_VERSION>` with the upcoming version number. -->
|
3
|
+
## Diff
|
4
4
|
|
5
|
-
-
|
5
|
+
https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage/compare/v<PREVIOUS_VERSION>...<COMMIT_UPDATING_VERSION>
|
6
6
|
|
7
|
-
|
7
|
+
## Checklist
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
- [ ] Diff link is up-to-date.
|
10
|
+
- [ ] Check the release notes: https://gitlab.com/api/v4/projects/3430480/repository/changelog?version=<NEW_VERSION>
|
11
|
+
- [ ] Based on the diff and the release notes, `version.rb` is updated, according to [SemVer](https://semver.org).
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
### Fixes
|
15
|
-
|
16
|
-
- !bbb <Title of the bbb MR>.
|
17
|
-
|
18
|
-
### Doc changes
|
19
|
-
|
20
|
-
- !ccc <Title of the ccc MR>.
|
21
|
-
|
22
|
-
### Other changes (tooling, technical debt)
|
23
|
-
|
24
|
-
- !ddd <Title of the ddd MR>.
|
25
|
-
```
|
26
|
-
|
27
|
-
- Checklist before merging:
|
28
|
-
- [ ] Diff link is up-to-date.
|
29
|
-
- [ ] Based on the diff, `lib/gitlab/triage/version.rb` is updated, according to [SemVer](https://semver.org).
|
30
|
-
- [ ] Release notes are accurate.
|
31
|
-
|
32
|
-
- Checklist after merging:
|
33
|
-
- [ ] [Update the release notes for the newly created tag](docs/release_process.md#how-to).
|
34
|
-
|
35
|
-
/label ~"Engineering Productivity" ~"ep::triage" ~tooling ~"tooling::workflow"
|
13
|
+
/label ~"type::tooling" ~"tooling::workflow" ~"ep::triage"
|
data/.gitlab-ci.yml
CHANGED
@@ -129,5 +129,11 @@ dry-run:custom:
|
|
129
129
|
allow_failure: true
|
130
130
|
|
131
131
|
include:
|
132
|
+
- template: Security/Dependency-Scanning.gitlab-ci.yml
|
133
|
+
- template: Security/License-Scanning.gitlab-ci.yml
|
134
|
+
- template: Security/SAST.gitlab-ci.yml
|
135
|
+
- template: Security/Secret-Detection.gitlab-ci.yml
|
132
136
|
- project: 'gitlab-org/quality/pipeline-common'
|
133
|
-
file:
|
137
|
+
file:
|
138
|
+
- '/ci/danger-review.yml'
|
139
|
+
- '/ci/gem-release.yml'
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.7.4
|
data/.tool-versions
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby 2.7.4
|
data/.yardopts
ADDED
data/Dangerfile
ADDED
data/Gemfile
CHANGED
@@ -3,6 +3,13 @@ source 'https://rubygems.org'
|
|
3
3
|
# Specify your gem's dependencies in gitlab-rubocop.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
+
gem "yard"
|
7
|
+
|
6
8
|
group :test do
|
7
9
|
gem 'gitlab-styles', '~> 3.3.0'
|
8
10
|
end
|
11
|
+
|
12
|
+
group :development, :test, :danger do
|
13
|
+
gem 'gitlab-dangerfiles', '~> 2.6.1', require: false
|
14
|
+
gem 'guard-rspec', '~> 4.7.3', require: false
|
15
|
+
end
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[](https://gitlab.com/gitlab-org/gitlab-triage
|
1
|
+
[](https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage/-/commits/master)
|
2
2
|
|
3
3
|
# GitLab Triage Project
|
4
4
|
|
@@ -12,12 +12,13 @@ The `gitlab-triage` gem aims to enable project managers and maintainers to
|
|
12
12
|
automatically triage Issues and Merge Requests in GitLab projects or groups
|
13
13
|
based on defined policies.
|
14
14
|
|
15
|
-
See [Running
|
15
|
+
See [Running with the installed gem](#running-with-the-installed-gem) for how to specify a project or a
|
16
16
|
group.
|
17
17
|
|
18
18
|
### What is a triage policy?
|
19
19
|
|
20
20
|
Triage policies are defined on a resource level basis, resources being:
|
21
|
+
- Epics
|
21
22
|
- Issues
|
22
23
|
- Merge Requests
|
23
24
|
|
@@ -90,7 +91,7 @@ resource_rules:
|
|
90
91
|
comment: |
|
91
92
|
{{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')}
|
92
93
|
summarize:
|
93
|
-
destination: gitlab-org/gitlab-triage
|
94
|
+
destination: gitlab-org/ruby/gems/gitlab-triage
|
94
95
|
title: |
|
95
96
|
#{resource[:type].capitalize} require labels
|
96
97
|
item: |
|
@@ -159,6 +160,7 @@ Available condition types:
|
|
159
160
|
- [`no_additional_labels` condition](#no-additional-labels-condition)
|
160
161
|
- [`author_member` condition](#author-member-condition)
|
161
162
|
- [`assignee_member` condition](#assignee-member-condition)
|
163
|
+
- [`draft` condition](#draft-condition)
|
162
164
|
- [`source_branch` condition](#source-branch-condition)
|
163
165
|
- [`target_branch` condition](#target-branch-condition)
|
164
166
|
- [`weight` condition](#weight-condition)
|
@@ -191,7 +193,14 @@ conditions:
|
|
191
193
|
|
192
194
|
##### Milestone condition
|
193
195
|
|
194
|
-
Accepts the name of a milestone to filter upon.
|
196
|
+
Accepts the name of a milestone to filter upon. Also accepts the following timebox values:
|
197
|
+
|
198
|
+
- `none`
|
199
|
+
- `any`
|
200
|
+
- `upcoming`
|
201
|
+
- `started`
|
202
|
+
|
203
|
+
See the [`milestone_id` API field documentation](https://docs.gitlab.com/ee/api/issues.html) for their meaning.
|
195
204
|
|
196
205
|
Example:
|
197
206
|
|
@@ -204,11 +213,12 @@ conditions:
|
|
204
213
|
|
205
214
|
Accepts a string.
|
206
215
|
|
207
|
-
| State
|
208
|
-
| ---------
|
209
|
-
| Closed issues
|
210
|
-
| Open issues
|
211
|
-
|
|
216
|
+
| State | Type | Value |
|
217
|
+
| --------- | ---- | ------ |
|
218
|
+
| Closed issues/MRs | string | `closed` |
|
219
|
+
| Open issues/MRs | string | `opened` |
|
220
|
+
| Locked issues | string | `locked` |
|
221
|
+
| Merged merge requests | string | `merged` |
|
212
222
|
|
213
223
|
Example:
|
214
224
|
|
@@ -411,7 +421,6 @@ conditions:
|
|
411
421
|
|
412
422
|
Accepts a boolean. If `true` the resource cannot have more labels than those specified by the `labels` condition.
|
413
423
|
|
414
|
-
|
415
424
|
Example:
|
416
425
|
|
417
426
|
```yml
|
@@ -467,8 +476,23 @@ conditions:
|
|
467
476
|
source_id: 9970
|
468
477
|
```
|
469
478
|
|
479
|
+
##### Draft condition
|
480
|
+
|
481
|
+
**This condition is only applicable for merge requests.**
|
482
|
+
|
483
|
+
Accepts a boolean. If `true`, only draft MRs are returned. If `false`, only non-draft MRs are returned.
|
484
|
+
|
485
|
+
Example:
|
486
|
+
|
487
|
+
```yml
|
488
|
+
conditions:
|
489
|
+
draft: true
|
490
|
+
```
|
491
|
+
|
470
492
|
##### Source branch condition
|
471
493
|
|
494
|
+
**This condition is only applicable for merge requests.**
|
495
|
+
|
472
496
|
Accepts the name of a source branch to filter upon.
|
473
497
|
|
474
498
|
Example:
|
@@ -480,6 +504,8 @@ conditions:
|
|
480
504
|
|
481
505
|
##### Target branch condition
|
482
506
|
|
507
|
+
**This condition is only applicable for merge requests.**
|
508
|
+
|
483
509
|
Accepts the name of a target branch to filter upon.
|
484
510
|
|
485
511
|
Example:
|
@@ -491,8 +517,9 @@ conditions:
|
|
491
517
|
|
492
518
|
##### Weight condition
|
493
519
|
|
520
|
+
**This condition is only applicable for issues.**
|
521
|
+
|
494
522
|
Accepts a string per the [API documentation](https://docs.gitlab.com/ee/api/issues.html#list-issues).
|
495
|
-
This condition is only applicable for issues (not merge requests).
|
496
523
|
|
497
524
|
| State | Type | Value |
|
498
525
|
| --------- | ---- | ------ |
|
@@ -716,8 +743,8 @@ The following placeholders are supported:
|
|
716
743
|
- `downvotes`: the resources's downvotes count
|
717
744
|
- `title`: the resource's title
|
718
745
|
- `web_url`: the web URL pointing to the resource
|
719
|
-
- `
|
720
|
-
|
746
|
+
- `full_reference`: the full reference of the resource as `namespace/project#12`, `namespace/project!42`, `namespace/project&72`
|
747
|
+
- `type`: the type of the resources. For now, only `issues`, `merge_requests`, and `epics` are supported.
|
721
748
|
|
722
749
|
If the resource doesn't respond to the placeholder, or if the field is `nil`,
|
723
750
|
the placeholder is not replaced.
|
@@ -827,8 +854,7 @@ The following placeholders are supported for `summary`:
|
|
827
854
|
|
828
855
|
- `title`: The title of the generated issue
|
829
856
|
- `items`: Concatenated markdown separated by a newline for each `item`
|
830
|
-
- `type`: The resource type for the summary. For now `issues` or
|
831
|
-
`merge_requests`
|
857
|
+
- `type`: The resource type for the summary. For now `issues`, `merge_requests`, or `epics`,
|
832
858
|
|
833
859
|
> **Note:**
|
834
860
|
> - Both `item` and `summary` fields act like a [comment action](#comment-action),
|
@@ -957,17 +983,6 @@ resource_rules:
|
|
957
983
|
issues:
|
958
984
|
summaries:
|
959
985
|
- name: Newest and oldest issues summary
|
960
|
-
actions:
|
961
|
-
summarize:
|
962
|
-
title: "Newest and oldest {{type}} summary"
|
963
|
-
summary: |
|
964
|
-
Please triage the following {{type}}:
|
965
|
-
|
966
|
-
{{items}}
|
967
|
-
|
968
|
-
Please take care of them before the end of #{7.days.from_now.strftime('%Y-%m-%d')}
|
969
|
-
|
970
|
-
/label ~"needs attention"
|
971
986
|
rules:
|
972
987
|
- name: New issues
|
973
988
|
conditions:
|
@@ -993,6 +1008,17 @@ resource_rules:
|
|
993
1008
|
Please triage the following old {{type}}:
|
994
1009
|
|
995
1010
|
{{items}}
|
1011
|
+
actions:
|
1012
|
+
summarize:
|
1013
|
+
title: "Newest and oldest {{type}} summary"
|
1014
|
+
summary: |
|
1015
|
+
Please triage the following {{type}}:
|
1016
|
+
|
1017
|
+
{{items}}
|
1018
|
+
|
1019
|
+
Please take care of them before the end of #{7.days.from_now.strftime('%Y-%m-%d')}
|
1020
|
+
|
1021
|
+
/label ~"needs attention"
|
996
1022
|
```
|
997
1023
|
|
998
1024
|
Which could generate an issue like:
|
@@ -1032,7 +1058,7 @@ Here's a list of currently available Ruby expression API:
|
|
1032
1058
|
|
1033
1059
|
| Name | Return type | Description |
|
1034
1060
|
| ---- | ---- | ---- |
|
1035
|
-
| resource | Hash | The hash containing the raw data of the resource |
|
1061
|
+
| resource | Hash | The hash containing the raw data of the resource. Note that `resource[:type]` is the type of the policy (`issues`, `merge_requests`, or `epics`), not the API `type` field. |
|
1036
1062
|
| author | String | The username of the resource author |
|
1037
1063
|
| milestone | Milestone | The milestone attached to the resource |
|
1038
1064
|
| labels | [Label] | A list of labels, having only names |
|
@@ -1041,7 +1067,7 @@ Here's a list of currently available Ruby expression API:
|
|
1041
1067
|
| label_events | [LabelEvent] | A list of label events on the resource |
|
1042
1068
|
| instance_version | InstanceVersion | The version for the GitLab instance we're triaging with |
|
1043
1069
|
| project_path | String | The path with namespace to the issues or merge requests project |
|
1044
|
-
| full_resource_reference | String | A full reference
|
1070
|
+
| full_resource_reference | String | A full reference including project path to the issue or merge request |
|
1045
1071
|
|
1046
1072
|
##### Methods for `MergeRequest` (merge request context)
|
1047
1073
|
|
@@ -1130,7 +1156,7 @@ Usage: gitlab-triage [options]
|
|
1130
1156
|
--init-ci Initialize the project with a .gitlab-ci.yml file
|
1131
1157
|
```
|
1132
1158
|
|
1133
|
-
#### Running
|
1159
|
+
#### Running with the installed gem
|
1134
1160
|
|
1135
1161
|
Triaging against a specific project:
|
1136
1162
|
|
@@ -1152,6 +1178,16 @@ gitlab-triage --dry-run --token $GITLAB_API_TOKEN --all-projects
|
|
1152
1178
|
|
1153
1179
|
> **Note:** The `--all-projects` option will process all resources for all projects visible to the specified `$GITLAB_API_TOKEN`
|
1154
1180
|
|
1181
|
+
#### Running from source
|
1182
|
+
|
1183
|
+
Execute the `gitlab-triage` script from the `./bin` directory.
|
1184
|
+
|
1185
|
+
For example- after cloning this project, from the root `gitlab-triage` directory:
|
1186
|
+
|
1187
|
+
```
|
1188
|
+
bundle exec bin/gitlab-triage --dry-run --token $GITLAB_API_TOKEN --source-id gitlab-org/triage
|
1189
|
+
```
|
1190
|
+
|
1155
1191
|
#### Running on GitLab CI pipeline
|
1156
1192
|
|
1157
1193
|
You can enforce policies using a scheduled pipeline:
|
data/gitlab-triage.gemspec
CHANGED
@@ -9,11 +9,20 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.email = ['gitlab_rubygems@gitlab.com']
|
10
10
|
|
11
11
|
spec.summary = 'GitLab triage automation project.'
|
12
|
-
spec.homepage = 'https://gitlab.com/gitlab-org/gitlab-triage'
|
12
|
+
spec.homepage = 'https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage'
|
13
13
|
spec.license = 'MIT'
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
14
15
|
|
15
|
-
spec.
|
16
|
-
|
16
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
17
|
+
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
+
spec.metadata["source_code_uri"] = "https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage"
|
20
|
+
spec.metadata["changelog_uri"] = "https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage/-/releases"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match?(%r{^(docs?|spec|tmp)/}) }
|
17
26
|
end
|
18
27
|
spec.bindir = 'bin'
|
19
28
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
@@ -29,6 +29,7 @@ module Gitlab
|
|
29
29
|
downvotes: "{{downvotes}}",
|
30
30
|
title: "{{title}}",
|
31
31
|
web_url: "{{web_url}}",
|
32
|
+
full_reference: "{{references.full}}",
|
32
33
|
type: "{{type}}",
|
33
34
|
items: "{{items}}"
|
34
35
|
}.freeze
|
@@ -120,7 +121,7 @@ module Gitlab
|
|
120
121
|
return attributes unless redact_confidential_attributes?
|
121
122
|
|
122
123
|
case path
|
123
|
-
when 'web_url', 'items', 'type'
|
124
|
+
when 'web_url', 'items', 'type', 'references.full'
|
124
125
|
attributes # No need to redact them
|
125
126
|
else
|
126
127
|
[Resource::Base::CONFIDENTIAL_TEXT]
|
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -37,6 +37,7 @@ module Gitlab
|
|
37
37
|
issues: %w[opened closed],
|
38
38
|
merge_requests: %w[opened closed merged]
|
39
39
|
}.with_indifferent_access.freeze
|
40
|
+
MILESTONE_TIMEBOX_VALUES = %w[none any upcoming started].freeze
|
40
41
|
EpicsTriagingForProjectImpossibleError = Class.new(StandardError)
|
41
42
|
|
42
43
|
def initialize(policies:, options:, network_adapter_class: DEFAULT_NETWORK_ADAPTER, graphql_network_adapter_class: DEFAULT_GRAPHQL_ADAPTER)
|
@@ -62,16 +63,16 @@ module Gitlab
|
|
62
63
|
puts Gitlab::Triage::UI.header("Triaging the `#{options.source_id}` #{options.source.to_s.singularize}", char: '=')
|
63
64
|
puts
|
64
65
|
|
65
|
-
resource_rules.each do |resource_type,
|
66
|
+
resource_rules.each do |resource_type, policy_definition|
|
66
67
|
if resource_type == 'epics' && options.source != :groups
|
67
68
|
raise(EpicsTriagingForProjectImpossibleError, "Epics can only be triaged at the group level. Please set the `--source groups` option.")
|
68
69
|
end
|
69
70
|
|
70
|
-
puts Gitlab::Triage::UI.header("Processing rules for #{resource_type}", char: '-')
|
71
|
+
puts Gitlab::Triage::UI.header("Processing summaries & rules for #{resource_type}", char: '-')
|
71
72
|
puts
|
72
73
|
|
73
|
-
process_summaries(resource_type,
|
74
|
-
process_rules(resource_type,
|
74
|
+
process_summaries(resource_type, policy_definition[:summaries])
|
75
|
+
process_rules(resource_type, policy_definition[:rules])
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
@@ -127,71 +128,165 @@ module Gitlab
|
|
127
128
|
rule.fetch(:limits) { {} }
|
128
129
|
end
|
129
130
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
131
|
+
# Process an array of +summary_definitions+.
|
132
|
+
#
|
133
|
+
# @example Example of an array of summary definitions (shown as YAML for readability).
|
134
|
+
#
|
135
|
+
# - name: Newest and oldest issues summary
|
136
|
+
# rules:
|
137
|
+
# - name: New issues
|
138
|
+
# conditions:
|
139
|
+
# state: opened
|
140
|
+
# limits:
|
141
|
+
# most_recent: 2
|
142
|
+
# actions:
|
143
|
+
# summarize:
|
144
|
+
# item: "- [ ] [{{title}}]({{web_url}}) {{labels}}"
|
145
|
+
# summary: |
|
146
|
+
# Please triage the following new {{type}}:
|
147
|
+
# {{items}}
|
148
|
+
# actions:
|
149
|
+
# summarize:
|
150
|
+
# title: "Newest and oldest {{type}} summary"
|
151
|
+
# summary: |
|
152
|
+
# Please triage the following {{type}}:
|
153
|
+
# {{items}}
|
154
|
+
# Please take care of them before the end of #{7.days.from_now.strftime('%Y-%m-%d')}
|
155
|
+
# /label ~"needs attention"
|
156
|
+
#
|
157
|
+
# @param summary_definitions [Array<Hash>] An array usually given as YAML in a triage policy file.
|
158
|
+
#
|
159
|
+
# @return [nil]
|
160
|
+
def process_summaries(resource_type, summary_definitions)
|
161
|
+
return if summary_definitions.blank?
|
162
|
+
|
163
|
+
summary_definitions.each do |summary_definition|
|
164
|
+
process_summary(resource_type, summary_definition)
|
135
165
|
end
|
136
166
|
end
|
137
167
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
168
|
+
# Process an array of +rule_definitions+.
|
169
|
+
#
|
170
|
+
# @example Example of an array of rule definitions.
|
171
|
+
#
|
172
|
+
# [{ name: "New issues", conditions: { state: opened }, limits: { most_recent: 2 }, actions: { labels: ["needs attention"] } }]
|
173
|
+
#
|
174
|
+
# @param rule_definitions [Array<Hash>] An array usually given as YAML in a triage policy file.
|
175
|
+
#
|
176
|
+
# @return [nil]
|
177
|
+
def process_rules(resource_type, rule_definitions)
|
178
|
+
return if rule_definitions.blank?
|
179
|
+
|
180
|
+
rule_definitions.each do |rule_definition|
|
181
|
+
resources_for_rule(resource_type, rule_definition) do |resources|
|
143
182
|
policy = Policies::RulePolicy.new(
|
144
|
-
resource_type,
|
183
|
+
resource_type, rule_definition, resources, network)
|
145
184
|
|
146
185
|
process_action(policy)
|
147
186
|
end
|
148
187
|
end
|
149
188
|
end
|
150
189
|
|
151
|
-
|
152
|
-
|
190
|
+
# Process a +summary_definition+.
|
191
|
+
#
|
192
|
+
# @example Example of a summary definition hash (shown as YAML for readability).
|
193
|
+
#
|
194
|
+
# name: Newest and oldest issues summary
|
195
|
+
# rules:
|
196
|
+
# - name: New issues
|
197
|
+
# conditions:
|
198
|
+
# state: opened
|
199
|
+
# limits:
|
200
|
+
# most_recent: 2
|
201
|
+
# actions:
|
202
|
+
# summarize:
|
203
|
+
# item: "- [ ] [{{title}}]({{web_url}}) {{labels}}"
|
204
|
+
# summary: |
|
205
|
+
# Please triage the following new {{type}}:
|
206
|
+
# {{items}}
|
207
|
+
# actions:
|
208
|
+
# summarize:
|
209
|
+
# title: "Newest and oldest {{type}} summary"
|
210
|
+
# summary: |
|
211
|
+
# Please triage the following {{type}}:
|
212
|
+
# {{items}}
|
213
|
+
# Please take care of them before the end of #{7.days.from_now.strftime('%Y-%m-%d')}
|
214
|
+
# /label ~"needs attention"
|
215
|
+
#
|
216
|
+
# @param resource_type [String] The resource type, e.g. +issues+ or +merge_requests+.
|
217
|
+
# @param summary_definition [Hash] A hash usually given as YAML in a triage policy file:
|
218
|
+
#
|
219
|
+
# @return [nil]
|
220
|
+
def process_summary(resource_type, summary_definition)
|
221
|
+
puts Gitlab::Triage::UI.header("Processing summary: **#{summary_definition[:name]}**", char: '~')
|
153
222
|
puts
|
154
223
|
|
155
|
-
summary_parts_for_rules(resource_type,
|
224
|
+
summary_parts_for_rules(resource_type, summary_definition[:rules]) do |summary_resources|
|
156
225
|
policy = Policies::SummaryPolicy.new(
|
157
|
-
resource_type,
|
226
|
+
resource_type, summary_definition, summary_resources, network)
|
158
227
|
|
159
228
|
process_action(policy)
|
160
229
|
end
|
161
230
|
end
|
162
231
|
|
163
|
-
|
232
|
+
# Transform an array of +rule_definitions+ into a +PoliciesResources::SummaryResources.new(rule => rule_resources)+ object.
|
233
|
+
#
|
234
|
+
# @example Example of an array of rule definitions.
|
235
|
+
#
|
236
|
+
# [{ name: "New issues", conditions: { state: opened }, limits: { most_recent: 2 }, actions: { labels: ["needs attention"] } }]
|
237
|
+
#
|
238
|
+
# @param resource_type [String] The resource type, e.g. +issues+ or +merge_requests+.
|
239
|
+
# @param rule_definitions [Array<Hash>] An array of rule definitions, e.g.
|
240
|
+
# +[{ name: 'Foo', conditions: { milestone: 'v1' } }, { name: 'Foo', conditions: { state: 'opened' } }]+.
|
241
|
+
#
|
242
|
+
# @yieldparam summary_resources [PoliciesResources::SummaryResources] An object which contains a +{ rule_definition => resources }+ hash.
|
243
|
+
# @yieldreturn [nil]
|
244
|
+
#
|
245
|
+
# @return [nil]
|
246
|
+
def summary_parts_for_rules(resource_type, rule_definitions)
|
164
247
|
# { summary_rule => resources }
|
165
|
-
parts =
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
result.merge(rule.merge(conditions: conditions) => resources)
|
173
|
-
end
|
248
|
+
parts = rule_definitions.each_with_object({}) do |rule_definition, result|
|
249
|
+
to_enum(:resources_for_rule, resource_type, rule_definition).each do |rule_resources, expanded_conditions|
|
250
|
+
# We replace the non-expanded rule conditions with the expanded ones
|
251
|
+
result.merge!(rule_definition.merge(conditions: expanded_conditions) => rule_resources)
|
252
|
+
end
|
253
|
+
|
254
|
+
result
|
174
255
|
end
|
175
256
|
|
176
257
|
yield(PoliciesResources::SummaryResources.new(parts))
|
177
258
|
end
|
178
259
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
260
|
+
# Transform a non-expanded +rule_definition+ into a +PoliciesResources::RuleResources.new(resources)+ object.
|
261
|
+
#
|
262
|
+
# @example Example of a rule definition hash.
|
263
|
+
#
|
264
|
+
# { name: "New issues", conditions: { state: opened }, limits: { most_recent: 2 }, actions: { labels: ["needs attention"] } }
|
265
|
+
#
|
266
|
+
# @param resource_type [String] The resource type, e.g. +issues+ or +merge_requests+.
|
267
|
+
# @param rule_definition [Hash] A rule definition, e.g. +{ name: 'Foo', conditions: { milestone: 'v1' } }+.
|
268
|
+
#
|
269
|
+
# @yieldparam rule_resources [PoliciesResources::RuleResources] An object which contains an array of resources.
|
270
|
+
# @yieldparam expanded_conditions [Hash] A hash of expanded conditions.
|
271
|
+
# @yieldreturn [nil]
|
272
|
+
#
|
273
|
+
# @return [nil]
|
274
|
+
def resources_for_rule(resource_type, rule_definition)
|
275
|
+
puts Gitlab::Triage::UI.header("Gathering resources for rule: **#{rule_definition[:name]}**", char: '-')
|
276
|
+
|
277
|
+
ExpandCondition.perform(rule_conditions(rule_definition)) do |expanded_conditions|
|
183
278
|
# retrieving the resources for every rule is inefficient
|
184
279
|
# however, previous rules may affect those upcoming
|
185
280
|
resources = []
|
186
281
|
|
187
|
-
if
|
188
|
-
graphql_query = build_graphql_query(resource_type,
|
282
|
+
if rule_definition[:api] == 'graphql'
|
283
|
+
graphql_query = build_graphql_query(resource_type, expanded_conditions, true)
|
189
284
|
resources = graphql_network.query(graphql_query, source: source_full_path)
|
190
285
|
else
|
191
|
-
resources = network.query_api(build_get_url(resource_type,
|
286
|
+
resources = network.query_api(build_get_url(resource_type, expanded_conditions))
|
192
287
|
iids = resources.pluck('iid').map(&:to_s)
|
193
288
|
|
194
|
-
graphql_query = build_graphql_query(resource_type,
|
289
|
+
graphql_query = build_graphql_query(resource_type, expanded_conditions)
|
195
290
|
graphql_resources = graphql_network.query(graphql_query, source: source_full_path, iids: iids) if graphql_query.any?
|
196
291
|
|
197
292
|
decorate_resources_with_graphql_data(resources, graphql_resources)
|
@@ -202,28 +297,19 @@ module Gitlab
|
|
202
297
|
|
203
298
|
puts "\n\n* Found #{resources.count} resources..."
|
204
299
|
print "* Filtering resources..."
|
205
|
-
resources = filter_resources(resources,
|
300
|
+
resources = filter_resources(resources, expanded_conditions)
|
206
301
|
puts "\n* Total after filtering: #{resources.count} resources"
|
207
302
|
print "* Limiting resources..."
|
208
|
-
resources = limit_resources(resources, rule_limits(
|
303
|
+
resources = limit_resources(resources, rule_limits(rule_definition))
|
209
304
|
puts "\n* Total after limiting: #{resources.count} resources"
|
210
305
|
puts
|
211
306
|
|
212
|
-
yield(PoliciesResources::RuleResources.new(resources),
|
307
|
+
yield(PoliciesResources::RuleResources.new(resources), expanded_conditions)
|
213
308
|
end
|
214
309
|
end
|
215
310
|
|
216
311
|
def attach_resource_type(resources, resource_type)
|
217
312
|
resources.each { |resource| resource[:type] = resource_type }
|
218
|
-
# TODO: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
219
|
-
# We should not overwrite the attribute here, but we need to
|
220
|
-
# fix it first. We should instead use something like
|
221
|
-
# gitlab_triage_resource_type so it won't conflict with the
|
222
|
-
# existing fields.
|
223
|
-
# And we need to retain the backward compatibility that using
|
224
|
-
# {{type}} will give us this value, rather than from the REST API,
|
225
|
-
# which will give us ISSUE from:
|
226
|
-
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59648
|
227
313
|
end
|
228
314
|
|
229
315
|
def decorate_resources_with_graphql_data(resources, graphql_resources)
|
@@ -308,19 +394,20 @@ module Gitlab
|
|
308
394
|
allowed_values: ALLOWED_STATE_VALUES[resource_type])
|
309
395
|
end
|
310
396
|
|
311
|
-
condition_builders <<
|
312
|
-
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('source_branch', conditions[:source_branch]) if conditions[:source_branch]
|
313
|
-
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('target_branch', conditions[:target_branch]) if conditions[:target_branch]
|
397
|
+
condition_builders << milestone_condition_builder(resource_type, conditions[:milestone]) if conditions[:milestone]
|
314
398
|
|
315
399
|
if conditions[:date] && APIQueryBuilders::DateQueryParamBuilder.applicable?(conditions[:date])
|
316
400
|
condition_builders << APIQueryBuilders::DateQueryParamBuilder.new(conditions.delete(:date))
|
317
401
|
end
|
318
402
|
|
319
|
-
|
320
|
-
|
403
|
+
case resource_type&.to_sym
|
404
|
+
when :issues
|
405
|
+
condition_builders.concat(issues_resource_query(conditions))
|
406
|
+
when :merge_requests
|
407
|
+
condition_builders.concat(merge_requests_resource_query(conditions))
|
321
408
|
end
|
322
409
|
|
323
|
-
condition_builders.each do |condition_builder|
|
410
|
+
condition_builders.compact.each do |condition_builder|
|
324
411
|
params[condition_builder.param_name] = condition_builder.param_content
|
325
412
|
end
|
326
413
|
|
@@ -334,6 +421,50 @@ module Gitlab
|
|
334
421
|
).build
|
335
422
|
end
|
336
423
|
|
424
|
+
def milestone_condition_builder(resource_type, milestone_condition)
|
425
|
+
milestone_value = Array(milestone_condition)[0].to_s # back-compatibility
|
426
|
+
return if milestone_value.empty?
|
427
|
+
|
428
|
+
# Issues API should use the `milestone_id` param for timebox values, and `milestone` for milestone title
|
429
|
+
args =
|
430
|
+
if resource_type.to_sym == :issues && MILESTONE_TIMEBOX_VALUES.include?(milestone_value.downcase)
|
431
|
+
['milestone_id', milestone_value.downcase]
|
432
|
+
else
|
433
|
+
['milestone', milestone_value]
|
434
|
+
end
|
435
|
+
|
436
|
+
APIQueryBuilders::SingleQueryParamBuilder.new(*args)
|
437
|
+
end
|
438
|
+
|
439
|
+
def issues_resource_query(conditions)
|
440
|
+
[].tap do |condition_builders|
|
441
|
+
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('weight', conditions[:weight]) if conditions[:weight]
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def merge_requests_resource_query(conditions)
|
446
|
+
[].tap do |condition_builders|
|
447
|
+
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('source_branch', conditions[:source_branch]) if conditions[:source_branch]
|
448
|
+
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('target_branch', conditions[:target_branch]) if conditions[:target_branch]
|
449
|
+
condition_builders << draft_condition_builder(conditions[:draft]) if conditions.key?(:draft)
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
def draft_condition_builder(draft_condittion)
|
454
|
+
# Issues API only accepts 'yes' and 'no' as strings: https://docs.gitlab.com/ee/api/merge_requests.html
|
455
|
+
wip =
|
456
|
+
case draft_condittion
|
457
|
+
when true
|
458
|
+
'yes'
|
459
|
+
when false
|
460
|
+
'no'
|
461
|
+
else
|
462
|
+
raise ArgumentError, 'The "draft" condition only accepts true or false.'
|
463
|
+
end
|
464
|
+
|
465
|
+
APIQueryBuilders::SingleQueryParamBuilder.new('wip', wip)
|
466
|
+
end
|
467
|
+
|
337
468
|
def build_graphql_query(resource_type, conditions, graphql_only = false)
|
338
469
|
Gitlab::Triage::GraphqlQueries::QueryBuilder
|
339
470
|
.new(options.source, resource_type, conditions, graphql_only: graphql_only)
|
@@ -66,7 +66,7 @@ module Gitlab
|
|
66
66
|
fields << 'assignees { nodes { id name username } }' if conditions.key?(:assignee_member)
|
67
67
|
fields << 'upvotes' if conditions.dig(:upvotes, :attribute).to_s == 'upvotes'
|
68
68
|
fields << 'downvotes' if conditions.dig(:upvotes, :attribute).to_s == 'downvotes'
|
69
|
-
fields
|
69
|
+
fields.push('draft', 'mergedAt') if resource_type == 'merge_requests'
|
70
70
|
end
|
71
71
|
|
72
72
|
fields
|
@@ -82,8 +82,13 @@ module Gitlab
|
|
82
82
|
condition_queries << QueryParamBuilders::BaseParamBuilder.new('milestoneTitle', condition_params) if condition.to_s == 'milestone'
|
83
83
|
condition_queries << QueryParamBuilders::BaseParamBuilder.new('state', condition_params, with_quotes: false) if condition.to_s == 'state'
|
84
84
|
|
85
|
-
|
86
|
-
|
85
|
+
case resource_type
|
86
|
+
when 'issues'
|
87
|
+
condition_queries << issues_label_query(condition, condition_params)
|
88
|
+
when 'merge_requests'
|
89
|
+
condition_queries << merge_requests_label_query(condition, condition_params)
|
90
|
+
condition_queries << merge_requests_resource_query(condition, condition_params)
|
91
|
+
end
|
87
92
|
end
|
88
93
|
|
89
94
|
condition_queries
|
@@ -92,26 +97,48 @@ module Gitlab
|
|
92
97
|
.join
|
93
98
|
end
|
94
99
|
|
100
|
+
def issues_label_query(condition, condition_params)
|
101
|
+
args =
|
102
|
+
case condition.to_s
|
103
|
+
when 'forbidden_labels'
|
104
|
+
['labelName', condition_params, { negated: true }]
|
105
|
+
when 'labels'
|
106
|
+
['labelName', condition_params]
|
107
|
+
else
|
108
|
+
return nil
|
109
|
+
end
|
110
|
+
|
111
|
+
QueryParamBuilders::LabelsParamBuilder.new(*args)
|
112
|
+
end
|
113
|
+
|
95
114
|
def merge_requests_resource_query(condition, condition_params)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
115
|
+
args =
|
116
|
+
case condition.to_s
|
117
|
+
when 'source_branch'
|
118
|
+
['sourceBranches', condition_params]
|
119
|
+
when 'target_branch'
|
120
|
+
['targetBranches', condition_params]
|
121
|
+
when 'draft'
|
122
|
+
['draft', condition_params, { with_quotes: false }]
|
123
|
+
else
|
124
|
+
return nil
|
125
|
+
end
|
126
|
+
|
127
|
+
QueryParamBuilders::BaseParamBuilder.new(*args)
|
106
128
|
end
|
107
129
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
130
|
+
def merge_requests_label_query(condition, condition_params)
|
131
|
+
args =
|
132
|
+
case condition.to_s
|
133
|
+
when 'forbidden_labels'
|
134
|
+
['labels', condition_params, { negated: true }]
|
135
|
+
when 'labels'
|
136
|
+
['labels', condition_params]
|
137
|
+
else
|
138
|
+
return nil
|
139
|
+
end
|
140
|
+
|
141
|
+
QueryParamBuilders::LabelsParamBuilder.new(*args)
|
115
142
|
end
|
116
143
|
end
|
117
144
|
end
|
@@ -57,12 +57,7 @@ module Gitlab
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def full_resource_reference
|
60
|
-
@full_resource_reference ||=
|
61
|
-
"#{project_path}#{reference}#{resource[:iid]}"
|
62
|
-
end
|
63
|
-
|
64
|
-
def reference
|
65
|
-
raise NotImplementedError
|
60
|
+
@full_resource_reference ||= resource.dig(:references, :full)
|
66
61
|
end
|
67
62
|
|
68
63
|
def root_id(
|
@@ -8,7 +8,7 @@ dry-run:triage:
|
|
8
8
|
script:
|
9
9
|
- gem install gitlab-triage
|
10
10
|
- gitlab-triage --help
|
11
|
-
- gitlab-triage --dry-run --token $GITLAB_API_TOKEN --source projects --source-id $CI_PROJECT_PATH
|
11
|
+
- gitlab-triage --dry-run --token $GITLAB_API_TOKEN --host-url $CI_SERVER_URL --source projects --source-id $CI_PROJECT_PATH
|
12
12
|
when: manual
|
13
13
|
except:
|
14
14
|
- schedules
|
@@ -17,6 +17,6 @@ run:triage:
|
|
17
17
|
stage: triage
|
18
18
|
script:
|
19
19
|
- gem install gitlab-triage
|
20
|
-
- gitlab-triage --token $GITLAB_API_TOKEN --source projects --source-id $CI_PROJECT_PATH
|
20
|
+
- gitlab-triage --token $GITLAB_API_TOKEN --host-url $CI_SERVER_URL --source projects --source-id $CI_PROJECT_PATH
|
21
21
|
only:
|
22
22
|
- schedules
|
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.21.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: 2022-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -148,9 +148,14 @@ files:
|
|
148
148
|
- ".gitignore"
|
149
149
|
- ".gitlab-ci.yml"
|
150
150
|
- ".gitlab/CODEOWNERS"
|
151
|
+
- ".gitlab/changelog_config.yml"
|
151
152
|
- ".gitlab/merge_request_templates/Release.md"
|
152
153
|
- ".rubocop.yml"
|
154
|
+
- ".ruby-version"
|
155
|
+
- ".tool-versions"
|
156
|
+
- ".yardopts"
|
153
157
|
- CONTRIBUTING.md
|
158
|
+
- Dangerfile
|
154
159
|
- Gemfile
|
155
160
|
- Guardfile
|
156
161
|
- LICENSE.md
|
@@ -233,10 +238,14 @@ files:
|
|
233
238
|
- lib/gitlab/triage/version.rb
|
234
239
|
- support/.gitlab-ci.example.yml
|
235
240
|
- support/.triage-policies.example.yml
|
236
|
-
homepage: https://gitlab.com/gitlab-org/gitlab-triage
|
241
|
+
homepage: https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage
|
237
242
|
licenses:
|
238
243
|
- MIT
|
239
|
-
metadata:
|
244
|
+
metadata:
|
245
|
+
allowed_push_host: https://rubygems.org
|
246
|
+
homepage_uri: https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage
|
247
|
+
source_code_uri: https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage
|
248
|
+
changelog_uri: https://gitlab.com/gitlab-org/ruby/gems/gitlab-triage/-/releases
|
240
249
|
post_install_message:
|
241
250
|
rdoc_options: []
|
242
251
|
require_paths:
|
@@ -245,7 +254,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
245
254
|
requirements:
|
246
255
|
- - ">="
|
247
256
|
- !ruby/object:Gem::Version
|
248
|
-
version:
|
257
|
+
version: 2.5.0
|
249
258
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
250
259
|
requirements:
|
251
260
|
- - ">="
|