plan_my_stuff 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6799008cb353423175bdd3132e0fa5f14981b3710378c3b0886a8e9a2394b610
4
- data.tar.gz: 75b8336ec9aabd1d77a2642de77c9e46310dda3da49e6abd97b997927e906d05
3
+ metadata.gz: 6457257ef431e923519943cd227ffc70488b023467253873e20f52c47fd75f52
4
+ data.tar.gz: b5a16da4dc6c6c3ce45b307879f5146720f2b878809b7a071b7cc9e33986d210
5
5
  SHA512:
6
- metadata.gz: fdb5b8790905ff4f529496ad2a5c63b13a59a58ce524a8df79b20b0efc536854bc986f0e2fe7896b77bfe9b0a15598b6212a83991d46c9925e4dbf9eecab4871
7
- data.tar.gz: b5c150efa84a7ed3143935216d0ac52ae9c8fa7d99cb45b6add6552c13f3b66fad2f604e1aa8871cb05fc69b3aa16e650bce14a60ad1490d0650af3a3e646d91
6
+ metadata.gz: efee5f594d9bd014b0ce575781790768a8d51277010d9ad9902ac11beea06c17d39232d65a77532768326eccece3ebcbe3bdb18c464341c963eda9b12b1d6075
7
+ data.tar.gz: ef94bc70d08874c2347e243d0f7967ca7bca2e631cda775494a714df6501d22fe5011a09db805dbb53f5fe8397eb32b8bdf19bbf8a235da39d3f5dd4810c011a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Breaking
6
+
7
+ - `config.should_send_request` and `config.job_classes` accessors — declared but never wired up. The request gateway that would honor them is deferred (see `requirements/09_request_gateway.md` and `designs/init/gem_mvp_deferred_notes.md`); they will return when the gateway lands
8
+
9
+ ### Added
10
+
11
+ - `CONFIGURATION.md` is now available bundled with the gem
12
+
13
+ ### Changed
14
+
15
+ - `config.repos` is now assignable as a whole hash (`config.repos = { atlas: 'Org/Atlas', autofill: 'Org/atlas-autofill-moz' }`) in addition to the existing `config.repos[:key] = '...'` form
16
+ - `Issue.create!` / `Issue.update!` `issue_type:` kwarg now accepts the Symbol nicknames (`'bug'`, `'feature'`, `'it_issue'`, `'other'`, `'performance'`, `'question'`, `'task'`) as Strings too, resolving them to the same canonical name
17
+
3
18
  ## 0.8.0
4
19
 
5
20
  ### Breaking
data/CONFIGURATION.md ADDED
@@ -0,0 +1,351 @@
1
+ # Configuration
2
+
3
+ Every PlanMyStuff option lives on `PlanMyStuff::Configuration`. The install generator drops a fully-commented copy at
4
+ `config/initializers/plan_my_stuff.rb` — this file documents the same options grouped by concern. Defaults shown apply
5
+ when the option is left unset.
6
+
7
+ ```ruby
8
+ PMS.configure do |config|
9
+ # ...
10
+ end
11
+ ```
12
+
13
+ `PMS` is an alias for `PlanMyStuff`; consuming apps can use either.
14
+
15
+ ## Authentication (required)
16
+
17
+ | Option | Type | Default | Description |
18
+ |---|---|---|---|
19
+ | `access_token` | `String` | — | GitHub PAT with `repo` and `project` scopes. Required. |
20
+ | `import_access_token` | `String, nil` | `nil` | Classic PAT (requires `repo` scope) for the Issues Import API. Fine-grained tokens are not supported by that endpoint. |
21
+ | `organization` | `String` | — | GitHub organization name. Required. |
22
+
23
+ Both `access_token` and `organization` are validated by `config.validate!`; missing or blank values raise `PlanMyStuff::ConfigurationError`.
24
+
25
+ > [!NOTE]
26
+ > `import_access_token` is only required if you choose to use `PMS::Issue.import!`/`PMS::Issue.check_import!`
27
+
28
+ ```ruby
29
+ config.access_token = Rails.application.credentials.dig(:plan_my_stuff, :github_token)
30
+ config.import_access_token = Rails.application.credentials.dig(:plan_my_stuff, :github_import_token)
31
+ config.organization = 'YourOrg'
32
+ ```
33
+
34
+ ## Repositories
35
+
36
+ | Option | Type | Default | Description |
37
+ |---|---|---|---|
38
+ | `repos` | `Hash{Symbol => String}` | `{}` | Named repo configs mapping a key to an `Org/Repo` string. |
39
+ | `default_repo` | `Symbol, nil` | `nil` | Repo key used when callers omit the `repo:` param. |
40
+
41
+ ```ruby
42
+ config.repos = { element: 'YourOrg/Element', underwriter: 'YourOrg/Underwriter' }
43
+ config.default_repo = :element
44
+ ```
45
+
46
+ `repos` can be mutated via `config.repos[:key] = '...'` or set via `config.repos = { key: 'MyOrg/MyRepo' }`
47
+
48
+ ## Projects
49
+
50
+ | Option | Type | Default | Description |
51
+ |---|---|---|---|
52
+ | `default_project_number` | `Integer, nil` | `nil` | Default Projects V2 number for `add_to_project: true`. |
53
+ | `testing_template_project_number` | `Integer, nil` | `nil` | Project to clone in `TestingProject.create!` instead of bootstrapping fields. |
54
+
55
+ ```ruby
56
+ config.default_project_number = 14
57
+ config.testing_template_project_number = 42
58
+ ```
59
+
60
+ ## App identity
61
+
62
+ | Option | Type | Default | Description |
63
+ |---|---|---|---|
64
+ | `app_name` | `String, nil` | `nil` | Stored in metadata so subscribers can attribute writes. |
65
+ | `issues_url_prefix` | `String, nil` | `nil` | Prefix for `Issue#user_link`; the gem appends the issue number. |
66
+
67
+ ```ruby
68
+ config.app_name = 'MyApp'
69
+
70
+ url_options = Rails.application.config.action_mailer.default_url_options
71
+ config.issues_url_prefix = "#{url_options[:protocol] || 'http'}://#{url_options[:host]}/issues"
72
+ ```
73
+
74
+ ## User integration
75
+
76
+ | Option | Type | Default | Description |
77
+ |---|---|---|---|
78
+ | `user_class` | `String` | `'User'` | Consuming app's user model class name, constantized for lookups. |
79
+ | `display_name_method` | `Symbol` | `:to_s` | Method called on a user to get the display name for comment headers. |
80
+ | `user_id_method` | `Symbol` | `:id` | Method called on a user to extract the app-side user ID. |
81
+ | `support_method` | `Symbol, Proc` | `:support?` | Method name on the user, or a proc receiving the user, returning whether they're support staff. |
82
+ | `github_login_for` | `Hash{Object => String}` | `{}` | Maps app user id (from `user_id_method`) to GitHub login. Powers the Take UI. |
83
+
84
+ `support_method` may be a method name on the user object or a proc that receives the user and returns boolean.
85
+ `github_login_for` maps app user id (whatever `user_id_method` returns) to GitHub login and powers the Take UI.
86
+
87
+ ```ruby
88
+ config.user_class = 'User'
89
+ config.display_name_method = :full_name
90
+ config.user_id_method = :id
91
+ config.support_method = :support?
92
+ # or: config.support_method = -> (user) { user.role.in?(%w[support admin]) }
93
+ config.github_login_for = {
94
+ 1 => 'some_username',
95
+ 2 => 'octocat',
96
+ }
97
+ ```
98
+
99
+ ## Engine authentication
100
+
101
+ | Option | Type | Default | Description |
102
+ |---|---|---|---|
103
+ | `authenticate_with` | `Proc` | `nil` | Block executed as a `before_action` on every engine controller. |
104
+
105
+ ```ruby
106
+ config.authenticate_with do
107
+ redirect_to main_app.login_path unless current_user
108
+ end
109
+ ```
110
+
111
+ ## Markdown rendering
112
+
113
+ | Option | Type | Default | Description |
114
+ |---|---|---|---|
115
+ | `markdown_renderer` | `Symbol` | `:commonmarker` | Which markdown gem to use: `:commonmarker`, `:redcarpet`, or `nil` (raw HTML-escaped). |
116
+ | `markdown_options` | `Hash` | `{}` | Default options passed to the renderer. Per-call options merge on top of these. |
117
+
118
+ Set `markdown_renderer` to `:commonmarker`, `:redcarpet`, or `nil` (raw HTML-escaped). The chosen gem must be in your
119
+ Gemfile.
120
+
121
+ ```ruby
122
+ config.markdown_renderer = :commonmarker
123
+ config.markdown_options = { render: { hardbreaks: true } }
124
+ ```
125
+
126
+ ## Notification actor
127
+
128
+ | Option | Type | Default | Description |
129
+ |---|---|---|---|
130
+ | `current_user` | `Proc, nil` | `nil` | Fallback actor for notification events when `user:` is not passed. |
131
+
132
+ ```ruby
133
+ config.current_user = -> { Current.user }
134
+ ```
135
+
136
+ ## Controller rescue
137
+
138
+ | Option | Type | Default | Description |
139
+ |---|---|---|---|
140
+ | `controller_rescue` | `Proc, nil` | `nil` | Receives the rescued exception. Forward to your monitoring service. |
141
+
142
+ ```ruby
143
+ config.controller_rescue = -> (error) { MonitoringService.notice_error(error) }
144
+ ```
145
+
146
+ ## Custom fields
147
+
148
+ App-defined fields stored in metadata. Keys are field names; values are hashes with `:type` and `:required`.
149
+ Supported types: `:string`, `:integer`, `:boolean`, `:array`, `:hash`.
150
+
151
+ | Option | Type | Default | Description |
152
+ |---|---|---|---|
153
+ | `custom_fields` | `Hash{Symbol => Hash}` | `{}` | Shared field definitions across all contexts. |
154
+ | `issue_custom_fields` | `Hash{Symbol => Hash}` | `{}` | Issue-only definitions; merged on top of shared. |
155
+ | `comment_custom_fields` | `Hash{Symbol => Hash}` | `{}` | Comment-only definitions; merged on top of shared. |
156
+ | `project_custom_fields` | `Hash{Symbol => Hash}` | `{}` | Project-only definitions; merged on top of shared. |
157
+ | `testing_custom_fields` | `Hash{Symbol => Hash}` | `{}` | Testing-project-only definitions; merged on top of shared. |
158
+
159
+ Context-specific config wins on key conflicts.
160
+
161
+ ```ruby
162
+ config.custom_fields = {
163
+ notification_recipients: { type: :array, required: true },
164
+ }
165
+ config.issue_custom_fields = {
166
+ ticket_type: { type: :string, required: true },
167
+ }
168
+ config.comment_custom_fields = {
169
+ internal_note: { type: :boolean },
170
+ }
171
+ config.project_custom_fields = {
172
+ team: { type: :string },
173
+ }
174
+ config.testing_custom_fields = {
175
+ test_plan_url: { type: :string },
176
+ }
177
+ ```
178
+
179
+ ## Issue types
180
+
181
+ | Option | Type | Default | Description |
182
+ |---|---|---|---|
183
+ | `issue_types` | `Hash{String => String}` | `{}` | Maps the gem's canonical issue type names to your org's display names. |
184
+
185
+ Maps the gem's canonical type names (`'Bug'`, `'Feature'`, `'IT Issue / Hardware'`, `'Other'`, `'Performance'`,
186
+ `'Question'`, `'Task'`) to whatever your org uses. Missing keys pass through unchanged.
187
+
188
+ ```ruby
189
+ config.issue_types = {
190
+ 'Bug' => 'User Bug',
191
+ 'Feature' => 'Enhancement',
192
+ }
193
+ ```
194
+
195
+ ## Release pipeline
196
+
197
+ | Option | Type | Default | Description |
198
+ |---|---|---|---|
199
+ | `pipeline_enabled` | `Boolean` | `true` | Whether the release pipeline feature is enabled. |
200
+ | `pipeline_project_number` | `Integer, nil` | `nil` | Projects V2 number for the pipeline board. Falls back to `default_project_number`. |
201
+ | `webhook_secret` | `String, nil` | `nil` | HMAC secret for GitHub webhook signature verification. Required when webhook routes are mounted. |
202
+ | `pipeline_statuses` | `Hash{String => String}` | `{}` | Display aliases for canonical pipeline status names. |
203
+ | `pipeline_testing_field_name` | `String` | `'Testing'` | Display name for the pipeline project's `Testing` single-select field. |
204
+ | `pipeline_testing_values` | `Hash{Symbol => String}` | `{ active: 'Testing', inactive: 'Not testing' }` | Display labels for the canonical `:active`/`:inactive` testing options. |
205
+ | `pipeline_completion_purge_enabled` | `Boolean` | `true` | Whether the sweep removes aged-out `Completed` items from the pipeline. |
206
+ | `pipeline_completion_ttl_hours` | `Integer` | `24` | Hours after a `Completed` item's last update at which the sweep removes it. |
207
+ | `main_branch` | `String` | `'main'` | Branch PRs merge into for the "Ready for Release" transition. |
208
+ | `production_branch` | `String` | `'production'` | Branch PRs mereg into for the "Release in Progress" transition. |
209
+
210
+ `pipeline_statuses` aliases the canonical status names (`'Submitted'`, `'Started'`, `'In Review'`, `'Testing'`,
211
+ `'Ready for Release'`, `'Release in Progress'`, `'Completed'`) for display only. The constants remain the internal
212
+ identifiers.
213
+
214
+ `pipeline_completion_*` controls the sweep that removes aged-out `Completed` items from the pipeline. `webhook_secret`
215
+ is required when webhook routes are mounted.
216
+
217
+ ```ruby
218
+ config.pipeline_enabled = true
219
+ config.pipeline_project_number = 14
220
+ config.webhook_secret = Rails.application.credentials.dig(:plan_my_stuff, :webhook_secret)
221
+ config.pipeline_statuses = {
222
+ 'Submitted' => 'Triaged',
223
+ 'Completed' => 'Done',
224
+ }
225
+ config.pipeline_testing_field_name = 'Testing'
226
+ config.pipeline_testing_values = { active: 'Testing', inactive: 'Not testing' }
227
+ config.pipeline_completion_purge_enabled = true
228
+ config.pipeline_completion_ttl_hours = 24
229
+ config.main_branch = 'main'
230
+ config.production_branch = 'production'
231
+ ```
232
+
233
+ ## Follow-up reminders
234
+
235
+ | Option | Type | Default | Description |
236
+ |---|---|---|---|
237
+ | `reminders_enabled` | `Boolean` | `true` | Whether the reminders sweep performs any work. |
238
+ | `reminder_days` | `Array<Integer>` | `[1, 3, 7, 10, 14, 18]` | Days-since-waiting at which reminder events fire. |
239
+ | `inactivity_close_days` | `Integer` | `30` | Days of inactivity after which the sweep auto-closes a waiting issue. |
240
+ | `waiting_on_user_label` | `String` | `'waiting-on-user'` | Label flagging issues waiting on an end-user reply. |
241
+ | `waiting_on_approval_label` | `String` | `'waiting-on-approval'` | Label flagging issues waiting on pending approvals. |
242
+ | `user_inactive_label` | `String` | `'user-inactive'` | Label applied to issues auto-closed by the inactivity sweep; removed when an issue is auto-reopened via a user reply. |
243
+
244
+ Per-issue reminder override: `issue.metadata.reminder_days = [...]`.
245
+
246
+ ```ruby
247
+ config.reminders_enabled = true
248
+ config.reminder_days = [1, 3, 7, 10, 14, 18]
249
+ config.inactivity_close_days = 30
250
+ config.waiting_on_user_label = 'waiting-on-user'
251
+ config.waiting_on_approval_label = 'waiting-on-approval'
252
+ config.user_inactive_label = 'user-inactive'
253
+ ```
254
+
255
+ ## Auto-archiving
256
+
257
+ | Option | Type | Default | Description |
258
+ |---|---|---|---|
259
+ | `archiving_enabled` | `Boolean` | `true` | Whether the archive sweep performs any work. |
260
+ | `archive_closed_after_days` | `Integer` | `90` | Days after `closed_at` at which a non-inactive-closed issue becomes an archive candidate. |
261
+ | `archived_label` | `String` | `'archived'` | Label added to archived issues; also used by the sweep as a skip marker. |
262
+
263
+ The archive sweep piggybacks `RemindersSweepJob`. Inactivity-closed and non-PMS issues are excluded.
264
+
265
+ ```ruby
266
+ config.archiving_enabled = true
267
+ config.archive_closed_after_days = 90
268
+ config.archived_label = 'archived'
269
+ ```
270
+
271
+ ## AWS webhook
272
+
273
+ | Option | Type | Default | Description |
274
+ |---|---|---|---|
275
+ | `sns_topic_arn` | `String, nil` | `nil` | Expected SNS topic ARN for AWS webhook validation. |
276
+ | `aws_service_identifier` | `String, nil` | `nil` | Suffix matched against ECS event resource ARNs (e.g. `'my-app-production-2-web-server'`). |
277
+ | `production_commit_sha` | `String, nil` | `nil` | Prefix-matched against issue metadata `commit_sha` on `SERVICE_DEPLOYMENT_COMPLETED` events. |
278
+ | `process_aws_webhooks` | `Boolean` | `Rails.env.production?` | Whether to process incoming AWS webhook events. |
279
+ | `sns_verifier_class` | `Class` | `Aws::SNS::MessageVerifier` (when defined) | Class instantiated per request for SNS signature verification. Must respond to `authenticate!(raw_body)`. |
280
+ | `sns_verifier_error` | `Class` | `Aws::SNS::MessageVerifier::VerificationError` (when defined) | Exception class rescued during SNS signature verification. |
281
+
282
+ `production_commit_sha` is prefix-matched against issue metadata `commit_sha` on `SERVICE_DEPLOYMENT_COMPLETED` events.
283
+ `sns_verifier_class` must respond to `authenticate!(raw_body)`.
284
+
285
+ ```ruby
286
+ config.sns_topic_arn = 'arn:aws:sns:us-east-1:123456:ecs-deploy-topic'
287
+ config.aws_service_identifier = 'myapp-production-web'
288
+ config.production_commit_sha = Rails.configuration.x.image_tag
289
+ config.process_aws_webhooks = Rails.env.production?
290
+ config.sns_verifier_class = Aws::SNS::MessageVerifier
291
+ config.sns_verifier_error = Aws::SNS::MessageVerifier::VerificationError
292
+ ```
293
+
294
+ ## Caching
295
+
296
+ | Option | Type | Default | Description |
297
+ |---|---|---|---|
298
+ | `cache_enabled` | `Boolean` | `true` | ETag-based HTTP caching of GitHub reads via `Rails.cache`. |
299
+ | `cache_version` | `String, nil` | `nil` | Opaque string baked into every PMS cache key; bump to invalidate. |
300
+
301
+ ```ruby
302
+ config.cache_enabled = true
303
+ config.cache_version = Rails.configuration.x.image_tag
304
+ ```
305
+
306
+ ## Route mounting
307
+
308
+ | Option | Type | Default | Description |
309
+ |---|---|---|---|
310
+ | `mount_groups` | `Hash{Symbol => Boolean}` | `{ webhooks: true, issues: true, projects: true }` | Per-group route mounting toggles. Set a key to `false` to skip mounting that group. |
311
+
312
+ ```ruby
313
+ config.mount_groups = { webhooks: true, issues: true, projects: true }
314
+ ```
315
+
316
+ ## Controller overrides
317
+
318
+ | Option | Type | Default | Description |
319
+ |---|---|---|---|
320
+ | `controllers` | `Hash{Symbol => String}` | `{}` | Per-route controller overrides. Keys are controllable route symbols; values are fully-qualified controller paths. |
321
+
322
+ Per-route controller overrides. Keys are the controllable route symbols defined in
323
+ `PlanMyStuff::Configuration::DEFAULT_CONTROLLERS`; values are fully-qualified controller paths. Unset keys fall back to
324
+ the gem default.
325
+
326
+ ```ruby
327
+ config.controllers[:issues] = 'my_app/issues'
328
+ ```
329
+
330
+ Controllable keys (with gem defaults):
331
+
332
+ | Key | Default |
333
+ |---|---|
334
+ | `:issues` | `plan_my_stuff/issues` |
335
+ | `:comments` | `plan_my_stuff/comments` |
336
+ | `:labels` | `plan_my_stuff/labels` |
337
+ | `:projects` | `plan_my_stuff/projects` |
338
+ | `:project_items` | `plan_my_stuff/project_items` |
339
+ | `:testing_projects` | `plan_my_stuff/testing_projects` |
340
+ | `:testing_project_items` | `plan_my_stuff/testing_project_items` |
341
+ | `:'issues/closures'` | `plan_my_stuff/issues/closures` |
342
+ | `:'issues/viewers'` | `plan_my_stuff/issues/viewers` |
343
+ | `:'issues/takes'` | `plan_my_stuff/issues/takes` |
344
+ | `:'issues/waitings'` | `plan_my_stuff/issues/waitings` |
345
+ | `:'issues/links'` | `plan_my_stuff/issues/links` |
346
+ | `:'issues/approvals'` | `plan_my_stuff/issues/approvals` |
347
+ | `:'project_items/statuses'` | `plan_my_stuff/project_items/statuses` |
348
+ | `:'project_items/assignments'` | `plan_my_stuff/project_items/assignments` |
349
+ | `:'testing_project_items/results'` | `plan_my_stuff/testing_project_items/results` |
350
+ | `:'webhooks/github'` | `plan_my_stuff/webhooks/github` |
351
+ | `:'webhooks/aws'` | `plan_my_stuff/webhooks/aws` |
@@ -21,7 +21,7 @@
21
21
  <%= button_to('Take', plan_my_stuff.issue_take_path(@issue.number, repo: @issue.repo.full_name), method: :post) %>
22
22
  <% end %>
23
23
  <% if @support_user && @pipeline_enabled && @current_user_login.present? && @issue.assignees.include?(@current_user_login) %>
24
- <%= button_to('Release', plan_my_stuff.issue_take_path(@issue.number, repo: @issue.repo.full_name), method: :delete) %>
24
+ <%= button_to('Unassign', plan_my_stuff.issue_take_path(@issue.number, repo: @issue.repo.full_name), method: :delete) %>
25
25
  <% end %>
26
26
  <% if @support_user %>
27
27
  <%= link_to('Start Testing Project', plan_my_stuff.new_testing_project_path(subject_url: @issue.html_url)) %>
@@ -1,4 +1,3 @@
1
- <%# locals: () %>
2
1
  <% if flash[:error].present? %>
3
2
  <p role="alert" style="color: red;"><%= flash[:error] %></p>
4
3
  <% end %>
@@ -106,19 +106,8 @@ PlanMyStuff.configure do |config|
106
106
  # "#{url_options[:protocol] || 'http'}://#{url_options[:host]}/issues"
107
107
 
108
108
  # --------------------------------------------------------------------------
109
- # Request gateway
109
+ # Notification actor
110
110
  # --------------------------------------------------------------------------
111
- # Proc returning boolean, or nil (always send). When it returns false the
112
- # request is deferred to a background job instead of hitting GitHub.
113
- # config.should_send_request = -> { !MaintenanceMode.active? }
114
-
115
- # Map of action type to job class name for deferred requests.
116
- # config.job_classes = {
117
- # create_ticket: 'PmsCreateTicketJob',
118
- # post_comment: 'PmsPostCommentJob',
119
- # update_status: 'PmsUpdateStatusJob'
120
- # }
121
-
122
111
  # Fallback actor for notification events (plan_my_stuff.*) when a caller
123
112
  # does not pass an explicit user: kwarg. Proc/lambda called at event time.
124
113
  # config.current_user = -> { Current.user }
@@ -406,7 +406,7 @@ module PlanMyStuff
406
406
  #
407
407
  def status_field
408
408
  status_field!
409
- rescue
409
+ rescue PlanMyStuff::APIError
410
410
  nil
411
411
  end
412
412
 
@@ -73,6 +73,7 @@ module PlanMyStuff
73
73
  repo: item_hash[:repo],
74
74
  state: item_hash[:state],
75
75
  status: item_hash[:status],
76
+ updated_at: item_hash[:updated_at],
76
77
  field_values: item_hash[:field_values] || {},
77
78
  project: project,
78
79
  )
@@ -32,6 +32,8 @@ module PlanMyStuff
32
32
  class << self
33
33
  # Creates a comment on a GitHub issue with PMS metadata and a visible header.
34
34
  #
35
+ # @raise [PlanMyStuff::LockedIssueError] if the parent issue is locked
36
+ #
35
37
  # @param issue [PlanMyStuff::Issue] parent issue
36
38
  # @param body [String]
37
39
  # @param user [Object, Integer] user object or user_id
@@ -41,8 +43,6 @@ module PlanMyStuff
41
43
  # @param waiting_on_reply [Boolean] when true and the author is a support user, marks the issue as waiting on
42
44
  # an end-user reply. Ignored for non-support authors.
43
45
  #
44
- # @raise [PlanMyStuff::LockedIssueError] if the parent issue is locked
45
- #
46
46
  # @return [PlanMyStuff::Comment]
47
47
  #
48
48
  def create!(
@@ -279,7 +279,9 @@ module PlanMyStuff
279
279
  )
280
280
  hydrate_from_comment(created)
281
281
  else
282
- update!(user: user, body: body)
282
+ update_attrs = { user: user, body: body }
283
+ update_attrs[:visibility] = visibility if visibility_changed?
284
+ update!(**update_attrs)
283
285
  end
284
286
 
285
287
  self
@@ -84,19 +84,6 @@ module PlanMyStuff
84
84
  #
85
85
  attr_accessor :markdown_options
86
86
 
87
- # Proc returning boolean, or nil (always send). When it returns false the request is deferred to a background job
88
- # instead of hitting GitHub.
89
- #
90
- # @return [Proc, nil]
91
- #
92
- attr_accessor :should_send_request
93
-
94
- # Map of action type to job class name for deferred requests. Keys: :create_ticket, :post_comment, :update_status.
95
- #
96
- # @return [Hash{Symbol => String}]
97
- #
98
- attr_accessor :job_classes
99
-
100
87
  # Fallback actor for notification events when a caller does not pass +user:+. Set to a proc/lambda that returns the
101
88
  # current request user.
102
89
  #
@@ -344,11 +331,12 @@ module PlanMyStuff
344
331
  #
345
332
  attr_accessor :sns_verifier_error
346
333
 
347
- # Named repo configs. Set via config.repos[:element] = 'BrandsInsurance/Element'.
334
+ # Named repo configs. Set via config.repos[:element] = 'BrandsInsurance/Element', or assign a whole hash with
335
+ # config.repos = { element: 'BrandsInsurance/Element', underwriter: 'BrandsInsurance/Underwriter' }.
348
336
  #
349
337
  # @return [Hash{Symbol => String}]
350
338
  #
351
- attr_reader :repos
339
+ attr_accessor :repos
352
340
 
353
341
  # @return [Configuration]
354
342
  def initialize
@@ -359,7 +347,6 @@ module PlanMyStuff
359
347
  @support_method = :support?
360
348
  @markdown_renderer = :commonmarker
361
349
  @markdown_options = {}
362
- @job_classes = {}
363
350
  @custom_fields = {}
364
351
  @issue_custom_fields = {}
365
352
  @comment_custom_fields = {}
@@ -363,7 +363,11 @@ module PlanMyStuff
363
363
  canonical =
364
364
  case value
365
365
  when String
366
- value
366
+ begin
367
+ resolve_issue_type!(value.to_sym)
368
+ rescue ArgumentError
369
+ value
370
+ end
367
371
  when Symbol
368
372
  ISSUE_TYPE_NICKNAMES[value] || raise(
369
373
  ArgumentError,
@@ -48,7 +48,7 @@ module PlanMyStuff
48
48
  client = PlanMyStuff.client
49
49
  client.rest(:label, repo, name)
50
50
  rescue PlanMyStuff::APIError => e
51
- raise unless e.status == 404
51
+ raise(e) unless e.status == 404
52
52
 
53
53
  create_label!(client, repo, name, color, description)
54
54
  end
@@ -62,11 +62,11 @@ module PlanMyStuff
62
62
  #
63
63
  # @return [Boolean]
64
64
  #
65
- def exists?(repo:, name:)
65
+ def exists?(repo:, name:, do_raise: true)
66
66
  PlanMyStuff.client.rest(:label, repo, name)
67
67
  true
68
68
  rescue PlanMyStuff::APIError => e
69
- raise unless e.status == 404
69
+ raise(e) if do_raise && e.status != 404
70
70
 
71
71
  false
72
72
  end
@@ -115,7 +115,7 @@ module PlanMyStuff
115
115
 
116
116
  client.rest(:add_label, repo, name, color, **options)
117
117
  rescue PlanMyStuff::APIError => e
118
- raise unless e.status == 422
118
+ raise(e) unless e.status == 422
119
119
  end
120
120
 
121
121
  # Hydrates a Label from a GitHub API response.
@@ -3,7 +3,7 @@
3
3
  module PlanMyStuff
4
4
  module VERSION
5
5
  MAJOR = 0
6
- MINOR = 8
6
+ MINOR = 9
7
7
  TINY = 0
8
8
 
9
9
  # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
@@ -46,7 +46,7 @@ namespace :plan_my_stuff do
46
46
  url = ENV.fetch('URL') { raise(ArgumentError, 'URL env var is required') }
47
47
  repo = ENV.fetch('REPO') do
48
48
  PlanMyStuff.client.resolve_repo!
49
- rescue
49
+ rescue PlanMyStuff::ConfigurationError, ArgumentError
50
50
  raise(ArgumentError, 'REPO env var is required or configured (e.g. BrandsInsurance/PlanMyStuff)')
51
51
  end
52
52
  default_events = %w[pull_request issues]
@@ -94,7 +94,7 @@ namespace :plan_my_stuff do
94
94
  end
95
95
 
96
96
  desc 'Continuously poll org + repo hooks and auto-replay new deliveries ' \
97
- '(ENDPOINT_URL=... [ORG_WEBHOOK_URL=...] [REPO_WEBHOOK_URL=... REPO=owner/name] [INTERVAL=15]). ' \
97
+ '(ENDPOINT_URL=... [ORG_WEBHOOK_URL=...] [REPO_WEBHOOK_URL=... REPO=owner/name] [INTERVAL=30]). ' \
98
98
  'At least one of ORG_WEBHOOK_URL or REPO_WEBHOOK_URL is required (raises ArgumentError if both absent).'
99
99
  task listen: :environment do
100
100
  require 'plan_my_stuff/webhook_replayer'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plan_my_stuff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brands Insurance
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-30 00:00:00.000000000 Z
11
+ date: 2026-05-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -52,6 +52,7 @@ extensions: []
52
52
  extra_rdoc_files: []
53
53
  files:
54
54
  - CHANGELOG.md
55
+ - CONFIGURATION.md
55
56
  - LICENSE
56
57
  - README.md
57
58
  - app/controllers/plan_my_stuff/application_controller.rb