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 plan_my_stuff.pipeline.<event> 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”).
265 266 267 268 269 270 271 272 273 274 |
# File 'lib/plan_my_stuff/pipeline.rb', line 265 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.
33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'lib/plan_my_stuff/pipeline.rb', line 33 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 plan_my_stuff.pipeline.<event> 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”).
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/plan_my_stuff/pipeline.rb', line 69 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.
131 132 133 134 135 136 |
# File 'lib/plan_my_stuff/pipeline.rb', line 131 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”.
179 180 181 182 183 184 185 186 |
# File 'lib/plan_my_stuff/pipeline.rb', line 179 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”.
212 213 214 215 216 217 218 219 |
# File 'lib/plan_my_stuff/pipeline.rb', line 212 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”.
148 149 150 151 152 153 154 155 156 |
# File 'lib/plan_my_stuff/pipeline.rb', line 148 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 plan_my_stuff.pipeline.removed. Additionally fires plan_my_stuff.pipeline.removed_late when prior_status is past “Started” (i.e. anything other than “Started”, accounting for configured status aliases). nil status is treated as not-late.
114 115 116 117 118 119 120 121 122 |
# File 'lib/plan_my_stuff/pipeline.rb', line 114 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.
195 196 197 198 199 200 201 202 203 204 |
# File 'lib/plan_my_stuff/pipeline.rb', line 195 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.
94 95 96 97 98 99 100 101 |
# File 'lib/plan_my_stuff/pipeline.rb', line 94 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.
53 54 55 |
# File 'lib/plan_my_stuff/pipeline.rb', line 53 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.
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/plan_my_stuff/pipeline.rb', line 234 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”.
164 165 166 167 168 169 170 171 |
# File 'lib/plan_my_stuff/pipeline.rb', line 164 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 |