A project that ships straight to its branch shows how often its workflow runs pass in Health, instead of "No checks have run recently"
4 files+37−40/4 viewed
| 63 | 63 | | **Active branches** | Branches other than the default, newest first. See [active branches](#active-branches). | | |
| 64 | 64 | | **Recent changes**, **Latest on main**, **Activity**, **Previews** | What landed, the default branch's latest commits, what happened (pull requests opened, readied and merged, issues opened and closed, checks, reviews, comments, pushes to the default branch and production deploys), and the previews that are up. | | |
| 65 | 65 | | **About** | Its description, its [links](#links), what it is, and its latest release (its newest tag). People who can change its settings edit the description and links from here. | | |
| 66 | − | | **Health**, **Clone** | How often checks pass, recent builds and open issues by age; the clone address. | | |
| 66 | + | | **Health**, **Clone** | How often checks pass (on pull requests, or the project's workflow runs when it ships without them), recent builds and open issues by age; the clone address. | | |
| 67 | 67 | ||
| 68 | 68 | ### The checklist | |
| 69 | 69 |
| 28 | 28 | nextSeen, | |
| 29 | 29 | pipelineStage, | |
| 30 | 30 | queuedNumbers, | |
| 31 | + | runHealth, | |
| 31 | 32 | rankNeeds, | |
| 32 | 33 | readCookie, | |
| 33 | 34 | sparkPoints, | |
| ⋯ | |||
| 179 | 180 | ]); | |
| 180 | 181 | assert.deepEqual(rate, { rate: 0.5, of: 2 }); | |
| 181 | 182 | assert.deepEqual(firstPassRate([]), { rate: null, of: 0 }); | |
| 183 | + | // Workflow runs: cancelled, skipped and unfinished ones don't count; a pass on a second attempt isn't a first-try pass. | |
| 184 | + | const runs = runHealth([ | |
| 185 | + | { attempt: 1, status: "completed", conclusion: "success" }, | |
| 186 | + | { attempt: 2, status: "completed", conclusion: "success" }, | |
| 187 | + | { attempt: 1, status: "completed", conclusion: "failure" }, | |
| 188 | + | { attempt: 1, status: "completed", conclusion: "cancelled" }, | |
| 189 | + | { attempt: 1, status: "in_progress", conclusion: null }, | |
| 190 | + | ]); | |
| 191 | + | assert.equal(runs.checkRuns, 3); | |
| 192 | + | assert.equal(runs.passRate, 2 / 3); | |
| 193 | + | assert.deepEqual(runs.firstPass, { rate: 1 / 3, of: 3 }); | |
| 194 | + | assert.deepEqual(runHealth([]), { passRate: null, firstPass: { rate: null, of: 0 }, checkRuns: 0 }); | |
| 182 | 195 | assert.equal(formatSpan(null), "—"); | |
| 183 | 196 | assert.equal(formatSpan(30 * MIN), "30m"); | |
| 184 | 197 | assert.equal(formatSpan(5 * HOUR), "5h"); | |
| 417 | 417 | return results.filter(Boolean).length / results.length; | |
| 418 | 418 | } | |
| 419 | 419 | ||
| 420 | + | /** | |
| 421 | + | * A project's health from its recent workflow runs, for one that ships | |
| 422 | + | * without pull requests (whose checks are what `checks.completed` counts). | |
| 423 | + | * Only runs that finished and passed or failed count: cancelled and skipped | |
| 424 | + | * say nothing about the code. A run passed on the first try when its first | |
| 425 | + | * attempt is the one that passed. | |
| 426 | + | */ | |
| 427 | + | export function runHealth(runs: { attempt: number; status: string; conclusion: string | null }[]): { passRate: number | null; firstPass: { rate: number | null; of: number }; checkRuns: number } { | |
| 428 | + | const decided = runs.filter((run) => run.status === "completed" && (run.conclusion === "success" || run.conclusion === "failure")); | |
| 429 | + | const passed = decided.map((run) => run.conclusion === "success"); | |
| 430 | + | const first = decided.filter((run) => run.conclusion === "success" && run.attempt <= 1).length; | |
| 431 | + | return { passRate: passRate(passed), firstPass: { rate: decided.length ? first / decided.length : null, of: decided.length }, checkRuns: decided.length }; | |
| 432 | + | } | |
| 433 | + | ||
| 420 | 434 | /** How long from an issue being opened to its change landing, for each that did, in ms. */ | |
| 421 | 435 | export function issueToMerge( | |
| 422 | 436 | opened: { repo: string; number: number; at: number }[], |
| 71 | 71 | projectFeed, | |
| 72 | 72 | queuedNumbers, | |
| 73 | 73 | rankNeeds, | |
| 74 | + | runHealth, | |
| 74 | 75 | stuckMinutes, | |
| 75 | 76 | } from "../../lib/mission"; | |
| 76 | 77 | import { agentWasAssigned, checklistPlan, hasInstructions, productionChecklist, releaseChecklist, startChecklist } from "../../lib/checklist"; | |
| ⋯ | |||
| 160 | 161 | const libraryRepoP = Promise.all([planP, repoP]).then(([plan, repo]) => (plan === "release" && repo?.ok ? repo.value : null)); | |
| 161 | 162 | const packagesP = libraryRepoP.then((repo) => (repo ? soft(packages.list(params.owner, viewer, { repoId: repo.id })) : null)); | |
| 162 | 163 | const workflowsP = planP.then((plan) => (plan && plan !== "production" ? forMembers(() => actions.workflows(path, viewer)) : null)); | |
| 164 | + | // Recent workflow runs, for Health when no pull request's checks have finished lately. | |
| 165 | + | const workflowRunsP = soft(actions.runs(path, viewer, { limit: 30 })); | |
| 163 | 166 | // Only the kinds the feed shows: the newest events are mostly session | |
| 164 | 167 | // steps and merge checks, which would otherwise crowd out everything. | |
| 165 | 168 | const eventsP = repoP.then((repo) => | |
| ⋯ | |||
| 231 | 234 | // The latest release, for About. | |
| 232 | 235 | soft(repos.tags(path, viewer)), | |
| 233 | 236 | ]); | |
| 237 | + | const workflowRuns = await workflowRunsP; | |
| 234 | 238 | const ok = <T,>(result: { ok: true; value: T } | { ok: false } | null): T | null => (result?.ok ? result.value : null); | |
| 235 | 239 | ||
| 236 | 240 | const openPulls = ok(open) ?? []; | |
| ⋯ | |||
| 458 | 462 | groups: groupActivity(items).slice(0, 30), | |
| 459 | 463 | eventsLoaded: recent != null, | |
| 460 | 464 | health: { | |
| 461 | − | passRate: passRate(checkEvents.map((c) => c.passed)), | |
| 462 | − | firstPass: firstPassRate(checkEvents), | |
| 463 | − | checkRuns: checkEvents.length, | |
| 465 | + | // A pull request's checks when any finished lately; otherwise the | |
| 466 | + | // project's workflow runs, for one that ships straight to its branch. | |
| 467 | + | ...(checkEvents.length > 0 || !workflowRuns?.ok | |
| 468 | + | ? { passRate: passRate(checkEvents.map((c) => c.passed)), firstPass: firstPassRate(checkEvents), checkRuns: checkEvents.length } | |
| 469 | + | : runHealth(workflowRuns.value)), | |
| 464 | 470 | ages: openIssues ? ageBuckets(openIssues.map((issue) => issue.createdAt), now) : null, | |
| 465 | 471 | }, | |
| 466 | 472 | knows: knows as Memory[], | |