Module: PlanMyStuff::Pipeline
- Defined in:
- lib/plan_my_stuff/pipeline.rb,
lib/plan_my_stuff/pipeline/status.rb,
lib/plan_my_stuff/pipeline/testing.rb,
lib/plan_my_stuff/pipeline/issue_linker.rb,
lib/plan_my_stuff/pipeline/completed_sweep.rb
Overview
High-level orchestration layer for the release pipeline.
Named lifecycle methods wrap the low-level ProjectItem.move_to! calls, resolve configurable status aliases, and fire ActiveSupport::Notifications events on every transition.
No transition enforcement – any status can move to any other.
Defined Under Namespace
Modules: CompletedSweep, IssueLinker, Status, Testing
Class Method Summary collapse
-
.complete_deployment!(project_item, deployment_id: nil) ⇒ Hash?
Moves a project item to “Completed” if the linked issue has
auto_completeenabled. -
.guard_approvals!(issue) ⇒ void
Raises
PlanMyStuff::PendingApprovalsErrorwhenissuehas pending manager approvals. -
.instrument(event, project_item, **extra) ⇒ void
Fires a pipeline_<event>.plan_my_stuff notification.
-
.late_removal?(prior_status) ⇒ Boolean
Returns true when
prior_statusis past “Started” in the pipeline. -
.mark_in_review!(project_item) ⇒ Hash
Moves a project item to “In Review”.
-
.mark_ready_for_release!(project_item) ⇒ Hash
Moves a project item to “Ready for Release”.
-
.release_cycle_locked?(project_item) ⇒ Boolean
Returns true when
project_itemis at a release-cycle status (“Ready for Release”, “Release in Progress”, or “Completed”). -
.remove!(project_item) ⇒ String?
Removes a project item from the pipeline project entirely.
-
.request_testing!(project_item) ⇒ Hash
Marks a project item as in testing by setting the
Testingcustom single-select field to its active value. -
.resolve_pipeline_project_number!(project_number = nil) ⇒ Integer
Returns the pipeline project number from the explicit argument,
pipeline_project_number, ordefault_project_number. -
.resolve_status_name(canonical) ⇒ String
Resolves a canonical status name to its configured display alias, falling back to the canonical name when no alias is configured.
-
.start_deployment!(project_number: nil, commit_sha: nil) ⇒ Array<PlanMyStuff::ProjectItem>
Finds ALL items at “Ready for Release” in the pipeline project and moves each to “Release in Progress”.
-
.take!(project_item) ⇒ Hash
Moves a project item to “Started”.
Class Method Details
.complete_deployment!(project_item, deployment_id: nil) ⇒ Hash?
Moves a project item to “Completed” if the linked issue has auto_complete enabled. Returns nil when auto-complete is off (item stays at “Release in Progress”).
260 261 262 263 264 265 266 267 268 269 |
# File 'lib/plan_my_stuff/pipeline.rb', line 260 def complete_deployment!(project_item, deployment_id: nil) issue = project_item.issue return unless issue..auto_complete? status = resolve_status_name(PlanMyStuff::Pipeline::Status::COMPLETED) result = project_item.move_to!(status) instrument(PlanMyStuff::Pipeline::Status::COMPLETED, project_item, deployment_id: deployment_id) result end |
.guard_approvals!(issue) ⇒ void
This method returns an undefined value.
Raises PlanMyStuff::PendingApprovalsError when issue has pending manager approvals. No-op for nil issues or issues that either have no approvers required or are fully approved.
Called at the top of every forward transition (take!, mark_in_review!, request_testing!, mark_ready_for_release!). Batch / CI-driven transitions (start_deployment!, complete_deployment!) and reverse transitions (remove!) are intentionally NOT gated – earlier forward transitions already required approval, and gating batch/automated paths would abort entire deploys on a single approval revoke.
28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/plan_my_stuff/pipeline.rb', line 28 def guard_approvals!(issue) return if issue.nil? return unless issue.approvals_required? return if issue.fully_approved? raise(PlanMyStuff::PendingApprovalsError.new( issue: issue, pending_count: issue.pending_approvals.count, )) end |
.instrument(event, project_item, **extra) ⇒ void
This method returns an undefined value.
Fires a pipeline_<event>.plan_my_stuff notification.
When event is a canonical Pipeline::Status name, the event key is derived via Status.key_for and the canonical status is added to the payload as :status. Otherwise event is used verbatim as the suffix (e.g. “removed”, “removed_late”, “testing”).
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/plan_my_stuff/pipeline.rb', line 64 def instrument(event, project_item, **extra) extra_to_use = { **extra } event_to_use = event if PlanMyStuff::Pipeline::Status::ALL.include?(event_to_use) extra_to_use = { status: event, **extra_to_use } event_to_use = PlanMyStuff::Pipeline::Status.key_for(event_to_use) end PlanMyStuff::Notifications.instrument( "pipeline_#{event_to_use}", project_item, issue_number: project_item.number, **extra_to_use, ) end |
.late_removal?(prior_status) ⇒ Boolean
Returns true when prior_status is past “Started” in the pipeline. Honors configured status aliases. nil is treated as not-late.
126 127 128 129 130 131 |
# File 'lib/plan_my_stuff/pipeline.rb', line 126 def late_removal?(prior_status) return false if prior_status.nil? started = resolve_status_name(PlanMyStuff::Pipeline::Status::STARTED) prior_status != started end |
.mark_in_review!(project_item) ⇒ Hash
Moves a project item to “In Review”.
174 175 176 177 178 179 180 181 |
# File 'lib/plan_my_stuff/pipeline.rb', line 174 def mark_in_review!(project_item) guard_approvals!(project_item&.issue) status = resolve_status_name(PlanMyStuff::Pipeline::Status::IN_REVIEW) result = project_item.move_to!(status) instrument(PlanMyStuff::Pipeline::Status::IN_REVIEW, project_item) result end |
.mark_ready_for_release!(project_item) ⇒ Hash
Moves a project item to “Ready for Release”.
207 208 209 210 211 212 213 214 |
# File 'lib/plan_my_stuff/pipeline.rb', line 207 def mark_ready_for_release!(project_item) guard_approvals!(project_item&.issue) status = resolve_status_name(PlanMyStuff::Pipeline::Status::READY_FOR_RELEASE) result = project_item.move_to!(status) instrument(PlanMyStuff::Pipeline::Status::READY_FOR_RELEASE, project_item) result end |
.release_cycle_locked?(project_item) ⇒ Boolean
Returns true when project_item is at a release-cycle status (“Ready for Release”, “Release in Progress”, or “Completed”). Honors configured status aliases.
Used to lock items against webhook-driven removals once they enter the release path – a stray unassign should not yank an item out of “Release in Progress”.
143 144 145 146 147 148 149 150 151 |
# File 'lib/plan_my_stuff/pipeline.rb', line 143 def release_cycle_locked?(project_item) locked_statuses = [ resolve_status_name(PlanMyStuff::Pipeline::Status::READY_FOR_RELEASE), resolve_status_name(PlanMyStuff::Pipeline::Status::RELEASE_IN_PROGRESS), resolve_status_name(PlanMyStuff::Pipeline::Status::COMPLETED), ] locked_statuses.include?(project_item.status) end |
.remove!(project_item) ⇒ String?
Removes a project item from the pipeline project entirely. Captures the prior status before deletion so subscribers can decide whether the removal happened late in the lifecycle.
Always fires pipeline_removed.plan_my_stuff. Additionally fires pipeline_removed_late.plan_my_stuff when prior_status is past “Started” (i.e. anything other than “Started”, accounting for configured status aliases). nil status is treated as not-late.
109 110 111 112 113 114 115 116 117 |
# File 'lib/plan_my_stuff/pipeline.rb', line 109 def remove!(project_item) prior_status = project_item.status result = project_item.destroy! instrument('removed', project_item, prior_status: prior_status) instrument('removed_late', project_item, prior_status: prior_status) if late_removal?(prior_status) result end |
.request_testing!(project_item) ⇒ Hash
Marks a project item as in testing by setting the Testing custom single-select field to its active value. Does NOT change the item’s Status – testing runs orthogonally to In Review.
190 191 192 193 194 195 196 197 198 199 |
# File 'lib/plan_my_stuff/pipeline.rb', line 190 def request_testing!(project_item) guard_approvals!(project_item&.issue) field_name = PlanMyStuff.configuration.pipeline_testing_field_name value = PlanMyStuff.configuration.pipeline_testing_values.fetch(:active) result = project_item.update_single_select_field!(field_name, value) instrument('testing', project_item, field_name: field_name, value: value) result end |
.resolve_pipeline_project_number!(project_number = nil) ⇒ Integer
Returns the pipeline project number from the explicit argument, pipeline_project_number, or default_project_number.
89 90 91 92 93 94 95 96 |
# File 'lib/plan_my_stuff/pipeline.rb', line 89 def resolve_pipeline_project_number!(project_number = nil) config = PlanMyStuff.configuration result = project_number || config.pipeline_project_number || config.default_project_number raise(ArgumentError, 'No pipeline project number configured') if result.nil? result end |
.resolve_status_name(canonical) ⇒ String
Resolves a canonical status name to its configured display alias, falling back to the canonical name when no alias is configured.
48 49 50 |
# File 'lib/plan_my_stuff/pipeline.rb', line 48 def resolve_status_name(canonical) PlanMyStuff.configuration.pipeline_statuses.fetch(canonical, canonical) end |
.start_deployment!(project_number: nil, commit_sha: nil) ⇒ Array<PlanMyStuff::ProjectItem>
Finds ALL items at “Ready for Release” in the pipeline project and moves each to “Release in Progress”. Fires an event per item.
When commit_sha is given (the merge_commit_sha of the PR into production), it is stamped onto every moved item’s linked issue metadata. AwsController#handle_deployment_completed later matches this sha against the configured production_commit_sha to decide which items to auto-complete, so this is the only commit sha in the release cycle that matters.
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/plan_my_stuff/pipeline.rb', line 229 def start_deployment!(project_number: nil, commit_sha: nil) number = resolve_pipeline_project_number!(project_number) project = PlanMyStuff::Project.find(number) ready_status = resolve_status_name(PlanMyStuff::Pipeline::Status::READY_FOR_RELEASE) in_progress_status = resolve_status_name(PlanMyStuff::Pipeline::Status::RELEASE_IN_PROGRESS) items = project.items.select { |item| item.status == ready_status } items.each do |item| if commit_sha.present? issue = item.issue issue..commit_sha = commit_sha issue.save! end item.move_to!(in_progress_status) instrument(PlanMyStuff::Pipeline::Status::RELEASE_IN_PROGRESS, item, commit_sha: commit_sha) end items end |
.take!(project_item) ⇒ Hash
Moves a project item to “Started”.
159 160 161 162 163 164 165 166 |
# File 'lib/plan_my_stuff/pipeline.rb', line 159 def take!(project_item) guard_approvals!(project_item&.issue) status = resolve_status_name(PlanMyStuff::Pipeline::Status::STARTED) result = project_item.move_to!(status) instrument(PlanMyStuff::Pipeline::Status::STARTED, project_item) result end |