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
|
@@ -2,9 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
4
|
class Configuration
|
|
5
|
+
# Default controller for each controllable route group. Consuming apps override by assigning values into
|
|
6
|
+
# +controllers+; lookups go through +controller_for+ which falls back to this table.
|
|
7
|
+
#
|
|
8
|
+
# @return [Hash{Symbol => String}]
|
|
9
|
+
#
|
|
10
|
+
DEFAULT_CONTROLLERS = {
|
|
11
|
+
'issues': 'plan_my_stuff/issues',
|
|
12
|
+
'comments': 'plan_my_stuff/comments',
|
|
13
|
+
'labels': 'plan_my_stuff/labels',
|
|
14
|
+
'projects': 'plan_my_stuff/projects',
|
|
15
|
+
'project_items': 'plan_my_stuff/project_items',
|
|
16
|
+
'testing_projects': 'plan_my_stuff/testing_projects',
|
|
17
|
+
'testing_project_items': 'plan_my_stuff/testing_project_items',
|
|
18
|
+
'issues/closures': 'plan_my_stuff/issues/closures',
|
|
19
|
+
'issues/viewers': 'plan_my_stuff/issues/viewers',
|
|
20
|
+
'issues/takes': 'plan_my_stuff/issues/takes',
|
|
21
|
+
'issues/testings': 'plan_my_stuff/issues/testings',
|
|
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
|
|
|
35
|
+
# Classic GitHub PAT used for the Issues Import API (golden-comet-preview). Requires the +repo+ scope (admin-level
|
|
36
|
+
# repository access). Fine-grained tokens are not supported by that endpoint.
|
|
37
|
+
#
|
|
38
|
+
# @return [String, nil]
|
|
39
|
+
#
|
|
40
|
+
attr_accessor :import_access_token
|
|
41
|
+
|
|
8
42
|
# @return [String] GitHub organization name. Required.
|
|
9
43
|
attr_accessor :organization
|
|
10
44
|
|
|
@@ -14,6 +48,11 @@ module PlanMyStuff
|
|
|
14
48
|
# @return [Integer, nil] default GitHub Projects V2 number for add_to_project calls
|
|
15
49
|
attr_accessor :default_project_number
|
|
16
50
|
|
|
51
|
+
# @return [Integer, nil] GitHub Projects V2 number of the template project to clone when creating new
|
|
52
|
+
# TestingProjects. When set, TestingProject.create! copies the template (preserving its fields and board layout)
|
|
53
|
+
# instead of bootstrapping fields from scratch. Leave nil to use the default bootstrap-fields path.
|
|
54
|
+
attr_accessor :testing_template_project_number
|
|
55
|
+
|
|
17
56
|
# @return [String] consuming app's user model class name, constantized for lookups
|
|
18
57
|
attr_accessor :user_class
|
|
19
58
|
|
|
@@ -23,8 +62,8 @@ module PlanMyStuff
|
|
|
23
62
|
# @return [Symbol] method called on user object to extract the app-side user ID
|
|
24
63
|
attr_accessor :user_id_method
|
|
25
64
|
|
|
26
|
-
# Determines if a user is support staff. Symbol (method name on user) or Proc that
|
|
27
|
-
#
|
|
65
|
+
# Determines if a user is support staff. Symbol (method name on user) or Proc that receives the user object and
|
|
66
|
+
# returns boolean.
|
|
28
67
|
#
|
|
29
68
|
# @return [Symbol, Proc]
|
|
30
69
|
#
|
|
@@ -33,8 +72,7 @@ module PlanMyStuff
|
|
|
33
72
|
# @return [Symbol] which markdown gem to use: :commonmarker or :redcarpet
|
|
34
73
|
attr_accessor :markdown_renderer
|
|
35
74
|
|
|
36
|
-
# Default options passed to the markdown renderer. Per-call options in
|
|
37
|
-
# Markdown.render merge on top of these.
|
|
75
|
+
# Default options passed to the markdown renderer. Per-call options in Markdown.render merge on top of these.
|
|
38
76
|
#
|
|
39
77
|
# For :commonmarker - passed as `options:` to `Commonmarker.to_html`
|
|
40
78
|
# e.g. `{ render: { hardbreaks: true } }`
|
|
@@ -47,35 +85,73 @@ module PlanMyStuff
|
|
|
47
85
|
#
|
|
48
86
|
attr_accessor :markdown_options
|
|
49
87
|
|
|
50
|
-
#
|
|
51
|
-
#
|
|
88
|
+
# Fallback actor for notification events when a caller does not pass +user:+. Set to a proc/lambda that returns the
|
|
89
|
+
# current request user.
|
|
90
|
+
#
|
|
91
|
+
# Example: +config.current_user = -> { Current.user }+
|
|
52
92
|
#
|
|
53
93
|
# @return [Proc, nil]
|
|
54
94
|
#
|
|
55
|
-
attr_accessor :
|
|
95
|
+
attr_accessor :current_user
|
|
56
96
|
|
|
57
|
-
#
|
|
58
|
-
#
|
|
97
|
+
# Callback invoked from gem controller +rescue+ blocks just after the error is logged and just before the
|
|
98
|
+
# user-facing redirect/render. Lets consuming apps forward swallowed errors to their monitoring service. Receives
|
|
99
|
+
# the rescued exception.
|
|
59
100
|
#
|
|
60
|
-
#
|
|
101
|
+
# Example:
|
|
102
|
+
# config.controller_rescue = ->(error) { MonitoringService.notice_error(error) }
|
|
61
103
|
#
|
|
62
|
-
|
|
104
|
+
# @return [Proc, nil]
|
|
105
|
+
#
|
|
106
|
+
attr_accessor :controller_rescue
|
|
63
107
|
|
|
64
|
-
#
|
|
65
|
-
|
|
108
|
+
# Shared field definitions stored in issue/comment metadata. Keys are field names, values are hashes with :type and
|
|
109
|
+
# :required. These fields apply to both issues and comments.
|
|
110
|
+
#
|
|
111
|
+
# @return [Hash{Symbol => Hash}]
|
|
112
|
+
#
|
|
113
|
+
attr_accessor :custom_fields
|
|
66
114
|
|
|
67
|
-
#
|
|
68
|
-
|
|
115
|
+
# Issue-only field definitions, deep-merged on top of shared custom_fields. Context-specific config wins on key
|
|
116
|
+
# conflicts.
|
|
117
|
+
#
|
|
118
|
+
# @return [Hash{Symbol => Hash}]
|
|
119
|
+
#
|
|
120
|
+
attr_accessor :issue_custom_fields
|
|
69
121
|
|
|
70
|
-
#
|
|
71
|
-
|
|
122
|
+
# Comment-only field definitions, deep-merged on top of shared custom_fields. Context-specific config wins on key
|
|
123
|
+
# conflicts.
|
|
124
|
+
#
|
|
125
|
+
# @return [Hash{Symbol => Hash}]
|
|
126
|
+
#
|
|
127
|
+
attr_accessor :comment_custom_fields
|
|
72
128
|
|
|
73
|
-
#
|
|
74
|
-
#
|
|
129
|
+
# Project-only field definitions, deep-merged on top of shared custom_fields. Context-specific config wins on key
|
|
130
|
+
# conflicts.
|
|
75
131
|
#
|
|
76
132
|
# @return [Hash{Symbol => Hash}]
|
|
77
133
|
#
|
|
78
|
-
attr_accessor :
|
|
134
|
+
attr_accessor :project_custom_fields
|
|
135
|
+
|
|
136
|
+
# Testing-project-only field definitions, deep-merged on top of shared custom_fields. Context-specific config wins
|
|
137
|
+
# on key conflicts.
|
|
138
|
+
#
|
|
139
|
+
# @return [Hash{Symbol => Hash}]
|
|
140
|
+
#
|
|
141
|
+
attr_accessor :testing_custom_fields
|
|
142
|
+
|
|
143
|
+
# Canonical-name to org-side display-name map for GitHub native issue types. Lets the consuming app rename the
|
|
144
|
+
# seven canonical types the gem knows about (+"Bug"+, +"Feature"+, +"IT Issue / Hardware"+, +"Other"+,
|
|
145
|
+
# +"Performance"+, +"Question"+, +"Task"+) to whatever their org actually uses. Both Symbol shortcuts (resolved via
|
|
146
|
+
# the gem's nickname map) and String inputs to +Issue.create!+ / +Issue.update!+ are passed through this map;
|
|
147
|
+
# missing keys fall through unchanged.
|
|
148
|
+
#
|
|
149
|
+
# Example: +config.issue_types = { 'Bug' => 'User Bug', 'Feature' => 'Enhancement' }+ then +issue_type: :feature+
|
|
150
|
+
# or +issue_type: 'Feature'+ both write +'Enhancement'+ to GitHub.
|
|
151
|
+
#
|
|
152
|
+
# @return [Hash{String => String}]
|
|
153
|
+
#
|
|
154
|
+
attr_accessor :issue_types
|
|
79
155
|
|
|
80
156
|
# @return [String, nil] URL prefix for building user-facing ticket URLs in the consuming app
|
|
81
157
|
attr_accessor :issues_url_prefix
|
|
@@ -83,23 +159,296 @@ module PlanMyStuff
|
|
|
83
159
|
# @return [String, nil] name of the consuming app, stored in metadata (e.g. "Atlas")
|
|
84
160
|
attr_accessor :app_name
|
|
85
161
|
|
|
86
|
-
#
|
|
162
|
+
# @return [Boolean] whether the release pipeline feature is enabled
|
|
163
|
+
attr_accessor :pipeline_enabled
|
|
164
|
+
|
|
165
|
+
# @return [Integer, nil] GitHub Projects V2 number for the pipeline board (falls back to default_project_number)
|
|
166
|
+
attr_accessor :pipeline_project_number
|
|
167
|
+
|
|
168
|
+
# @return [String, nil] HMAC secret for GitHub webhook signature verification (required when webhooks mounted)
|
|
169
|
+
attr_accessor :webhook_secret
|
|
170
|
+
|
|
171
|
+
# @return [String, nil] expected SNS topic ARN for AWS webhook validation
|
|
172
|
+
attr_accessor :sns_topic_arn
|
|
173
|
+
|
|
174
|
+
# @return [String, nil] suffix matched against ECS event resource ARNs (e.g. 'rawr-production-2-web')
|
|
175
|
+
attr_accessor :aws_service_identifier
|
|
176
|
+
|
|
177
|
+
# @return [String, nil] commit hash of the deploying build, prefix-matched against issue metadata commit_sha
|
|
178
|
+
attr_accessor :production_commit_sha
|
|
179
|
+
|
|
180
|
+
# Canonical status name to display alias map. Allows consuming apps to rename pipeline statuses (e.g. "Started" to
|
|
181
|
+
# "In Progress").
|
|
182
|
+
#
|
|
183
|
+
# @return [Hash{String => String}]
|
|
184
|
+
#
|
|
185
|
+
attr_accessor :pipeline_statuses
|
|
186
|
+
|
|
187
|
+
# Display name for the +Testing+ single-select custom field on the pipeline project. Defaults to +"Testing"+.
|
|
188
|
+
#
|
|
189
|
+
# @return [String]
|
|
190
|
+
#
|
|
191
|
+
attr_accessor :pipeline_testing_field_name
|
|
192
|
+
|
|
193
|
+
# Map of canonical testing field option keys (+:active+, +:inactive+) to display labels. Allows consuming apps to
|
|
194
|
+
# rename the option labels without changing the canonical identifiers.
|
|
87
195
|
#
|
|
88
196
|
# @return [Hash{Symbol => String}]
|
|
89
197
|
#
|
|
90
|
-
|
|
198
|
+
attr_accessor :pipeline_testing_values
|
|
199
|
+
|
|
200
|
+
# Canonical Issue Field name to consumer field name map. Lets a consuming org rename the native issue fields the
|
|
201
|
+
# gem refers to internally (e.g. +"Issue Status"+ -> +"Status"+) without touching gem code. Applied via
|
|
202
|
+
# +PlanMyStuff::IssueFieldTranslation+ on reads, writes, and filters; unmapped names pass through unchanged.
|
|
203
|
+
#
|
|
204
|
+
# @return [Hash{String => String}]
|
|
205
|
+
#
|
|
206
|
+
attr_accessor :issue_field_names
|
|
207
|
+
|
|
208
|
+
# Canonical Issue Field value translations, nested by canonical field name:
|
|
209
|
+
# +{ 'Issue Status' => { 'Waiting on Reply' => 'Awaiting Customer', ... } }+. Lets a consuming org rename
|
|
210
|
+
# single-select option labels without touching gem code. Applied via +PlanMyStuff::IssueFieldTranslation+;
|
|
211
|
+
# unmapped fields / values pass through unchanged.
|
|
212
|
+
#
|
|
213
|
+
# @return [Hash{String => Hash{String => String}}]
|
|
214
|
+
#
|
|
215
|
+
attr_accessor :issue_field_values
|
|
216
|
+
|
|
217
|
+
# @return [String] branch name that PRs merge into for "Ready for release" transition
|
|
218
|
+
attr_accessor :main_branch
|
|
219
|
+
|
|
220
|
+
# @return [String] branch name that triggers deployment when a PR merges
|
|
221
|
+
attr_accessor :production_branch
|
|
222
|
+
|
|
223
|
+
# Hash mapping consuming-app user id to GitHub login. Used by the "Take" UI flow to assign the GitHub user when a
|
|
224
|
+
# support user claims an issue. Keys are whatever +config.user_id_method+ returns on the current user.
|
|
225
|
+
#
|
|
226
|
+
# Example: +config.github_login_for = { 1 => 'some_username', 2 => 'octocat' }+
|
|
227
|
+
#
|
|
228
|
+
# @return [Hash{Object => String}]
|
|
229
|
+
#
|
|
230
|
+
attr_accessor :github_login_for
|
|
231
|
+
|
|
232
|
+
# Per-group route mounting toggles. Keys: :webhooks, :issues, :projects. Set a key to false to skip mounting that
|
|
233
|
+
# route group.
|
|
234
|
+
#
|
|
235
|
+
# @return [Hash{Symbol => Boolean}]
|
|
236
|
+
#
|
|
237
|
+
attr_accessor :mount_groups
|
|
238
|
+
|
|
239
|
+
# Per-route controller overrides. Keys are the controllable route symbols defined in +DEFAULT_CONTROLLERS+; values
|
|
240
|
+
# are fully-qualified controller paths (e.g. +'my_app/issues'+). Unset keys fall back to the gem default. Consuming
|
|
241
|
+
# apps typically subclass the gem controller to add before_actions or tweak responses, then swap their subclass in
|
|
242
|
+
# here.
|
|
243
|
+
#
|
|
244
|
+
# @return [Hash{Symbol => String}]
|
|
245
|
+
#
|
|
246
|
+
attr_accessor :controllers
|
|
247
|
+
|
|
248
|
+
# Parent class string for +PlanMyStuff::ApplicationController+. Defaults to +'::ApplicationController'+. Override
|
|
249
|
+
# when the consuming app does not use the +ApplicationController+ name (e.g. +'ActionController::Base'+,
|
|
250
|
+
# +'RawrApplicationController'+). Must be set before the gem's controllers load - the standard
|
|
251
|
+
# +config/initializers/plan_my_stuff.rb+ location is correct, since Rails resolves the superclass once at class
|
|
252
|
+
# definition time.
|
|
253
|
+
#
|
|
254
|
+
# @return [String]
|
|
255
|
+
#
|
|
256
|
+
attr_accessor :parent_controller
|
|
257
|
+
|
|
258
|
+
# Whether to use Rails.cache for ETag-based HTTP caching of GitHub reads. Defaults to true; set to false to bypass
|
|
259
|
+
# the cache entirely.
|
|
260
|
+
#
|
|
261
|
+
# @return [Boolean]
|
|
262
|
+
#
|
|
263
|
+
attr_accessor :cache_enabled
|
|
264
|
+
|
|
265
|
+
# Opaque app-supplied version string embedded in every PMS cache key. Bumping this string invalidates all cached
|
|
266
|
+
# entries from the consuming app's side (e.g. after a deploy or schema change). Defaults to nil.
|
|
267
|
+
#
|
|
268
|
+
# @return [String, nil]
|
|
269
|
+
#
|
|
270
|
+
attr_accessor :cache_version
|
|
271
|
+
|
|
272
|
+
# Whether the reminders sweep performs any work. Defaults to +true+. Set to +false+ in apps that don't want
|
|
273
|
+
# follow-up reminders or inactivity auto-close.
|
|
274
|
+
#
|
|
275
|
+
# @return [Boolean]
|
|
276
|
+
#
|
|
277
|
+
attr_accessor :reminders_enabled
|
|
278
|
+
|
|
279
|
+
# Days-since-waiting at which reminder events fire. Per-issue override via +metadata.reminder_days+.
|
|
280
|
+
#
|
|
281
|
+
# @return [Array<Integer>]
|
|
282
|
+
#
|
|
283
|
+
attr_accessor :reminder_days
|
|
284
|
+
|
|
285
|
+
# Days of inactivity after which the sweep auto-closes a waiting issue.
|
|
286
|
+
#
|
|
287
|
+
# @return [Integer]
|
|
288
|
+
#
|
|
289
|
+
attr_accessor :inactivity_close_days
|
|
290
|
+
|
|
291
|
+
# Label name used to flag issues waiting on an end-user reply.
|
|
292
|
+
#
|
|
293
|
+
# @return [String]
|
|
294
|
+
#
|
|
295
|
+
attr_accessor :waiting_on_user_label
|
|
296
|
+
|
|
297
|
+
# Label name used to flag issues waiting on pending approvals.
|
|
298
|
+
#
|
|
299
|
+
# @return [String]
|
|
300
|
+
#
|
|
301
|
+
attr_accessor :waiting_on_approval_label
|
|
302
|
+
|
|
303
|
+
# Label name applied to issues auto-closed by the inactivity sweep. Removed when an issue is auto-reopened via a
|
|
304
|
+
# user reply.
|
|
305
|
+
#
|
|
306
|
+
# @return [String]
|
|
307
|
+
#
|
|
308
|
+
attr_accessor :user_inactive_label
|
|
309
|
+
|
|
310
|
+
# Whether the archive sweep performs any work. Defaults to +true+. Set to +false+ in apps that don't want
|
|
311
|
+
# auto-archiving of aged-closed issues.
|
|
312
|
+
#
|
|
313
|
+
# @return [Boolean]
|
|
314
|
+
#
|
|
315
|
+
attr_accessor :archiving_enabled
|
|
316
|
+
|
|
317
|
+
# Days after +closed_at+ at which a non-inactive-closed issue becomes an archive candidate.
|
|
318
|
+
#
|
|
319
|
+
# @return [Integer]
|
|
320
|
+
#
|
|
321
|
+
attr_accessor :archive_closed_after_days
|
|
322
|
+
|
|
323
|
+
# Label name added to archived issues. Also used by the sweep as a skip marker to avoid re-archiving the same
|
|
324
|
+
# issue.
|
|
325
|
+
#
|
|
326
|
+
# @return [String]
|
|
327
|
+
#
|
|
328
|
+
attr_accessor :archived_label
|
|
329
|
+
|
|
330
|
+
# Whether the pipeline sweep removes aged-out +Completed+ items. Defaults to +true+. Set to +false+ to keep items
|
|
331
|
+
# in +Completed+ indefinitely.
|
|
332
|
+
#
|
|
333
|
+
# @return [Boolean]
|
|
334
|
+
#
|
|
335
|
+
attr_accessor :pipeline_completion_purge_enabled
|
|
336
|
+
|
|
337
|
+
# Hours after a project item's last update at which the sweep removes it from the pipeline if its status is
|
|
338
|
+
# +Completed+. Defaults to +24+.
|
|
339
|
+
#
|
|
340
|
+
# @return [Integer]
|
|
341
|
+
#
|
|
342
|
+
attr_accessor :pipeline_completion_ttl_hours
|
|
343
|
+
|
|
344
|
+
# Whether to process incoming AWS webhook events. Defaults to +Rails.env.production?+.
|
|
345
|
+
#
|
|
346
|
+
# @return [Boolean]
|
|
347
|
+
#
|
|
348
|
+
attr_accessor :process_aws_webhooks
|
|
349
|
+
|
|
350
|
+
# Class instantiated per request for SNS signature verification. Must respond to +authenticate!(raw_body)+.
|
|
351
|
+
#
|
|
352
|
+
# @return [Class]
|
|
353
|
+
#
|
|
354
|
+
attr_accessor :sns_verifier_class
|
|
355
|
+
|
|
356
|
+
# Exception class rescued during SNS signature verification.
|
|
357
|
+
#
|
|
358
|
+
# @return [Class]
|
|
359
|
+
#
|
|
360
|
+
attr_accessor :sns_verifier_error
|
|
361
|
+
|
|
362
|
+
# Named repo configs. Set via config.repos[:element] = 'BrandsInsurance/Element', or assign a whole hash with
|
|
363
|
+
# config.repos = { element: 'BrandsInsurance/Element', underwriter: 'BrandsInsurance/Underwriter' }.
|
|
364
|
+
#
|
|
365
|
+
# @return [Hash{Symbol => String}]
|
|
366
|
+
#
|
|
367
|
+
attr_accessor :repos
|
|
368
|
+
|
|
369
|
+
# Human-readable repo names used as the +to_param+ prefix on +PlanMyStuff::Issue+ instances. Symbol-keyed
|
|
370
|
+
# against +repos+ -- missing keys fall back to +key.to_s.titleize+, so only entries that diverge from a simple
|
|
371
|
+
# +titleize+ of the key (e.g. +:safety+ -> +"Compliance"+) need to be listed.
|
|
372
|
+
#
|
|
373
|
+
# @return [Hash{Symbol => String}]
|
|
374
|
+
#
|
|
375
|
+
attr_accessor :repo_nicknames
|
|
376
|
+
|
|
377
|
+
# Bare repo name (under +config.organization+) that stores uploaded attachment binaries. Defaults to
|
|
378
|
+
# +'pms-attachments'+. The repo must exist; the uploader does not create it. Attachments commit onto
|
|
379
|
+
# +config.main_branch+ and live under +<repo_key_or_name>/issue-<number>/<uuid>.<ext>+.
|
|
380
|
+
#
|
|
381
|
+
# @return [String]
|
|
382
|
+
#
|
|
383
|
+
attr_accessor :attachment_repo
|
|
384
|
+
|
|
385
|
+
# Whether GitHub's Issue Fields (public preview) are wired up for the configured org. Defaults to +true+ (opt-out):
|
|
386
|
+
# when +false+, +Issue#issue_fields+ returns an empty +IssueFieldValueSet+ without making a request and
|
|
387
|
+
# +Issue#set_issue_fields!+ / +IssueField.list+ raise +IssueFieldsNotEnabledError+. Set to +false+ if your org has
|
|
388
|
+
# not been admitted to the Issue Fields preview, to avoid raw GraphQL errors from GitHub.
|
|
389
|
+
#
|
|
390
|
+
# @return [Boolean]
|
|
391
|
+
#
|
|
392
|
+
attr_accessor :issue_fields_enabled
|
|
393
|
+
|
|
394
|
+
# Whether to eager-load the engine's controllers on host boot. Defaults to +false+ (opt-in). When +true+, the engine
|
|
395
|
+
# walks +app/controllers+ during +after_initialize+ so +defined?(PlanMyStuff::SomeController)+ resolves without
|
|
396
|
+
# first referencing the constant. Enable in host apps that rely on +defined?+ probes against engine controllers in
|
|
397
|
+
# dev mode.
|
|
398
|
+
#
|
|
399
|
+
# @return [Boolean]
|
|
400
|
+
#
|
|
401
|
+
attr_accessor :eager_load_controllers_on_boot
|
|
91
402
|
|
|
92
403
|
# @return [Configuration]
|
|
93
404
|
def initialize
|
|
94
405
|
@repos = {}
|
|
406
|
+
@repo_nicknames = {}
|
|
407
|
+
@attachment_repo = 'pms-attachments'
|
|
95
408
|
@user_class = 'User'
|
|
96
409
|
@display_name_method = :to_s
|
|
97
410
|
@user_id_method = :id
|
|
98
411
|
@support_method = :support?
|
|
99
412
|
@markdown_renderer = :commonmarker
|
|
100
413
|
@markdown_options = {}
|
|
101
|
-
@job_classes = {}
|
|
102
414
|
@custom_fields = {}
|
|
415
|
+
@issue_custom_fields = {}
|
|
416
|
+
@comment_custom_fields = {}
|
|
417
|
+
@project_custom_fields = {}
|
|
418
|
+
@testing_custom_fields = {}
|
|
419
|
+
@issue_types = {}
|
|
420
|
+
@pipeline_enabled = true
|
|
421
|
+
@pipeline_statuses = {}
|
|
422
|
+
@pipeline_testing_field_name = PlanMyStuff::Pipeline::Testing::FIELD_NAME
|
|
423
|
+
@pipeline_testing_values = PlanMyStuff::Pipeline::Testing::VALUES.dup
|
|
424
|
+
@issue_field_names = {}
|
|
425
|
+
@issue_field_values = {}
|
|
426
|
+
@main_branch = 'main'
|
|
427
|
+
@production_branch = 'production'
|
|
428
|
+
@mount_groups = { webhooks: true, issues: true, projects: true }
|
|
429
|
+
@controllers = {}
|
|
430
|
+
@parent_controller = '::ApplicationController'
|
|
431
|
+
@cache_enabled = true
|
|
432
|
+
@github_login_for = {}
|
|
433
|
+
@reminders_enabled = true
|
|
434
|
+
@reminder_days = [1, 3, 7, 10, 14, 18].freeze
|
|
435
|
+
@inactivity_close_days = 30
|
|
436
|
+
@waiting_on_user_label = 'waiting-on-user'
|
|
437
|
+
@waiting_on_approval_label = 'waiting-on-approval'
|
|
438
|
+
@user_inactive_label = 'user-inactive'
|
|
439
|
+
@archiving_enabled = true
|
|
440
|
+
@archive_closed_after_days = 90
|
|
441
|
+
@archived_label = 'archived'
|
|
442
|
+
@pipeline_completion_purge_enabled = true
|
|
443
|
+
@pipeline_completion_ttl_hours = 24
|
|
444
|
+
@issue_fields_enabled = true
|
|
445
|
+
@eager_load_controllers_on_boot = false
|
|
446
|
+
@process_aws_webhooks = Rails.env.production?
|
|
447
|
+
@sns_verifier_class = ::Aws::SNS::MessageVerifier if defined?(::Aws::SNS::MessageVerifier)
|
|
448
|
+
@sns_verifier_error =
|
|
449
|
+
if defined?(::Aws::SNS::MessageVerifier::VerificationError)
|
|
450
|
+
::Aws::SNS::MessageVerifier::VerificationError
|
|
451
|
+
end
|
|
103
452
|
end
|
|
104
453
|
|
|
105
454
|
# Sets the authentication block for engine controllers.
|
|
@@ -125,13 +474,95 @@ module PlanMyStuff
|
|
|
125
474
|
missing << 'access_token' if access_token.nil? || access_token.to_s.strip.empty?
|
|
126
475
|
missing << 'organization' if organization.nil? || organization.to_s.strip.empty?
|
|
127
476
|
|
|
128
|
-
|
|
477
|
+
if missing.present?
|
|
478
|
+
raise(
|
|
479
|
+
PlanMyStuff::ConfigurationError,
|
|
480
|
+
"Missing required PlanMyStuff configuration: #{missing.join(', ')}",
|
|
481
|
+
)
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
validate_repo_nicknames!
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Returns the merged custom fields schema for the given context. Context-specific fields deep-merge on top of
|
|
488
|
+
# shared fields.
|
|
489
|
+
#
|
|
490
|
+
# @param context [Symbol] :issue or :comment
|
|
491
|
+
#
|
|
492
|
+
# @return [Hash{Symbol => Hash}]
|
|
493
|
+
#
|
|
494
|
+
def custom_fields_for(context)
|
|
495
|
+
context_fields =
|
|
496
|
+
case context
|
|
497
|
+
when :issue then issue_custom_fields
|
|
498
|
+
when :comment then comment_custom_fields
|
|
499
|
+
when :project then project_custom_fields
|
|
500
|
+
when :testing then testing_custom_fields
|
|
501
|
+
else {}
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
custom_fields.deep_merge(context_fields)
|
|
505
|
+
end
|
|
129
506
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
507
|
+
# Returns the controller path for the given route group, preferring a consuming-app override from +controllers+
|
|
508
|
+
# and falling back to the gem default from +DEFAULT_CONTROLLERS+. The returned value always begins with +'/'+ so
|
|
509
|
+
# the isolated engine does not re-prefix it with +plan_my_stuff/+.
|
|
510
|
+
#
|
|
511
|
+
# @raise [KeyError] if +key+ is not a controllable route group
|
|
512
|
+
#
|
|
513
|
+
# @param key [Symbol]
|
|
514
|
+
#
|
|
515
|
+
# @return [String]
|
|
516
|
+
#
|
|
517
|
+
def controller_for(key)
|
|
518
|
+
path = controllers[key] || DEFAULT_CONTROLLERS.fetch(key)
|
|
519
|
+
path.start_with?('/') ? path : "/#{path}"
|
|
134
520
|
end
|
|
521
|
+
|
|
522
|
+
# Human-readable nickname for a repo key, used as the +to_param+ prefix on +PlanMyStuff::Issue+ instances. Falls
|
|
523
|
+
# back to +key.to_s.titleize+ when no explicit entry exists in +repo_nicknames+.
|
|
524
|
+
#
|
|
525
|
+
# @param key [Symbol, String]
|
|
526
|
+
#
|
|
527
|
+
# @return [String]
|
|
528
|
+
#
|
|
529
|
+
def repo_nickname_for(key)
|
|
530
|
+
repo_nicknames[key&.to_sym] || key.to_s.titleize
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
private
|
|
534
|
+
|
|
535
|
+
# Resolved nicknames feed directly into +Issue#to_param+ and route +:id+ tokens, so collisions or chars outside
|
|
536
|
+
# +[A-Za-z0-9_]+ (notably +-+, the nickname/number separator) break URL round-tripping.
|
|
537
|
+
#
|
|
538
|
+
# @raise [ConfigurationError] when any resolved nickname collides with another or contains non-token chars
|
|
539
|
+
#
|
|
540
|
+
# @return [void]
|
|
541
|
+
#
|
|
542
|
+
def validate_repo_nicknames!
|
|
543
|
+
resolved = repos.keys.index_with { |key| repo_nickname_for(key).to_s }
|
|
544
|
+
|
|
545
|
+
invalid = resolved.reject { |_key, nickname| nickname.match?(/\A[A-Za-z0-9_]+\z/) }
|
|
546
|
+
if invalid.present?
|
|
547
|
+
pairs = invalid.map { |key, nickname| "#{key.inspect} => #{nickname.inspect}" }
|
|
548
|
+
raise(
|
|
549
|
+
PlanMyStuff::ConfigurationError,
|
|
550
|
+
"Invalid repo nickname(s) (must match /\\A[A-Za-z0-9_]+\\z/): #{pairs.join(', ')}",
|
|
551
|
+
)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
dupes = resolved.group_by { |_key, nickname| nickname }.select { |_n, entries| entries.size > 1 }
|
|
555
|
+
return if dupes.blank?
|
|
556
|
+
|
|
557
|
+
details = dupes.map do |nickname, entries|
|
|
558
|
+
keys = entries.map { |entry| entry.first.inspect }.join(', ')
|
|
559
|
+
"#{nickname.inspect} (#{keys})"
|
|
560
|
+
end
|
|
561
|
+
raise(
|
|
562
|
+
PlanMyStuff::ConfigurationError,
|
|
563
|
+
"Duplicate repo nickname(s): #{details.join('; ')}",
|
|
564
|
+
)
|
|
565
|
+
end
|
|
135
566
|
end
|
|
136
567
|
|
|
137
568
|
class ConfigurationError < StandardError
|