gitlab-triage 0.14.1 → 0.15.0

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