gitlab-triage 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +3 -11
- data/README.md +60 -2
- data/bin/gitlab-triage +8 -2
- data/lib/gitlab/triage/action/comment.rb +2 -2
- data/lib/gitlab/triage/api_query_builders/base_query_param_builder.rb +1 -1
- data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +1 -1
- data/lib/gitlab/triage/engine.rb +3 -0
- data/lib/gitlab/triage/expand_condition/expansion.rb +203 -0
- data/lib/gitlab/triage/expand_condition/list.rb +25 -0
- data/lib/gitlab/triage/expand_condition/sequence.rb +8 -2
- data/lib/gitlab/triage/expand_condition.rb +2 -0
- data/lib/gitlab/triage/version.rb +1 -1
- metadata +5 -5
- data/.triage-policies.yml +0 -218
- data/lib/gitlab/triage/expand_condition/sequence/expansion.rb +0 -187
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 587e71e912b7d8906ecbc081b1a07258aa6cfe80643ea34c38314256a97ab503
|
4
|
+
data.tar.gz: a83dc6d31326079c8315d24af259d9015c13bb2d759684831633fd295dd0b8a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec66a05c1d691deb9eab031e69062762e492301a09fe27e2c3bf85f876b36459d715d2e6c82b6dd39706e568171643316a7558046347d07bb034740d5e0a4d32
|
7
|
+
data.tar.gz: '0359fe4894503d53ba358687b66854d9153a53177ba04ebbadb9106d6d824ae0ad1bc3c2d570834b3ed81cb3ca90f2dd67555c3758ae7e254e1ca8555a825aa8'
|
data/.gitlab-ci.yml
CHANGED
@@ -75,15 +75,16 @@ codequality:
|
|
75
75
|
##################
|
76
76
|
## Triage stage ##
|
77
77
|
##################
|
78
|
-
dry-run:
|
78
|
+
dry-run:gitlab-triage:
|
79
79
|
<<: *pull-cache
|
80
80
|
stage: triage
|
81
81
|
script:
|
82
82
|
- bundle exec rake install:local
|
83
83
|
- gitlab-triage --help
|
84
|
+
- gitlab-triage --init
|
84
85
|
- gitlab-triage --dry-run --debug --token $API_TOKEN --project-id $CI_PROJECT_PATH
|
85
86
|
|
86
|
-
dry-run:
|
87
|
+
dry-run:gitlab-ce:
|
87
88
|
<<: *pull-cache
|
88
89
|
stage: triage
|
89
90
|
script:
|
@@ -91,12 +92,3 @@ dry-run:bin:gitlab-ce:
|
|
91
92
|
- gitlab-triage --help
|
92
93
|
- gitlab-triage --dry-run --debug --token $API_TOKEN --project-id gitlab-org/gitlab-ce
|
93
94
|
when: manual
|
94
|
-
|
95
|
-
dry-run:gem:gitlab-triage:
|
96
|
-
<<: *pull-cache
|
97
|
-
stage: triage
|
98
|
-
script:
|
99
|
-
- gem install gitlab-triage
|
100
|
-
- gitlab-triage --help
|
101
|
-
- gitlab-triage --dry-run --debug --token $API_TOKEN --project-id $CI_PROJECT_PATH
|
102
|
-
when: manual
|
data/README.md
CHANGED
@@ -20,7 +20,8 @@ Each policy can declare a number of conditions that must all be satisfied before
|
|
20
20
|
|
21
21
|
### Defining a policy
|
22
22
|
|
23
|
-
Policies are defined in a policy file (by default
|
23
|
+
Policies are defined in a policy file (by default `./.triage-policies.yml`).
|
24
|
+
The format of the file is [YAML](https://en.wikipedia.org/wiki/YAML).
|
24
25
|
|
25
26
|
> Note: You can use the [`--init`](#usage) option to add an example [`.triage-policies.yml` file](support/.triage-policies.example.yml) to your project
|
26
27
|
|
@@ -189,7 +190,64 @@ conditions:
|
|
189
190
|
- feature proposal
|
190
191
|
```
|
191
192
|
|
192
|
-
###### Labels
|
193
|
+
###### Labels brace expansion
|
194
|
+
|
195
|
+
We could expand the labels by using brace expansion, which is a pattern
|
196
|
+
surrounded by using braces: `{}`. For now, we support 2 kinds of brace
|
197
|
+
expansion:
|
198
|
+
|
199
|
+
1. List: `{ apple, orange }`
|
200
|
+
2. Sequence: `{1..4}`
|
201
|
+
|
202
|
+
> Note:
|
203
|
+
> - Spaces around the items are ignored.
|
204
|
+
> - Do not rely on the expansion ordering. This is subject to change.
|
205
|
+
|
206
|
+
###### List
|
207
|
+
|
208
|
+
The name of a label can contain a list of items, written like
|
209
|
+
`{ apple, orange }`. For each item, the rule will be duplicated with the new
|
210
|
+
label name.
|
211
|
+
|
212
|
+
Example:
|
213
|
+
|
214
|
+
```yml
|
215
|
+
resource_rules:
|
216
|
+
issues:
|
217
|
+
rules:
|
218
|
+
- name: Add missing ~Quality label
|
219
|
+
conditions:
|
220
|
+
labels:
|
221
|
+
- Quality:test-{ gap, infra }
|
222
|
+
actions:
|
223
|
+
labels:
|
224
|
+
- Quality
|
225
|
+
```
|
226
|
+
|
227
|
+
Which will be expanded into:
|
228
|
+
|
229
|
+
```yml
|
230
|
+
resource_rules:
|
231
|
+
issues:
|
232
|
+
rules:
|
233
|
+
- name: Add missing ~Quality label
|
234
|
+
conditions:
|
235
|
+
labels:
|
236
|
+
- Quality:test-gap
|
237
|
+
actions:
|
238
|
+
labels:
|
239
|
+
- Quality
|
240
|
+
|
241
|
+
- name: Add missing ~Quality label
|
242
|
+
conditions:
|
243
|
+
labels:
|
244
|
+
- Quality:test-infra
|
245
|
+
actions:
|
246
|
+
labels:
|
247
|
+
- Quality
|
248
|
+
```
|
249
|
+
|
250
|
+
###### Sequence
|
193
251
|
|
194
252
|
The name of a label can contain one or more sequence conditions, written
|
195
253
|
like `{0..9}`, which means `0`, `1`, `2`, and so on up to `9`. For each
|
data/bin/gitlab-triage
CHANGED
@@ -52,12 +52,18 @@ class TriageOptionParser
|
|
52
52
|
end
|
53
53
|
|
54
54
|
opts.on('--init', 'Initialize the project with a policy file') do
|
55
|
-
|
55
|
+
example_path =
|
56
|
+
File.expand_path('../support/.triage-policies.example.yml', __dir__)
|
57
|
+
|
58
|
+
FileUtils.cp(example_path, '.triage-policies.yml')
|
56
59
|
exit
|
57
60
|
end
|
58
61
|
|
59
62
|
opts.on('--init-ci', 'Initialize the project with a .gitlab-ci.yml file') do
|
60
|
-
|
63
|
+
example_path =
|
64
|
+
File.expand_path('../support/.gitlab-ci.example.yml', __dir__)
|
65
|
+
|
66
|
+
FileUtils.cp(example_path, '.gitlab-ci.yml')
|
61
67
|
exit
|
62
68
|
end
|
63
69
|
end
|
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -39,6 +39,9 @@ module Gitlab
|
|
39
39
|
def perform
|
40
40
|
puts "Performing a dry run.\n\n" if options.dry_run
|
41
41
|
|
42
|
+
puts Gitlab::Triage::UI.header("Triaging the `#{options.project_id}` project", char: '=')
|
43
|
+
puts
|
44
|
+
|
42
45
|
resource_rules.each do |type, resource|
|
43
46
|
puts Gitlab::Triage::UI.header("Processing rules for #{type}", char: '-')
|
44
47
|
puts
|
@@ -0,0 +1,203 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Triage
|
3
|
+
module ExpandCondition
|
4
|
+
class Expansion
|
5
|
+
# @pattern describes how we're looking for the pattern, and
|
6
|
+
# @compile is a block which should compile the scanned data
|
7
|
+
# into a list of results.
|
8
|
+
#
|
9
|
+
# Please see the comments for #perform to see actual example.
|
10
|
+
def initialize(pattern, &compile)
|
11
|
+
@pattern = pattern
|
12
|
+
@compile = compile
|
13
|
+
end
|
14
|
+
|
15
|
+
# This method will take a list of strings, which contains
|
16
|
+
# some kind of pattern, described by @pattern and expand them
|
17
|
+
# into each possible matches via @compile. For example,
|
18
|
+
# suppose @pattern is:
|
19
|
+
#
|
20
|
+
# /\{(\d+)\.\.(\d+)\}/
|
21
|
+
#
|
22
|
+
# And @compile is:
|
23
|
+
#
|
24
|
+
# do |(lower, upper)|
|
25
|
+
# Integer(lower)..Integer(upper)
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# And the input is:
|
29
|
+
#
|
30
|
+
# * a:{0..1}
|
31
|
+
# * b:{2..3}
|
32
|
+
# * c
|
33
|
+
#
|
34
|
+
# The result would be:
|
35
|
+
#
|
36
|
+
# * * a:0
|
37
|
+
# * b:2
|
38
|
+
# * c
|
39
|
+
# * * a:0
|
40
|
+
# * b:3
|
41
|
+
# * c
|
42
|
+
# * * a:1
|
43
|
+
# * b:2
|
44
|
+
# * c
|
45
|
+
# * * a:1
|
46
|
+
# * b:3
|
47
|
+
# * c
|
48
|
+
#
|
49
|
+
# We get this by picking the 1st number from the 1st string,
|
50
|
+
# which is 0 from "a:{0..1}", and the 1st number from the
|
51
|
+
# 2nd string, which is 2 from "b:{2..3}", and since the 3rd
|
52
|
+
# string is just a fixed string, we just pick it.
|
53
|
+
#
|
54
|
+
# This way we have the first possible match, that is:
|
55
|
+
#
|
56
|
+
# * a:0
|
57
|
+
# * b:2
|
58
|
+
# * c
|
59
|
+
#
|
60
|
+
# Then we repeat the process by picking the next number for the
|
61
|
+
# next possible match, starting from the least significant
|
62
|
+
# string, which is c, but there's nothing more to pick. Then we
|
63
|
+
# go to the next one, which will be the 2nd string: "b:{2..3}",
|
64
|
+
# and we pick the 2nd number for it: 3. Since we have a new pick,
|
65
|
+
# we have a new possible match:
|
66
|
+
#
|
67
|
+
# * a:0
|
68
|
+
# * b:3
|
69
|
+
# * c
|
70
|
+
#
|
71
|
+
# Again we repeat the process, and 2nd string doesn't have more
|
72
|
+
# choices therefore we need to go to the 1st string now. When
|
73
|
+
# this happens, we'll need to reset the picks from the previous
|
74
|
+
# string, thus 2nd string will go back to 2. The next number for
|
75
|
+
# the 1st string is 1, and then we form the new match:
|
76
|
+
#
|
77
|
+
# * a:1
|
78
|
+
# * b:2
|
79
|
+
# * c
|
80
|
+
#
|
81
|
+
# The next step will be the last match by picking the next number
|
82
|
+
# from the 2nd string again: 3, and we get:
|
83
|
+
#
|
84
|
+
# * a:1
|
85
|
+
# * b:3
|
86
|
+
# * c
|
87
|
+
#
|
88
|
+
# The method will stop here because it had walked through all the
|
89
|
+
# possible combinations. The total number of results is the product
|
90
|
+
# of numbers of sequences.
|
91
|
+
#
|
92
|
+
# Note that a string can contain multiple sequences, and it will
|
93
|
+
# also walk through them one by one. For example, given:
|
94
|
+
#
|
95
|
+
# * a:{0..1}:{2..3}
|
96
|
+
# * c
|
97
|
+
#
|
98
|
+
# We'll get:
|
99
|
+
#
|
100
|
+
# * * a:0:2
|
101
|
+
# * c
|
102
|
+
# * * a:0:3
|
103
|
+
# * c
|
104
|
+
# * * a:1:2
|
105
|
+
# * c
|
106
|
+
# * * a:1:3
|
107
|
+
# * c
|
108
|
+
def perform(strings)
|
109
|
+
expanded_strings =
|
110
|
+
strings.map(&:strip).map(&method(:expand_patterns))
|
111
|
+
|
112
|
+
product_of_all(expanded_strings)
|
113
|
+
end
|
114
|
+
|
115
|
+
# This method returns the product of list of lists. For example,
|
116
|
+
# giving it [%w[a:0 a:1], %w[b:2 b:3], %w[c]] will return:
|
117
|
+
#
|
118
|
+
# [%w[a:0 b:2 c], %w[a:0 b:3 c], %w[a:1 b:2 c], %w[a:1 b:3 c]]
|
119
|
+
def product_of_all(expanded_strings)
|
120
|
+
expanded_strings.first.product(*expanded_strings.drop(1))
|
121
|
+
end
|
122
|
+
|
123
|
+
# This method expands the string from the sequences. For example,
|
124
|
+
# giving it "a:{0..1}:{2..3}" will return:
|
125
|
+
#
|
126
|
+
# %w[
|
127
|
+
# a:0:2
|
128
|
+
# a:0:3
|
129
|
+
# a:1:2
|
130
|
+
# a:1:3
|
131
|
+
# ]
|
132
|
+
def expand_patterns(string)
|
133
|
+
expand(string, scan_patterns(string))
|
134
|
+
end
|
135
|
+
|
136
|
+
# This method extracts the sequences from the string. For example,
|
137
|
+
# giving it "a:{0..1}:{2..3}" will return:
|
138
|
+
#
|
139
|
+
# [0..1, 2..3]
|
140
|
+
def scan_patterns(string)
|
141
|
+
string.scan(@pattern).map(&@compile)
|
142
|
+
end
|
143
|
+
|
144
|
+
# This recursive method does the heavy lifting. It substitutes the
|
145
|
+
# sequence patterns in a string with a picked number from the
|
146
|
+
# sequence, and collect all the results. Here's an example:
|
147
|
+
#
|
148
|
+
# expand("a:{0..1}:{2..3}", [0..1, 2..3])
|
149
|
+
#
|
150
|
+
# This means that we want to pick the numbers from the sequences,
|
151
|
+
# and fill them back to the string containing the pattern in the
|
152
|
+
# respective order. We don't care which pattern it is because
|
153
|
+
# the order should have spoken for it. The result will be:
|
154
|
+
#
|
155
|
+
# %w[
|
156
|
+
# a:0:2
|
157
|
+
# a:0:3
|
158
|
+
# a:1:2
|
159
|
+
# a:1:3
|
160
|
+
# ]
|
161
|
+
#
|
162
|
+
# We start by picking the first sequence, which is 0..1 here. We
|
163
|
+
# want all the possible picks, thus we flat_map on it, substituting
|
164
|
+
# the first pattern with the picked number. This means we get:
|
165
|
+
#
|
166
|
+
# "a:0:{2..3}"
|
167
|
+
#
|
168
|
+
# For the first iteration. Before we jump to the next pick from the
|
169
|
+
# sequence, we recursively do this again on the current string,
|
170
|
+
# which only has one sequence pattern left. It will be called like:
|
171
|
+
#
|
172
|
+
# expand("a:0:{2..3}", [2..3])
|
173
|
+
#
|
174
|
+
# Because we also dropped the first sequence we have already used.
|
175
|
+
# On the next recursive call, we don't have any sequences left,
|
176
|
+
# therefore we just return the current string: "a:0:2".
|
177
|
+
#
|
178
|
+
# Flattening the recursion, it might look like this:
|
179
|
+
#
|
180
|
+
# (0..1).flat_map do |x|
|
181
|
+
# (2..3).flat_map do |y|
|
182
|
+
# "a:{0..1}:{2..3}".sub(PATTERN, x.to_s).sub(PATTERN, y.to_s)
|
183
|
+
# end
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
# So here we could clearly see that we go deep first, substituting
|
187
|
+
# the least significant pattern first, and then go back to the
|
188
|
+
# previous one, until there's nothing more to pick.
|
189
|
+
def expand(string, items)
|
190
|
+
if items.empty?
|
191
|
+
[string]
|
192
|
+
else
|
193
|
+
remainings = items.drop(1)
|
194
|
+
|
195
|
+
items.first.flat_map do |item|
|
196
|
+
expand(string.sub(@pattern, item.to_s), remainings)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'expansion'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module ExpandCondition
|
6
|
+
module List
|
7
|
+
PATTERN = /\{.+?,.+?\}/m
|
8
|
+
|
9
|
+
def self.expand(conditions)
|
10
|
+
labels = conditions[:labels]
|
11
|
+
|
12
|
+
return conditions unless labels
|
13
|
+
|
14
|
+
expansion = Expansion.new(PATTERN) do |list|
|
15
|
+
list.gsub(/\{|\}/, '').split(',').map(&:strip)
|
16
|
+
end
|
17
|
+
|
18
|
+
expansion.perform(labels).map do |new_labels|
|
19
|
+
conditions.merge(labels: new_labels)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,15 +1,21 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'expansion'
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module Triage
|
5
5
|
module ExpandCondition
|
6
6
|
module Sequence
|
7
|
+
PATTERN = /\{\s*(\d+)\s*\.\.\s*(\d+)\s*\}/
|
8
|
+
|
7
9
|
def self.expand(conditions)
|
8
10
|
labels = conditions[:labels]
|
9
11
|
|
10
12
|
return conditions unless labels
|
11
13
|
|
12
|
-
Expansion.
|
14
|
+
expansion = Expansion.new(PATTERN) do |(lower, upper)|
|
15
|
+
Integer(lower)..Integer(upper)
|
16
|
+
end
|
17
|
+
|
18
|
+
expansion.perform(labels).map do |new_labels|
|
13
19
|
conditions.merge(labels: new_labels)
|
14
20
|
end
|
15
21
|
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: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitLab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-11-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -122,7 +122,6 @@ files:
|
|
122
122
|
- ".gitlab/CODEOWNERS"
|
123
123
|
- ".gitlab/issue_templates/Policy.md"
|
124
124
|
- ".rubocop.yml"
|
125
|
-
- ".triage-policies.yml"
|
126
125
|
- CONTRIBUTING.md
|
127
126
|
- Gemfile
|
128
127
|
- Guardfile
|
@@ -149,8 +148,9 @@ files:
|
|
149
148
|
- lib/gitlab/triage/command_builders/text_content_builder.rb
|
150
149
|
- lib/gitlab/triage/engine.rb
|
151
150
|
- lib/gitlab/triage/expand_condition.rb
|
151
|
+
- lib/gitlab/triage/expand_condition/expansion.rb
|
152
|
+
- lib/gitlab/triage/expand_condition/list.rb
|
152
153
|
- lib/gitlab/triage/expand_condition/sequence.rb
|
153
|
-
- lib/gitlab/triage/expand_condition/sequence/expansion.rb
|
154
154
|
- lib/gitlab/triage/filters/assignee_member_conditions_filter.rb
|
155
155
|
- lib/gitlab/triage/filters/author_member_conditions_filter.rb
|
156
156
|
- lib/gitlab/triage/filters/base_conditions_filter.rb
|
@@ -199,7 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
199
199
|
version: '0'
|
200
200
|
requirements: []
|
201
201
|
rubyforge_project:
|
202
|
-
rubygems_version: 2.7.
|
202
|
+
rubygems_version: 2.7.8
|
203
203
|
signing_key:
|
204
204
|
specification_version: 4
|
205
205
|
summary: GitLab triage automation project.
|
data/.triage-policies.yml
DELETED
@@ -1,218 +0,0 @@
|
|
1
|
-
resource_rules:
|
2
|
-
issues:
|
3
|
-
rules:
|
4
|
-
- name: Mark stale issues with no milestone for closure
|
5
|
-
conditions:
|
6
|
-
date:
|
7
|
-
attribute: updated_at
|
8
|
-
condition: older_than
|
9
|
-
interval_type: months
|
10
|
-
interval: 12
|
11
|
-
milestone:
|
12
|
-
- No Milestone
|
13
|
-
state: opened
|
14
|
-
actions:
|
15
|
-
labels:
|
16
|
-
- awaiting feedback
|
17
|
-
- auto updated
|
18
|
-
mention:
|
19
|
-
- markglenfletcher
|
20
|
-
comment: |
|
21
|
-
Hi {{author}},
|
22
|
-
|
23
|
-
First of all, thank you for raising an issue to help improve the GitLab product. We're sorry about this, but this particular issue has gone unnoticed for quite some time. To establish order in the GitLab-CE Issue Tracker, we must ensure that every issue is correctly labelled and triaged, to get the proper attention.
|
24
|
-
|
25
|
-
This issue will be closed, as it meets the following criteria:
|
26
|
-
* No activity in the past 12 months
|
27
|
-
* No milestone (unscheduled)
|
28
|
-
|
29
|
-
We'd like to ask you to help us out and determine whether this issue should be reopened.
|
30
|
-
|
31
|
-
If this issue is reporting a bug, please can you attempt to reproduce on the latest version of GitLab or GitLab.com, to help us to understand whether the bug still needs our attention.
|
32
|
-
|
33
|
-
If this issue is proposing a new feature, please can you verify whether the feature proposal is still relevant.
|
34
|
-
|
35
|
-
Thanks for your help
|
36
|
-
- name: Mark stale issues for closure
|
37
|
-
conditions:
|
38
|
-
date:
|
39
|
-
attribute: updated_at
|
40
|
-
condition: older_than
|
41
|
-
interval_type: months
|
42
|
-
interval: 3
|
43
|
-
labels:
|
44
|
-
- No Label
|
45
|
-
state: opened
|
46
|
-
actions:
|
47
|
-
labels:
|
48
|
-
- awaiting feedback
|
49
|
-
- auto updated
|
50
|
-
mention:
|
51
|
-
- markglenfletcher
|
52
|
-
comment: |
|
53
|
-
Hi {{author}},
|
54
|
-
|
55
|
-
First of all, thank you for raising an issue to help improve the GitLab product. We're sorry about this, but this particular issue has gone unnoticed for quite some time. To establish order in the GitLab-CE Issue Tracker, we must ensure that every issue is correctly labelled and triaged, to get the proper attention.
|
56
|
-
|
57
|
-
This issue will be closed, as it meets the following criteria:
|
58
|
-
* No activity in the past 3 months
|
59
|
-
* Unlabelled
|
60
|
-
|
61
|
-
We'd like to ask you to help us out and determine whether this issue should be reopened.
|
62
|
-
|
63
|
-
If this issue is reporting a bug, please can you attempt to reproduce on the latest version of GitLab or GitLab.com, to help us to understand whether the bug still needs our attention.
|
64
|
-
|
65
|
-
If this issue is proposing a new feature, please can you verify whether the feature proposal is still relevant.
|
66
|
-
|
67
|
-
Thanks for your help
|
68
|
-
- name: Close non-updated issues
|
69
|
-
conditions:
|
70
|
-
date:
|
71
|
-
attribute: updated_at
|
72
|
-
condition: older_than
|
73
|
-
interval_type: weeks
|
74
|
-
interval: 2
|
75
|
-
labels:
|
76
|
-
- awaiting feedback
|
77
|
-
- auto updated
|
78
|
-
state: opened
|
79
|
-
actions:
|
80
|
-
labels:
|
81
|
-
- auto closed
|
82
|
-
mention:
|
83
|
-
- markglenfletcher
|
84
|
-
status: close
|
85
|
-
comment: |
|
86
|
-
{{author}} Closing this issue down in accordance with our [Issue Triage Policies](https://gitlab.com/gitlab-org/triage). Please reopen the issue if you feel that the issue is still relevant. Thanks!
|
87
|
-
- name: Mark potentially interesting feature proposals
|
88
|
-
conditions:
|
89
|
-
labels:
|
90
|
-
- feature proposal
|
91
|
-
state: opened
|
92
|
-
upvotes:
|
93
|
-
attribute: upvotes
|
94
|
-
condition: greater_than
|
95
|
-
threshold: 10
|
96
|
-
milestone:
|
97
|
-
- No Milestone
|
98
|
-
actions:
|
99
|
-
labels:
|
100
|
-
- auto updated
|
101
|
-
- potential proposal
|
102
|
-
- name: Mark unpopular, old feature proposals for closure
|
103
|
-
conditions:
|
104
|
-
date:
|
105
|
-
attribute: updated_at
|
106
|
-
condition: older_than
|
107
|
-
interval_type: years
|
108
|
-
interval: 1
|
109
|
-
labels:
|
110
|
-
- feature proposal
|
111
|
-
state: opened
|
112
|
-
upvotes:
|
113
|
-
attribute: upvotes
|
114
|
-
condition: less_than
|
115
|
-
threshold: 10
|
116
|
-
milestone:
|
117
|
-
- No Milestone
|
118
|
-
actions:
|
119
|
-
labels:
|
120
|
-
- auto updated
|
121
|
-
- awaiting feedback
|
122
|
-
mention:
|
123
|
-
- markglenfletcher
|
124
|
-
comment: |
|
125
|
-
Hi {{author}},
|
126
|
-
|
127
|
-
First of all, thank you for raising an issue to help improve the GitLab product. This issue was labelled as a ~"feature proposal" in the past. In order to maintain order in the issue tracker, we are starting to close off old, unpopular feature proposals that have not gained many votes since opening.
|
128
|
-
|
129
|
-
This issue will be closed, as it meets the following criteria:
|
130
|
-
* No activity in the past 12 months (last update was {{updated_at}})
|
131
|
-
* Current labels ({{labels}}) include ~"feature proposal"
|
132
|
-
* Unscheduled (not associated with a milestone)
|
133
|
-
* Less than 10 upvotes (currently {{upvotes}} upvotes)
|
134
|
-
|
135
|
-
Thanks for your help and please raise any new feature proposals as a new issue.
|
136
|
-
- name: Mark very popular, unscheduled feature proposals
|
137
|
-
conditions:
|
138
|
-
labels:
|
139
|
-
- feature proposal
|
140
|
-
state: opened
|
141
|
-
milestone:
|
142
|
-
- No Milestone
|
143
|
-
upvotes:
|
144
|
-
attribute: upvotes
|
145
|
-
condition: greater_than
|
146
|
-
threshold: 50
|
147
|
-
actions:
|
148
|
-
labels:
|
149
|
-
- auto updated
|
150
|
-
- popular proposal
|
151
|
-
- name: Mark stale bugs for closure
|
152
|
-
conditions:
|
153
|
-
date:
|
154
|
-
attribute: updated_at
|
155
|
-
condition: older_than
|
156
|
-
interval_type: months
|
157
|
-
interval: 6
|
158
|
-
labels:
|
159
|
-
- bug
|
160
|
-
milestone:
|
161
|
-
- No Milestone
|
162
|
-
state: opened
|
163
|
-
actions:
|
164
|
-
labels:
|
165
|
-
- awaiting feedback
|
166
|
-
- auto updated
|
167
|
-
mention:
|
168
|
-
- markglenfletcher
|
169
|
-
comment: |
|
170
|
-
Hi {{author}},
|
171
|
-
|
172
|
-
First of all, thank you for raising an issue to help improve the GitLab product. We're sorry about this, but this particular issue has gone unnoticed for quite some time. To establish order in the GitLab-CE Issue Tracker, we must ensure that every issue is correctly labelled and triaged, to get the proper attention.
|
173
|
-
|
174
|
-
This issue will be marked for closure, as it meets the following criteria:
|
175
|
-
* Issue is open
|
176
|
-
* No activity in the past 6 months (last update was {{updated_at}})
|
177
|
-
* Current labels ({{labels}}) include ~bug
|
178
|
-
* Unscheduled (no milestone)
|
179
|
-
|
180
|
-
We'd like to ask you to help us out and determine whether this issue should be reopened.
|
181
|
-
|
182
|
-
Because this issue is reporting a bug, please can you attempt to reproduce on the latest version of GitLab or on GitLab.com, to help us to understand whether the bug still needs our attention.
|
183
|
-
|
184
|
-
Thanks for your help
|
185
|
-
merge_requests:
|
186
|
-
rules:
|
187
|
-
- name: Encourage old Community Merge Requests to completion
|
188
|
-
conditions:
|
189
|
-
date:
|
190
|
-
attribute: updated_at
|
191
|
-
condition: older_than
|
192
|
-
interval_type: months
|
193
|
-
interval: 6
|
194
|
-
labels:
|
195
|
-
- Community Contribution
|
196
|
-
state: opened
|
197
|
-
actions:
|
198
|
-
labels:
|
199
|
-
- awaiting feedback
|
200
|
-
- auto updated
|
201
|
-
mention:
|
202
|
-
- rymai
|
203
|
-
comment: |
|
204
|
-
Hi {{author}},
|
205
|
-
|
206
|
-
First of all, thank you for creating a Merge Request to help improve the GitLab product. We are running through old Merge Requests and asking authors to update their Merge Requests
|
207
|
-
|
208
|
-
This Merge Request was chosen, as it meets the following criteria:
|
209
|
-
* Open
|
210
|
-
* No activity in the past 6 months (last update was {{updated_at}})
|
211
|
-
* Current labels ({{labels}}) include ~"Community Contribution"
|
212
|
-
|
213
|
-
We'd like to ask you to help us out and let us know whether:
|
214
|
-
* You would like to continue the work here
|
215
|
-
* You would like us to take on the Merge Request for you
|
216
|
-
* You would like us to close down the Merge Request
|
217
|
-
|
218
|
-
Thanks for your help
|
@@ -1,187 +0,0 @@
|
|
1
|
-
module Gitlab
|
2
|
-
module Triage
|
3
|
-
module ExpandCondition
|
4
|
-
module Sequence
|
5
|
-
module Expansion
|
6
|
-
PATTERN = /\{(\d+)\.\.(\d+)\}/
|
7
|
-
|
8
|
-
# This method will take a list of strings, which contains the
|
9
|
-
# sequence pattern, and expand them into each possible matches.
|
10
|
-
# For example, suppose the input is:
|
11
|
-
#
|
12
|
-
# * a:{0..1}
|
13
|
-
# * b:{2..3}
|
14
|
-
# * c
|
15
|
-
#
|
16
|
-
# The result would be:
|
17
|
-
#
|
18
|
-
# * * a:0
|
19
|
-
# * b:2
|
20
|
-
# * c
|
21
|
-
# * * a:0
|
22
|
-
# * b:3
|
23
|
-
# * c
|
24
|
-
# * * a:1
|
25
|
-
# * b:2
|
26
|
-
# * c
|
27
|
-
# * * a:1
|
28
|
-
# * b:3
|
29
|
-
# * c
|
30
|
-
#
|
31
|
-
# We get this by picking the 1st number from the 1st string,
|
32
|
-
# which is 0 from "a:{0..1}", and the 1st number from the
|
33
|
-
# 2nd string, which is 2 from "b:{2..3}", and since the 3rd
|
34
|
-
# string is just a fixed string, we just pick it.
|
35
|
-
#
|
36
|
-
# This way we have the first possible match, that is:
|
37
|
-
#
|
38
|
-
# * a:0
|
39
|
-
# * b:2
|
40
|
-
# * c
|
41
|
-
#
|
42
|
-
# Then we repeat the process by picking the next number for the
|
43
|
-
# next possible match, starting from the least significant
|
44
|
-
# string, which is c, but there's nothing more to pick. Then we
|
45
|
-
# go to the next one, which will be the 2nd string: "b:{2..3}",
|
46
|
-
# and we pick the 2nd number for it: 3. Since we have a new pick,
|
47
|
-
# we have a new possible match:
|
48
|
-
#
|
49
|
-
# * a:0
|
50
|
-
# * b:3
|
51
|
-
# * c
|
52
|
-
#
|
53
|
-
# Again we repeat the process, and 2nd string doesn't have more
|
54
|
-
# choices therefore we need to go to the 1st string now. When
|
55
|
-
# this happens, we'll need to reset the picks from the previous
|
56
|
-
# string, thus 2nd string will go back to 2. The next number for
|
57
|
-
# the 1st string is 1, and then we form the new match:
|
58
|
-
#
|
59
|
-
# * a:1
|
60
|
-
# * b:2
|
61
|
-
# * c
|
62
|
-
#
|
63
|
-
# The next step will be the last match by picking the next number
|
64
|
-
# from the 2nd string again: 3, and we get:
|
65
|
-
#
|
66
|
-
# * a:1
|
67
|
-
# * b:3
|
68
|
-
# * c
|
69
|
-
#
|
70
|
-
# The method will stop here because it had walked through all the
|
71
|
-
# possible combinations. The total number of results is the product
|
72
|
-
# of numbers of sequences.
|
73
|
-
#
|
74
|
-
# Note that a string can contain multiple sequences, and it will
|
75
|
-
# also walk through them one by one. For example, given:
|
76
|
-
#
|
77
|
-
# * a:{0..1}:{2..3}
|
78
|
-
# * c
|
79
|
-
#
|
80
|
-
# We'll get:
|
81
|
-
#
|
82
|
-
# * * a:0:2
|
83
|
-
# * c
|
84
|
-
# * * a:0:3
|
85
|
-
# * c
|
86
|
-
# * * a:1:2
|
87
|
-
# * c
|
88
|
-
# * * a:1:3
|
89
|
-
# * c
|
90
|
-
def self.perform(strings)
|
91
|
-
expanded_strings = strings.map(&method(:expand_sequences))
|
92
|
-
|
93
|
-
product_of_all(expanded_strings)
|
94
|
-
end
|
95
|
-
|
96
|
-
# This method returns the product of list of lists. For example,
|
97
|
-
# giving it [%w[a:0 a:1], %w[b:2 b:3], %w[c]] will return:
|
98
|
-
#
|
99
|
-
# [%w[a:0 b:2 c], %w[a:0 b:3 c], %w[a:1 b:2 c], %w[a:1 b:3 c]]
|
100
|
-
def self.product_of_all(expanded_strings)
|
101
|
-
expanded_strings.first.product(*expanded_strings.drop(1))
|
102
|
-
end
|
103
|
-
|
104
|
-
# This method expands the string from the sequences. For example,
|
105
|
-
# giving it "a:{0..1}:{2..3}" will return:
|
106
|
-
#
|
107
|
-
# %w[
|
108
|
-
# a:0:2
|
109
|
-
# a:0:3
|
110
|
-
# a:1:2
|
111
|
-
# a:1:3
|
112
|
-
# ]
|
113
|
-
def self.expand_sequences(string)
|
114
|
-
expand(string, scan_sequences(string))
|
115
|
-
end
|
116
|
-
|
117
|
-
# This method extracts the sequences from the string. For example,
|
118
|
-
# giving it "a:{0..1}:{2..3}" will return:
|
119
|
-
#
|
120
|
-
# [0..1, 2..3]
|
121
|
-
def self.scan_sequences(string)
|
122
|
-
string.scan(PATTERN).map do |(lower, upper)|
|
123
|
-
Integer(lower)..Integer(upper)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
# This recursive method does the heavy lifting. It substitutes the
|
128
|
-
# sequence patterns in a string with a picked number from the
|
129
|
-
# sequence, and collect all the results. Here's an example:
|
130
|
-
#
|
131
|
-
# expand("a:{0..1}:{2..3}", [0..1, 2..3])
|
132
|
-
#
|
133
|
-
# This means that we want to pick the numbers from the sequences,
|
134
|
-
# and fill them back to the string containing the pattern in the
|
135
|
-
# respective order. We don't care which pattern it is because
|
136
|
-
# the order should have spoken for it. The result will be:
|
137
|
-
#
|
138
|
-
# %w[
|
139
|
-
# a:0:2
|
140
|
-
# a:0:3
|
141
|
-
# a:1:2
|
142
|
-
# a:1:3
|
143
|
-
# ]
|
144
|
-
#
|
145
|
-
# We start by picking the first sequence, which is 0..1 here. We
|
146
|
-
# want all the possible picks, thus we flat_map on it, substituting
|
147
|
-
# the first pattern with the picked number. This means we get:
|
148
|
-
#
|
149
|
-
# "a:0:{2..3}"
|
150
|
-
#
|
151
|
-
# For the first iteration. Before we jump to the next pick from the
|
152
|
-
# sequence, we recursively do this again on the current string,
|
153
|
-
# which only has one sequence pattern left. It will be called like:
|
154
|
-
#
|
155
|
-
# expand("a:0:{2..3}", [2..3])
|
156
|
-
#
|
157
|
-
# Because we also dropped the first sequence we have already used.
|
158
|
-
# On the next recursive call, we don't have any sequences left,
|
159
|
-
# therefore we just return the current string: "a:0:2".
|
160
|
-
#
|
161
|
-
# Flattening the recursion, it might look like this:
|
162
|
-
#
|
163
|
-
# (0..1).flat_map do |x|
|
164
|
-
# (2..3).flat_map do |y|
|
165
|
-
# "a:{0..1}:{2..3}".sub(PATTERN, x.to_s).sub(PATTERN, y.to_s)
|
166
|
-
# end
|
167
|
-
# end
|
168
|
-
#
|
169
|
-
# So here we could clearly see that we go deep first, substituting
|
170
|
-
# the least significant pattern first, and then go back to the
|
171
|
-
# previous one, until there's nothing more to pick.
|
172
|
-
def self.expand(string, sequences)
|
173
|
-
if sequences.empty?
|
174
|
-
[string]
|
175
|
-
else
|
176
|
-
remainings = sequences.drop(1)
|
177
|
-
|
178
|
-
sequences.first.flat_map do |number|
|
179
|
-
expand(string.sub(PATTERN, number.to_s), remainings)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|