gitlab-triage 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +131 -1
- data/gitlab-triage.gemspec +1 -0
- data/lib/gitlab/triage/api_query_builders/base_query_param_builder.rb +18 -0
- data/lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb +20 -0
- data/lib/gitlab/triage/api_query_builders/single_query_param_builder.rb +13 -0
- data/lib/gitlab/triage/engine.rb +45 -34
- data/lib/gitlab/triage/expand_condition.rb +21 -0
- data/lib/gitlab/triage/expand_condition/sequence.rb +19 -0
- data/lib/gitlab/triage/expand_condition/sequence/expansion.rb +187 -0
- data/lib/gitlab/triage/filters/assignee_member_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/filters/author_member_conditions_filter.rb +13 -0
- data/lib/gitlab/triage/{limiters/base_conditions_limiter.rb → filters/base_conditions_filter.rb} +24 -12
- data/lib/gitlab/triage/{limiters/date_conditions_limiter.rb → filters/date_conditions_filter.rb} +4 -4
- data/lib/gitlab/triage/{limiters/forbidden_labels_conditions_limiter.rb → filters/forbidden_labels_conditions_filter.rb} +3 -7
- data/lib/gitlab/triage/{limiters/member_conditions_limiter.rb → filters/member_conditions_filter.rb} +5 -5
- data/lib/gitlab/triage/{limiters/name_conditions_limiter.rb → filters/name_conditions_filter.rb} +3 -7
- data/lib/gitlab/triage/{limiters/no_additional_labels_conditions_limiter.rb → filters/no_additional_labels_conditions_filter.rb} +4 -4
- data/lib/gitlab/triage/filters/ruby_conditions_filter.rb +31 -0
- data/lib/gitlab/triage/{limiters/votes_conditions_limiter.rb → filters/votes_conditions_filter.rb} +4 -4
- data/lib/gitlab/triage/network.rb +5 -0
- data/lib/gitlab/triage/resource/base.rb +46 -0
- data/lib/gitlab/triage/resource/context.rb +34 -0
- data/lib/gitlab/triage/resource/milestone.rb +82 -0
- data/lib/gitlab/triage/version.rb +1 -1
- metadata +35 -14
- data/lib/gitlab/triage/filter_builders/base_filter_builder.rb +0 -18
- data/lib/gitlab/triage/filter_builders/multi_filter_builder.rb +0 -20
- data/lib/gitlab/triage/filter_builders/single_filter_builder.rb +0 -13
- data/lib/gitlab/triage/limiters/assignee_member_conditions_limiter.rb +0 -13
- data/lib/gitlab/triage/limiters/author_member_conditions_limiter.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ab21a6dffa12d92f9321ac3c6d2bc0c92627fad703fceb7ebf68494d8f5b259
|
4
|
+
data.tar.gz: e5868058935e55d70ccadaada8030a8bec92dee6ceeeea2f9996cd60f3823cdf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a875bebf70cd7dcc2a6c5f064d692535cdf98b664323fb640b22fed7f52ac4d5f49e8b21adb5b4942f626e4f4638d23f3042109ae13c495ae9c732d177e9298f
|
7
|
+
data.tar.gz: 01a627062e23ba73d40be5cc7260a92aa7be9ec30b9fb694585305ae919435174099a701d6045998c03738ee7fd7dcf0cac9ebf821abfea9b4697fd6f8f55e06
|
data/README.md
CHANGED
@@ -44,7 +44,7 @@ resource_rules:
|
|
44
44
|
interval_type: days
|
45
45
|
interval: 5
|
46
46
|
state: opened
|
47
|
-
|
47
|
+
labels:
|
48
48
|
- No Label
|
49
49
|
actions:
|
50
50
|
labels:
|
@@ -89,6 +89,7 @@ Available condition types:
|
|
89
89
|
- [`no_additional_labels` condition](#no-additional-labels-condition)
|
90
90
|
- [`author_member` condition](#author-member-condition)
|
91
91
|
- [`assignee_member` condition](#assignee-member-condition)
|
92
|
+
- [`ruby` condition](#ruby-condition)
|
92
93
|
|
93
94
|
##### Date condition
|
94
95
|
|
@@ -177,6 +178,71 @@ conditions:
|
|
177
178
|
- feature proposal
|
178
179
|
```
|
179
180
|
|
181
|
+
###### Labels over sequences
|
182
|
+
|
183
|
+
The name of a label can contain one or more sequence conditions, written
|
184
|
+
like `{0..9}`, which means `0`, `1`, `2`, and so on up to `9`. For each
|
185
|
+
number, the rule will be duplicated with the new label name.
|
186
|
+
|
187
|
+
Example:
|
188
|
+
|
189
|
+
```yml
|
190
|
+
resource_rules:
|
191
|
+
issues:
|
192
|
+
rules:
|
193
|
+
- name: Add missing ~"missed\-deliverable" label
|
194
|
+
conditions:
|
195
|
+
labels:
|
196
|
+
- missed:{10..11}.{0..1}
|
197
|
+
- deliverable
|
198
|
+
actions:
|
199
|
+
labels:
|
200
|
+
- missed deliverable
|
201
|
+
```
|
202
|
+
|
203
|
+
Which will be expanded into:
|
204
|
+
|
205
|
+
```yml
|
206
|
+
resource_rules:
|
207
|
+
issues:
|
208
|
+
rules:
|
209
|
+
- name: Add missing ~"missed\-deliverable" label
|
210
|
+
conditions:
|
211
|
+
labels:
|
212
|
+
- missed:10.0
|
213
|
+
- deliverable
|
214
|
+
actions:
|
215
|
+
labels:
|
216
|
+
- missed deliverable
|
217
|
+
|
218
|
+
- name: Add missing ~"missed\-deliverable" label
|
219
|
+
conditions:
|
220
|
+
labels:
|
221
|
+
- missed:10.1
|
222
|
+
- deliverable
|
223
|
+
actions:
|
224
|
+
labels:
|
225
|
+
- missed deliverable
|
226
|
+
|
227
|
+
- name: Add missing ~"missed\-deliverable" label
|
228
|
+
conditions:
|
229
|
+
labels:
|
230
|
+
- missed:11.0
|
231
|
+
- deliverable
|
232
|
+
actions:
|
233
|
+
labels:
|
234
|
+
- missed deliverable
|
235
|
+
|
236
|
+
- name: Add missing ~"missed\-deliverable" label
|
237
|
+
conditions:
|
238
|
+
labels:
|
239
|
+
- missed:11.1
|
240
|
+
- deliverable
|
241
|
+
actions:
|
242
|
+
labels:
|
243
|
+
- missed deliverable
|
244
|
+
```
|
245
|
+
|
180
246
|
##### Forbidden labels condition
|
181
247
|
|
182
248
|
Accepts an array of strings. Each element in the array represents the name of a label to filter on.
|
@@ -251,6 +317,70 @@ conditions:
|
|
251
317
|
source_id: 9970
|
252
318
|
```
|
253
319
|
|
320
|
+
##### Ruby condition
|
321
|
+
|
322
|
+
This condition allows users to write a Ruby expression to be evaluated for
|
323
|
+
each resource. If it evaluates to a truthy value, it satisfies the condition.
|
324
|
+
If it evaluates to a falsey value, it does not satisfy the condition.
|
325
|
+
|
326
|
+
Accepts a string as the Ruby expression.
|
327
|
+
|
328
|
+
Example:
|
329
|
+
|
330
|
+
```yml
|
331
|
+
conditions:
|
332
|
+
ruby: Date.today > milestone.succ.start_date
|
333
|
+
```
|
334
|
+
|
335
|
+
In the above example, this describes that we want to act on the resources
|
336
|
+
which passed the next active milestone's starting date.
|
337
|
+
|
338
|
+
Here `milestone` will return a `Gitlab::Triage::Resource::Milestone` object,
|
339
|
+
representing the milestone of the questioning resource. `Milestone#succ` would
|
340
|
+
return the next active milestone, based on the `start_date` of all milestones
|
341
|
+
along with the representing milestone. If the milestone was coming from a
|
342
|
+
project, then it's based on all active milestones in that project. If the
|
343
|
+
milestone was coming from a group, then it's based on all active milestones
|
344
|
+
in the group.
|
345
|
+
|
346
|
+
If we also want to handle some edge cases, for example, a resource might not
|
347
|
+
have a milestone, and a milestone might not be active, and there might not
|
348
|
+
have a next milestone. We could instead write something like:
|
349
|
+
|
350
|
+
```yml
|
351
|
+
conditions:
|
352
|
+
ruby: milestone&.active? && milestone&.succ && Date.today > milestone.succ.start_date
|
353
|
+
```
|
354
|
+
|
355
|
+
This will make it only act on resources which have active milestones and
|
356
|
+
there exists next milestone which has already started.
|
357
|
+
|
358
|
+
Here's a list of currently available API:
|
359
|
+
|
360
|
+
##### API
|
361
|
+
|
362
|
+
| Name | Return type | Description |
|
363
|
+
| ---- | ---- | ---- |
|
364
|
+
| milestone | Milestone | The milestone attached to the resource |
|
365
|
+
|
366
|
+
##### Methods for `Milestone`
|
367
|
+
|
368
|
+
| Method | Return type | Description |
|
369
|
+
| ---- | ---- | ---- |
|
370
|
+
| id | Integer | The id of the milestone |
|
371
|
+
| iid | Integer | The iid of the milestone |
|
372
|
+
| project_id | Integer | The project id of the milestone if available |
|
373
|
+
| group_id | Integer | The group id of the milestone if available |
|
374
|
+
| title | String | The title of the milestone |
|
375
|
+
| description | String | The description of the milestone |
|
376
|
+
| state | String | The state of the milestone. Could be `active` or `closed` |
|
377
|
+
| due_date | Date | The due date of the milestone. Could be `nil` |
|
378
|
+
| start_date | Date | The start date of the milestone. Could be `nil` |
|
379
|
+
| updated_at | Time | The updated timestamp of the milestone |
|
380
|
+
| created_at | Time | The created timestamp of the milestone |
|
381
|
+
| succ | Milestone | The next active milestone beside this milestone |
|
382
|
+
| active? | Boolean | `true` if `state` is `active`; `false` otherwise |
|
383
|
+
|
254
384
|
#### Actions field
|
255
385
|
|
256
386
|
Used to declare an action to be carried out on a resource if **all** conditions are satisfied.
|
data/gitlab-triage.gemspec
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Gitlab
|
2
|
+
module Triage
|
3
|
+
module APIQueryBuilders
|
4
|
+
class BaseQueryParamBuilder
|
5
|
+
attr_reader :param_name, :param_contents
|
6
|
+
|
7
|
+
def initialize(param_name, param_contents)
|
8
|
+
@param_name = param_name
|
9
|
+
@param_contents = param_contents
|
10
|
+
end
|
11
|
+
|
12
|
+
def build_param
|
13
|
+
"&#{param_name}=#{param_content}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'base_query_param_builder'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module APIQueryBuilders
|
6
|
+
class MultiQueryParamBuilder < BaseQueryParamBuilder
|
7
|
+
attr_reader :separator
|
8
|
+
|
9
|
+
def initialize(param_name, param_contents, separator)
|
10
|
+
@separator = separator
|
11
|
+
super(param_name, param_contents)
|
12
|
+
end
|
13
|
+
|
14
|
+
def param_content
|
15
|
+
param_contents.join(separator)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/gitlab/triage/engine.rb
CHANGED
@@ -1,19 +1,21 @@
|
|
1
1
|
require 'active_support/all'
|
2
2
|
|
3
|
-
require_relative '
|
4
|
-
require_relative '
|
5
|
-
require_relative '
|
6
|
-
require_relative '
|
7
|
-
require_relative '
|
8
|
-
require_relative '
|
3
|
+
require_relative 'expand_condition'
|
4
|
+
require_relative 'filters/date_conditions_filter'
|
5
|
+
require_relative 'filters/votes_conditions_filter'
|
6
|
+
require_relative 'filters/forbidden_labels_conditions_filter'
|
7
|
+
require_relative 'filters/no_additional_labels_conditions_filter'
|
8
|
+
require_relative 'filters/author_member_conditions_filter'
|
9
|
+
require_relative 'filters/assignee_member_conditions_filter'
|
10
|
+
require_relative 'filters/ruby_conditions_filter'
|
9
11
|
require_relative 'command_builders/comment_body_builder'
|
10
12
|
require_relative 'command_builders/comment_command_builder'
|
11
13
|
require_relative 'command_builders/label_command_builder'
|
12
14
|
require_relative 'command_builders/remove_label_command_builder'
|
13
15
|
require_relative 'command_builders/cc_command_builder'
|
14
16
|
require_relative 'command_builders/status_command_builder'
|
15
|
-
require_relative '
|
16
|
-
require_relative '
|
17
|
+
require_relative 'api_query_builders/single_query_param_builder'
|
18
|
+
require_relative 'api_query_builders/multi_query_param_builder'
|
17
19
|
require_relative 'url_builders/url_builder'
|
18
20
|
require_relative 'network'
|
19
21
|
require_relative 'network_adapters/httparty_adapter'
|
@@ -42,10 +44,10 @@ module Gitlab
|
|
42
44
|
puts "Performing a dry run.\n\n" if options.dry_run
|
43
45
|
|
44
46
|
resource_rules.each do |type, resource|
|
45
|
-
puts Gitlab::Triage::UI.header("Processing rules for #{type}")
|
47
|
+
puts Gitlab::Triage::UI.header("Processing rules for #{type}", char: '-')
|
46
48
|
puts
|
47
49
|
resource[:rules].each do |rule|
|
48
|
-
puts Gitlab::Triage::UI.header("Processing rule:
|
50
|
+
puts Gitlab::Triage::UI.header("Processing rule: **#{rule[:name]}**", char: '-')
|
49
51
|
puts
|
50
52
|
process_rule(type, rule)
|
51
53
|
end
|
@@ -87,40 +89,49 @@ module Gitlab
|
|
87
89
|
end
|
88
90
|
|
89
91
|
def process_rule(resource_type, rule)
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
92
|
+
ExpandCondition.perform(rule_conditions(rule)) do |conditions|
|
93
|
+
# retrieving the resources for every rule is inefficient
|
94
|
+
# however, previous rules may affect those upcoming
|
95
|
+
resources = network.query_api(options.token, build_get_url(resource_type, conditions))
|
96
|
+
puts "\n\n* Found #{resources.count} resources..."
|
97
|
+
print "* Limiting resources..."
|
98
|
+
resources = limit_resources(resources, conditions)
|
99
|
+
puts "\n* Total resource after limiting: #{resources.count} resources"
|
100
|
+
process_resources(resource_type, resources, rule)
|
101
|
+
end
|
98
102
|
end
|
99
103
|
|
100
104
|
def limit_resources(resources, conditions)
|
105
|
+
net = { host_url: host_url, api_version: api_version, token: options.token, network: network }
|
106
|
+
|
101
107
|
resources.select do |resource|
|
102
108
|
results = []
|
103
|
-
|
104
|
-
results <<
|
105
|
-
results <<
|
106
|
-
results <<
|
107
|
-
results <<
|
108
|
-
results <<
|
109
|
-
|
109
|
+
|
110
|
+
results << Filters::DateConditionsFilter.new(resource, conditions[:date]).calculate if conditions[:date]
|
111
|
+
results << Filters::VotesConditionsFilter.new(resource, conditions[:upvotes]).calculate if conditions[:upvotes]
|
112
|
+
results << Filters::ForbiddenLabelsConditionsFilter.new(resource, conditions[:forbidden_labels]).calculate if conditions[:forbidden_labels]
|
113
|
+
results << Filters::NoAdditionalLabelsConditionsFilter.new(resource, conditions.fetch(:labels) { [] }).calculate if conditions[:no_additional_labels]
|
114
|
+
results << Filters::AuthorMemberConditionsFilter.new(resource, conditions[:author_member], net).calculate if conditions[:author_member]
|
115
|
+
results << Filters::AssigneeMemberConditionsFilter.new(resource, conditions[:assignee_member], net).calculate if conditions[:assignee_member]
|
116
|
+
results << Filters::RubyConditionsFilter.new(resource, conditions, net).calculate if conditions[:ruby]
|
117
|
+
|
118
|
+
results.all?
|
110
119
|
end
|
111
120
|
end
|
112
121
|
|
113
122
|
def process_resources(resource_type, resources, rule)
|
123
|
+
if options.dry_run
|
124
|
+
puts "\nThe following comments would be posted for the rule **#{rule[:name]}**:\n\n"
|
125
|
+
end
|
126
|
+
|
114
127
|
resources.each do |resource|
|
115
128
|
comment = build_comment(rule_actions(rule), resource: resource)
|
116
129
|
|
117
130
|
if options.dry_run
|
118
|
-
puts "
|
119
|
-
|
120
|
-
|
131
|
+
puts "# #{resource[:web_url]}\n```\n#{comment}\n```\n"
|
132
|
+
else
|
133
|
+
network.post_api(options.token, build_post_url(resource_type, resource), comment)
|
121
134
|
end
|
122
|
-
|
123
|
-
network.post_api(options.token, build_post_url(resource_type, resource), comment)
|
124
135
|
end
|
125
136
|
end
|
126
137
|
|
@@ -144,12 +155,12 @@ module Gitlab
|
|
144
155
|
}
|
145
156
|
|
146
157
|
condition_builders = []
|
147
|
-
condition_builders <<
|
148
|
-
condition_builders <<
|
149
|
-
condition_builders <<
|
158
|
+
condition_builders << APIQueryBuilders::MultiQueryParamBuilder.new('labels', conditions[:labels], ',') if conditions[:labels]
|
159
|
+
condition_builders << APIQueryBuilders::SingleQueryParamBuilder.new('state', conditions[:state]) if conditions[:state]
|
160
|
+
condition_builders << APIQueryBuilders::MultiQueryParamBuilder.new('milestone', conditions[:milestone], ',') if conditions[:milestone]
|
150
161
|
|
151
162
|
condition_builders.each do |condition_builder|
|
152
|
-
params[condition_builder.
|
163
|
+
params[condition_builder.param_name] = condition_builder.param_content
|
153
164
|
end
|
154
165
|
|
155
166
|
get_url = UrlBuilders::UrlBuilder.new(
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'expand_condition/sequence'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module ExpandCondition
|
6
|
+
PIPELINE = [
|
7
|
+
Sequence
|
8
|
+
].freeze
|
9
|
+
|
10
|
+
def self.perform(conditions, pipeline = PIPELINE, &block)
|
11
|
+
expand([conditions], pipeline).each(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.expand(conditions, pipeline = PIPELINE)
|
15
|
+
pipeline.inject(conditions) do |result, job|
|
16
|
+
result.flat_map(&job.method(:expand))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative 'sequence/expansion'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module ExpandCondition
|
6
|
+
module Sequence
|
7
|
+
def self.expand(conditions)
|
8
|
+
labels = conditions[:labels]
|
9
|
+
|
10
|
+
return conditions unless labels
|
11
|
+
|
12
|
+
Expansion.perform(labels).map do |new_labels|
|
13
|
+
conditions.merge(labels: new_labels)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,187 @@
|
|
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
|
data/lib/gitlab/triage/{limiters/base_conditions_limiter.rb → filters/base_conditions_filter.rb}
RENAMED
@@ -2,61 +2,73 @@ require 'active_support/all'
|
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module Triage
|
5
|
-
module
|
6
|
-
class
|
5
|
+
module Filters
|
6
|
+
class BaseConditionsFilter
|
7
7
|
def initialize(resource, condition)
|
8
8
|
@resource = resource
|
9
9
|
validate_condition(condition)
|
10
10
|
initialize_variables(condition)
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
|
13
|
+
def calculate
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.filter_parameters
|
18
|
+
[]
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.params_filter_names(params = nil)
|
22
|
+
params ||= filter_parameters
|
15
23
|
|
16
24
|
params.map do |param|
|
17
25
|
param[:name]
|
18
26
|
end
|
19
27
|
end
|
20
28
|
|
21
|
-
def self.
|
22
|
-
|
29
|
+
def self.all_params_filter_names
|
30
|
+
params_filter_names
|
23
31
|
end
|
24
32
|
|
25
33
|
def self.params_checking_condition_value
|
26
|
-
|
34
|
+
params_filter_names params_check_for_field(:values)
|
27
35
|
end
|
28
36
|
|
29
37
|
def self.params_checking_condition_type
|
30
|
-
|
38
|
+
params_filter_names params_check_for_field(:type)
|
31
39
|
end
|
32
40
|
|
33
41
|
def self.params_check_for_field(field)
|
34
|
-
|
42
|
+
filter_parameters.select do |param|
|
35
43
|
param[field].present?
|
36
44
|
end
|
37
45
|
end
|
38
46
|
|
47
|
+
private
|
48
|
+
|
39
49
|
def validate_condition(condition)
|
40
50
|
validate_required_parameters(condition)
|
41
51
|
validate_parameter_types(condition)
|
42
52
|
validate_parameter_content(condition)
|
43
53
|
end
|
44
54
|
|
55
|
+
def initialize_variables(condition); end
|
56
|
+
|
45
57
|
def validate_required_parameters(condition)
|
46
|
-
self.class.
|
58
|
+
self.class.filter_parameters.each do |param|
|
47
59
|
raise ArgumentError, "#{param[:name]} is a required parameter" unless condition[param[:name]]
|
48
60
|
end
|
49
61
|
end
|
50
62
|
|
51
63
|
def validate_parameter_types(condition)
|
52
|
-
self.class.
|
64
|
+
self.class.filter_parameters.each do |param|
|
53
65
|
param_types = Array(param[:type]).flatten
|
54
66
|
raise ArgumentError, "#{param[:name]} must be of type #{param[:type]}" unless param_types.any? { |type| condition[param[:name]].is_a?(type) }
|
55
67
|
end
|
56
68
|
end
|
57
69
|
|
58
70
|
def validate_parameter_content(condition)
|
59
|
-
self.class.
|
71
|
+
self.class.filter_parameters.each do |param|
|
60
72
|
if param[:values]
|
61
73
|
raise ArgumentError, "#{param[:name]} must be of one of #{param[:values].join(',')}" unless param[:values].include?(condition[param[:name]])
|
62
74
|
end
|
data/lib/gitlab/triage/{limiters/date_conditions_limiter.rb → filters/date_conditions_filter.rb}
RENAMED
@@ -1,14 +1,14 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'base_conditions_filter'
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module Triage
|
5
|
-
module
|
6
|
-
class
|
5
|
+
module Filters
|
6
|
+
class DateConditionsFilter < BaseConditionsFilter
|
7
7
|
ATTRIBUTES = %w[updated_at created_at].freeze
|
8
8
|
CONDITIONS = %w[older_than newer_than].freeze
|
9
9
|
INTERVAL_TYPES = %w[days weeks months years].freeze
|
10
10
|
|
11
|
-
def self.
|
11
|
+
def self.filter_parameters
|
12
12
|
[
|
13
13
|
{
|
14
14
|
name: :attribute,
|
@@ -1,13 +1,9 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'base_conditions_filter'
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module Triage
|
5
|
-
module
|
6
|
-
class
|
7
|
-
def self.limiter_parameters
|
8
|
-
[]
|
9
|
-
end
|
10
|
-
|
5
|
+
module Filters
|
6
|
+
class ForbiddenLabelsConditionsFilter < BaseConditionsFilter
|
11
7
|
def validate_condition(condition)
|
12
8
|
raise ArgumentError, 'condition must be an array containing forbidden label values' unless condition.is_a?(Array)
|
13
9
|
end
|
data/lib/gitlab/triage/{limiters/member_conditions_limiter.rb → filters/member_conditions_filter.rb}
RENAMED
@@ -1,10 +1,10 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'base_conditions_filter'
|
2
2
|
require_relative '../url_builders/url_builder'
|
3
3
|
|
4
4
|
module Gitlab
|
5
5
|
module Triage
|
6
|
-
module
|
7
|
-
class
|
6
|
+
module Filters
|
7
|
+
class MemberConditionsFilter < BaseConditionsFilter
|
8
8
|
SOURCES = %w[project group].freeze
|
9
9
|
CONDITIONS = %w[member_of not_member_of].freeze
|
10
10
|
|
@@ -14,7 +14,7 @@ module Gitlab
|
|
14
14
|
super(resource, condition)
|
15
15
|
end
|
16
16
|
|
17
|
-
def self.
|
17
|
+
def self.filter_parameters
|
18
18
|
[
|
19
19
|
{
|
20
20
|
name: :source,
|
@@ -61,7 +61,7 @@ module Gitlab
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def members
|
64
|
-
@members ||= @network.
|
64
|
+
@members ||= @network.query_api_cached(@net[:token], member_url)
|
65
65
|
end
|
66
66
|
|
67
67
|
def member_url
|
data/lib/gitlab/triage/{limiters/name_conditions_limiter.rb → filters/name_conditions_filter.rb}
RENAMED
@@ -1,13 +1,9 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'base_conditions_filter'
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module Triage
|
5
|
-
module
|
6
|
-
class
|
7
|
-
def self.limiter_parameters
|
8
|
-
[]
|
9
|
-
end
|
10
|
-
|
5
|
+
module Filters
|
6
|
+
class NameConditionsFilter < BaseConditionsFilter
|
11
7
|
def initialize_variables(matching_name)
|
12
8
|
@attribute = :name
|
13
9
|
@matching_name = matching_name
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'base_conditions_filter'
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module Triage
|
5
|
-
module
|
6
|
-
class
|
7
|
-
def self.
|
5
|
+
module Filters
|
6
|
+
class NoAdditionalLabelsConditionsFilter < BaseConditionsFilter
|
7
|
+
def self.filter_parameters
|
8
8
|
[]
|
9
9
|
end
|
10
10
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'base_conditions_filter'
|
2
|
+
require_relative '../resource/context'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Triage
|
7
|
+
module Filters
|
8
|
+
class RubyConditionsFilter < BaseConditionsFilter
|
9
|
+
def self.limiter_parameters
|
10
|
+
[{ name: :ruby, type: String }]
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(resource, condition, net = {})
|
14
|
+
super(resource, condition)
|
15
|
+
|
16
|
+
@net = net
|
17
|
+
end
|
18
|
+
|
19
|
+
def calculate
|
20
|
+
!!Resource::Context.new(@resource, @net).eval(@expression)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def initialize_variables(condition)
|
26
|
+
@expression = condition[:ruby]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/gitlab/triage/{limiters/votes_conditions_limiter.rb → filters/votes_conditions_filter.rb}
RENAMED
@@ -1,13 +1,13 @@
|
|
1
|
-
require_relative '
|
1
|
+
require_relative 'base_conditions_filter'
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module Triage
|
5
|
-
module
|
6
|
-
class
|
5
|
+
module Filters
|
6
|
+
class VotesConditionsFilter < BaseConditionsFilter
|
7
7
|
ATTRIBUTES = %w[upvotes downvotes].freeze
|
8
8
|
CONDITIONS = %w[greater_than less_than].freeze
|
9
9
|
|
10
|
-
def self.
|
10
|
+
def self.filter_parameters
|
11
11
|
[
|
12
12
|
{
|
13
13
|
name: :attribute,
|
@@ -14,6 +14,11 @@ module Gitlab
|
|
14
14
|
def initialize(adapter, options = {})
|
15
15
|
@adapter = adapter
|
16
16
|
@options = options
|
17
|
+
@cache = Hash.new { |hash, key| hash[key] = {} }
|
18
|
+
end
|
19
|
+
|
20
|
+
def query_api_cached(token, url)
|
21
|
+
@cache.dig(token, url) || @cache[token][url] = query_api(token, url)
|
17
22
|
end
|
18
23
|
|
19
24
|
def query_api(token, url)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative '../url_builders/url_builder'
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Triage
|
5
|
+
module Resource
|
6
|
+
class Base
|
7
|
+
attr_reader :resource, :net
|
8
|
+
|
9
|
+
def initialize(new_resource, new_net)
|
10
|
+
@resource = new_resource
|
11
|
+
@net = new_net
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def network
|
17
|
+
net[:network]
|
18
|
+
end
|
19
|
+
|
20
|
+
def url(params = {})
|
21
|
+
UrlBuilders::UrlBuilder.new(
|
22
|
+
net_opts.merge(params: { per_page: 100 }.merge(params))
|
23
|
+
).build
|
24
|
+
end
|
25
|
+
|
26
|
+
def net_opts
|
27
|
+
{
|
28
|
+
host_url: net[:host_url],
|
29
|
+
api_version: net[:api_version],
|
30
|
+
resource_type: self.class.name.demodulize.underscore.pluralize,
|
31
|
+
source: source,
|
32
|
+
source_id: resource[:"#{source.singularize}_id"]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def source
|
37
|
+
if resource[:project_id]
|
38
|
+
'projects'
|
39
|
+
elsif resource[:group_id]
|
40
|
+
'groups'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require_relative 'milestone'
|
3
|
+
|
4
|
+
module Gitlab
|
5
|
+
module Triage
|
6
|
+
module Resource
|
7
|
+
class Context < Base
|
8
|
+
EvaluationError = Class.new(RuntimeError)
|
9
|
+
|
10
|
+
def eval(ruby)
|
11
|
+
instance_eval <<~RUBY
|
12
|
+
begin
|
13
|
+
#{ruby}
|
14
|
+
rescue StandardError, ScriptError => e
|
15
|
+
raise EvaluationError.new(e.message)
|
16
|
+
end
|
17
|
+
RUBY
|
18
|
+
rescue EvaluationError => e
|
19
|
+
# This way we could obtain the original backtrace and error
|
20
|
+
# If we just let instance_eval raise an error, the backtrace
|
21
|
+
# won't contain the actual line where it's giving an error.
|
22
|
+
raise e.cause
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def milestone
|
28
|
+
@milestone ||=
|
29
|
+
resource[:milestone] && Milestone.new(resource[:milestone], net)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Triage
|
7
|
+
module Resource
|
8
|
+
class Milestone < Base
|
9
|
+
FIELDS = %i[
|
10
|
+
id
|
11
|
+
iid
|
12
|
+
project_id
|
13
|
+
group_id
|
14
|
+
title
|
15
|
+
description
|
16
|
+
state
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
DATE_FIELDS = %i[
|
20
|
+
due_date
|
21
|
+
start_date
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
TIME_FIELDS = %i[
|
25
|
+
updated_at
|
26
|
+
created_at
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
FIELDS.each do |field|
|
30
|
+
define_method(field) do
|
31
|
+
resource[field]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
DATE_FIELDS.each do |field|
|
36
|
+
define_method(field) do
|
37
|
+
value = resource[field]
|
38
|
+
|
39
|
+
Date.parse(value) if value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
TIME_FIELDS.each do |field|
|
44
|
+
define_method(field) do
|
45
|
+
value = resource[field]
|
46
|
+
|
47
|
+
Time.parse(value) if value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def succ
|
52
|
+
index = current_index
|
53
|
+
|
54
|
+
all_active_with_start_date[index.succ] if index
|
55
|
+
end
|
56
|
+
|
57
|
+
def active?
|
58
|
+
state == 'active'
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def current_index
|
64
|
+
all_active_with_start_date
|
65
|
+
.index { |milestone| milestone.id == id }
|
66
|
+
end
|
67
|
+
|
68
|
+
def all_active_with_start_date
|
69
|
+
@all_active_with_start_date ||=
|
70
|
+
all_active.select(&:start_date).sort_by(&:start_date)
|
71
|
+
end
|
72
|
+
|
73
|
+
def all_active
|
74
|
+
@all_active ||=
|
75
|
+
network
|
76
|
+
.query_api_cached(net[:token], url(state: 'active'))
|
77
|
+
.map { |milestone| self.class.new(milestone, net) }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
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.7.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-08-
|
11
|
+
date: 2018-08-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webmock
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.4'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.4'
|
97
111
|
description:
|
98
112
|
email:
|
99
113
|
- remy@rymai.me
|
@@ -117,6 +131,9 @@ files:
|
|
117
131
|
- bin/gitlab-triage
|
118
132
|
- gitlab-triage.gemspec
|
119
133
|
- lib/gitlab/triage.rb
|
134
|
+
- lib/gitlab/triage/api_query_builders/base_query_param_builder.rb
|
135
|
+
- lib/gitlab/triage/api_query_builders/multi_query_param_builder.rb
|
136
|
+
- lib/gitlab/triage/api_query_builders/single_query_param_builder.rb
|
120
137
|
- lib/gitlab/triage/command_builders/base_command_builder.rb
|
121
138
|
- lib/gitlab/triage/command_builders/cc_command_builder.rb
|
122
139
|
- lib/gitlab/triage/command_builders/comment_body_builder.rb
|
@@ -125,22 +142,26 @@ files:
|
|
125
142
|
- lib/gitlab/triage/command_builders/remove_label_command_builder.rb
|
126
143
|
- lib/gitlab/triage/command_builders/status_command_builder.rb
|
127
144
|
- lib/gitlab/triage/engine.rb
|
128
|
-
- lib/gitlab/triage/
|
129
|
-
- lib/gitlab/triage/
|
130
|
-
- lib/gitlab/triage/
|
131
|
-
- lib/gitlab/triage/
|
132
|
-
- lib/gitlab/triage/
|
133
|
-
- lib/gitlab/triage/
|
134
|
-
- lib/gitlab/triage/
|
135
|
-
- lib/gitlab/triage/
|
136
|
-
- lib/gitlab/triage/
|
137
|
-
- lib/gitlab/triage/
|
138
|
-
- lib/gitlab/triage/
|
139
|
-
- lib/gitlab/triage/
|
145
|
+
- lib/gitlab/triage/expand_condition.rb
|
146
|
+
- lib/gitlab/triage/expand_condition/sequence.rb
|
147
|
+
- lib/gitlab/triage/expand_condition/sequence/expansion.rb
|
148
|
+
- lib/gitlab/triage/filters/assignee_member_conditions_filter.rb
|
149
|
+
- lib/gitlab/triage/filters/author_member_conditions_filter.rb
|
150
|
+
- lib/gitlab/triage/filters/base_conditions_filter.rb
|
151
|
+
- lib/gitlab/triage/filters/date_conditions_filter.rb
|
152
|
+
- lib/gitlab/triage/filters/forbidden_labels_conditions_filter.rb
|
153
|
+
- lib/gitlab/triage/filters/member_conditions_filter.rb
|
154
|
+
- lib/gitlab/triage/filters/name_conditions_filter.rb
|
155
|
+
- lib/gitlab/triage/filters/no_additional_labels_conditions_filter.rb
|
156
|
+
- lib/gitlab/triage/filters/ruby_conditions_filter.rb
|
157
|
+
- lib/gitlab/triage/filters/votes_conditions_filter.rb
|
140
158
|
- lib/gitlab/triage/network.rb
|
141
159
|
- lib/gitlab/triage/network_adapters/base_adapter.rb
|
142
160
|
- lib/gitlab/triage/network_adapters/httparty_adapter.rb
|
143
161
|
- lib/gitlab/triage/network_adapters/test_adapter.rb
|
162
|
+
- lib/gitlab/triage/resource/base.rb
|
163
|
+
- lib/gitlab/triage/resource/context.rb
|
164
|
+
- lib/gitlab/triage/resource/milestone.rb
|
144
165
|
- lib/gitlab/triage/retryable.rb
|
145
166
|
- lib/gitlab/triage/ui.rb
|
146
167
|
- lib/gitlab/triage/url_builders/url_builder.rb
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module Gitlab
|
2
|
-
module Triage
|
3
|
-
module FilterBuilders
|
4
|
-
class BaseFilterBuilder
|
5
|
-
attr_reader :filter_name, :filter_contents
|
6
|
-
|
7
|
-
def initialize(filter_name, filter_contents)
|
8
|
-
@filter_name = filter_name
|
9
|
-
@filter_contents = filter_contents
|
10
|
-
end
|
11
|
-
|
12
|
-
def build_filter
|
13
|
-
"&#{filter_name}=#{filter_content}"
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require_relative 'base_filter_builder'
|
2
|
-
|
3
|
-
module Gitlab
|
4
|
-
module Triage
|
5
|
-
module FilterBuilders
|
6
|
-
class MultiFilterBuilder < BaseFilterBuilder
|
7
|
-
attr_reader :separator
|
8
|
-
|
9
|
-
def initialize(filter_name, filter_contents, separator)
|
10
|
-
@separator = separator
|
11
|
-
super(filter_name, filter_contents)
|
12
|
-
end
|
13
|
-
|
14
|
-
def filter_content
|
15
|
-
filter_contents.join(separator)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|