plan_my_stuff 0.1.0 → 1.0.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 +595 -0
- data/CONFIGURATION.md +487 -0
- data/README.md +612 -88
- data/app/controllers/plan_my_stuff/application_controller.rb +27 -5
- data/app/controllers/plan_my_stuff/comments_controller.rb +50 -19
- data/app/controllers/plan_my_stuff/issues/approvals_controller.rb +127 -0
- data/app/controllers/plan_my_stuff/issues/closures_controller.rb +53 -0
- data/app/controllers/plan_my_stuff/issues/links_controller.rb +129 -0
- data/app/controllers/plan_my_stuff/issues/takes_controller.rb +161 -0
- data/app/controllers/plan_my_stuff/issues/testings_controller.rb +82 -0
- data/app/controllers/plan_my_stuff/issues/viewers_controller.rb +62 -0
- data/app/controllers/plan_my_stuff/issues/waitings_controller.rb +55 -0
- data/app/controllers/plan_my_stuff/issues_controller.rb +53 -70
- data/app/controllers/plan_my_stuff/labels_controller.rb +32 -10
- data/app/controllers/plan_my_stuff/project_items/assignments_controller.rb +88 -0
- data/app/controllers/plan_my_stuff/project_items/statuses_controller.rb +44 -0
- data/app/controllers/plan_my_stuff/project_items_controller.rb +32 -69
- data/app/controllers/plan_my_stuff/projects_controller.rb +81 -3
- data/app/controllers/plan_my_stuff/testing_project_items/results_controller.rb +67 -0
- data/app/controllers/plan_my_stuff/testing_project_items_controller.rb +49 -0
- data/app/controllers/plan_my_stuff/testing_projects_controller.rb +121 -0
- data/app/controllers/plan_my_stuff/webhooks/aws_controller.rb +202 -0
- data/app/controllers/plan_my_stuff/webhooks/github_controller.rb +371 -0
- data/app/jobs/plan_my_stuff/application_job.rb +8 -0
- data/app/jobs/plan_my_stuff/reminders_sweep_job.rb +75 -0
- data/app/views/plan_my_stuff/comments/edit.html.erb +1 -3
- data/app/views/plan_my_stuff/comments/partials/_form.html.erb +8 -0
- data/app/views/plan_my_stuff/issues/edit.html.erb +2 -4
- data/app/views/plan_my_stuff/issues/index.html.erb +5 -5
- data/app/views/plan_my_stuff/issues/new.html.erb +2 -4
- data/app/views/plan_my_stuff/issues/partials/_approvals.html.erb +108 -0
- data/app/views/plan_my_stuff/issues/partials/_form.html.erb +11 -6
- data/app/views/plan_my_stuff/issues/partials/_labels.html.erb +4 -3
- data/app/views/plan_my_stuff/issues/partials/_links.html.erb +113 -0
- data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +4 -3
- data/app/views/plan_my_stuff/issues/show.html.erb +67 -6
- data/app/views/plan_my_stuff/partials/_flash.html.erb +3 -0
- data/app/views/plan_my_stuff/projects/edit.html.erb +5 -0
- data/app/views/plan_my_stuff/projects/index.html.erb +18 -2
- data/app/views/plan_my_stuff/projects/new.html.erb +5 -0
- data/app/views/plan_my_stuff/projects/partials/_form.html.erb +30 -0
- data/app/views/plan_my_stuff/projects/show.html.erb +30 -11
- data/app/views/plan_my_stuff/testing_project_items/new.html.erb +10 -0
- data/app/views/plan_my_stuff/testing_project_items/results/new.html.erb +20 -0
- data/app/views/plan_my_stuff/testing_projects/edit.html.erb +5 -0
- data/app/views/plan_my_stuff/testing_projects/new.html.erb +5 -0
- data/app/views/plan_my_stuff/testing_projects/partials/_form.html.erb +40 -0
- data/app/views/plan_my_stuff/testing_projects/partials/_item.html.erb +52 -0
- data/app/views/plan_my_stuff/testing_projects/partials/items/_form.html.erb +36 -0
- data/app/views/plan_my_stuff/testing_projects/show.html.erb +65 -0
- data/config/routes.rb +43 -15
- data/lib/generators/plan_my_stuff/install/templates/initializer.rb +302 -20
- data/lib/plan_my_stuff/application_record.rb +158 -1
- data/lib/plan_my_stuff/approval.rb +88 -0
- data/lib/plan_my_stuff/archive/sweep.rb +85 -0
- data/lib/plan_my_stuff/archive.rb +12 -0
- data/lib/plan_my_stuff/attachment.rb +83 -0
- data/lib/plan_my_stuff/attachment_uploader.rb +245 -0
- data/lib/plan_my_stuff/aws_sns_simulator.rb +116 -0
- data/lib/plan_my_stuff/base_metadata.rb +25 -28
- data/lib/plan_my_stuff/base_project.rb +502 -0
- data/lib/plan_my_stuff/base_project_extractions/graphql_hydration.rb +186 -0
- data/lib/plan_my_stuff/base_project_item.rb +588 -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 +139 -64
- data/lib/plan_my_stuff/comment.rb +225 -100
- data/lib/plan_my_stuff/comment_metadata.rb +68 -5
- data/lib/plan_my_stuff/configuration.rb +459 -28
- data/lib/plan_my_stuff/custom_fields.rb +96 -12
- data/lib/plan_my_stuff/engine.rb +14 -2
- data/lib/plan_my_stuff/errors.rb +65 -5
- data/lib/plan_my_stuff/graphql/queries.rb +454 -0
- data/lib/plan_my_stuff/issue.rb +1097 -166
- data/lib/plan_my_stuff/issue_extractions/approvals.rb +370 -0
- data/lib/plan_my_stuff/issue_extractions/links.rb +525 -0
- data/lib/plan_my_stuff/issue_extractions/viewers.rb +75 -0
- data/lib/plan_my_stuff/issue_extractions/waiting.rb +171 -0
- data/lib/plan_my_stuff/issue_field.rb +126 -0
- data/lib/plan_my_stuff/issue_field_translation.rb +67 -0
- data/lib/plan_my_stuff/issue_field_value_set.rb +68 -0
- data/lib/plan_my_stuff/issue_metadata.rb +132 -21
- data/lib/plan_my_stuff/label.rb +100 -13
- data/lib/plan_my_stuff/link.rb +144 -0
- data/lib/plan_my_stuff/markdown.rb +13 -7
- data/lib/plan_my_stuff/metadata_parser.rb +51 -12
- data/lib/plan_my_stuff/notifications.rb +148 -0
- data/lib/plan_my_stuff/pipeline/completed_sweep.rb +46 -0
- data/lib/plan_my_stuff/pipeline/issue_linker.rb +62 -0
- data/lib/plan_my_stuff/pipeline/status.rb +40 -0
- data/lib/plan_my_stuff/pipeline/testing.rb +23 -0
- data/lib/plan_my_stuff/pipeline.rb +310 -0
- data/lib/plan_my_stuff/project.rb +63 -465
- data/lib/plan_my_stuff/project_item.rb +3 -409
- 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 +12 -0
- data/lib/plan_my_stuff/repo.rb +145 -0
- data/lib/plan_my_stuff/test_helpers.rb +265 -25
- data/lib/plan_my_stuff/testing_project.rb +292 -0
- data/lib/plan_my_stuff/testing_project_item.rb +218 -0
- data/lib/plan_my_stuff/testing_project_metadata.rb +94 -0
- data/lib/plan_my_stuff/user_resolver.rb +24 -3
- data/lib/plan_my_stuff/verifier.rb +10 -0
- data/lib/plan_my_stuff/version.rb +2 -2
- data/lib/plan_my_stuff/webhook_replayer.rb +292 -0
- data/lib/plan_my_stuff.rb +55 -20
- data/lib/tasks/plan_my_stuff.rake +331 -0
- metadata +99 -4
data/CONFIGURATION.md
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
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
|
+
| `repo_nicknames` | `Hash{Symbol => String}` | `{}` | `Issue#to_param` prefix override (default `key.titleize`). |
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
config.repos = { element: 'YourOrg/Element', underwriter: 'YourOrg/Underwriter' }
|
|
44
|
+
config.default_repo = :element
|
|
45
|
+
config.repo_nicknames = { safety: 'Compliance' } # :element -> "Element", :underwriter -> "Underwriter" come free
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`repos` can be mutated via `config.repos[:key] = '...'` or set via `config.repos = { key: 'MyOrg/MyRepo' }`.
|
|
49
|
+
`Issue#to_param` then returns `"Element-1234"` / `"Compliance-567"`, encoding both repo and number in a single
|
|
50
|
+
URL segment so `youtrack_issue_path(@issue)` works without a `repo:` query param.
|
|
51
|
+
|
|
52
|
+
## Attachments
|
|
53
|
+
|
|
54
|
+
| Option | Type | Default | Description |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| `attachment_repo` | `String` | `'pms-attachments'` | Bare repo (under `organization`) for uploaded attachments. |
|
|
57
|
+
|
|
58
|
+
The repo must already exist; the uploader does not create it. Attachments commit onto
|
|
59
|
+
`config.main_branch` under `<repo_key_or_name>/issue-<number>/<uuid>.<ext>`.
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
config.attachment_repo = 'pms-attachments'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Projects
|
|
66
|
+
|
|
67
|
+
| Option | Type | Default | Description |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| `default_project_number` | `Integer, nil` | `nil` | Default Projects V2 number for `add_to_project: true`. |
|
|
70
|
+
| `testing_template_project_number` | `Integer, nil` | `nil` | Project to clone in `TestingProject.create!` instead of bootstrapping fields. |
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
config.default_project_number = 14
|
|
74
|
+
config.testing_template_project_number = 42
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## App identity
|
|
78
|
+
|
|
79
|
+
| Option | Type | Default | Description |
|
|
80
|
+
|---|---|---|---|
|
|
81
|
+
| `app_name` | `String, nil` | `nil` | Stored in metadata so subscribers can attribute writes. |
|
|
82
|
+
| `issues_url_prefix` | `String, nil` | `nil` | Prefix for `Issue#user_link`; the gem appends the issue number. |
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
config.app_name = 'MyApp'
|
|
86
|
+
|
|
87
|
+
url_options = Rails.application.routes.default_url_options
|
|
88
|
+
config.issues_url_prefix = "#{url_options[:protocol] || 'http'}://#{url_options[:host]}/issues"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## User integration
|
|
92
|
+
|
|
93
|
+
| Option | Type | Default | Description |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| `user_class` | `String` | `'User'` | Consuming app's user model class name, constantized for lookups. |
|
|
96
|
+
| `display_name_method` | `Symbol` | `:to_s` | Method called on a user to get the display name for comment headers. |
|
|
97
|
+
| `user_id_method` | `Symbol` | `:id` | Method called on a user to extract the app-side user ID. |
|
|
98
|
+
| `support_method` | `Symbol, Proc` | `:support?` | Method name on the user, or a proc receiving the user, returning whether they're support staff. |
|
|
99
|
+
| `github_login_for` | `Hash{Object => String}` | `{}` | Maps app user id (from `user_id_method`) to GitHub login. Powers the Take UI. |
|
|
100
|
+
|
|
101
|
+
`support_method` may be a method name on the user object or a proc that receives the user and returns boolean.
|
|
102
|
+
`github_login_for` maps app user id (whatever `user_id_method` returns) to GitHub login and powers the Take UI.
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
config.user_class = 'User'
|
|
106
|
+
config.display_name_method = :full_name
|
|
107
|
+
config.user_id_method = :id
|
|
108
|
+
config.support_method = :support?
|
|
109
|
+
# or: config.support_method = -> (user) { user.role.in?(%w[support admin]) }
|
|
110
|
+
config.github_login_for = {
|
|
111
|
+
1 => 'some_username',
|
|
112
|
+
2 => 'octocat',
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Engine authentication
|
|
117
|
+
|
|
118
|
+
| Option | Type | Default | Description |
|
|
119
|
+
|---|---|---|---|
|
|
120
|
+
| `authenticate_with` | `Proc` | `nil` | Block executed as a `before_action` on every engine controller. |
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
config.authenticate_with do
|
|
124
|
+
redirect_to main_app.login_path unless current_user
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Markdown rendering
|
|
129
|
+
|
|
130
|
+
| Option | Type | Default | Description |
|
|
131
|
+
|---|---|---|---|
|
|
132
|
+
| `markdown_renderer` | `Symbol` | `:commonmarker` | Which markdown gem to use: `:commonmarker`, `:redcarpet`, or `nil` (raw HTML-escaped). |
|
|
133
|
+
| `markdown_options` | `Hash` | `{}` | Default options passed to the renderer. Per-call options merge on top of these. |
|
|
134
|
+
|
|
135
|
+
Set `markdown_renderer` to `:commonmarker`, `:redcarpet`, or `nil` (raw HTML-escaped). The chosen gem must be in your
|
|
136
|
+
Gemfile.
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
config.markdown_renderer = :commonmarker
|
|
140
|
+
config.markdown_options = { render: { hardbreaks: true } }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Notification actor
|
|
144
|
+
|
|
145
|
+
| Option | Type | Default | Description |
|
|
146
|
+
|---|---|---|---|
|
|
147
|
+
| `current_user` | `Proc, nil` | `nil` | Fallback actor for notification events when `user:` is not passed. |
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
config.current_user = -> { Current.user }
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Controller rescue
|
|
154
|
+
|
|
155
|
+
| Option | Type | Default | Description |
|
|
156
|
+
|---|---|---|---|
|
|
157
|
+
| `controller_rescue` | `Proc, nil` | `nil` | Receives the rescued exception. Forward to your monitoring service. |
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
config.controller_rescue = -> (error) { MonitoringService.notice_error(error) }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Custom fields
|
|
164
|
+
|
|
165
|
+
App-defined fields stored in metadata. Keys are field names; values are hashes with `:type` and `:required`.
|
|
166
|
+
Supported types: `:string`, `:integer`, `:boolean`, `:array`, `:hash`.
|
|
167
|
+
|
|
168
|
+
| Option | Type | Default | Description |
|
|
169
|
+
|---|---|---|---|
|
|
170
|
+
| `custom_fields` | `Hash{Symbol => Hash}` | `{}` | Shared field definitions across all contexts. |
|
|
171
|
+
| `issue_custom_fields` | `Hash{Symbol => Hash}` | `{}` | Issue-only definitions; merged on top of shared. |
|
|
172
|
+
| `comment_custom_fields` | `Hash{Symbol => Hash}` | `{}` | Comment-only definitions; merged on top of shared. |
|
|
173
|
+
| `project_custom_fields` | `Hash{Symbol => Hash}` | `{}` | Project-only definitions; merged on top of shared. |
|
|
174
|
+
| `testing_custom_fields` | `Hash{Symbol => Hash}` | `{}` | Testing-project-only definitions; merged on top of shared. |
|
|
175
|
+
|
|
176
|
+
Context-specific config wins on key conflicts.
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
config.custom_fields = {
|
|
180
|
+
notification_recipients: { type: :array, required: true },
|
|
181
|
+
}
|
|
182
|
+
config.issue_custom_fields = {
|
|
183
|
+
ticket_type: { type: :string, required: true },
|
|
184
|
+
}
|
|
185
|
+
config.comment_custom_fields = {
|
|
186
|
+
internal_note: { type: :boolean },
|
|
187
|
+
}
|
|
188
|
+
config.project_custom_fields = {
|
|
189
|
+
team: { type: :string },
|
|
190
|
+
}
|
|
191
|
+
config.testing_custom_fields = {
|
|
192
|
+
test_plan_url: { type: :string },
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Issue types
|
|
197
|
+
|
|
198
|
+
| Option | Type | Default | Description |
|
|
199
|
+
|---|---|---|---|
|
|
200
|
+
| `issue_types` | `Hash{String => String}` | `{}` | Maps the gem's canonical issue type names to your org's display names. |
|
|
201
|
+
|
|
202
|
+
Maps the gem's canonical type names (`'Bug'`, `'Feature'`, `'IT Issue / Hardware'`, `'Other'`, `'Performance'`,
|
|
203
|
+
`'Question'`, `'Task'`) to whatever your org uses. Missing keys pass through unchanged.
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
config.issue_types = {
|
|
207
|
+
'Bug' => 'User Bug',
|
|
208
|
+
'Feature' => 'Enhancement',
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Issue Fields (public preview)
|
|
213
|
+
|
|
214
|
+
| Option | Type | Default | Description |
|
|
215
|
+
|---|---|---|---|
|
|
216
|
+
| `issue_fields_enabled` | `Boolean` | `true` | Whether the Issue Fields public preview is wired up for the org. |
|
|
217
|
+
| `issue_field_names` | `Hash{String => String}` | `{}` | Canonical Issue Field name => the consumer org's field name. |
|
|
218
|
+
| `issue_field_values` | `Hash` | `{}` | Per field: canonical value => consumer value (single-select labels). |
|
|
219
|
+
|
|
220
|
+
GitHub Issue Fields are structured per-issue metadata (text, number, date, or single-select)
|
|
221
|
+
configured once at the org level. The preview is rolling out org-by-org. Leave this flag at its
|
|
222
|
+
default (`true`) once your org has been admitted; flip to `false` to keep the gem from issuing
|
|
223
|
+
calls that would otherwise return raw GraphQL errors.
|
|
224
|
+
|
|
225
|
+
With the flag off:
|
|
226
|
+
|
|
227
|
+
- `Issue#issue_fields` returns an empty `IssueFieldValueSet` without making a request.
|
|
228
|
+
- `Issue#set_issue_fields!(...)` and `IssueField.list` raise `IssueFieldsNotEnabledError`.
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
config.issue_fields_enabled = false # org not admitted to the preview yet
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`issue_field_names` and `issue_field_values` let a consuming org rename the native Issue Fields (and their
|
|
235
|
+
single-select option labels) the gem refers to internally, the same way `pipeline_statuses` aliases pipeline
|
|
236
|
+
statuses. Translation is bidirectional via `PlanMyStuff::IssueFieldTranslation`: canonical => consumer on writes and
|
|
237
|
+
filters, consumer => canonical on reads, so internal comparisons (`awaiting_reply?`, `priority_list?`) keep working.
|
|
238
|
+
Unmapped names / values pass through unchanged.
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
config.issue_field_names = {
|
|
242
|
+
'Issue Status' => 'Status',
|
|
243
|
+
'Priority' => 'Prio',
|
|
244
|
+
}
|
|
245
|
+
config.issue_field_values = {
|
|
246
|
+
'Issue Status' => {
|
|
247
|
+
'Submitted' => 'Triaged',
|
|
248
|
+
'Waiting on Reply' => 'Awaiting Customer',
|
|
249
|
+
'Open' => 'Open',
|
|
250
|
+
'Reopened' => 'Reopened',
|
|
251
|
+
},
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Release pipeline
|
|
256
|
+
|
|
257
|
+
| Option | Type | Default | Description |
|
|
258
|
+
|---|---|---|---|
|
|
259
|
+
| `pipeline_enabled` | `Boolean` | `true` | Whether the release pipeline feature is enabled. |
|
|
260
|
+
| `pipeline_project_number` | `Integer, nil` | `nil` | Projects V2 number for the pipeline board. Falls back to `default_project_number`. |
|
|
261
|
+
| `webhook_secret` | `String, nil` | `nil` | HMAC secret for GitHub webhook signature verification. Required when webhook routes are mounted. |
|
|
262
|
+
| `pipeline_statuses` | `Hash{String => String}` | `{}` | Display aliases for canonical pipeline status names. |
|
|
263
|
+
| `pipeline_testing_field_name` | `String` | `'Testing'` | Display name for the pipeline project's `Testing` single-select field. |
|
|
264
|
+
| `pipeline_testing_values` | `Hash{Symbol => String}` | `{ active: 'Testing', inactive: 'Not testing' }` | Display labels for the canonical `:active`/`:inactive` testing options. |
|
|
265
|
+
| `pipeline_completion_purge_enabled` | `Boolean` | `true` | Whether the sweep removes aged-out `Completed` items from the pipeline. |
|
|
266
|
+
| `pipeline_completion_ttl_hours` | `Integer` | `24` | Hours after a `Completed` item's last update at which the sweep removes it. |
|
|
267
|
+
| `main_branch` | `String` | `'main'` | Branch PRs merge into for the "Ready for Release" transition. |
|
|
268
|
+
| `production_branch` | `String` | `'production'` | Branch PRs mereg into for the "Release in Progress" transition. |
|
|
269
|
+
|
|
270
|
+
`pipeline_statuses` aliases the canonical status names (`'Submitted'`, `'Started'`, `'In Review'`, `'Testing'`,
|
|
271
|
+
`'Ready for Release'`, `'Release in Progress'`, `'Completed'`) for display only. The constants remain the internal
|
|
272
|
+
identifiers.
|
|
273
|
+
|
|
274
|
+
`pipeline_completion_*` controls the sweep that removes aged-out `Completed` items from the pipeline. `webhook_secret`
|
|
275
|
+
is required when webhook routes are mounted.
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
config.pipeline_enabled = true
|
|
279
|
+
config.pipeline_project_number = 14
|
|
280
|
+
config.webhook_secret = Rails.application.credentials.dig(:plan_my_stuff, :webhook_secret)
|
|
281
|
+
config.pipeline_statuses = {
|
|
282
|
+
'Submitted' => 'Triaged',
|
|
283
|
+
'Completed' => 'Done',
|
|
284
|
+
}
|
|
285
|
+
config.pipeline_testing_field_name = 'Testing'
|
|
286
|
+
config.pipeline_testing_values = { active: 'Testing', inactive: 'Not testing' }
|
|
287
|
+
config.pipeline_completion_purge_enabled = true
|
|
288
|
+
config.pipeline_completion_ttl_hours = 24
|
|
289
|
+
config.main_branch = 'main'
|
|
290
|
+
config.production_branch = 'production'
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Follow-up reminders
|
|
294
|
+
|
|
295
|
+
| Option | Type | Default | Description |
|
|
296
|
+
|---|---|---|---|
|
|
297
|
+
| `reminders_enabled` | `Boolean` | `true` | Whether the reminders sweep performs any work. |
|
|
298
|
+
| `reminder_days` | `Array<Integer>` | `[1, 3, 7, 10, 14, 18]` | Days-since-waiting at which reminder events fire. |
|
|
299
|
+
| `inactivity_close_days` | `Integer` | `30` | Days of inactivity after which the sweep auto-closes a waiting issue. |
|
|
300
|
+
| `waiting_on_user_label` | `String` | `'waiting-on-user'` | Label flagging issues waiting on an end-user reply. |
|
|
301
|
+
| `waiting_on_approval_label` | `String` | `'waiting-on-approval'` | Label flagging issues waiting on pending approvals. |
|
|
302
|
+
| `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. |
|
|
303
|
+
|
|
304
|
+
Per-issue reminder override: `issue.metadata.reminder_days = [...]`.
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
config.reminders_enabled = true
|
|
308
|
+
config.reminder_days = [1, 3, 7, 10, 14, 18]
|
|
309
|
+
config.inactivity_close_days = 30
|
|
310
|
+
config.waiting_on_user_label = 'waiting-on-user'
|
|
311
|
+
config.waiting_on_approval_label = 'waiting-on-approval'
|
|
312
|
+
config.user_inactive_label = 'user-inactive'
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## Auto-archiving
|
|
316
|
+
|
|
317
|
+
| Option | Type | Default | Description |
|
|
318
|
+
|---|---|---|---|
|
|
319
|
+
| `archiving_enabled` | `Boolean` | `true` | Whether the archive sweep performs any work. |
|
|
320
|
+
| `archive_closed_after_days` | `Integer` | `90` | Days after `closed_at` at which a non-inactive-closed issue becomes an archive candidate. |
|
|
321
|
+
| `archived_label` | `String` | `'archived'` | Label added to archived issues; also used by the sweep as a skip marker. |
|
|
322
|
+
|
|
323
|
+
The archive sweep piggybacks `RemindersSweepJob`. Inactivity-closed and non-PMS issues are excluded.
|
|
324
|
+
|
|
325
|
+
```ruby
|
|
326
|
+
config.archiving_enabled = true
|
|
327
|
+
config.archive_closed_after_days = 90
|
|
328
|
+
config.archived_label = 'archived'
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## AWS webhook
|
|
332
|
+
|
|
333
|
+
| Option | Type | Default | Description |
|
|
334
|
+
|---|---|---|---|
|
|
335
|
+
| `sns_topic_arn` | `String, nil` | `nil` | Expected SNS topic ARN for AWS webhook validation. |
|
|
336
|
+
| `aws_service_identifier` | `String, nil` | `nil` | Suffix matched against ECS event resource ARNs (e.g. `'my-app-production-2-web-server'`). |
|
|
337
|
+
| `production_commit_sha` | `String, nil` | `nil` | Prefix-matched against issue metadata `commit_sha` on `SERVICE_DEPLOYMENT_COMPLETED` events. |
|
|
338
|
+
| `process_aws_webhooks` | `Boolean` | `Rails.env.production?` | Whether to process incoming AWS webhook events. |
|
|
339
|
+
| `sns_verifier_class` | `Class` | `Aws::SNS::MessageVerifier` (when defined) | Class instantiated per request for SNS signature verification. Must respond to `authenticate!(raw_body)`. |
|
|
340
|
+
| `sns_verifier_error` | `Class` | `Aws::SNS::MessageVerifier::VerificationError` (when defined) | Exception class rescued during SNS signature verification. |
|
|
341
|
+
|
|
342
|
+
`production_commit_sha` is prefix-matched against issue metadata `commit_sha` on `SERVICE_DEPLOYMENT_COMPLETED` events.
|
|
343
|
+
`sns_verifier_class` must respond to `authenticate!(raw_body)`.
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
config.sns_topic_arn = 'arn:aws:sns:us-east-1:123456:ecs-deploy-topic'
|
|
347
|
+
config.aws_service_identifier = 'myapp-production-web'
|
|
348
|
+
config.production_commit_sha = Rails.configuration.x.image_tag
|
|
349
|
+
config.process_aws_webhooks = Rails.env.production?
|
|
350
|
+
config.sns_verifier_class = Aws::SNS::MessageVerifier
|
|
351
|
+
config.sns_verifier_error = Aws::SNS::MessageVerifier::VerificationError
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Caching
|
|
355
|
+
|
|
356
|
+
| Option | Type | Default | Description |
|
|
357
|
+
|---|---|---|---|
|
|
358
|
+
| `cache_enabled` | `Boolean` | `true` | ETag-based HTTP caching of GitHub reads via `Rails.cache`. |
|
|
359
|
+
| `cache_version` | `String, nil` | `nil` | Opaque string baked into every PMS cache key; bump to invalidate. |
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
config.cache_enabled = true
|
|
363
|
+
config.cache_version = Rails.configuration.x.image_tag
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Route mounting
|
|
367
|
+
|
|
368
|
+
| Option | Type | Default | Description |
|
|
369
|
+
|---|---|---|---|
|
|
370
|
+
| `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. |
|
|
371
|
+
|
|
372
|
+
```ruby
|
|
373
|
+
config.mount_groups = { webhooks: true, issues: true, projects: true }
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Boot behavior
|
|
377
|
+
|
|
378
|
+
| Option | Type | Default | Description |
|
|
379
|
+
|---|---|---|---|
|
|
380
|
+
| `eager_load_controllers_on_boot` | `Boolean` | `false` | Eager-load engine controllers in `after_initialize`. |
|
|
381
|
+
|
|
382
|
+
Opt in if the host app probes engine controllers via `defined?` in dev mode. When `true`, the
|
|
383
|
+
engine walks `app/controllers` once on boot so `defined?(PlanMyStuff::SomeController)` resolves
|
|
384
|
+
without first referencing the constant.
|
|
385
|
+
|
|
386
|
+
```ruby
|
|
387
|
+
config.eager_load_controllers_on_boot = true
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Controller overrides
|
|
391
|
+
|
|
392
|
+
| Option | Type | Default | Description |
|
|
393
|
+
|---|---|---|---|
|
|
394
|
+
| `controllers` | `Hash{Symbol => String}` | `{}` | Per-route controller overrides. Keys are controllable route symbols; values are fully-qualified controller paths. |
|
|
395
|
+
|
|
396
|
+
Per-route controller overrides. Keys are the controllable route symbols defined in
|
|
397
|
+
`PlanMyStuff::Configuration::DEFAULT_CONTROLLERS`; values are fully-qualified controller paths. Unset keys fall back to
|
|
398
|
+
the gem default.
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
config.controllers[:issues] = 'my_app/issues'
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
Controllable keys (with gem defaults):
|
|
405
|
+
|
|
406
|
+
| Key | Default |
|
|
407
|
+
|---|---|
|
|
408
|
+
| `:issues` | `plan_my_stuff/issues` |
|
|
409
|
+
| `:comments` | `plan_my_stuff/comments` |
|
|
410
|
+
| `:labels` | `plan_my_stuff/labels` |
|
|
411
|
+
| `:projects` | `plan_my_stuff/projects` |
|
|
412
|
+
| `:project_items` | `plan_my_stuff/project_items` |
|
|
413
|
+
| `:testing_projects` | `plan_my_stuff/testing_projects` |
|
|
414
|
+
| `:testing_project_items` | `plan_my_stuff/testing_project_items` |
|
|
415
|
+
| `:'issues/closures'` | `plan_my_stuff/issues/closures` |
|
|
416
|
+
| `:'issues/viewers'` | `plan_my_stuff/issues/viewers` |
|
|
417
|
+
| `:'issues/takes'` | `plan_my_stuff/issues/takes` |
|
|
418
|
+
| `:'issues/testings'` | `plan_my_stuff/issues/testings` |
|
|
419
|
+
| `:'issues/waitings'` | `plan_my_stuff/issues/waitings` |
|
|
420
|
+
| `:'issues/links'` | `plan_my_stuff/issues/links` |
|
|
421
|
+
| `:'issues/approvals'` | `plan_my_stuff/issues/approvals` |
|
|
422
|
+
| `:'project_items/statuses'` | `plan_my_stuff/project_items/statuses` |
|
|
423
|
+
| `:'project_items/assignments'` | `plan_my_stuff/project_items/assignments` |
|
|
424
|
+
| `:'testing_project_items/results'` | `plan_my_stuff/testing_project_items/results` |
|
|
425
|
+
| `:'webhooks/github'` | `plan_my_stuff/webhooks/github` |
|
|
426
|
+
| `:'webhooks/aws'` | `plan_my_stuff/webhooks/aws` |
|
|
427
|
+
|
|
428
|
+
### Customizing per-action behavior
|
|
429
|
+
|
|
430
|
+
Every mounted route resolves its controller through `config.controller_for(key)`. Subclass a gem controller in your
|
|
431
|
+
own app and register it to wedge in `before_action`s, authentication, or response tweaks - no monkey patching:
|
|
432
|
+
|
|
433
|
+
```ruby
|
|
434
|
+
# app/controllers/my_app/issues_controller.rb
|
|
435
|
+
class MyApp::IssuesController < PMS::IssuesController
|
|
436
|
+
before_action :authenticate_user!
|
|
437
|
+
before_action :authorize_ticket_access
|
|
438
|
+
end
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
```ruby
|
|
442
|
+
# config/initializers/plan_my_stuff.rb
|
|
443
|
+
PlanMyStuff.configure do |config|
|
|
444
|
+
config.controllers[:issues] = 'my_app/issues'
|
|
445
|
+
end
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
For per-action side effects (audit log, metrics, notifications) without rewriting the action, yielding actions
|
|
449
|
+
pass their primary object to an optional block on the happy path. Call `super do |obj| ... end` from a subclass:
|
|
450
|
+
|
|
451
|
+
```ruby
|
|
452
|
+
class MyApp::IssuesController < PMS::IssuesController
|
|
453
|
+
def create
|
|
454
|
+
super do |issue|
|
|
455
|
+
AuditLog.record(actor: current_user, action: :issue_created, target: issue)
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
def update
|
|
460
|
+
super do |issue|
|
|
461
|
+
AuditLog.record(actor: current_user, action: :issue_updated, target: issue)
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Contract:
|
|
468
|
+
|
|
469
|
+
- The yield fires on the happy path, after the model load / write succeeds and before the gem's default
|
|
470
|
+
`flash[:success]` + `redirect_to`. Error branches, `not_found`, and authorization redirects do not yield.
|
|
471
|
+
- Read actions (`index`, `show`, `new`, `edit`) yield before the implicit render; `index` yields the collection,
|
|
472
|
+
the rest yield the single object.
|
|
473
|
+
- If your block calls `render` or `redirect_to`, the gem's default response is skipped (the action checks
|
|
474
|
+
`performed?`), so you can fully replace the response from the block.
|
|
475
|
+
|
|
476
|
+
### Parent controller
|
|
477
|
+
|
|
478
|
+
| Option | Type | Default | Description |
|
|
479
|
+
|---|---|---|---|
|
|
480
|
+
| `parent_controller` | `String` | `'::ApplicationController'` | Parent of `PlanMyStuff::ApplicationController`. |
|
|
481
|
+
|
|
482
|
+
Set this in `config/initializers/plan_my_stuff.rb`; Rails resolves the superclass once at class definition, so the
|
|
483
|
+
value must be set before the engine's controllers load.
|
|
484
|
+
|
|
485
|
+
```ruby
|
|
486
|
+
config.parent_controller = 'RawrApplicationController'
|
|
487
|
+
```
|