plan_my_stuff 0.20.0 → 0.21.1
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 +28 -0
- data/CONFIGURATION.md +27 -0
- data/lib/generators/plan_my_stuff/install/templates/initializer.rb +19 -0
- data/lib/plan_my_stuff/attachment.rb +26 -20
- data/lib/plan_my_stuff/attachment_uploader.rb +9 -4
- data/lib/plan_my_stuff/comment_metadata.rb +48 -4
- data/lib/plan_my_stuff/configuration.rb +10 -0
- data/lib/plan_my_stuff/engine.rb +16 -0
- data/lib/plan_my_stuff/metadata_parser.rb +40 -8
- data/lib/plan_my_stuff/version.rb +2 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4eda8634e75b318cd847d512405f71d4ffe2b0d7a6beccd6404912a6c64f1c2b
|
|
4
|
+
data.tar.gz: 80c7edb2a0e861c3dc0f76b06ae23232bde0ed52d04f4cdada69bd29aa8107dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3336630d6d5ee6b8bad51fdfa08a647aa58933e0f30a36241fd137a4d3f5b67656386d36fd0990d289499a7cf2643469e32c1642d4f2d5811be5fc671ea9d420
|
|
7
|
+
data.tar.gz: 15db293bc2203d60ca7e2b5d102035b1a8d064a74b84666e13d0fbcad1ab15dbe576f02af2f7582c56c9f65246dbb23e41043f6036e304801722c40890c463fc
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.21.1
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `config.eager_load_controllers_on_boot` (default `false`). When `true`, the engine eager-loads
|
|
8
|
+
`app/controllers/` in `after_initialize` so consuming apps with `eager_load = false` (dev mode) see
|
|
9
|
+
`PlanMyStuff::*Controller` constants without having to reference them first. Opt in if the host app
|
|
10
|
+
probes engine controllers via `defined?`, e.g. `defined?(PlanMyStuff::Issues::TakesController)` would
|
|
11
|
+
otherwise return `nil` until something triggered Zeitwerk autoload. Falls back to `Kernel#require` on
|
|
12
|
+
Zeitwerk < 2.6.2 (which lacks `eager_load_dir`).
|
|
13
|
+
|
|
14
|
+
## 0.21.0
|
|
15
|
+
|
|
16
|
+
### Breaking
|
|
17
|
+
|
|
18
|
+
- `PMS::Attachment` no longer accepts/stores a single `url:` attribute. Construct with explicit
|
|
19
|
+
`filename:`, `owner:`, `repo:`, `sha:`, `path:` instead; `#url` is now a derived method that returns the
|
|
20
|
+
GitHub blob viewer URL (`https://github.com/{owner}/{repo}/blob/{sha}/{path}`) rather than the
|
|
21
|
+
`raw.githubusercontent.com` URL. `#to_h` emits the structured fields and no longer includes a `url` key.
|
|
22
|
+
Persisted comment metadata in the legacy `{filename, url}` shape is migrated on read and rewritten in the
|
|
23
|
+
new shape on next save (closes #70).
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Comment bodies now render a visible `<details><summary>attachments (N)</summary>` block listing each
|
|
28
|
+
attachment as a markdown link to its blob URL, between the `pms-metadata` block and the user body. The
|
|
29
|
+
parser strips this block on read so round-trips stay clean.
|
|
30
|
+
|
|
3
31
|
## 0.20.0
|
|
4
32
|
|
|
5
33
|
### Breaking
|
data/CONFIGURATION.md
CHANGED
|
@@ -49,6 +49,19 @@ config.repo_nicknames = { safety: 'Compliance' } # :element -> "Element", :under
|
|
|
49
49
|
`Issue#to_param` then returns `"Element-1234"` / `"Compliance-567"`, encoding both repo and number in a single
|
|
50
50
|
URL segment so `youtrack_issue_path(@issue)` works without a `repo:` query param.
|
|
51
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
|
+
|
|
52
65
|
## Projects
|
|
53
66
|
|
|
54
67
|
| Option | Type | Default | Description |
|
|
@@ -337,6 +350,20 @@ config.cache_version = Rails.configuration.x.image_tag
|
|
|
337
350
|
config.mount_groups = { webhooks: true, issues: true, projects: true }
|
|
338
351
|
```
|
|
339
352
|
|
|
353
|
+
## Boot behavior
|
|
354
|
+
|
|
355
|
+
| Option | Type | Default | Description |
|
|
356
|
+
|---|---|---|---|
|
|
357
|
+
| `eager_load_controllers_on_boot` | `Boolean` | `false` | Eager-load engine controllers in `after_initialize`. |
|
|
358
|
+
|
|
359
|
+
Opt in if the host app probes engine controllers via `defined?` in dev mode. When `true`, the
|
|
360
|
+
engine walks `app/controllers` once on boot so `defined?(PlanMyStuff::SomeController)` resolves
|
|
361
|
+
without first referencing the constant.
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
config.eager_load_controllers_on_boot = true
|
|
365
|
+
```
|
|
366
|
+
|
|
340
367
|
## Controller overrides
|
|
341
368
|
|
|
342
369
|
| Option | Type | Default | Description |
|
|
@@ -30,6 +30,15 @@ PlanMyStuff.configure do |config|
|
|
|
30
30
|
# need an entry.
|
|
31
31
|
# config.repo_nicknames = { safety: 'Compliance' }
|
|
32
32
|
|
|
33
|
+
# --------------------------------------------------------------------------
|
|
34
|
+
# Attachments
|
|
35
|
+
# --------------------------------------------------------------------------
|
|
36
|
+
# Bare repo name (under `organization`) that stores uploaded attachment
|
|
37
|
+
# binaries. The repo must already exist; the uploader does not create it.
|
|
38
|
+
# Attachments commit onto `config.main_branch` under
|
|
39
|
+
# `<repo_key_or_name>/issue-<number>/<uuid>.<ext>`.
|
|
40
|
+
# config.attachment_repo = 'pms-attachments'
|
|
41
|
+
|
|
33
42
|
# --------------------------------------------------------------------------
|
|
34
43
|
# Projects
|
|
35
44
|
# --------------------------------------------------------------------------
|
|
@@ -184,6 +193,16 @@ PlanMyStuff.configure do |config|
|
|
|
184
193
|
#
|
|
185
194
|
# config.issue_fields_enabled = false
|
|
186
195
|
|
|
196
|
+
# --------------------------------------------------------------------------
|
|
197
|
+
# Boot behavior
|
|
198
|
+
# --------------------------------------------------------------------------
|
|
199
|
+
# Eager-load the engine's controllers in after_initialize so that
|
|
200
|
+
# `defined?(PlanMyStuff::SomeController)` resolves without first referencing
|
|
201
|
+
# the constant. Opt in if the host app probes engine controllers via
|
|
202
|
+
# `defined?` in dev mode.
|
|
203
|
+
#
|
|
204
|
+
# config.eager_load_controllers_on_boot = true
|
|
205
|
+
|
|
187
206
|
# --------------------------------------------------------------------------
|
|
188
207
|
# Release pipeline
|
|
189
208
|
# --------------------------------------------------------------------------
|
|
@@ -6,33 +6,40 @@ require 'tmpdir'
|
|
|
6
6
|
module PlanMyStuff
|
|
7
7
|
# Value object representing a single attachment record on a +Comment+.
|
|
8
8
|
# Persisted in +CommentMetadata#attachments+; the gem owns the upload
|
|
9
|
-
# (see +PlanMyStuff::AttachmentUploader+) and stores
|
|
10
|
-
# +
|
|
11
|
-
# across later branch rewrites.
|
|
9
|
+
# (see +PlanMyStuff::AttachmentUploader+) and stores the structured
|
|
10
|
+
# location (+owner+/+repo+/+sha+/+path+) of the uploaded file so the
|
|
11
|
+
# blob remains addressable across later branch rewrites.
|
|
12
12
|
#
|
|
13
13
|
# Mirrors +PlanMyStuff::Link+ / +PlanMyStuff::Approval+:
|
|
14
14
|
# +ActiveModel::Attributes+-backed, with +Serializers::JSON+ for
|
|
15
15
|
# round-trip through the metadata blob.
|
|
16
16
|
#
|
|
17
17
|
class Attachment
|
|
18
|
-
RAW_URL_REGEX =
|
|
19
|
-
%r{\Ahttps://raw\.githubusercontent\.com/(?<owner>[^/\s]+)/(?<repo>[^/\s]+)/(?<sha>[^/\s]+)/(?<path>.+)\z}
|
|
20
|
-
|
|
21
18
|
include ActiveModel::Model
|
|
22
19
|
include ActiveModel::Attributes
|
|
23
20
|
include ActiveModel::Serializers::JSON
|
|
24
21
|
|
|
25
22
|
# @return [String] display filename (user-provided original name)
|
|
26
23
|
attribute :filename, :string
|
|
27
|
-
# @return [String]
|
|
28
|
-
attribute :
|
|
24
|
+
# @return [String] owner of the attachment repo (e.g. +"BrandsInsurance"+)
|
|
25
|
+
attribute :owner, :string
|
|
26
|
+
# @return [String] attachment repo name
|
|
27
|
+
attribute :repo, :string
|
|
28
|
+
# @return [String] commit SHA pinning the file
|
|
29
|
+
attribute :sha, :string
|
|
30
|
+
# @return [String] path within the attachment repo (no leading slash)
|
|
31
|
+
attribute :path, :string
|
|
32
|
+
|
|
33
|
+
validates :filename, :owner, :repo, :sha, :path, presence: true
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
# @return [String] blob-viewer URL for the pinned file
|
|
36
|
+
def url
|
|
37
|
+
"https://github.com/#{owner}/#{repo}/blob/#{sha}/#{path}"
|
|
38
|
+
end
|
|
32
39
|
|
|
33
40
|
# @return [Hash]
|
|
34
41
|
def to_h
|
|
35
|
-
{ filename: filename,
|
|
42
|
+
{ filename: filename, owner: owner, repo: repo, sha: sha, path: path }
|
|
36
43
|
end
|
|
37
44
|
|
|
38
45
|
# @param other [Object]
|
|
@@ -56,22 +63,21 @@ module PlanMyStuff
|
|
|
56
63
|
# Defaults the destination to +Dir.tmpdir+ joined with +File.basename(filename)+ (directory components
|
|
57
64
|
# stripped to prevent path traversal).
|
|
58
65
|
#
|
|
59
|
-
# @param
|
|
66
|
+
# @param dest [String, nil] destination path; defaults to +File.join(Dir.tmpdir, File.basename(filename))+
|
|
60
67
|
#
|
|
61
68
|
# @return [String] the path the bytes were written to
|
|
62
69
|
#
|
|
63
|
-
def download_to(
|
|
64
|
-
|
|
65
|
-
match = RAW_URL_REGEX.match(url)
|
|
70
|
+
def download_to(dest = nil)
|
|
71
|
+
dest ||= File.join(Dir.tmpdir, File.basename(filename.to_s))
|
|
66
72
|
body = PlanMyStuff.client.rest(
|
|
67
73
|
:contents,
|
|
68
|
-
"#{
|
|
69
|
-
path:
|
|
70
|
-
ref:
|
|
74
|
+
"#{owner}/#{repo}",
|
|
75
|
+
path: path,
|
|
76
|
+
ref: sha,
|
|
71
77
|
accept: 'application/vnd.github.raw',
|
|
72
78
|
)
|
|
73
|
-
File.binwrite(
|
|
74
|
-
|
|
79
|
+
File.binwrite(dest, body)
|
|
80
|
+
dest
|
|
75
81
|
end
|
|
76
82
|
end
|
|
77
83
|
end
|
|
@@ -6,8 +6,8 @@ require 'securerandom'
|
|
|
6
6
|
|
|
7
7
|
module PlanMyStuff
|
|
8
8
|
# Uploads attachment files to a shared attachment repo (+config.attachment_repo+ under +config.organization+) using
|
|
9
|
-
# GitHub's Git Data API, returning
|
|
10
|
-
# +
|
|
9
|
+
# GitHub's Git Data API, returning +PlanMyStuff::Attachment+ instances populated with the structured
|
|
10
|
+
# +owner+/+repo+/+sha+/+path+ location of each uploaded file.
|
|
11
11
|
#
|
|
12
12
|
# One commit per batch (atomic): all files in a single +upload_all!+ call land in the same tree/commit so partial
|
|
13
13
|
# failures cannot leave +config.main_branch+ in an inconsistent state.
|
|
@@ -202,8 +202,13 @@ module PlanMyStuff
|
|
|
202
202
|
# @return [PlanMyStuff::Attachment]
|
|
203
203
|
#
|
|
204
204
|
def build_attachment(commit_sha:, slot:)
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
PlanMyStuff::Attachment.new(
|
|
206
|
+
filename: slot[:filename],
|
|
207
|
+
owner: PlanMyStuff.configuration.organization,
|
|
208
|
+
repo: PlanMyStuff.configuration.attachment_repo,
|
|
209
|
+
sha: commit_sha,
|
|
210
|
+
path: slot[:path],
|
|
211
|
+
)
|
|
207
212
|
end
|
|
208
213
|
|
|
209
214
|
# @return [String] +"<organization>/<attachment_repo>"+
|
|
@@ -2,6 +2,17 @@
|
|
|
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
|
+
|
|
5
16
|
# @return [Boolean] true if this comment holds the issue's body content
|
|
6
17
|
attr_accessor :issue_body
|
|
7
18
|
# @return [Array<PlanMyStuff::Attachment>] consuming-app attachment records associated with this comment
|
|
@@ -82,9 +93,14 @@ module PlanMyStuff
|
|
|
82
93
|
private
|
|
83
94
|
|
|
84
95
|
# Builds a +PlanMyStuff::Attachment+ from each parsed entry.
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
96
|
+
# Accepts:
|
|
97
|
+
# - an existing +PlanMyStuff::Attachment+
|
|
98
|
+
# - the new shape +{filename:, owner:, repo:, sha:, path:}+
|
|
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).
|
|
103
|
+
#
|
|
88
104
|
# Malformed hash entries are silently dropped so a single bad
|
|
89
105
|
# historical entry doesn't crash +Comment.find+. An already-
|
|
90
106
|
# constructed +Attachment+ that fails validation raises so the
|
|
@@ -102,7 +118,7 @@ module PlanMyStuff
|
|
|
102
118
|
if entry.is_a?(PlanMyStuff::Attachment)
|
|
103
119
|
entry
|
|
104
120
|
elsif entry.respond_to?(:transform_keys)
|
|
105
|
-
PlanMyStuff::Attachment.new(entry.transform_keys(&:to_sym))
|
|
121
|
+
PlanMyStuff::Attachment.new(structured_attrs(entry.transform_keys(&:to_sym)))
|
|
106
122
|
end
|
|
107
123
|
next if attachment.nil?
|
|
108
124
|
|
|
@@ -114,5 +130,33 @@ module PlanMyStuff
|
|
|
114
130
|
next
|
|
115
131
|
end
|
|
116
132
|
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
|
|
117
161
|
end
|
|
118
162
|
end
|
|
@@ -373,6 +373,15 @@ module PlanMyStuff
|
|
|
373
373
|
#
|
|
374
374
|
attr_accessor :issue_fields_enabled
|
|
375
375
|
|
|
376
|
+
# Whether to eager-load the engine's controllers on host boot. Defaults to +false+ (opt-in). When +true+, the engine
|
|
377
|
+
# walks +app/controllers+ during +after_initialize+ so +defined?(PlanMyStuff::SomeController)+ resolves without
|
|
378
|
+
# first referencing the constant. Enable in host apps that rely on +defined?+ probes against engine controllers in
|
|
379
|
+
# dev mode.
|
|
380
|
+
#
|
|
381
|
+
# @return [Boolean]
|
|
382
|
+
#
|
|
383
|
+
attr_accessor :eager_load_controllers_on_boot
|
|
384
|
+
|
|
376
385
|
# @return [Configuration]
|
|
377
386
|
def initialize
|
|
378
387
|
@repos = {}
|
|
@@ -413,6 +422,7 @@ module PlanMyStuff
|
|
|
413
422
|
@pipeline_completion_purge_enabled = true
|
|
414
423
|
@pipeline_completion_ttl_hours = 24
|
|
415
424
|
@issue_fields_enabled = true
|
|
425
|
+
@eager_load_controllers_on_boot = false
|
|
416
426
|
@process_aws_webhooks = Rails.env.production?
|
|
417
427
|
@sns_verifier_class = ::Aws::SNS::MessageVerifier if defined?(::Aws::SNS::MessageVerifier)
|
|
418
428
|
@sns_verifier_error =
|
data/lib/plan_my_stuff/engine.rb
CHANGED
|
@@ -3,5 +3,21 @@
|
|
|
3
3
|
module PlanMyStuff
|
|
4
4
|
class Engine < ::Rails::Engine
|
|
5
5
|
isolate_namespace PlanMyStuff
|
|
6
|
+
|
|
7
|
+
# Opt-in (via +config.eager_load_controllers_on_boot+): eager-load the engine's controllers regardless of the host
|
|
8
|
+
# app's eager_load setting. Without this, `defined?(PlanMyStuff::SomeController)` returns nil in host-app dev mode
|
|
9
|
+
# until something explicitly references the constant, since `defined?` does not trigger Zeitwerk autoload.
|
|
10
|
+
config.after_initialize do
|
|
11
|
+
next unless PlanMyStuff.configuration.eager_load_controllers_on_boot
|
|
12
|
+
|
|
13
|
+
controllers_dir = File.expand_path('../../app/controllers', __dir__)
|
|
14
|
+
loader = Rails.autoloaders.main
|
|
15
|
+
|
|
16
|
+
if loader.respond_to?(:eager_load_dir)
|
|
17
|
+
loader.eager_load_dir(controllers_dir)
|
|
18
|
+
else
|
|
19
|
+
Dir.glob(File.join(controllers_dir, '**/*.rb')).sort.each { |path| require path }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
6
22
|
end
|
|
7
23
|
end
|
|
@@ -18,6 +18,15 @@ module PlanMyStuff
|
|
|
18
18
|
</details>\n*
|
|
19
19
|
}mx
|
|
20
20
|
|
|
21
|
+
# Visible attachments block emitted after the metadata block when the
|
|
22
|
+
# metadata carries non-empty +attachments+ (issue #70). Parse strips it
|
|
23
|
+
# so round-trips stay clean.
|
|
24
|
+
ATTACHMENTS_PATTERN = %r{
|
|
25
|
+
\A<details><summary>attachments\ \(\d+\)</summary>\n\n
|
|
26
|
+
.*?\n\n
|
|
27
|
+
</details>\n*
|
|
28
|
+
}mx
|
|
29
|
+
|
|
21
30
|
# Legacy format kept for parsing only - existing issues serialized before 0.17.0
|
|
22
31
|
# used a hidden HTML comment. They migrate to the new format on the next write.
|
|
23
32
|
LEGACY_METADATA_PATTERN = /\A<!-- pms-metadata:(.*?) -->\n*/m
|
|
@@ -40,7 +49,7 @@ module PlanMyStuff
|
|
|
40
49
|
|
|
41
50
|
match = raw_body.match(pattern)
|
|
42
51
|
metadata = JSON.parse(match[1], symbolize_names: true)
|
|
43
|
-
body = raw_body.sub(pattern, '')
|
|
52
|
+
body = raw_body.sub(pattern, '').sub(ATTACHMENTS_PATTERN, '')
|
|
44
53
|
|
|
45
54
|
{ metadata: metadata, body: body }
|
|
46
55
|
rescue JSON::ParserError
|
|
@@ -61,14 +70,37 @@ module PlanMyStuff
|
|
|
61
70
|
raise(ArgumentError, "metadata must be a Hash or PlanMyStuff::CustomFields, got #{metadata.class}")
|
|
62
71
|
end
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
73
|
+
hash = metadata.is_a?(PlanMyStuff::CustomFields) ? metadata.to_h : metadata
|
|
74
|
+
json = JSON.pretty_generate(hash)
|
|
75
|
+
|
|
76
|
+
"<details><summary>pms-metadata</summary>\n\n```json\n#{json}\n```\n\n</details>\n\n" \
|
|
77
|
+
"#{attachments_block(hash)}#{body}"
|
|
78
|
+
end
|
|
70
79
|
|
|
71
|
-
|
|
80
|
+
# Renders the visible attachments block when +metadata+ carries
|
|
81
|
+
# non-empty +:attachments+, otherwise returns an empty string.
|
|
82
|
+
#
|
|
83
|
+
# @param metadata [Hash]
|
|
84
|
+
#
|
|
85
|
+
# @return [String]
|
|
86
|
+
#
|
|
87
|
+
def attachments_block(metadata)
|
|
88
|
+
attachments = metadata[:attachments]
|
|
89
|
+
return '' if attachments.blank?
|
|
90
|
+
|
|
91
|
+
lines = attachments.map { |a| attachment_line(a) }.join("\n")
|
|
92
|
+
"<details><summary>attachments (#{attachments.size})</summary>\n\n#{lines}\n\n</details>\n\n"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @param attachment [Hash{Symbol=>String}]
|
|
96
|
+
#
|
|
97
|
+
# @return [String]
|
|
98
|
+
#
|
|
99
|
+
def attachment_line(attachment)
|
|
100
|
+
url = "https://github.com/#{attachment[:owner]}/#{attachment[:repo]}" \
|
|
101
|
+
"/blob/#{attachment[:sha]}/#{attachment[:path]}"
|
|
102
|
+
safe_filename = attachment[:filename].to_s.gsub(']', '\]')
|
|
103
|
+
"- [#{safe_filename}](#{url})"
|
|
72
104
|
end
|
|
73
105
|
|
|
74
106
|
# @param raw_body [String]
|
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.
|
|
4
|
+
version: 0.21.1
|
|
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-05-
|
|
11
|
+
date: 2026-05-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|