plan_my_stuff 0.29.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 +38 -0
- data/README.md +20 -4
- data/app/controllers/plan_my_stuff/webhooks/aws_controller.rb +15 -3
- data/lib/plan_my_stuff/comment_metadata.rb +3 -46
- data/lib/plan_my_stuff/issue.rb +0 -24
- data/lib/plan_my_stuff/issue_metadata.rb +0 -66
- data/lib/plan_my_stuff/metadata_parser.rb +5 -28
- data/lib/plan_my_stuff/notifications.rb +6 -2
- data/lib/plan_my_stuff/pipeline.rb +15 -4
- data/lib/plan_my_stuff/version.rb +2 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3993e1306f27793791833787c546d81a42ed7bdf23d1420a658ec3877acf284b
|
|
4
|
+
data.tar.gz: 4593de2a00ea81ad22d0d951e1a97eb858a3d26071510c42e2e1aab9b99ae0f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a9e140d9c6371186961903c5251143a63f4bedb3c238fc2d2b69e25ef3263824bdeacf767494c46473a5c7e9560c50b057e9b82136505b3615bcacdc742b3b6c
|
|
7
|
+
data.tar.gz: f73cb36b19ced07d713ae6e1aee02ab314292b3410d8b14d160e2156ceedd4a202f2fcecff7f67b61d25c93887bbc7e694f4415b89bab1203faa1e8bf7d7566c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
Official release.
|
|
6
|
+
|
|
7
|
+
### Breaking
|
|
8
|
+
|
|
9
|
+
- `MetadataParser.parse` no longer recognizes the legacy `<!-- pms-metadata:... -->` HTML-comment format.
|
|
10
|
+
Bodies written before 0.17.0 that have not been re-saved will now be returned as plain body text with
|
|
11
|
+
empty metadata. The deprecation warning emitted by 0.17.0+ has been removed along with the legacy
|
|
12
|
+
pattern.
|
|
13
|
+
- Removed the deprecated `IssueMetadata#priority_list`, `#priority_list=`, `#priority_list?`,
|
|
14
|
+
`#priority_list_priority`, and `#priority_list_priority=` accessors, along with the legacy-write forwarding
|
|
15
|
+
that turned `metadata.priority_list = true` into `Priority List` / `Priority List Priority` issue-field writes
|
|
16
|
+
on `save!` / `update!` / `create!`. Use `Issue#priority_list?` / `#priority_list_priority` for reads and
|
|
17
|
+
`Issue#add_to_priority_list!(priority: N)` / `#remove_from_priority_list!` for writes (closes #57).
|
|
18
|
+
- `CommentMetadata` no longer parses the legacy `{filename, url}` attachment shape into structured fields.
|
|
19
|
+
Entries must now carry the `{filename, owner, repo, sha, path}` shape; legacy entries that were never
|
|
20
|
+
re-saved are dropped as malformed. The `LEGACY_URL_REGEXES` / `LEGACY_ATTACHMENT_DEPRECATION_MESSAGE`
|
|
21
|
+
constants and the migrate-on-write deprecation warning have been removed.
|
|
22
|
+
|
|
23
|
+
## 0.30.0
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- The AWS deployment-completed webhook now fires a single `pipeline_deployment_completed.plan_my_stuff` batch event
|
|
28
|
+
after the per-item sweep, carrying every item that actually transitioned (payload: `:project_items`, `:issue_numbers`,
|
|
29
|
+
`:commit_sha`). Per-item `pipeline_completed.plan_my_stuff` events still fire. The batch event is skipped when nothing
|
|
30
|
+
transitioned, giving subscribers (release-notes mailer, Slack summary, changelog generator) one clean
|
|
31
|
+
"deployment finished" signal without debouncing N per-item events (closes #97).
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- `PlanMyStuff::Notifications.instrument` now accepts an `Array` resource. The payload key is the pluralized element
|
|
36
|
+
key (a batch of project items keys as `:project_items`, a batch of issues as `:issues`), so batch events carry a full
|
|
37
|
+
set under one key.
|
|
38
|
+
- `PlanMyStuff::Pipeline.instrument` now accepts an `Array` of items for a batch event; the payload carries
|
|
39
|
+
`:issue_numbers` (the linked number of every item) in place of the single-item `:issue_number`.
|
|
40
|
+
|
|
3
41
|
## 0.29.0
|
|
4
42
|
|
|
5
43
|
### Changed
|
data/README.md
CHANGED
|
@@ -612,12 +612,20 @@ Cache keys include both the gem's `CACHE_VERSION` ("v1") and `config.cache_versi
|
|
|
612
612
|
|
|
613
613
|
## Metadata
|
|
614
614
|
|
|
615
|
-
All state lives on GitHub. Each domain class carries a typed metadata object
|
|
615
|
+
All state lives on GitHub. Each domain class carries a typed metadata object embedded in the corresponding GitHub
|
|
616
|
+
body (issue/comment body, project readme) as a collapsible `<details>` block containing a JSON code fence, which
|
|
617
|
+
GitHub renders in-page:
|
|
616
618
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
+
````markdown
|
|
620
|
+
<details><summary>pms-metadata</summary>
|
|
621
|
+
|
|
622
|
+
```json
|
|
623
|
+
{"schema_version":1,"gem_version":"0.0.0","app_name":"MyApp", ...}
|
|
619
624
|
```
|
|
620
625
|
|
|
626
|
+
</details>
|
|
627
|
+
````
|
|
628
|
+
|
|
621
629
|
`MetadataParser` strips the block out of the raw body on read and re-inserts it on write — callers see `issue.body` (human text) and `issue.metadata` (typed object) separately.
|
|
622
630
|
|
|
623
631
|
### Metadata classes
|
|
@@ -698,7 +706,15 @@ The engine mounts two webhook endpoints when the pipeline is enabled:
|
|
|
698
706
|
- `POST /webhooks/github` — takes GitHub `pull_request` events. On `closed` against a tracked branch, the gem calls `IssueLinker` to extract `#123` references from the PR body and commit messages, then transitions each linked issue (e.g. merged to main -> `start_deployment!`).
|
|
699
707
|
- `POST /webhooks/aws` — takes CodeDeploy/SNS lifecycle events. On a successful deployment, the gem flips matching items to `Completed`.
|
|
700
708
|
|
|
701
|
-
|
|
709
|
+
On a successful AWS deployment the gem completes each matching item individually (one
|
|
710
|
+
`pipeline_completed.plan_my_stuff` event apiece), then fires a single
|
|
711
|
+
`pipeline_deployment_completed.plan_my_stuff` batch event carrying every item that actually transitioned
|
|
712
|
+
(payload: `:project_items`, `:issue_numbers`, `:commit_sha`). It is skipped when nothing transitioned, so subscribers
|
|
713
|
+
(release-notes mailer, Slack summary, changelog generator) get one clean "deployment finished" signal
|
|
714
|
+
without debouncing the per-item events.
|
|
715
|
+
|
|
716
|
+
See `designs/release_cycle/plan.md` for the full design, including the `projects_v2_item` path that fires
|
|
717
|
+
`Pipeline.take!` when a human drags an item to Started on github.com.
|
|
702
718
|
|
|
703
719
|
### "Take" button
|
|
704
720
|
|
|
@@ -147,7 +147,10 @@ module PlanMyStuff
|
|
|
147
147
|
end
|
|
148
148
|
|
|
149
149
|
# Finds "Release in Progress" items whose linked issue commit SHA matches the configured production
|
|
150
|
-
# commit SHA (prefix match), then completes deployment for each.
|
|
150
|
+
# commit SHA (prefix match), then completes deployment for each. After the per-item sweep, fires one batch
|
|
151
|
+
# +pipeline_deployment_completed.plan_my_stuff+ event carrying every item that actually transitioned
|
|
152
|
+
# (+complete_deployment!+ returns +nil+ when auto-complete is off, so those are excluded). The batch event is
|
|
153
|
+
# skipped when nothing transitioned so subscribers only see real deployments.
|
|
151
154
|
#
|
|
152
155
|
# @return [void]
|
|
153
156
|
#
|
|
@@ -162,13 +165,22 @@ module PlanMyStuff
|
|
|
162
165
|
return
|
|
163
166
|
end
|
|
164
167
|
|
|
165
|
-
release_in_progress_items.
|
|
168
|
+
completed_items = release_in_progress_items.filter_map do |item|
|
|
166
169
|
next if item.draft?
|
|
167
170
|
|
|
168
171
|
next unless item.issue.metadata.commit_sha&.start_with?(sha)
|
|
169
172
|
|
|
170
|
-
PlanMyStuff::Pipeline.complete_deployment!(item)
|
|
173
|
+
result = PlanMyStuff::Pipeline.complete_deployment!(item)
|
|
174
|
+
item if result.present?
|
|
171
175
|
end
|
|
176
|
+
|
|
177
|
+
return if completed_items.empty?
|
|
178
|
+
|
|
179
|
+
PlanMyStuff::Pipeline.instrument(
|
|
180
|
+
'deployment_completed',
|
|
181
|
+
completed_items,
|
|
182
|
+
commit_sha: sha,
|
|
183
|
+
)
|
|
172
184
|
end
|
|
173
185
|
|
|
174
186
|
# Finds all pipeline project items at "Release in Progress" status.
|
|
@@ -2,17 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
4
|
class CommentMetadata < PlanMyStuff::BaseMetadata
|
|
5
|
-
LEGACY_URL_REGEXES = [
|
|
6
|
-
%r{\Ahttps://raw\.githubusercontent\.com/(?<owner>[^/\s]+)/(?<repo>[^/\s]+)/(?<sha>[^/\s]+)/(?<path>.+)\z},
|
|
7
|
-
%r{\Ahttps://github\.com/(?<owner>[^/\s]+)/(?<repo>[^/\s]+)/blob/(?<sha>[^/\s]+)/(?<path>.+)\z},
|
|
8
|
-
].freeze
|
|
9
|
-
|
|
10
|
-
LEGACY_ATTACHMENT_DEPRECATION_MESSAGE =
|
|
11
|
-
'PlanMyStuff: legacy attachment metadata shape {filename, url} detected. It will continue to parse ' \
|
|
12
|
-
'until 1.0.0, at which point legacy detection will be removed. New writes use the structured ' \
|
|
13
|
-
'{filename, owner, repo, sha, path} shape introduced in #70; existing entries migrate on their ' \
|
|
14
|
-
'next write.'
|
|
15
|
-
|
|
16
5
|
# @return [Boolean] true if this comment holds the issue's body content
|
|
17
6
|
attr_accessor :issue_body
|
|
18
7
|
# @return [Array<PlanMyStuff::Attachment>] consuming-app attachment records associated with this comment
|
|
@@ -95,11 +84,7 @@ module PlanMyStuff
|
|
|
95
84
|
# Builds a +PlanMyStuff::Attachment+ from each parsed entry.
|
|
96
85
|
# Accepts:
|
|
97
86
|
# - an existing +PlanMyStuff::Attachment+
|
|
98
|
-
# - the
|
|
99
|
-
# - the legacy shape +{filename:, url:}+ where +url+ is a raw
|
|
100
|
-
# +raw.githubusercontent.com+ permalink or a +github.com/.../blob/...+
|
|
101
|
-
# blob-viewer URL; parsed once into structured fields so the
|
|
102
|
-
# next write drops the +url+ key (migrate-on-write).
|
|
87
|
+
# - the structured shape +{filename:, owner:, repo:, sha:, path:}+
|
|
103
88
|
#
|
|
104
89
|
# Malformed hash entries are silently dropped so a single bad
|
|
105
90
|
# historical entry doesn't crash +Comment.find+. An already-
|
|
@@ -118,45 +103,17 @@ module PlanMyStuff
|
|
|
118
103
|
if entry.is_a?(PlanMyStuff::Attachment)
|
|
119
104
|
entry
|
|
120
105
|
elsif entry.respond_to?(:transform_keys)
|
|
121
|
-
PlanMyStuff::Attachment.new(
|
|
106
|
+
PlanMyStuff::Attachment.new(entry.transform_keys(&:to_sym))
|
|
122
107
|
end
|
|
123
108
|
next if attachment.nil?
|
|
124
109
|
|
|
125
110
|
attachment.validate!
|
|
126
111
|
attachment
|
|
127
|
-
rescue ActiveModel::ValidationError, ArgumentError
|
|
112
|
+
rescue ActiveModel::ValidationError, ActiveModel::UnknownAttributeError, ArgumentError
|
|
128
113
|
raise if entry.is_a?(PlanMyStuff::Attachment)
|
|
129
114
|
|
|
130
115
|
next
|
|
131
116
|
end
|
|
132
117
|
end
|
|
133
|
-
|
|
134
|
-
# Returns +attrs+ untouched when it already carries structured
|
|
135
|
-
# fields, otherwise parses a legacy +url+ into
|
|
136
|
-
# +owner+/+repo+/+sha+/+path+. Unparseable URLs yield a hash
|
|
137
|
-
# with no structured fields, leaving +Attachment+ validation to
|
|
138
|
-
# drop the entry.
|
|
139
|
-
#
|
|
140
|
-
# @param attrs [Hash{Symbol=>Object}]
|
|
141
|
-
#
|
|
142
|
-
# @return [Hash{Symbol=>Object}]
|
|
143
|
-
#
|
|
144
|
-
def structured_attrs(attrs)
|
|
145
|
-
return attrs if attrs.key?(:owner)
|
|
146
|
-
|
|
147
|
-
PlanMyStuff.deprecator.warn(LEGACY_ATTACHMENT_DEPRECATION_MESSAGE)
|
|
148
|
-
|
|
149
|
-
url = attrs[:url].to_s
|
|
150
|
-
match = LEGACY_URL_REGEXES.lazy.filter_map { |re| re.match(url) }.first
|
|
151
|
-
|
|
152
|
-
return attrs.except(:url) if match.nil?
|
|
153
|
-
|
|
154
|
-
attrs.except(:url).merge(
|
|
155
|
-
owner: match[:owner],
|
|
156
|
-
repo: match[:repo],
|
|
157
|
-
sha: match[:sha],
|
|
158
|
-
path: match[:path],
|
|
159
|
-
)
|
|
160
|
-
end
|
|
161
118
|
end
|
|
162
119
|
end
|
data/lib/plan_my_stuff/issue.rb
CHANGED
|
@@ -901,8 +901,6 @@ module PlanMyStuff
|
|
|
901
901
|
# @return [self]
|
|
902
902
|
#
|
|
903
903
|
def save!(user: nil, skip_notification: false)
|
|
904
|
-
forward_legacy_priority_list_metadata!
|
|
905
|
-
|
|
906
904
|
if new_record?
|
|
907
905
|
created = self.class.create!(
|
|
908
906
|
title: title,
|
|
@@ -1243,28 +1241,6 @@ module PlanMyStuff
|
|
|
1243
1241
|
reload
|
|
1244
1242
|
end
|
|
1245
1243
|
|
|
1246
|
-
# Forwards a pending legacy +metadata.priority_list+ /
|
|
1247
|
-
# +#priority_list_priority+ write into +@pending_issue_fields+ so the next
|
|
1248
|
-
# +save!+ persists the values into the +Priority List+ /
|
|
1249
|
-
# +Priority List Priority+ GitHub Issue Fields. Caller-supplied
|
|
1250
|
-
# +issue_fields:+ entries win on key collision. Silently skipped when
|
|
1251
|
-
# +config.issue_fields_enabled+ is +false+.
|
|
1252
|
-
#
|
|
1253
|
-
# @return [void]
|
|
1254
|
-
#
|
|
1255
|
-
def forward_legacy_priority_list_metadata!
|
|
1256
|
-
return unless PlanMyStuff.configuration.issue_fields_enabled
|
|
1257
|
-
return unless metadata.instance_variable_get(:@legacy_priority_list_pending)
|
|
1258
|
-
|
|
1259
|
-
legacy_pl = metadata.instance_variable_get(:@priority_list)
|
|
1260
|
-
legacy_plp = metadata.instance_variable_get(:@priority_list_priority)
|
|
1261
|
-
|
|
1262
|
-
legacy_fields = { 'Priority List' => legacy_pl ? 'Yes' : nil }
|
|
1263
|
-
legacy_fields['Priority List Priority'] = legacy_plp unless legacy_plp == -1
|
|
1264
|
-
|
|
1265
|
-
@pending_issue_fields = legacy_fields.merge(@pending_issue_fields || {})
|
|
1266
|
-
end
|
|
1267
|
-
|
|
1268
1244
|
# Applies in-memory updates from an +update!+ kwargs hash. Top-level scalars go through their setters so
|
|
1269
1245
|
# +@body_dirty+ and friends stay in sync; +metadata:+ is merged into +@metadata+ (top-level attrs assigned
|
|
1270
1246
|
# directly, custom_fields merged key-by-key).
|
|
@@ -2,13 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module PlanMyStuff
|
|
4
4
|
class IssueMetadata < PlanMyStuff::BaseMetadata
|
|
5
|
-
PRIORITY_LIST_METADATA_DEPRECATION =
|
|
6
|
-
'PlanMyStuff: IssueMetadata#priority_list / #priority_list_priority are deprecated. priority_list moved to ' \
|
|
7
|
-
"GitHub Issue Fields ('Priority List' single_select, 'Priority List Priority' number) in 0.20.0. Reads " \
|
|
8
|
-
'now come from Issue#priority_list? / #priority_list_priority; for writes, use ' \
|
|
9
|
-
'Issue#add_to_priority_list!(priority: N) and Issue#remove_from_priority_list!. The metadata accessors ' \
|
|
10
|
-
'will be removed in 1.0.0.'
|
|
11
|
-
|
|
12
5
|
# @return [Time, nil] first support action timestamp, nil until set
|
|
13
6
|
attr_accessor :responded_at
|
|
14
7
|
# @return [String, nil] user-facing URL in the consuming app
|
|
@@ -53,14 +46,6 @@ module PlanMyStuff
|
|
|
53
46
|
|
|
54
47
|
metadata.responded_at = parse_time(hash[:responded_at])
|
|
55
48
|
metadata.issues_url = hash[:issues_url]
|
|
56
|
-
if hash.key?(:priority_list) || hash.key?(:priority_list_priority)
|
|
57
|
-
PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
|
|
58
|
-
metadata.instance_variable_set(:@priority_list, hash[:priority_list]) if hash.key?(:priority_list)
|
|
59
|
-
if hash.key?(:priority_list_priority)
|
|
60
|
-
metadata.instance_variable_set(:@priority_list_priority, hash[:priority_list_priority])
|
|
61
|
-
end
|
|
62
|
-
metadata.instance_variable_set(:@legacy_priority_list_pending, true)
|
|
63
|
-
end
|
|
64
49
|
metadata.visibility_allowlist = Array.wrap(hash[:visibility_allowlist])
|
|
65
50
|
metadata.commit_sha = hash[:commit_sha]
|
|
66
51
|
metadata.auto_complete = hash.fetch(:auto_complete, true)
|
|
@@ -193,57 +178,6 @@ module PlanMyStuff
|
|
|
193
178
|
!!auto_complete
|
|
194
179
|
end
|
|
195
180
|
|
|
196
|
-
# @deprecated Use +Issue#priority_list?+. Removed in 1.0.0.
|
|
197
|
-
#
|
|
198
|
-
# @return [Object, nil]
|
|
199
|
-
#
|
|
200
|
-
def priority_list
|
|
201
|
-
PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
|
|
202
|
-
@priority_list
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# @deprecated Use +Issue#add_to_priority_list!(priority: N)+ / +Issue#remove_from_priority_list!+. Removed in 1.0.0.
|
|
206
|
-
#
|
|
207
|
-
# @param value [Object]
|
|
208
|
-
#
|
|
209
|
-
# @return [Object]
|
|
210
|
-
#
|
|
211
|
-
def priority_list=(value)
|
|
212
|
-
PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
|
|
213
|
-
@legacy_priority_list_pending = true
|
|
214
|
-
@priority_list = value
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
# @deprecated Use +Issue#priority_list?+. Removed in 1.0.0.
|
|
218
|
-
#
|
|
219
|
-
# @return [Boolean]
|
|
220
|
-
#
|
|
221
|
-
def priority_list?
|
|
222
|
-
PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
|
|
223
|
-
!!@priority_list
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
# @deprecated Use +Issue#priority_list_priority+. Removed in 1.0.0.
|
|
227
|
-
#
|
|
228
|
-
# @return [Object, nil]
|
|
229
|
-
#
|
|
230
|
-
def priority_list_priority
|
|
231
|
-
PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
|
|
232
|
-
@priority_list_priority
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# @deprecated Use +Issue#add_to_priority_list!(priority: N)+ / +Issue#remove_from_priority_list!+. Removed in 1.0.0.
|
|
236
|
-
#
|
|
237
|
-
# @param value [Object]
|
|
238
|
-
#
|
|
239
|
-
# @return [Object]
|
|
240
|
-
#
|
|
241
|
-
def priority_list_priority=(value)
|
|
242
|
-
PlanMyStuff.deprecator.warn(PRIORITY_LIST_METADATA_DEPRECATION)
|
|
243
|
-
@legacy_priority_list_pending = true
|
|
244
|
-
@priority_list_priority = value
|
|
245
|
-
end
|
|
246
|
-
|
|
247
181
|
# @return [Boolean]
|
|
248
182
|
def responded?
|
|
249
183
|
!responded_at.nil?
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'active_support/deprecation'
|
|
4
3
|
require 'json'
|
|
5
4
|
|
|
6
5
|
module PlanMyStuff
|
|
7
6
|
module MetadataParser
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'until 1.0.0, at which point legacy detection will be removed. New writes already use the visible ' \
|
|
11
|
-
'<details> block format introduced in 0.17.0; existing bodies migrate on their next write.'
|
|
12
|
-
|
|
13
|
-
# New format: collapsible <details> block containing a JSON code fence.
|
|
14
|
-
# Renders visibly on GitHub (issue #58) instead of being hidden in an HTML comment.
|
|
7
|
+
# Collapsible <details> block containing a JSON code fence. Renders visibly on
|
|
8
|
+
# GitHub (issue #58) instead of being hidden in an HTML comment.
|
|
15
9
|
METADATA_PATTERN = %r{
|
|
16
10
|
\A<details><summary>pms-metadata</summary>\n\n
|
|
17
11
|
```json\n(.*?)\n```\n\n
|
|
@@ -27,10 +21,6 @@ module PlanMyStuff
|
|
|
27
21
|
</details>\n*
|
|
28
22
|
}mx
|
|
29
23
|
|
|
30
|
-
# Legacy format kept for parsing only - existing issues serialized before 0.17.0
|
|
31
|
-
# used a hidden HTML comment. They migrate to the new format on the next write.
|
|
32
|
-
LEGACY_METADATA_PATTERN = /\A<!-- pms-metadata:(.*?) -->\n*/m
|
|
33
|
-
|
|
34
24
|
module_function
|
|
35
25
|
|
|
36
26
|
# Extracts metadata JSON from the raw body
|
|
@@ -41,15 +31,11 @@ module PlanMyStuff
|
|
|
41
31
|
#
|
|
42
32
|
def parse(raw_body)
|
|
43
33
|
return { metadata: {}, body: '' } if raw_body.blank?
|
|
34
|
+
return { metadata: {}, body: raw_body } unless raw_body.match?(METADATA_PATTERN)
|
|
44
35
|
|
|
45
|
-
|
|
46
|
-
return { metadata: {}, body: raw_body } if pattern.nil?
|
|
47
|
-
|
|
48
|
-
PlanMyStuff.deprecator.warn(LEGACY_DEPRECATION_MESSAGE) if pattern == LEGACY_METADATA_PATTERN
|
|
49
|
-
|
|
50
|
-
match = raw_body.match(pattern)
|
|
36
|
+
match = raw_body.match(METADATA_PATTERN)
|
|
51
37
|
metadata = JSON.parse(match[1], symbolize_names: true)
|
|
52
|
-
body = raw_body.sub(
|
|
38
|
+
body = raw_body.sub(METADATA_PATTERN, '').sub(ATTACHMENTS_PATTERN, '')
|
|
53
39
|
|
|
54
40
|
{ metadata: metadata, body: body }
|
|
55
41
|
rescue JSON::ParserError
|
|
@@ -102,14 +88,5 @@ module PlanMyStuff
|
|
|
102
88
|
safe_filename = attachment[:filename].to_s.gsub(']', '\]')
|
|
103
89
|
"- [#{safe_filename}](#{url})"
|
|
104
90
|
end
|
|
105
|
-
|
|
106
|
-
# @param raw_body [String]
|
|
107
|
-
#
|
|
108
|
-
# @return [Regexp, nil] the pattern that matches at the start of +raw_body+, or +nil+ when neither matches
|
|
109
|
-
#
|
|
110
|
-
def matching_pattern(raw_body)
|
|
111
|
-
return METADATA_PATTERN if raw_body.match?(METADATA_PATTERN)
|
|
112
|
-
return LEGACY_METADATA_PATTERN if raw_body.match?(LEGACY_METADATA_PATTERN)
|
|
113
|
-
end
|
|
114
91
|
end
|
|
115
92
|
end
|
|
@@ -21,7 +21,8 @@ module PlanMyStuff
|
|
|
21
21
|
# Fires +<event>.plan_my_stuff+ with a normalized payload.
|
|
22
22
|
#
|
|
23
23
|
# @param event [String] e.g. +'issue_created'+
|
|
24
|
-
# @param resource [Object] domain object (+Issue+, +Comment+, +ProjectItem+, ...)
|
|
24
|
+
# @param resource [Object] domain object (+Issue+, +Comment+, +ProjectItem+, ...), or an +Array+ of resources for a
|
|
25
|
+
# batch event (keyed by the pluralized element key, e.g. +:project_items+)
|
|
25
26
|
# @param user [Object, nil] explicit actor; falls back to +config.current_user+
|
|
26
27
|
# @param extra [Hash] additional payload entries (+changes:+, +labels:+, +user_ids:+, ...)
|
|
27
28
|
#
|
|
@@ -63,7 +64,9 @@ module PlanMyStuff
|
|
|
63
64
|
payload.merge(extra)
|
|
64
65
|
end
|
|
65
66
|
|
|
66
|
-
# Maps a resource object to its payload key.
|
|
67
|
+
# Maps a resource object to its payload key. An +Array+ recurses on its first element and pluralizes that key, so
|
|
68
|
+
# batch events carry the full set under one key (a batch of project items keys as +:project_items+, a batch of
|
|
69
|
+
# issues as +:issues+, an empty/unknown batch as +:resources+).
|
|
67
70
|
#
|
|
68
71
|
# @param resource [Object]
|
|
69
72
|
#
|
|
@@ -74,6 +77,7 @@ module PlanMyStuff
|
|
|
74
77
|
when PlanMyStuff::Issue then :issue
|
|
75
78
|
when PlanMyStuff::Comment then :comment
|
|
76
79
|
when PlanMyStuff::BaseProjectItem then :project_item
|
|
80
|
+
when Array then :"#{infer_resource_key(resource.first)}s"
|
|
77
81
|
else :resource
|
|
78
82
|
end
|
|
79
83
|
end
|
|
@@ -55,13 +55,17 @@ module PlanMyStuff
|
|
|
55
55
|
# canonical status is added to the payload as +:status+. Otherwise +event+ is used verbatim as the suffix (e.g.
|
|
56
56
|
# +"removed"+, +"removed_late"+, +"testing"+).
|
|
57
57
|
#
|
|
58
|
+
# +resource+ is normally a single project item (payload carries +:issue_number+). Pass an +Array+ for a batch
|
|
59
|
+
# event (e.g. the deployment-completed sweep) and the payload instead carries +:issue_numbers+ -- the linked
|
|
60
|
+
# number of every item.
|
|
61
|
+
#
|
|
58
62
|
# @param event [String] status name or literal event suffix
|
|
59
|
-
# @param
|
|
63
|
+
# @param resource [PlanMyStuff::BaseProjectItem, Array<PlanMyStuff::BaseProjectItem>] item or batch of items
|
|
60
64
|
# @param extra [Hash] additional payload entries
|
|
61
65
|
#
|
|
62
66
|
# @return [void]
|
|
63
67
|
#
|
|
64
|
-
def instrument(event,
|
|
68
|
+
def instrument(event, resource, **extra)
|
|
65
69
|
extra_to_use = { **extra }
|
|
66
70
|
event_to_use = event
|
|
67
71
|
if PlanMyStuff::Pipeline::Status::ALL.include?(event_to_use)
|
|
@@ -69,10 +73,17 @@ module PlanMyStuff
|
|
|
69
73
|
event_to_use = PlanMyStuff::Pipeline::Status.key_for(event_to_use)
|
|
70
74
|
end
|
|
71
75
|
|
|
76
|
+
number_fields =
|
|
77
|
+
if resource.is_a?(Array)
|
|
78
|
+
{ issue_numbers: resource.map(&:number) }
|
|
79
|
+
else
|
|
80
|
+
{ issue_number: resource.number }
|
|
81
|
+
end
|
|
82
|
+
|
|
72
83
|
PlanMyStuff::Notifications.instrument(
|
|
73
84
|
"pipeline_#{event_to_use}",
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
resource,
|
|
86
|
+
**number_fields,
|
|
76
87
|
**extra_to_use,
|
|
77
88
|
)
|
|
78
89
|
end
|