Unify definition of a "failed work order" everywhere - #5153
Open
lmac-1 wants to merge 11 commits into
Open
Conversation
failure_states/0 now excludes :cancelled as well as :success, and a new outcome/1 buckets any state into success/cancelled/pending/failed, exhaustive over states() by construction. Behaviour-neutral: stats.ex is still the only caller, and it subtracts cancelled itself.
WorkOrder.failure_states/0 already excludes it now, so the narrowing here was redundant. Behaviour-neutral.
count_workorders/1 and batch_count_workorders/1 now derive their bucket from WorkOrder.outcome/1 instead of hand-rolled state checks, so cancelled work orders get their own bucket instead of counting as failed. aggregate_metrics/2 adds cancelled back into its total so the "Work Orders" card total doesn't shrink, only the failed slice does. Also fixes the "Latest failure" chip: it only re-checked the exclusion query when the newest work order was exactly :success, so a cancelled one displayed as "Latest failure" outright. The guard is now outcome(state) != :failed, in both the batched path the workflow list actually uses and the equivalent private helper for the (currently uncalled) single-workflow path.
filter_days_ago/2 becomes /3, taking the date column (default :inserted_at). The four work-order queries pass :last_activity; count_runs/1 and count_steps/1 keep the default, folding their inline date math into the same helper along the way. A work order retried after sitting stale no longer falls out of the 30-day count depending which screen you're looking at. Run and work-order counts will no longer tie for a retried work order — the old run is outside the run window, the new one inside. Correct and unavoidable, per the plan. Fixed an existing test pinned to the old inserted_at boundary, and added the plan's test covering cancelled-not-failed, the window shift, and the latest-failure chip together.
failed_wo_filters and its duplicate in project_metrics/1 (failed_filters) now build their state list from WorkOrder.failure_states() instead of a hand-typed list that had cancelled and not rejected. All four dashboard filter links move from wo_date_after (inserted_at) to date_after (last_activity), so a link's window matches the count it sits under, now that commit 4 moved those counts to last_activity. The plan only named the per-row link and left wo_filters on wo_date_after "unless the total card moves too" - it does, so wo_filters moves too, and project_metrics's identical hand-typed duplicate gets the same fix.
build_failed_status_params/1 and the digest copy's parenthetical both switch from Lightning.Run.failure_states() to Lightning.WorkOrder.failure_states(). This is a real fix, not a rename: the params this builds filter work orders, not runs, and Run has no :rejected state (a rejected work order never gets a run), so those were silently missing from the digest's failed count. The digest now also stops counting cancelled. Run.failure_states/0 has no callers left - deleted. Leaving a second function with the same name is how this drifted in the first place. Updated the test helper that attaches runs to each failure state: :rejected work orders don't get a run, matching how a real one is created. Added a CHANGELOG entry (issue link left as a TODO for you to fill in).
Security Review |
lmac-1
force-pushed
the
unify-failed-definition
branch
from
September 8, 2026 22:40
42bfc70 to
a498e76
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## frank/con-106 #5153 +/- ##
=============================================
Coverage 90.8% 90.8%
=============================================
Files 422 422
Lines 20875 20850 -25
=============================================
- Hits 18954 18937 -17
+ Misses 1921 1913 -8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
batch_get_last_workorders filtered the 30-day window on last_activity but sorted on inserted_at, so the work order shown as "last failure" was not the most recent one in the window. A work order created 45 days ago and retried today sorted below one created 20 days ago, making the row disagree with the failure count beside it. Delete get_workflow_stats/1 and its five private helpers. Each was a duplicate of a batch_* function and nothing called it, so the same sort bug existed twice and the tests asserted the new failure behaviour against the copy the dashboard never runs. Tests now go through get_workflows_stats/1. Give runs and steps a three-key empty default; only work orders have a :cancelled bucket.
The cards counted a 30-day window while the history links they sit on asked for one calendar month, so a link could open a wider range than the number beside it. Add DashboardStats.window_start/0 and read it from both the queries and the links. filter_days_ago/3 drops its days argument; every caller passed @days_back. failed_percentage divides by success + failed instead of by every work order. Cancelled work orders left the numerator when failure was redefined but stayed in the denominator, so a project that cancels heavily reported a near-zero failure rate.
failure_states/0 returns schema declaration order, which puts :rejected first. Recipients opened the digest led by the one state that means nothing outside the team: a webhook dropped for exceeding the run limit. Sort the list and restore the "etc." the hardcoded copy had. Sorting needs no per-state handling, so a new failure state slots in without anyone touching this. The list stays derived from failure_states/0, so cancelled still cannot appear in it.
Dividing by success + failed made the number defensible in the cancel-heavy case but unreadable in the ordinary one: a workflow with one success, one failure and one cancelled work order showed 50% beside a page listing three. The card names no denominator, so any figure other than "of all work orders" has to be guessed at. Which denominator is right is a design question for the dashboard rework, not for a change about what counts as a failure. The count still excludes cancelled; only the percentage goes back.
Both branches dropped :cancelled from WorkOrder.failure_states/0. Kept our version, which also carries outcome/1. Took main's stats.ex, where the error_signatures rework already inlines the calls our @failure_states and @state_reasons attributes stood in for.
lmac-1
force-pushed
the
unify-failed-definition
branch
from
September 9, 2026 16:43
50b896e to
604fdaa
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This PR makes "a failed work order" mean the same thing everywhere it's counted: the workflow list, its failure link, the workflow health page, and the digest email.
Before this, each of those screens counted a different set of states as a failure and measured its "last 30 days" from a different date. The same work order could show as failed on one screen and fine on another, and a work order retried after sitting untouched for 40 days could drop out of the count on some screens but not others.
What changes:
Closes CON-177
Validation steps
Additional notes for the reviewer
:success.last_activitybut picked its row byinserted_at, so a work order retried today could lose to an older one and the chip could name a failure the count had already moved past. Both now uselast_activity.DashboardStatscarried two copies of every query, one per workflow and one batched. Only the batched set is reachable from the page, so the same sort bug existed twice and the tests asserted against the copy nothing renders. The unreachable copy is deleted rather than fixed.DashboardStats.window_start/0, read by both the queries and the history links. Previously the counts used 30 days and the links asked for one calendar month.(crashed, exception, failed, killed, lost, rejected etc.)instead of leading withrejected. Sorting needs no per-state handling as states are added.lib/lightning/pipeline/failure_alerter.exstill alerts on any non-success run, cancellations included. That's a decision about run-level alerting on a different surface, and it's out of scope here.AI Usage
Please disclose whether you've used AI anywhere in this PR (it's cool, we just
want to know!):
You can read more details in our
Responsible AI Policy
Pre-submission checklist
/reviewwith Claude Code)
(e.g.,
:owner,:admin,:editor,:viewer)