plan_my_stuff 0.2.0 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +569 -38
- data/app/controllers/plan_my_stuff/comments_controller.rb +5 -1
- data/app/controllers/plan_my_stuff/issues/approvals_controller.rb +102 -0
- data/app/controllers/plan_my_stuff/issues/closures_controller.rb +37 -0
- data/app/controllers/plan_my_stuff/issues/links_controller.rb +127 -0
- data/app/controllers/plan_my_stuff/issues/takes_controller.rb +88 -0
- data/app/controllers/plan_my_stuff/issues/viewers_controller.rb +48 -0
- data/app/controllers/plan_my_stuff/issues/waitings_controller.rb +47 -0
- data/app/controllers/plan_my_stuff/issues_controller.rb +22 -55
- data/app/controllers/plan_my_stuff/labels_controller.rb +4 -4
- data/app/controllers/plan_my_stuff/project_items/assignments_controller.rb +75 -0
- data/app/controllers/plan_my_stuff/project_items/statuses_controller.rb +40 -0
- data/app/controllers/plan_my_stuff/project_items_controller.rb +0 -75
- data/app/controllers/plan_my_stuff/projects_controller.rb +65 -1
- data/app/controllers/plan_my_stuff/testing_project_items/results_controller.rb +54 -0
- data/app/controllers/plan_my_stuff/testing_project_items_controller.rb +39 -0
- data/app/controllers/plan_my_stuff/testing_projects_controller.rb +93 -0
- data/app/controllers/plan_my_stuff/webhooks/aws_controller.rb +148 -0
- data/app/controllers/plan_my_stuff/webhooks/github_controller.rb +284 -0
- data/app/jobs/plan_my_stuff/application_job.rb +9 -0
- data/app/jobs/plan_my_stuff/reminders_sweep_job.rb +81 -0
- data/app/views/plan_my_stuff/comments/partials/_form.html.erb +7 -0
- data/app/views/plan_my_stuff/issues/partials/_approvals.html.erb +87 -0
- data/app/views/plan_my_stuff/issues/partials/_labels.html.erb +2 -2
- data/app/views/plan_my_stuff/issues/partials/_links.html.erb +70 -0
- data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +2 -2
- data/app/views/plan_my_stuff/issues/show.html.erb +46 -3
- data/app/views/plan_my_stuff/projects/edit.html.erb +7 -0
- data/app/views/plan_my_stuff/projects/index.html.erb +17 -1
- data/app/views/plan_my_stuff/projects/new.html.erb +7 -0
- data/app/views/plan_my_stuff/projects/partials/_form.html.erb +29 -0
- data/app/views/plan_my_stuff/projects/show.html.erb +11 -4
- data/app/views/plan_my_stuff/testing_project_items/new.html.erb +12 -0
- data/app/views/plan_my_stuff/testing_project_items/results/new.html.erb +22 -0
- data/app/views/plan_my_stuff/testing_projects/edit.html.erb +7 -0
- data/app/views/plan_my_stuff/testing_projects/new.html.erb +7 -0
- data/app/views/plan_my_stuff/testing_projects/partials/_form.html.erb +39 -0
- data/app/views/plan_my_stuff/testing_projects/partials/_item.html.erb +51 -0
- data/app/views/plan_my_stuff/testing_projects/partials/items/_form.html.erb +35 -0
- data/app/views/plan_my_stuff/testing_projects/show.html.erb +65 -0
- data/config/routes.rb +38 -15
- data/lib/generators/plan_my_stuff/install/templates/initializer.rb +138 -5
- data/lib/plan_my_stuff/application_record.rb +144 -0
- data/lib/plan_my_stuff/approval.rb +80 -0
- data/lib/plan_my_stuff/archive/sweep.rb +85 -0
- data/lib/plan_my_stuff/archive.rb +14 -0
- data/lib/plan_my_stuff/aws_sns_simulator.rb +110 -0
- data/lib/plan_my_stuff/base_metadata.rb +0 -11
- data/lib/plan_my_stuff/base_project.rb +661 -0
- data/lib/plan_my_stuff/base_project_item.rb +562 -0
- data/lib/plan_my_stuff/base_project_metadata.rb +16 -0
- data/lib/plan_my_stuff/cache.rb +197 -0
- data/lib/plan_my_stuff/client.rb +7 -0
- data/lib/plan_my_stuff/comment.rb +174 -54
- data/lib/plan_my_stuff/configuration.rb +254 -8
- data/lib/plan_my_stuff/custom_fields.rb +31 -17
- data/lib/plan_my_stuff/engine.rb +0 -4
- data/lib/plan_my_stuff/errors.rb +49 -0
- data/lib/plan_my_stuff/graphql/queries.rb +392 -0
- data/lib/plan_my_stuff/issue.rb +1477 -174
- data/lib/plan_my_stuff/issue_metadata.rb +122 -0
- data/lib/plan_my_stuff/label.rb +82 -11
- data/lib/plan_my_stuff/link.rb +144 -0
- data/lib/plan_my_stuff/notifications.rb +142 -0
- data/lib/plan_my_stuff/pipeline/issue_linker.rb +62 -0
- data/lib/plan_my_stuff/pipeline/status.rb +44 -0
- data/lib/plan_my_stuff/pipeline.rb +293 -0
- data/lib/plan_my_stuff/project.rb +62 -468
- data/lib/plan_my_stuff/project_item.rb +3 -417
- data/lib/plan_my_stuff/project_item_metadata.rb +55 -0
- data/lib/plan_my_stuff/project_metadata.rb +47 -0
- data/lib/plan_my_stuff/reminders/closer.rb +70 -0
- data/lib/plan_my_stuff/reminders/fire.rb +129 -0
- data/lib/plan_my_stuff/reminders/sweep.rb +54 -0
- data/lib/plan_my_stuff/reminders.rb +16 -0
- data/lib/plan_my_stuff/test_helpers.rb +260 -15
- data/lib/plan_my_stuff/testing_project.rb +291 -0
- data/lib/plan_my_stuff/testing_project_item.rb +184 -0
- data/lib/plan_my_stuff/testing_project_metadata.rb +94 -0
- data/lib/plan_my_stuff/user_resolver.rb +8 -3
- data/lib/plan_my_stuff/version.rb +1 -1
- data/lib/plan_my_stuff/webhook_replayer.rb +280 -0
- data/lib/plan_my_stuff.rb +16 -0
- data/lib/tasks/plan_my_stuff.rake +163 -0
- metadata +54 -2
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
4
|
class Configuration
|
|
5
|
+
# Default controller for each controllable route group. Consuming apps
|
|
6
|
+
# override by assigning values into +controllers+; lookups go through
|
|
7
|
+
# +controller_for+ which falls back to this table.
|
|
8
|
+
#
|
|
9
|
+
# @return [Hash{Symbol => String}]
|
|
10
|
+
#
|
|
11
|
+
DEFAULT_CONTROLLERS = {
|
|
12
|
+
'issues': 'plan_my_stuff/issues',
|
|
13
|
+
'comments': 'plan_my_stuff/comments',
|
|
14
|
+
'labels': 'plan_my_stuff/labels',
|
|
15
|
+
'projects': 'plan_my_stuff/projects',
|
|
16
|
+
'project_items': 'plan_my_stuff/project_items',
|
|
17
|
+
'testing_projects': 'plan_my_stuff/testing_projects',
|
|
18
|
+
'testing_project_items': 'plan_my_stuff/testing_project_items',
|
|
19
|
+
'issues/closures': 'plan_my_stuff/issues/closures',
|
|
20
|
+
'issues/viewers': 'plan_my_stuff/issues/viewers',
|
|
21
|
+
'issues/takes': 'plan_my_stuff/issues/takes',
|
|
22
|
+
'issues/waitings': 'plan_my_stuff/issues/waitings',
|
|
23
|
+
'issues/links': 'plan_my_stuff/issues/links',
|
|
24
|
+
'issues/approvals': 'plan_my_stuff/issues/approvals',
|
|
25
|
+
'project_items/statuses': 'plan_my_stuff/project_items/statuses',
|
|
26
|
+
'project_items/assignments': 'plan_my_stuff/project_items/assignments',
|
|
27
|
+
'testing_project_items/results': 'plan_my_stuff/testing_project_items/results',
|
|
28
|
+
'webhooks/github': 'plan_my_stuff/webhooks/github',
|
|
29
|
+
'webhooks/aws': 'plan_my_stuff/webhooks/aws',
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
5
32
|
# @return [String] GitHub PAT with repo and project scopes. Required.
|
|
6
33
|
attr_accessor :access_token
|
|
7
34
|
|
|
@@ -14,6 +41,12 @@ module PlanMyStuff
|
|
|
14
41
|
# @return [Integer, nil] default GitHub Projects V2 number for add_to_project calls
|
|
15
42
|
attr_accessor :default_project_number
|
|
16
43
|
|
|
44
|
+
# @return [Integer, nil] GitHub Projects V2 number of the template project to clone
|
|
45
|
+
# when creating new TestingProjects. When set, TestingProject.create! copies the
|
|
46
|
+
# template (preserving its fields and board layout) instead of bootstrapping fields
|
|
47
|
+
# from scratch. Leave nil to use the default bootstrap-fields path.
|
|
48
|
+
attr_accessor :testing_template_project_number
|
|
49
|
+
|
|
17
50
|
# @return [String] consuming app's user model class name, constantized for lookups
|
|
18
51
|
attr_accessor :user_class
|
|
19
52
|
|
|
@@ -61,14 +94,14 @@ module PlanMyStuff
|
|
|
61
94
|
#
|
|
62
95
|
attr_accessor :job_classes
|
|
63
96
|
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
#
|
|
71
|
-
attr_accessor :
|
|
97
|
+
# Fallback actor for notification events when a caller does not pass +user:+.
|
|
98
|
+
# Set to a proc/lambda that returns the current request user.
|
|
99
|
+
#
|
|
100
|
+
# Example: +config.current_user = -> { Current.user }+
|
|
101
|
+
#
|
|
102
|
+
# @return [Proc, nil]
|
|
103
|
+
#
|
|
104
|
+
attr_accessor :current_user
|
|
72
105
|
|
|
73
106
|
# Shared field definitions stored in issue/comment metadata.
|
|
74
107
|
# Keys are field names, values are hashes with :type and :required.
|
|
@@ -92,12 +125,181 @@ module PlanMyStuff
|
|
|
92
125
|
#
|
|
93
126
|
attr_accessor :comment_custom_fields
|
|
94
127
|
|
|
128
|
+
# Project-only field definitions, deep-merged on top of shared custom_fields.
|
|
129
|
+
# Context-specific config wins on key conflicts.
|
|
130
|
+
#
|
|
131
|
+
# @return [Hash{Symbol => Hash}]
|
|
132
|
+
#
|
|
133
|
+
attr_accessor :project_custom_fields
|
|
134
|
+
|
|
135
|
+
# Testing-project-only field definitions, deep-merged on top of shared custom_fields.
|
|
136
|
+
# Context-specific config wins on key conflicts.
|
|
137
|
+
#
|
|
138
|
+
# @return [Hash{Symbol => Hash}]
|
|
139
|
+
#
|
|
140
|
+
attr_accessor :testing_custom_fields
|
|
141
|
+
|
|
95
142
|
# @return [String, nil] URL prefix for building user-facing ticket URLs in the consuming app
|
|
96
143
|
attr_accessor :issues_url_prefix
|
|
97
144
|
|
|
98
145
|
# @return [String, nil] name of the consuming app, stored in metadata (e.g. "Atlas")
|
|
99
146
|
attr_accessor :app_name
|
|
100
147
|
|
|
148
|
+
# @return [Boolean] whether the release pipeline feature is enabled
|
|
149
|
+
attr_accessor :pipeline_enabled
|
|
150
|
+
|
|
151
|
+
# @return [Integer, nil] GitHub Projects V2 number for the pipeline board (falls back to default_project_number)
|
|
152
|
+
attr_accessor :pipeline_project_number
|
|
153
|
+
|
|
154
|
+
# @return [String, nil] HMAC secret for GitHub webhook signature verification (required when webhooks mounted)
|
|
155
|
+
attr_accessor :webhook_secret
|
|
156
|
+
|
|
157
|
+
# @return [String, nil] expected SNS topic ARN for AWS webhook validation
|
|
158
|
+
attr_accessor :sns_topic_arn
|
|
159
|
+
|
|
160
|
+
# @return [String, nil] suffix matched against ECS event resource ARNs (e.g. 'rawr-production-2-web')
|
|
161
|
+
attr_accessor :aws_service_identifier
|
|
162
|
+
|
|
163
|
+
# @return [String, nil] commit hash of the deploying build, prefix-matched against issue metadata commit_sha
|
|
164
|
+
attr_accessor :production_commit_sha
|
|
165
|
+
|
|
166
|
+
# Canonical status name to display alias map. Allows consuming apps to rename
|
|
167
|
+
# pipeline statuses (e.g. "Submitted" to "Triaged").
|
|
168
|
+
#
|
|
169
|
+
# @return [Hash{String => String}]
|
|
170
|
+
#
|
|
171
|
+
attr_accessor :pipeline_statuses
|
|
172
|
+
|
|
173
|
+
# @return [String] branch name that PRs merge into for "Ready for release" transition
|
|
174
|
+
attr_accessor :main_branch
|
|
175
|
+
|
|
176
|
+
# @return [String] branch name that triggers deployment when a PR merges
|
|
177
|
+
attr_accessor :production_branch
|
|
178
|
+
|
|
179
|
+
# Hash mapping consuming-app user id to GitHub login. Used by the
|
|
180
|
+
# "Take" UI flow to assign the GitHub user when a support user claims
|
|
181
|
+
# an issue. Keys are whatever +config.user_id_method+ returns on the
|
|
182
|
+
# current user.
|
|
183
|
+
#
|
|
184
|
+
# Example: +config.github_login_for = { 1 => 'some_username', 2 => 'octocat' }+
|
|
185
|
+
#
|
|
186
|
+
# @return [Hash{Object => String}]
|
|
187
|
+
#
|
|
188
|
+
attr_accessor :github_login_for
|
|
189
|
+
|
|
190
|
+
# Per-group route mounting toggles. Keys: :webhooks, :issues, :projects.
|
|
191
|
+
# Set a key to false to skip mounting that route group.
|
|
192
|
+
#
|
|
193
|
+
# @return [Hash{Symbol => Boolean}]
|
|
194
|
+
#
|
|
195
|
+
attr_accessor :mount_groups
|
|
196
|
+
|
|
197
|
+
# Per-route controller overrides. Keys are the controllable route symbols
|
|
198
|
+
# defined in +DEFAULT_CONTROLLERS+; values are fully-qualified controller
|
|
199
|
+
# paths (e.g. +'my_app/issues'+). Unset keys fall back to the gem default.
|
|
200
|
+
# Consuming apps typically subclass the gem controller to add before_actions
|
|
201
|
+
# or tweak responses, then swap their subclass in here.
|
|
202
|
+
#
|
|
203
|
+
# @return [Hash{Symbol => String}]
|
|
204
|
+
#
|
|
205
|
+
attr_accessor :controllers
|
|
206
|
+
|
|
207
|
+
# Whether to use Rails.cache for ETag-based HTTP caching of GitHub reads.
|
|
208
|
+
# Defaults to true; set to false to bypass the cache entirely.
|
|
209
|
+
#
|
|
210
|
+
# @return [Boolean]
|
|
211
|
+
#
|
|
212
|
+
attr_accessor :cache_enabled
|
|
213
|
+
|
|
214
|
+
# Opaque app-supplied version string embedded in every PMS cache key.
|
|
215
|
+
# Bumping this string invalidates all cached entries from the consuming
|
|
216
|
+
# app's side (e.g. after a deploy or schema change). Defaults to nil.
|
|
217
|
+
#
|
|
218
|
+
# @return [String, nil]
|
|
219
|
+
#
|
|
220
|
+
attr_accessor :cache_version
|
|
221
|
+
|
|
222
|
+
# Whether the reminders sweep performs any work. Defaults to +true+.
|
|
223
|
+
# Set to +false+ in apps that don't want follow-up reminders or inactivity
|
|
224
|
+
# auto-close.
|
|
225
|
+
#
|
|
226
|
+
# @return [Boolean]
|
|
227
|
+
#
|
|
228
|
+
attr_accessor :reminders_enabled
|
|
229
|
+
|
|
230
|
+
# Days-since-waiting at which reminder events fire. Per-issue override
|
|
231
|
+
# via +metadata.reminder_days+.
|
|
232
|
+
#
|
|
233
|
+
# @return [Array<Integer>]
|
|
234
|
+
#
|
|
235
|
+
attr_accessor :reminder_days
|
|
236
|
+
|
|
237
|
+
# Days of inactivity after which the sweep auto-closes a waiting issue.
|
|
238
|
+
#
|
|
239
|
+
# @return [Integer]
|
|
240
|
+
#
|
|
241
|
+
attr_accessor :inactivity_close_days
|
|
242
|
+
|
|
243
|
+
# Label name used to flag issues waiting on an end-user reply.
|
|
244
|
+
#
|
|
245
|
+
# @return [String]
|
|
246
|
+
#
|
|
247
|
+
attr_accessor :waiting_on_user_label
|
|
248
|
+
|
|
249
|
+
# Label name used to flag issues waiting on pending approvals.
|
|
250
|
+
#
|
|
251
|
+
# @return [String]
|
|
252
|
+
#
|
|
253
|
+
attr_accessor :waiting_on_approval_label
|
|
254
|
+
|
|
255
|
+
# Label name applied to issues auto-closed by the inactivity sweep.
|
|
256
|
+
# Removed when an issue is auto-reopened via a user reply.
|
|
257
|
+
#
|
|
258
|
+
# @return [String]
|
|
259
|
+
#
|
|
260
|
+
attr_accessor :user_inactive_label
|
|
261
|
+
|
|
262
|
+
# Whether the archive sweep performs any work. Defaults to +true+.
|
|
263
|
+
# Set to +false+ in apps that don't want auto-archiving of aged-closed
|
|
264
|
+
# issues.
|
|
265
|
+
#
|
|
266
|
+
# @return [Boolean]
|
|
267
|
+
#
|
|
268
|
+
attr_accessor :archiving_enabled
|
|
269
|
+
|
|
270
|
+
# Days after +closed_at+ at which a non-inactive-closed issue becomes an
|
|
271
|
+
# archive candidate.
|
|
272
|
+
#
|
|
273
|
+
# @return [Integer]
|
|
274
|
+
#
|
|
275
|
+
attr_accessor :archive_closed_after_days
|
|
276
|
+
|
|
277
|
+
# Label name added to archived issues. Also used by the sweep as a
|
|
278
|
+
# skip marker to avoid re-archiving the same issue.
|
|
279
|
+
#
|
|
280
|
+
# @return [String]
|
|
281
|
+
#
|
|
282
|
+
attr_accessor :archived_label
|
|
283
|
+
|
|
284
|
+
# Whether to process incoming AWS webhook events. Defaults to +Rails.env.production?+.
|
|
285
|
+
#
|
|
286
|
+
# @return [Boolean]
|
|
287
|
+
#
|
|
288
|
+
attr_accessor :process_aws_webhooks
|
|
289
|
+
|
|
290
|
+
# Class instantiated per request for SNS signature verification.
|
|
291
|
+
# Must respond to +authenticate!(raw_body)+.
|
|
292
|
+
#
|
|
293
|
+
# @return [Class]
|
|
294
|
+
#
|
|
295
|
+
attr_accessor :sns_verifier_class
|
|
296
|
+
|
|
297
|
+
# Exception class rescued during SNS signature verification.
|
|
298
|
+
#
|
|
299
|
+
# @return [Class]
|
|
300
|
+
#
|
|
301
|
+
attr_accessor :sns_verifier_error
|
|
302
|
+
|
|
101
303
|
# Named repo configs. Set via config.repos[:element] = 'BrandsInsurance/Element'.
|
|
102
304
|
#
|
|
103
305
|
# @return [Hash{Symbol => String}]
|
|
@@ -117,6 +319,31 @@ module PlanMyStuff
|
|
|
117
319
|
@custom_fields = {}
|
|
118
320
|
@issue_custom_fields = {}
|
|
119
321
|
@comment_custom_fields = {}
|
|
322
|
+
@project_custom_fields = {}
|
|
323
|
+
@testing_custom_fields = {}
|
|
324
|
+
@pipeline_enabled = true
|
|
325
|
+
@pipeline_statuses = {}
|
|
326
|
+
@main_branch = 'main'
|
|
327
|
+
@production_branch = 'production'
|
|
328
|
+
@mount_groups = { webhooks: true, issues: true, projects: true }
|
|
329
|
+
@controllers = {}
|
|
330
|
+
@cache_enabled = true
|
|
331
|
+
@github_login_for = {}
|
|
332
|
+
@reminders_enabled = true
|
|
333
|
+
@reminder_days = [1, 3, 7, 10, 14, 18].freeze
|
|
334
|
+
@inactivity_close_days = 30
|
|
335
|
+
@waiting_on_user_label = 'waiting-on-user'
|
|
336
|
+
@waiting_on_approval_label = 'waiting-on-approval'
|
|
337
|
+
@user_inactive_label = 'user-inactive'
|
|
338
|
+
@archiving_enabled = true
|
|
339
|
+
@archive_closed_after_days = 90
|
|
340
|
+
@archived_label = 'archived'
|
|
341
|
+
@process_aws_webhooks = Rails.env.production?
|
|
342
|
+
@sns_verifier_class = ::Aws::SNS::MessageVerifier if defined?(::Aws::SNS::MessageVerifier)
|
|
343
|
+
@sns_verifier_error =
|
|
344
|
+
if defined?(::Aws::SNS::MessageVerifier::VerificationError)
|
|
345
|
+
::Aws::SNS::MessageVerifier::VerificationError
|
|
346
|
+
end
|
|
120
347
|
end
|
|
121
348
|
|
|
122
349
|
# Sets the authentication block for engine controllers.
|
|
@@ -162,11 +389,30 @@ module PlanMyStuff
|
|
|
162
389
|
case context
|
|
163
390
|
when :issue then issue_custom_fields
|
|
164
391
|
when :comment then comment_custom_fields
|
|
392
|
+
when :project then project_custom_fields
|
|
393
|
+
when :testing then testing_custom_fields
|
|
165
394
|
else {}
|
|
166
395
|
end
|
|
167
396
|
|
|
168
397
|
custom_fields.deep_merge(context_fields)
|
|
169
398
|
end
|
|
399
|
+
|
|
400
|
+
# Returns the controller path for the given route group, preferring a
|
|
401
|
+
# consuming-app override from +controllers+ and falling back to the gem
|
|
402
|
+
# default from +DEFAULT_CONTROLLERS+. The returned value always begins
|
|
403
|
+
# with +'/'+ so the isolated engine does not re-prefix it with
|
|
404
|
+
# +plan_my_stuff/+.
|
|
405
|
+
#
|
|
406
|
+
# @param key [Symbol]
|
|
407
|
+
#
|
|
408
|
+
# @raise [KeyError] if +key+ is not a controllable route group
|
|
409
|
+
#
|
|
410
|
+
# @return [String]
|
|
411
|
+
#
|
|
412
|
+
def controller_for(key)
|
|
413
|
+
path = controllers[key] || DEFAULT_CONTROLLERS.fetch(key)
|
|
414
|
+
path.start_with?('/') ? path : "/#{path}"
|
|
415
|
+
end
|
|
170
416
|
end
|
|
171
417
|
|
|
172
418
|
class ConfigurationError < StandardError
|
|
@@ -60,28 +60,42 @@ module PlanMyStuff
|
|
|
60
60
|
to_h.to_json(...)
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
# @param method_name [Symbol]
|
|
64
|
+
# @param include_private [Boolean]
|
|
65
|
+
#
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
#
|
|
68
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
69
|
+
key = method_name.to_s.delete_suffix('=').to_sym
|
|
70
|
+
@schema.key?(key) || @data.key?(key) || super
|
|
71
|
+
end
|
|
69
72
|
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
# Dynamic reader/writer access to custom field values. Only resolves field
|
|
74
|
+
# names that appear in the schema or already have a value in @data; unknown
|
|
75
|
+
# names fall through to super (raising NoMethodError).
|
|
76
|
+
#
|
|
77
|
+
# @param method_name [Symbol]
|
|
78
|
+
# @param args [Array]
|
|
79
|
+
#
|
|
80
|
+
# @return [Object]
|
|
81
|
+
#
|
|
82
|
+
def method_missing(method_name, *args)
|
|
83
|
+
name = method_name.to_s
|
|
72
84
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end
|
|
78
|
-
elsif @schema.key?(method_name) || @data.key?(method_name)
|
|
79
|
-
return @data[method_name]
|
|
85
|
+
if name.end_with?('=')
|
|
86
|
+
key = name.delete_suffix('=').to_sym
|
|
87
|
+
if @schema.key?(key) || @data.key?(key)
|
|
88
|
+
return @data[key] = args.first
|
|
80
89
|
end
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
elsif @schema.key?(method_name) || @data.key?(method_name)
|
|
91
|
+
return @data[method_name]
|
|
83
92
|
end
|
|
84
93
|
|
|
94
|
+
super
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
85
99
|
# @return [void]
|
|
86
100
|
def validate_custom_fields
|
|
87
101
|
validate_unknown_fields
|
data/lib/plan_my_stuff/engine.rb
CHANGED
data/lib/plan_my_stuff/errors.rb
CHANGED
|
@@ -66,6 +66,17 @@ module PlanMyStuff
|
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
+
# Raised when a webhook HMAC-SHA256 signature check fails
|
|
70
|
+
class WebhookSignatureError < Error
|
|
71
|
+
def initialize(message = 'Invalid webhook signature')
|
|
72
|
+
super
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Raised when a pipeline operation fails
|
|
77
|
+
class PipelineError < Error
|
|
78
|
+
end
|
|
79
|
+
|
|
69
80
|
# Raised when custom field validation fails (Phase 1)
|
|
70
81
|
class ValidationError < Error
|
|
71
82
|
# @return [String, nil]
|
|
@@ -84,4 +95,42 @@ module PlanMyStuff
|
|
|
84
95
|
super(message)
|
|
85
96
|
end
|
|
86
97
|
end
|
|
98
|
+
|
|
99
|
+
# Raised when a caller is not authorized to perform an action (e.g.
|
|
100
|
+
# a non-support user attempts to manage approvers on an issue).
|
|
101
|
+
class AuthorizationError < Error
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Raised when an operation is attempted on an issue whose conversation
|
|
105
|
+
# is locked on GitHub (archived or manually locked).
|
|
106
|
+
class LockedIssueError < Error
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Raised by +PlanMyStuff::Pipeline+ forward transitions when the linked
|
|
110
|
+
# +Issue+ has any pending manager approvals.
|
|
111
|
+
class PendingApprovalsError < ValidationError
|
|
112
|
+
# @return [PlanMyStuff::Issue, nil]
|
|
113
|
+
attr_reader :issue
|
|
114
|
+
|
|
115
|
+
# @return [Integer]
|
|
116
|
+
attr_reader :pending_count
|
|
117
|
+
|
|
118
|
+
# @param message [String, nil]
|
|
119
|
+
# @param issue [PlanMyStuff::Issue, nil]
|
|
120
|
+
# @param pending_count [Integer]
|
|
121
|
+
#
|
|
122
|
+
def initialize(message = nil, issue: nil, pending_count: 0)
|
|
123
|
+
@issue = issue
|
|
124
|
+
@pending_count = pending_count
|
|
125
|
+
super(message || default_message)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
# @return [String]
|
|
131
|
+
def default_message
|
|
132
|
+
"Issue ##{issue&.number} has #{pending_count} pending approval(s); " \
|
|
133
|
+
'cannot move forward through pipeline.'
|
|
134
|
+
end
|
|
135
|
+
end
|
|
87
136
|
end
|