gitlab-triage 1.53.1 → 1.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +4 -3
  4. data/README.md +173 -5
  5. data/lib/gitlab/triage/action/base.rb +62 -0
  6. data/lib/gitlab/triage/action/comment.rb +1 -9
  7. data/lib/gitlab/triage/action/comment_on_summary.rb +0 -6
  8. data/lib/gitlab/triage/action/issue.rb +36 -3
  9. data/lib/gitlab/triage/action/summarize.rb +26 -1
  10. data/lib/gitlab/triage/action/update.rb +454 -0
  11. data/lib/gitlab/triage/action/work_item.rb +0 -27
  12. data/lib/gitlab/triage/action.rb +18 -2
  13. data/lib/gitlab/triage/command_builders/cc_command_builder.rb +0 -4
  14. data/lib/gitlab/triage/engine.rb +13 -4
  15. data/lib/gitlab/triage/entity_builders/issue_builder.rb +8 -0
  16. data/lib/gitlab/triage/entity_builders/issue_fields.rb +65 -0
  17. data/lib/gitlab/triage/entity_builders/summary_builder.rb +8 -0
  18. data/lib/gitlab/triage/graphql_network.rb +22 -5
  19. data/lib/gitlab/triage/graphql_queries/query_builder.rb +46 -3
  20. data/lib/gitlab/triage/network.rb +4 -0
  21. data/lib/gitlab/triage/network_adapters/httparty_adapter.rb +24 -0
  22. data/lib/gitlab/triage/network_adapters/test_adapter.rb +8 -0
  23. data/lib/gitlab/triage/policies/base_policy.rb +10 -2
  24. data/lib/gitlab/triage/policies/rule_policy.rb +18 -8
  25. data/lib/gitlab/triage/policies/summary_policy.rb +9 -0
  26. data/lib/gitlab/triage/rest_api_network.rb +22 -0
  27. data/lib/gitlab/triage/validators/policy_validator.rb +1 -3
  28. data/lib/gitlab/triage/version.rb +1 -1
  29. metadata +4 -6
  30. data/lib/gitlab/triage/command_builders/label_command_builder.rb +0 -42
  31. data/lib/gitlab/triage/command_builders/move_command_builder.rb +0 -21
  32. data/lib/gitlab/triage/command_builders/remove_label_command_builder.rb +0 -17
  33. data/lib/gitlab/triage/command_builders/status_command_builder.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0b9314a216bceaf2701553359d22450e602b1d1964c7cf44b5fad5b2f932395
4
- data.tar.gz: 841fbddcf617cb4e14ef9679e8a56887bc5f6ddb3741232f9e79219589b5ae8c
3
+ metadata.gz: 4c82752f4e75557f79131d794be458ada5ea59f6c6e128a9e9a1f85d9228b04c
4
+ data.tar.gz: e7b98029e513b4990e61a546b0d60d347468857a5a713ad571fec55df7015302
5
5
  SHA512:
6
- metadata.gz: f84a9f71e56ea5a9de1c329161eb0858dc970694e0b319200ba951c85ede770a3d24053ce93f68f5fe77119e2243ba696a27c539670b2fc79f13f7ff7025ea66
7
- data.tar.gz: b44869dfd04f5419fbe7e418fec0e700e52366ae20fa6f7190d8c27fda4c95c7d2946c7716cd82db675f023eaf2b475b04b8b212f6725b5c760520f06b244f72
6
+ metadata.gz: 1799682dbf8208cf4bfa5609d3e5fffc99726f2f5a7e8425c52a6a900bc525cf4fb540243ddc2c514a7e0110f55f5fdf008019281fe4afb057937ee391cbd101
7
+ data.tar.gz: 76dc04fa7184e8fb766b7861879e9578b2c0adeb4177887f4fd034f40510f1b02191c10912917a9ecfd308838d11c1bd1e9714d5286fbbb8dfd037d0a259f73b
data/Gemfile CHANGED
@@ -4,3 +4,6 @@ source 'https://rubygems.org'
4
4
 
5
5
  # Specify your gem's dependencies in gitlab-rubocop.gemspec
6
6
  gemspec
7
+
8
+ # minitest 5.26.2+ requires Ruby 3.1+; pin to exact version for Ruby 3.0 CI
9
+ gem 'minitest', '5.26.1'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab-triage (1.53.1)
4
+ gitlab-triage (1.55.0)
5
5
  activesupport (>= 5.1)
6
6
  globalid (~> 1.0, >= 1.0.1)
7
7
  graphql (< 2.1.0)
@@ -58,7 +58,7 @@ GEM
58
58
  danger
59
59
  gitlab (~> 4.2, >= 4.2.0)
60
60
  diff-lcs (1.5.1)
61
- drb (2.2.1)
61
+ drb (2.2.3)
62
62
  faraday (2.9.1)
63
63
  faraday-net_http (>= 2.0, < 3.2)
64
64
  faraday-http-cache (2.5.1)
@@ -124,7 +124,7 @@ GEM
124
124
  lumberjack (1.2.10)
125
125
  method_source (1.1.0)
126
126
  mini_mime (1.1.5)
127
- minitest (5.23.1)
127
+ minitest (5.26.1)
128
128
  multi_xml (0.6.0)
129
129
  mutex_m (0.2.0)
130
130
  nap (1.1.0)
@@ -230,6 +230,7 @@ DEPENDENCIES
230
230
  gitlab-triage!
231
231
  guard-rspec (~> 4.7.3)
232
232
  lefthook (~> 1.3)
233
+ minitest (= 5.26.1)
233
234
  rake (~> 13.2)
234
235
  rspec (~> 3.8)
235
236
  webmock (~> 3.4)
data/README.md CHANGED
@@ -903,6 +903,15 @@ Used to declare an action to be carried out on a resource if **all** conditions
903
903
  Available action types:
904
904
  - [`labels` action](#labels-action)
905
905
  - [`remove_labels` action](#remove-labels-action)
906
+ - [`assignees` action](#assignees-action)
907
+ - [`reviewers` action](#reviewers-action)
908
+ - [`remove_assignees` and `remove_reviewers` actions](#remove-assignees-and-remove-reviewers-actions)
909
+ - [`milestone` action](#milestone-action)
910
+ - [`due_date` action](#due-date-action)
911
+ - [`weight` action](#weight-action)
912
+ - [`health_status` action](#health-status-action)
913
+ - [`title` action](#title-action)
914
+ - [`discussion_locked` action](#discussion-locked-action)
906
915
  - [`status` action](#status-action)
907
916
  - [`work_item_status` action](#work-item-status-action)
908
917
  - [`mention` action](#mention-action)
@@ -919,7 +928,9 @@ Available action types:
919
928
 
920
929
  Adds a number of labels to the resource.
921
930
 
922
- Accepts an array of strings. Each element is the name of a label to add.
931
+ Accepts an array of strings. Each element is the name of a label to add. An
932
+ element may interpolate to a comma-separated list, which is expanded into one
933
+ label per value.
923
934
 
924
935
  If any of the labels doesn't exist, the automation will stop immediately so
925
936
  that if a label is renamed or deleted, you'll have to explicitly update or remove
@@ -934,11 +945,23 @@ actions:
934
945
  - awaiting feedback
935
946
  ```
936
947
 
948
+ Each entry supports Ruby string interpolation. An entry that interpolates to an
949
+ empty string is dropped, so a label can be made conditional on the resource:
950
+
951
+ ```yml
952
+ actions:
953
+ labels:
954
+ - Community contribution
955
+ - "#{'1st contribution' if first_contribution?}"
956
+ ```
957
+
937
958
  ##### Remove labels action
938
959
 
939
960
  Removes a number of labels from the resource.
940
961
 
941
- Accepts an array of strings. Each element is the name of a label to remove.
962
+ Accepts an array of strings. Each element is the name of a label to remove. An
963
+ element may interpolate to a comma-separated list, which is expanded into one
964
+ label per value.
942
965
 
943
966
  If any of the labels doesn't exist, the automation will stop immediately so
944
967
  that if a label is renamed or deleted, you'll have to explicitly update or remove
@@ -953,6 +976,142 @@ actions:
953
976
  - awaiting feedback
954
977
  ```
955
978
 
979
+ ##### Assignees action
980
+
981
+ Sets the assignees on the resource. Applies to issues and merge requests.
982
+
983
+ Accepts an array of usernames (without the leading `@`). If any username does not exist, the automation stops immediately.
984
+
985
+ Setting an empty array (`[]`) removes all assignees.
986
+
987
+ Example:
988
+
989
+ ```yml
990
+ actions:
991
+ assignees:
992
+ - username1
993
+ - username2
994
+ ```
995
+
996
+ ##### Reviewers action
997
+
998
+ Sets the reviewers on the resource. Applies to merge requests only.
999
+
1000
+ Accepts an array of usernames (without the leading `@`). If any username does not exist, the automation stops immediately.
1001
+
1002
+ Setting an empty array (`[]`) removes all reviewers.
1003
+
1004
+ Example:
1005
+
1006
+ ```yml
1007
+ actions:
1008
+ reviewers:
1009
+ - username1
1010
+ ```
1011
+
1012
+ Both actions replace the whole list rather than adding to it, and both support Ruby string interpolation in each entry. An entry that interpolates to a comma-separated list is expanded, so a policy can compute the list from the resource.
1013
+
1014
+ Entries that interpolate to nothing are dropped. If every entry drops out, the action is skipped rather than clearing the list, so a conditional assignee cannot unassign by accident. Only a literal `[]` clears.
1015
+
1016
+ ##### Remove assignees and remove reviewers actions
1017
+
1018
+ Removes the named users and keeps the rest. `remove_assignees` applies to issues and merge requests, `remove_reviewers` to merge requests only.
1019
+
1020
+ Accepts an array of usernames (without the leading `@`), with the same interpolation and comma expansion as the actions above. Names that are not on the resource are ignored, and a resource with nobody assigned is left alone.
1021
+
1022
+ Example:
1023
+
1024
+ ```yml
1025
+ actions:
1026
+ remove_reviewers:
1027
+ - some-bot
1028
+ ```
1029
+
1030
+ Prefer these over computing the surviving list yourself: they read the ids straight off the resource, so they cost no extra API call and cannot clear the list by accident.
1031
+
1032
+ ##### Milestone action
1033
+
1034
+ Sets the milestone on the resource. Applies to issues and merge requests.
1035
+
1036
+ Accepts a string containing the milestone title. If the milestone does not exist, the automation stops immediately.
1037
+
1038
+ Supports Ruby string interpolation in the value.
1039
+
1040
+ Example:
1041
+
1042
+ ```yml
1043
+ actions:
1044
+ milestone: "17.0"
1045
+ ```
1046
+
1047
+ ##### Due date action
1048
+
1049
+ Sets the due date on the resource. Applies to issues only. The epics endpoint takes `due_date_fixed` and `due_date_is_fixed` instead, and merge requests have no due date.
1050
+
1051
+ Accepts a string in `YYYY-MM-DD` format. Supports Ruby string interpolation.
1052
+
1053
+ Example:
1054
+
1055
+ ```yml
1056
+ actions:
1057
+ due_date: "#{(Date.today + 7).iso8601}"
1058
+ ```
1059
+
1060
+ ##### Weight action
1061
+
1062
+ Sets the weight on an issue. Issues only.
1063
+
1064
+ Accepts an integer, or `~` (YAML null) to clear the weight.
1065
+
1066
+ Example:
1067
+
1068
+ ```yml
1069
+ actions:
1070
+ weight: 3
1071
+ ```
1072
+
1073
+ ##### Health status action
1074
+
1075
+ Sets the health status on an issue. Issues only.
1076
+
1077
+ Accepts one of `on_track`, `needs_attention`, or `at_risk`, or `~` (YAML null) to clear it.
1078
+
1079
+ This one goes through the work item GraphQL API, because the REST issue update
1080
+ endpoint does not accept a health status parameter.
1081
+
1082
+ Example:
1083
+
1084
+ ```yml
1085
+ actions:
1086
+ health_status: needs_attention
1087
+ ```
1088
+
1089
+ ##### Title action
1090
+
1091
+ Updates the title of the resource.
1092
+
1093
+ Accepts a string. Supports Ruby string interpolation.
1094
+
1095
+ Example:
1096
+
1097
+ ```yml
1098
+ actions:
1099
+ title: "#{resource[:title]} [archived]"
1100
+ ```
1101
+
1102
+ ##### Discussion locked action
1103
+
1104
+ Locks or unlocks discussions on the resource. Applies to issues and merge requests.
1105
+
1106
+ Accepts `true` or `false`.
1107
+
1108
+ Example:
1109
+
1110
+ ```yml
1111
+ actions:
1112
+ discussion_locked: true
1113
+ ```
1114
+
956
1115
  ##### Status action
957
1116
 
958
1117
  Changes the open or closed state of the resource.
@@ -999,7 +1158,7 @@ actions:
999
1158
 
1000
1159
  ##### Mention action
1001
1160
 
1002
- Mentions a number of users.
1161
+ Mentions a number of users. The usernames are appended to the comment body as `@username` references. Prior to 1.54.0 they were emitted as `/cc @username`; the mention effect is identical but the rendered text differs.
1003
1162
 
1004
1163
  Accepts an array of strings. Each element is the username of a user to mention.
1005
1164
 
@@ -1018,6 +1177,9 @@ Moves an issue (merge request is not supported yet) to the specified project.
1018
1177
 
1019
1178
  Accepts a string containing the target project path.
1020
1179
 
1180
+ When combined with other update actions, the move runs last, because it gives the
1181
+ issue a new internal ID at the destination.
1182
+
1021
1183
  Example:
1022
1184
 
1023
1185
  ```yml
@@ -1188,7 +1350,10 @@ Accepts a hash of fields.
1188
1350
  | `destination` | integer or string | The project ID or path to create the generated issue in | no | no | no | source project |
1189
1351
  | `item` | string | Template representing each triaged resource | no | yes | yes | |
1190
1352
  | `summary` | string | The description of the generated issue | no | Only `{{title}}`, `{{items}}`, `{{type}}` | yes | |
1353
+ | `labels` | array of strings | Labels to add to the generated issue. An element may interpolate to a comma-separated list, which is expanded into one label per value | no | yes | yes | |
1354
+ | `assignees` | array of strings | Usernames to assign to the generated issue. An element may interpolate to a comma-separated list, which is expanded into one assignee per value | no | yes | yes | |
1191
1355
  | `redact_confidential_resources` | boolean | Whether redact fields for confidential resources | no | no | no | true |
1356
+ | `confidential` | boolean or string | Create the issue confidential. A string is interpolated and counts as true when it evaluates to `true` | no | no | yes | false |
1192
1357
 
1193
1358
  The following placeholders are supported for `summary`:
1194
1359
 
@@ -1208,8 +1373,8 @@ The following placeholders are supported for `summary`:
1208
1373
  > - `redact_confidential_resources` defaults to `true`, so fields on
1209
1374
  > confidential resources will be converted to `(confidential)` except for
1210
1375
  > `{{web_url}}`. Setting it to `false` will reveal the confidential fields.
1211
- > This will be useful if the summary is confidential itself (not implemented
1212
- > yet), or if we're posting to another private project (not implemented yet).
1376
+ > This is useful when the summary is itself `confidential`, or if we're
1377
+ > posting to another private project (not implemented yet).
1213
1378
 
1214
1379
  Example:
1215
1380
 
@@ -1322,7 +1487,10 @@ Accepts a hash of fields.
1322
1487
  | `title` | string | The title of the generated issue | yes | yes | yes | |
1323
1488
  | `destination` | integer or string | The project ID or path to create the generated issue in | no | no | no | source project |
1324
1489
  | `description` | string | The description of the generated issue | no | yes | yes | |
1490
+ | `labels` | array of strings | Labels to add to the generated issue. An element may interpolate to a comma-separated list, which is expanded into one label per value | no | yes | yes | |
1491
+ | `assignees` | array of strings | Usernames to assign to the generated issue. An element may interpolate to a comma-separated list, which is expanded into one assignee per value | no | yes | yes | |
1325
1492
  | `redact_confidential_resources` | boolean | Whether redact fields for confidential resources | no | no | no | true |
1493
+ | `confidential` | boolean or string | Create the issue confidential. A string is interpolated and counts as true when it evaluates to `true` | no | no | yes | false |
1326
1494
 
1327
1495
  The placeholders available in `title` and `destination` are the properties of the resource being used to generate the issue.
1328
1496
 
@@ -1,15 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cgi'
4
+
5
+ require_relative '../url_builders/url_builder'
6
+ require_relative '../resource/label'
7
+
3
8
  module Gitlab
4
9
  module Triage
5
10
  module Action
6
11
  class Base
12
+ AssigneeDoesntExistError = Class.new(StandardError)
13
+ MilestoneDoesntExistError = Class.new(StandardError)
14
+
7
15
  attr_reader :policy, :network
8
16
 
9
17
  def initialize(**args)
10
18
  @policy = args[:policy]
11
19
  @network = args[:network]
12
20
  end
21
+
22
+ # Action.process calls this on every active action before any of them
23
+ # writes. Actions with nothing to look up have nothing to check.
24
+ def verify!; end
25
+
26
+ private
27
+
28
+ def ensure_labels_exist!(labels, source_id, source_id_key: :project_id)
29
+ labels.each do |label|
30
+ label_opts = { source_id_key => source_id, name: label }
31
+ next if Resource::Label.new(label_opts, network: network).exist?
32
+
33
+ raise Resource::Label::LabelDoesntExistError, "Label `#{label}` doesn't exist!"
34
+ end
35
+ end
36
+
37
+ def resolve_user_ids(usernames, error_class = AssigneeDoesntExistError)
38
+ Array(usernames).map do |username|
39
+ id = resolve_user_id(username)
40
+ raise error_class, "User `#{username}` doesn't exist!" unless id
41
+
42
+ id
43
+ end
44
+ end
45
+ alias_method :resolve_assignee_ids, :resolve_user_ids
46
+
47
+ def resolve_user_id(username)
48
+ users = network.query_api_cached(users_url(username))
49
+ users.first&.dig(:id)
50
+ end
51
+
52
+ def users_url(username)
53
+ opts = network.options
54
+ "#{opts.host_url}/api/#{opts.api_version}/users?username=#{CGI.escape(username)}"
55
+ end
56
+
57
+ # include_ancestors matches the /milestone quick action, which also resolves
58
+ # milestones defined on a parent group.
59
+ def milestones_url(source_id, title, source: 'projects')
60
+ UrlBuilders::UrlBuilder.new(
61
+ network_options: network.options,
62
+ source: source,
63
+ source_id: source_id,
64
+ resource_type: 'milestones',
65
+ params: { title: title, include_ancestors: true }
66
+ ).build
67
+ end
68
+
69
+ def resolve_milestone_id(source_id, title, source: 'projects')
70
+ milestone = network.query_api_cached(milestones_url(source_id, title, source: source)).first
71
+ raise MilestoneDoesntExistError, "Milestone `#{title}` doesn't exist!" unless milestone
72
+
73
+ milestone[:id]
74
+ end
13
75
  end
14
76
  end
15
77
  end
@@ -3,11 +3,7 @@
3
3
  require_relative 'base'
4
4
  require_relative '../command_builders/text_content_builder'
5
5
  require_relative '../command_builders/comment_command_builder'
6
- require_relative '../command_builders/label_command_builder'
7
- require_relative '../command_builders/remove_label_command_builder'
8
6
  require_relative '../command_builders/cc_command_builder'
9
- require_relative '../command_builders/status_command_builder'
10
- require_relative '../command_builders/move_command_builder'
11
7
 
12
8
  module Gitlab
13
9
  module Triage
@@ -51,11 +47,7 @@ module Gitlab
51
47
  network: network,
52
48
  redact_confidentials: policy.actions.fetch(:redact_confidential_resources, true)
53
49
  ).build_command,
54
- CommandBuilders::LabelCommandBuilder.new(policy.actions[:labels], resource: resource, network: network).build_command,
55
- CommandBuilders::RemoveLabelCommandBuilder.new(policy.actions[:remove_labels], resource: resource, network: network).build_command,
56
- CommandBuilders::CcCommandBuilder.new(policy.actions[:mention]).build_command,
57
- CommandBuilders::MoveCommandBuilder.new(policy.actions[:move]).build_command,
58
- CommandBuilders::StatusCommandBuilder.new(policy.actions[:status]).build_command
50
+ CommandBuilders::CcCommandBuilder.new(policy.actions[:mention]).build_command
59
51
  ]
60
52
  ).build_command
61
53
  end
@@ -2,12 +2,6 @@
2
2
 
3
3
  require_relative 'base'
4
4
  require_relative '../command_builders/text_content_builder'
5
- require_relative '../command_builders/comment_command_builder'
6
- require_relative '../command_builders/label_command_builder'
7
- require_relative '../command_builders/remove_label_command_builder'
8
- require_relative '../command_builders/cc_command_builder'
9
- require_relative '../command_builders/status_command_builder'
10
- require_relative '../command_builders/move_command_builder'
11
5
 
12
6
  module Gitlab
13
7
  module Triage
@@ -19,11 +19,34 @@ module Gitlab
19
19
  puts ">>>"
20
20
  puts "* Project: #{issue.destination || resource[policy.source_id_sym]}"
21
21
  puts "* Title: #{issue.title}"
22
+ puts "* Labels: #{issue.labels.join(', ')}" if issue.labels.any?
23
+ puts "* Assignees: #{issue.assignee_usernames.join(', ')}" if issue.assignee_usernames&.any?
24
+ puts "* Due date: #{issue.due_date}" if issue.due_date
25
+ puts "* Milestone: #{issue.milestone_title}" if issue.milestone_title
26
+ puts "* Confidential: true" if issue.confidential?
22
27
  puts "* Description: #{issue.description}"
23
28
  puts ">>>"
24
29
  end
25
30
  end
26
31
 
32
+ # Resolving the assignees and milestone here too keeps a rule naming an
33
+ # unknown one from failing after Comment has already posted.
34
+ def verify!
35
+ policy.resources.each do |resource|
36
+ issue = policy.build_issue(resource)
37
+ next unless issue.valid?
38
+
39
+ destination_id = issue.destination || resource[policy.source_id_sym]
40
+
41
+ ensure_labels_exist!(issue.labels, destination_id)
42
+ resolve_assignee_ids(issue.assignee_usernames) if issue.assignee_usernames.any?
43
+ resolve_milestone_id(destination_id, issue.milestone_title) if issue.milestone_title
44
+ # Evaluated here too, so a bad confidential: template raises before
45
+ # any action writes rather than mid-way through post_issue_body.
46
+ issue.confidential?
47
+ end
48
+ end
49
+
27
50
  def act
28
51
  policy.resources.each do |resource|
29
52
  issue = policy.build_issue(resource)
@@ -35,7 +58,7 @@ module Gitlab
35
58
  private
36
59
 
37
60
  def perform(resource, issue)
38
- network.post_api(build_post_url(resource, issue), post_issue_body(issue))
61
+ network.post_api(build_post_url(resource, issue), post_issue_body(resource, issue))
39
62
  end
40
63
 
41
64
  def build_post_url(resource, issue)
@@ -52,11 +75,21 @@ module Gitlab
52
75
  post_url
53
76
  end
54
77
 
55
- def post_issue_body(issue)
56
- {
78
+ def post_issue_body(resource, issue)
79
+ destination_id = issue.destination || resource[policy.source_id_sym]
80
+
81
+ body = {
57
82
  title: issue.title,
58
83
  description: issue.description
59
84
  }
85
+
86
+ body[:labels] = issue.labels.join(',') if issue.labels.any?
87
+ body[:assignee_ids] = resolve_assignee_ids(issue.assignee_usernames) if issue.assignee_usernames.any?
88
+ body[:due_date] = issue.due_date if issue.due_date
89
+ body[:milestone_id] = resolve_milestone_id(destination_id, issue.milestone_title) if issue.milestone_title
90
+ body[:confidential] = true if issue.confidential?
91
+
92
+ body
60
93
  end
61
94
  end
62
95
  end
@@ -27,11 +27,28 @@ module Gitlab
27
27
  puts "The following issue would be created in project `#{destination}` for the rule **#{policy.name}**:\n\n"
28
28
  puts ">>>"
29
29
  puts "* Title: #{issue.title}"
30
+ puts "* Labels: #{issue.labels.join(', ')}" if issue.labels.any?
31
+ puts "* Assignees: #{issue.assignee_usernames.join(', ')}" if issue.assignee_usernames&.any?
32
+ puts "* Due date: #{issue.due_date}" if issue.due_date
33
+ puts "* Milestone: #{issue.milestone_title}" if issue.milestone_title
34
+ puts "* Confidential: true" if issue.confidential?
30
35
  puts "* Description: #{issue.description}"
31
36
  puts ">>>"
32
37
  end
33
38
  end
34
39
 
40
+ def verify!
41
+ return unless issue.valid?
42
+ return if group_summary_without_destination?
43
+
44
+ ensure_labels_exist!(issue.labels, destination)
45
+ resolve_assignee_ids(issue.assignee_usernames) if issue.assignee_usernames.any?
46
+ resolve_milestone_id(destination, issue.milestone_title) if issue.milestone_title
47
+ # Evaluated here too, so a bad confidential: template raises before
48
+ # any action writes rather than mid-way through post_issue_body.
49
+ issue.confidential?
50
+ end
51
+
35
52
  def act
36
53
  perform if issue.valid?
37
54
  end
@@ -75,10 +92,18 @@ module Gitlab
75
92
  end
76
93
 
77
94
  def post_issue_body
78
- {
95
+ body = {
79
96
  title: issue.title,
80
97
  description: issue.description
81
98
  }
99
+
100
+ body[:labels] = issue.labels.join(',') if issue.labels.any?
101
+ body[:assignee_ids] = resolve_assignee_ids(issue.assignee_usernames) if issue.assignee_usernames.any?
102
+ body[:due_date] = issue.due_date if issue.due_date
103
+ body[:milestone_id] = resolve_milestone_id(destination, issue.milestone_title) if issue.milestone_title
104
+ body[:confidential] = true if issue.confidential?
105
+
106
+ body
82
107
  end
83
108
  end
84
109
  end