plan_my_stuff 0.28.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9aac5ec777cf9b53a459843e9bdbf1177178fe1623457a60d266e993db3d2d93
4
- data.tar.gz: 1b7089b760fa0552e3b861732472e43cab09667699e1528703809a2ce753254f
3
+ metadata.gz: bf390e409623eaacc5e3dcd727984ed93212cdc23847fc9a848de150d1c025af
4
+ data.tar.gz: 70eeabeadf4d593c5a97806ad013e24da2175123e4f6e5bf071b3beee9da2937
5
5
  SHA512:
6
- metadata.gz: 84f1cc4e7dd87ed3e33f2095d3c0f57c9ad61972ffaa5ed1d9379dfb6bb0e7bc1b07491c76c6c4f4618c39b977d7240689de5552a8c9f60fe1b474c29dde2637
7
- data.tar.gz: 1d355a051c6d6f0903c80558019790ab0e1d84adb8885b28b0c18b57802ed16c338ab7793ca24f535dc01980296fcc33b19eacc3acd204d08586db25d63138d4
6
+ metadata.gz: 928aa5abb220e785a6bb04586645af1a5bdc0c3a889f49d08ddc4882ecb0b21f5aa0bf53a724749d2458905ad58da4a116e09e31aeb59686ddeda1b16f53b9e1
7
+ data.tar.gz: 66d5e0ec45da7e3b5ed2e9d2a0e1a088012008a5bca1ff5dd4773d66fd3d84ad08ffb977c29ff9276611d011b25a543464d00c0063fda29786a6610d27c6166f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.30.0
4
+
5
+ ### Added
6
+
7
+ - The AWS deployment-completed webhook now fires a single `pipeline_deployment_completed.plan_my_stuff` batch event
8
+ after the per-item sweep, carrying every item that actually transitioned (payload: `:project_items`, `:issue_numbers`,
9
+ `:commit_sha`). Per-item `pipeline_completed.plan_my_stuff` events still fire. The batch event is skipped when nothing
10
+ transitioned, giving subscribers (release-notes mailer, Slack summary, changelog generator) one clean
11
+ "deployment finished" signal without debouncing N per-item events (closes #97).
12
+
13
+ ### Changed
14
+
15
+ - `PlanMyStuff::Notifications.instrument` now accepts an `Array` resource. The payload key is the pluralized element
16
+ key (a batch of project items keys as `:project_items`, a batch of issues as `:issues`), so batch events carry a full
17
+ set under one key.
18
+ - `PlanMyStuff::Pipeline.instrument` now accepts an `Array` of items for a batch event; the payload carries
19
+ `:issue_numbers` (the linked number of every item) in place of the single-item `:issue_number`.
20
+
21
+ ## 0.29.0
22
+
23
+ ### Changed
24
+
25
+ - `Pipeline.complete_deployment!` now sets the linked issue's `Issue Status` field to `Fixed` on the auto-complete
26
+ success path (alongside moving the item to `Completed`). Gated on `config.issue_fields_enabled`, so completion still
27
+ succeeds when issue fields are disabled. The auto-complete-off early return is unchanged (closes #96).
28
+
3
29
  ## 0.28.0
4
30
 
5
31
  ### Added
data/README.md CHANGED
@@ -698,7 +698,15 @@ The engine mounts two webhook endpoints when the pipeline is enabled:
698
698
  - `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
699
  - `POST /webhooks/aws` — takes CodeDeploy/SNS lifecycle events. On a successful deployment, the gem flips matching items to `Completed`.
700
700
 
701
- See `designs/release_cycle/plan.md` for the full design, including the `projects_v2_item` path that fires `Pipeline.take!` when a human drags an item to Started on github.com.
701
+ On a successful AWS deployment the gem completes each matching item individually (one
702
+ `pipeline_completed.plan_my_stuff` event apiece), then fires a single
703
+ `pipeline_deployment_completed.plan_my_stuff` batch event carrying every item that actually transitioned
704
+ (payload: `:project_items`, `:issue_numbers`, `:commit_sha`). It is skipped when nothing transitioned, so subscribers
705
+ (release-notes mailer, Slack summary, changelog generator) get one clean "deployment finished" signal
706
+ without debouncing the per-item events.
707
+
708
+ See `designs/release_cycle/plan.md` for the full design, including the `projects_v2_item` path that fires
709
+ `Pipeline.take!` when a human drags an item to Started on github.com.
702
710
 
703
711
  ### "Take" button
704
712
 
@@ -23,10 +23,11 @@ module PlanMyStuff
23
23
  return
24
24
  end
25
25
 
26
- issue = PlanMyStuff::Issue.find(params[:issue_id])
27
- issue.request_approvals!(user_ids: user_ids, user: pms_current_user)
26
+ find_issue
28
27
 
29
- yield(issue) if block_given?
28
+ @issue.request_approvals!(user_ids: user_ids, user: pms_current_user)
29
+
30
+ yield(@issue) if block_given?
30
31
  return if performed?
31
32
 
32
33
  flash[:success] = 'Approvers were successfully added.'
@@ -47,7 +48,7 @@ module PlanMyStuff
47
48
  return
48
49
  end
49
50
 
50
- issue = PlanMyStuff::Issue.find(params[:issue_id])
51
+ find_issue
51
52
  caller_id = pms_current_user.present? ? PlanMyStuff::UserResolver.user_id(pms_current_user) : nil
52
53
 
53
54
  case status
@@ -56,25 +57,25 @@ module PlanMyStuff
56
57
  redirect_to_unauthorized(show_path)
57
58
  return
58
59
  end
59
- issue.approve!(user: pms_current_user)
60
+ @issue.approve!(user: pms_current_user)
60
61
  flash[:success] = 'Approval recorded.'
61
62
  when 'rejected'
62
63
  unless caller_id == target_id
63
64
  redirect_to_unauthorized(show_path)
64
65
  return
65
66
  end
66
- issue.reject!(user: pms_current_user)
67
+ @issue.reject!(user: pms_current_user)
67
68
  flash[:success] = 'Rejection recorded.'
68
69
  else
69
70
  if caller_id != target_id && !support_user?
70
71
  redirect_to_unauthorized(show_path)
71
72
  return
72
73
  end
73
- issue.revoke_approval!(user: pms_current_user, target_user_id: target_id)
74
+ @issue.revoke_approval!(user: pms_current_user, target_user_id: target_id)
74
75
  flash[:success] = 'Response revoked.'
75
76
  end
76
77
 
77
- yield(issue) if block_given?
78
+ yield(@issue) if block_given?
78
79
  return if performed?
79
80
 
80
81
  redirect_to(show_path)
@@ -91,10 +92,10 @@ module PlanMyStuff
91
92
  return
92
93
  end
93
94
 
94
- issue = PlanMyStuff::Issue.find(params[:issue_id])
95
- issue.remove_approvers!(user_ids: [params[:id].to_i], user: pms_current_user)
95
+ find_issue
96
+ @issue.remove_approvers!(user_ids: [params[:id].to_i], user: pms_current_user)
96
97
 
97
- yield(issue) if block_given?
98
+ yield(@issue) if block_given?
98
99
  return if performed?
99
100
 
100
101
  flash[:success] = 'Approver was successfully removed.'
@@ -112,9 +113,14 @@ module PlanMyStuff
112
113
  params.fetch(:approval, {}).permit(:status, :user_ids)
113
114
  end
114
115
 
116
+ # :nodoc:
117
+ def find_issue
118
+ @issue = PlanMyStuff::Issue.find(params[:issue_id])
119
+ end
120
+
115
121
  # @return [String]
116
122
  def show_path
117
- plan_my_stuff.issue_path(params[:issue_id])
123
+ plan_my_stuff.issue_path(@issue || params[:issue_id])
118
124
  end
119
125
  end
120
126
  end
@@ -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.each do |item|
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.
@@ -16,7 +16,7 @@ module PlanMyStuff
16
16
  if loader.respond_to?(:eager_load_dir)
17
17
  loader.eager_load_dir(controllers_dir)
18
18
  else
19
- Dir.glob(File.join(controllers_dir, '**/*.rb')).sort.each { |path| require path }
19
+ Dir.glob(File.join(controllers_dir, '**/*.rb')).each { |path| require path }
20
20
  end
21
21
  end
22
22
  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 project_item [PlanMyStuff::BaseProjectItem]
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, project_item, **extra)
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
- project_item,
75
- issue_number: project_item.number,
85
+ resource,
86
+ **number_fields,
76
87
  **extra_to_use,
77
88
  )
78
89
  end
@@ -273,8 +284,10 @@ module PlanMyStuff
273
284
  items
274
285
  end
275
286
 
276
- # Moves a project item to "Completed" if the linked issue has +auto_complete+ enabled. Returns +nil+ when
277
- # auto-complete is off (item stays at "Release in Progress").
287
+ # Moves a project item to "Completed" if the linked issue has +auto_complete+ enabled, and sets the linked issue's
288
+ # "Issue Status" field to "Fixed". Returns +nil+ when auto-complete is off (item stays at "Release in Progress" and
289
+ # no field is touched). The "Issue Status" update is skipped when +config.issue_fields_enabled+ is +false+ so
290
+ # completion still succeeds.
278
291
  #
279
292
  # @param project_item [PlanMyStuff::ProjectItem]
280
293
  # @param deployment_id [String, nil]
@@ -288,6 +301,8 @@ module PlanMyStuff
288
301
  status = resolve_status_name(PlanMyStuff::Pipeline::Status::COMPLETED)
289
302
  result = project_item.move_to!(status)
290
303
 
304
+ issue.set_issue_fields!('Issue Status' => 'Fixed') if PlanMyStuff.configuration.issue_fields_enabled
305
+
291
306
  instrument(PlanMyStuff::Pipeline::Status::COMPLETED, project_item, deployment_id: deployment_id)
292
307
  result
293
308
  end
@@ -3,7 +3,7 @@
3
3
  module PlanMyStuff
4
4
  module VERSION
5
5
  MAJOR = 0
6
- MINOR = 28
6
+ MINOR = 30
7
7
  TINY = 0
8
8
 
9
9
  # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
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.28.0
4
+ version: 0.30.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brands Insurance
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-26 00:00:00.000000000 Z
11
+ date: 2026-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails