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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e43782bbf5e45d1c7699a1eb9ecb0168bc3d5c0744321e4e35e7266a8c557e54
4
- data.tar.gz: 9845eea26534334f705fd2832e5c47c646f81bb3b8c1499e2b9f6006daad8284
3
+ metadata.gz: 587e71e912b7d8906ecbc081b1a07258aa6cfe80643ea34c38314256a97ab503
4
+ data.tar.gz: a83dc6d31326079c8315d24af259d9015c13bb2d759684831633fd295dd0b8a1
5
5
  SHA512:
6
- metadata.gz: 9c49e920f42913e5c275f8cc166e6c4a17dc7d1462b613aaf7efc9d2e61bad58338d9d138598c5688eb56864b18882b4aad420d3b03330aa5a7c679690e4daaa
7
- data.tar.gz: 753b412633000a762e44bdcf22790d29058ecf09ee1b93dda2cd110da3bc999a2d7a84b5fcea783aaee63e380452a840d29b1bfdb7dab722710f8577b3e08d24
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:bin:gitlab-triage:
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:bin:gitlab-ce:
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 [.triage-policies.yml](.triage-policies.yml)). The format of the file is [YAML](https://en.wikipedia.org/wiki/YAML).
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 over sequences
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
- FileUtils.cp('./support/.triage-policies.example.yml', './.triage-policies.yml')
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
- FileUtils.cp('./support/.gitlab-ci.example.yml', './.gitlab-ci.yml')
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
@@ -28,9 +28,9 @@ module Gitlab
28
28
 
29
29
  def act
30
30
  resources.each do |resource|
31
- comment = build_comment(resource)
31
+ comment = build_comment(resource).strip
32
32
 
33
- perform(resource, comment)
33
+ perform(resource, comment) unless comment.empty?
34
34
  end
35
35
  end
36
36
 
@@ -10,7 +10,7 @@ module Gitlab
10
10
  end
11
11
 
12
12
  def build_param
13
- "&#{param_name}=#{param_content}"
13
+ "&#{param_name}=#{param_content.strip}"
14
14
  end
15
15
  end
16
16
  end
@@ -12,7 +12,7 @@ module Gitlab
12
12
  end
13
13
 
14
14
  def param_content
15
- param_contents.join(separator)
15
+ param_contents.map(&:strip).join(separator)
16
16
  end
17
17
  end
18
18
  end
@@ -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 'sequence/expansion'
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.perform(labels).map do |new_labels|
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
@@ -1,9 +1,11 @@
1
+ require_relative 'expand_condition/list'
1
2
  require_relative 'expand_condition/sequence'
2
3
 
3
4
  module Gitlab
4
5
  module Triage
5
6
  module ExpandCondition
6
7
  PIPELINE = [
8
+ List,
7
9
  Sequence
8
10
  ].freeze
9
11
 
@@ -1,5 +1,5 @@
1
1
  module Gitlab
2
2
  module Triage
3
- VERSION = '0.12.0'.freeze
3
+ VERSION = '0.13.0'.freeze
4
4
  end
5
5
  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.12.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-10-30 00:00:00.000000000 Z
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.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