Repository navigation
Postmortems across every incident: a status for each postmortem, Incidents > Postmortems to view and filter them, and a simpler Postmortem page - #5171
Merged
Conversation
…t's status, the moves OneUptime makes and the backfill Every incident's postmortem is in one of its project's postmortem statuses (IncidentPostmortemStatus): Not Started, Draft, Completed and Not Needed by default, drag-ordered like incident states, with the not started, draft and completed statuses built in. A new incident's postmortem starts not started, writing its first note moves it to draft and publishing it on a status page to completed (Common/Utils/Incident/PostmortemStatus). Existing projects get the defaults and existing incidents the status their postmortem is in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and a postmortem card with its status and owners Incidents > Postmortems lists every incident's postmortem with counts per status, Owner, Labels, Postmortem Status, Severity, Incident State and Affected Resources chips, a status pill to move each one, and Change Postmortem Status for many at once. Incidents > Settings > Postmortem Statuses manages the statuses (drag order, archive). An incident's Postmortem page shows its status and owners, offers Write Postmortem while nothing is written, and folds Apply Template and Generate with AI into a menu once it is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ard, the list fits beside the side menu Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…age hold the statuses, the summary and the folded menu Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…new postmortem starts, what writing and publishing move, and the project's defaults Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pills and the bulk move - the statuses as the Dashboard offers them, and the backfill Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tem card in 16 languages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…one list, the statuses settings and the card's new buttons, in 17 languages The MCP server, the CLI and the audit log list Postmortem Status too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tings in a browser, offline; a status is named in the reader's language in the tiles' and the pill's labels too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eports # Conflicts: # packages/Common/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts
…ed Postgres; the archive migrations' suites leave the statuses table, created archivable, to its own The table migration creates every column, index and key the model declares under TypeORM's names, and adds the incident's column without rewriting the table; the indexes and key are built online and safe to build again; the database refuses to delete a status incidents are in. The backfill gives each incident the status its postmortem is in, a batch at a time, the project's first built-in status of each kind, deleted ones and projects with none left alone. Wired into the Postgres Schema Drift workflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ped, every chip, private incidents, who may read, add and move - and status pages never read where a postmortem stands Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a project with no postmortem statuses yet; the tiles test types its facet calls; the spec escapes a tile's name Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… incident's page offers a template only when there is one, and postmortem statuses are archived over MCP Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eports # Conflicts: # packages/App/FeatureSet/Dashboard/src/Locales/da.json # packages/App/FeatureSet/Dashboard/src/Locales/de.json # packages/App/FeatureSet/Dashboard/src/Locales/en.json # packages/App/FeatureSet/Dashboard/src/Locales/es.json # packages/App/FeatureSet/Dashboard/src/Locales/fa.json # packages/App/FeatureSet/Dashboard/src/Locales/fr.json # packages/App/FeatureSet/Dashboard/src/Locales/hi.json # packages/App/FeatureSet/Dashboard/src/Locales/it.json # packages/App/FeatureSet/Dashboard/src/Locales/ja.json # packages/App/FeatureSet/Dashboard/src/Locales/ko.json # packages/App/FeatureSet/Dashboard/src/Locales/nl.json # packages/App/FeatureSet/Dashboard/src/Locales/no.json # packages/App/FeatureSet/Dashboard/src/Locales/pt.json # packages/App/FeatureSet/Dashboard/src/Locales/ru.json # packages/App/FeatureSet/Dashboard/src/Locales/sv.json # packages/App/FeatureSet/Dashboard/src/Locales/zh-CN.json # packages/App/FeatureSet/Dashboard/src/Locales/zh-TW.json
…ts type-check (jest-mock types, a typed requireActual), and the docs facts read a label's declaration however it wraps Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…iles that fit a tablet, a list that fits in German, Built-in tags on their name's line - The empty Postmortem card's Start from Template and Generate with AI are white bordered buttons beside the primary Write Postmortem (they were the outline style, which drew bare words with an icon), stacked full width on a phone, like a list's empty state. A viewer finds Write Postmortem alone, dimmed, with why under it, not three locked buttons. - The Owners field offers Change owners only to someone who may add an owner to the incident. - The Postmortems tiles are two to a line below lg (four in the ~480px a tablet leaves beside the side menu were ~105px each and their captions ran out of the tiles); captions are clamped at three lines, whole on hover, and left out on a phone. - The list's Declared column is the date (German ran 57px past the card at 1280px, the dates under the sticky View), and the Incident column names its own floor so a fitting table's spare room goes to the title, not to "INC-101" (titles wrapped to three lines at 1280px). - A refused move's reason wraps inside its row instead of pushing the status column ~200px wider. - The empty list shows a write-up's icon and plain words: "When an incident is declared, its postmortem shows up here." (renamed in place in all 17 locales). - The Write Postmortem dialog's note hint ends in one full stop and is translated (the Markdown cheatsheet helper added a second one). - State settings pages keep the Built-in tag on the name's line in a table row (it wrapped under the longer names only). Tests: offline browser suite +4 specs and new checks (tablet tiles, German fit, title room, refused-move width, button look and phone stack, viewer, Built-in tag line), each proven against the break it guards; list, page, state table and tiles unit tests updated; docs facts follow the source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eports-ui-review Conflicts: the migrations index (master's 1805830000000 and 1805840000000 first, then this branch's 1805900000000 and 1805910000000) and the Dashboard locales (merged by i18n:extract). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… moves, which keep their places before the last slot Placed between RemoveRunbookCredentials and AddAuditLogMcpClientColumns, it broke RemoveRunbookCredentials and MoveRunbookApprovalsBeforeTheirSteps, whose suites assert each neighbour. It has no ordering requirement among data migrations (they run after every schema migration), so it sits where AddProcessColumnsToSecurityEvent does, and its own suite now pins that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simlarsen
pushed a commit
that referenced
this pull request
Oct 22, 2026
…he CLI README lists postmortem-status Two more reds on master aeea62e: - Common Test (1/8): VMwareHostPage's Network tile read 23.5 MB/s, not 31.5. The test built each series row with its own minutesAgo(2), so a millisecond tick between two rows split one aggregate bucket in two - the host's own total in one, a NIC in the other - and the page, rightly, averaged the two buckets of the recent window. The aggregate returns every row of a bucket with that bucket's exact time; the test's rows now read the clock once. With a tick forced between rows, the old test reads 24.3 MB/s; with one clock read it passes, every time. - CLI Test: #5171 made postmortem statuses a CLI resource (postmortem-status), and ReadmeResources requires the README's records table to list every resource command. It is in the Incidents row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
The problem
A customer asked: "Is there any way I can view and filter Postmortem reports across all incidents? I am trying to filter by team/owner, severity, postmortem report status (not-started / draft / and a few custom statuses)." The maintainer asked to make postmortems a lot better overall: easy to use, easy to understand, the advanced things out of the way.
Before this PR a postmortem was a set of fields on the incident (note, attachments, Publish on Status Page), written on the incident's Postmortem page. There was no list across incidents, and a postmortem had no status of its own, so "which postmortems are still owed, by whom?" had no answer.
What changed (and why)
1. A postmortem status, with the fewest states that answer "which are still owed?"
IncidentPostmortemStatus(API/api/incident-postmortem-status, OpenAPI, MCP tools*_postmortem_status, CLIpostmortem-status, Terraformoneuptime_postmortem_status, workflows, audit log), andIncident.postmortemStatusId(API, OpenAPI, Terraformpostmortem_status_id).Common/Utils/Incident/PostmortemStatus.ts, one rule for server, list and tests):It never moves back (clearing the note or unpublishing leaves it), and a status picked in the same save always stands. The move is a hook-free compare-and-set from the status it was in, so a status someone picked meanwhile is never overwritten.
StateOrder): a new status goes just above Completed; where a status sits decides what it counts as (above Draft: not started; from Draft down: in progress; from Completed down: done). Not Started, Draft and Completed are built in: renamable, never deleted or archived, kept in order (the server says why it refuses a move). Statuses get Archive/Unarchive like every other value (archive-config-values), so a retired status is never picked again while old incidents keep it.2. Incidents > Postmortems: every incident's postmortem in one list
3. The incident's Postmortem page, simpler
4. Existing data
1805900000000-AddIncidentPostmortemStatus(new table + a nullable column on Incident, no rewrite) and1805910000000-AddIncidentPostmortemStatusIndexesAndKey(the Incident indexes and key built online through OnlineDdl, outside a transaction). Generated, not hand-written.AddDefaultPostmortemStatusesToExistingProjects: gives every existing project the four statuses (idempotent), then backfills every existing incident, in batches of 5,000: Completed when its postmortem is published (switch on and a non-blank note), Draft when it has a note, Not Started otherwise. Nobody is given Not Needed: that is the team's call, from the list.What changes for existing customers
Deliberately left out
Tests
Common/Tests/Utils/Incident/PostmortemStatus.test.ts: starting status, progress by position, owed/done ids, every automatic move and every non-move (picked status stands, no moving back, unknown status left alone).IncidentPostmortemStatusModel.test.ts,ArchivableValueModels,ListOrderColumnModels,RelationListReadCoverage,ArchivableValues(+ApiAndTerraform),StateOrder.IncidentPostmortemStatusService.test.ts(guards, project statuses, backfill loop),IncidentPostmortemStatusOnCreate.test.ts(where a new incident's postmortem starts, a picked status stands, a failed read never blocks declaring),IncidentPostmortemStatusMoves.test.ts(writing/publishing moves through the real update hooks, compare-and-set from the status it was in),UpdatedFeedRealChanges,PostmortemNotifyOnPublish,StateListServicesOrder,ProjectServiceDefaultPostmortemStatuses(+ project-create suites).Common/Tests/Server/API/PostmortemsListThroughTheApi.test.ts(new, 27 tests): the list and tile counts through BaseAPI and the real permission layer - held to the caller's project whatever project the query names, every chip reaches the database, private incidents follow the privacy rule, viewers/incident viewers/members read, someone without incident read is refused, who may add a status (owner/admin/incident admin/Create Postmortem Status) and who may move one (members, incident members, Edit+Read Incident; viewers and Edit-alone refused).StatusPagePostmortemVisibility.test.ts: the overview, the incident list and an incident's page never select the postmortem's status.AddIncidentPostmortemStatusMigration.test.ts(new, 24 tests: every column/index/key under TypeORM's names, no rewrite, online indexes and key, down() undoes all) andAddArchiveToValuesMigration.test.ts(the statuses table is created archivable by its own migration).IncidentPostmortemStatusPostgres.test.ts(new, opt-inRUN_POSTGRES_POSTMORTEM_STATUS_TESTS, wired into the Postgres Schema Drift workflow): the migrations on a copy of the migrated incidents table holding incidents (no table rewrite - same relfilenode, indexes valid, key validated and NO ACTION, safe to run twice, a status in use can't be deleted), the backfill on real rows (each case above, whitespace-only notes, batching, first built-in status by order, deleted statuses and projects without statuses left alone, idempotent), and the list's reads through the incident service.ArchivableValuesPostgresnow leaves the statuses table to its own migration.App/Tests/Workers/DataMigrations/AddDefaultPostmortemStatusesToExistingProjects.test.ts.PostmortemsList.test.tsx(tiles, chips, row pills, bulk move),IncidentPostmortemPage.test.tsx(status + owners, empty state vs. written, folded ⋯, permissions),IncidentsSideMenu.test.tsx,PostmortemStatusChoices.test.ts,PostmortemStatusTiles.test.ts,ArchivedValuePickersGuard,ListDeletePolicy,SettingsPathsInCopy, MCPArchivableValueTools.packages/E2E/Postmortems(21 specs after the UI review, Chromium, ~42 s locally, ~1.5 min with its bundle): the list (tiles filter, row move, refused move put back, bulk move, viewer, empty project), the incident page (written / empty / no template and no AI / moving the status), the statuses settings (counts as, archived folded), at 1280 and 390 px, dark mode and German. Added to Dashboard Offline UI group 9 (about 18.5 min with it).incidents/postmortems,incidents/settings,incidents/index,ai/mcp-server,cli/command-reference,configuration/audit-logsin all 17 languages;CriteriaGroupsPostmortemsDocsFacts,AiDocsFacts,CliDocsFactsupdated.What I ran locally
npm run compilein Common, App and E2E;npm run compile-testsin Common;tsc --noEmit --listFilesin all three (nothing outside each package and Common).CreatedClosedNoAutomations(incident creates now read the project's statuses),SettingsPathsInCopyandListDeletePolicy- fixed here.npx jest Guardplus UpdateChecksReadHeldRows, DeleteChecksReadHeldRows, HookWritesCallersMayMake, ListOrderColumnModels, PermissionCatalogueCoverage, RequiredColumnsWithDefaults, RelationListReadCoverage, RelationListPermission, PlanGatedTableLeftovers: 168 suites), App's root-level tests and its Guard/Covers/Locale/ShortDescriptions/WrongComponentKey/RelationSelectColumns suites (101), the MCP tests (53), Common's Locale suites (37); my suites with billing on and off, and with ee/ hidden.ArchivableValuesPostgresagainst a throwaway Postgres migrated to head (container removed).*DocsTranslationssuites;docs:check-anchors,i18n:validate,i18n:check.Master failure seen that is not this PR's:
App/Tests/Dashboard/CardDescriptionBoilerplateGuardfails onSecurityEvents/ThreatIntelSourcesCopy.ts("List of indicators", from #5165).UI review
A second agent reviewed and polished every surface before merging.
What I looked at. Every surface rendered for real in the offline fixture (the production pages beside the real side menus, Inter, Tailwind, Theme.css), at 390, 768, 1024, 1280, 1440 and 1920 px, light and dark, in English, German, French, Spanish, Russian and Japanese. I checked them as a project owner and as a viewer, with an empty project and a full one, with the project's own statuses and an archived one, and with a move the server refuses. The pages:
What I changed, and why:
md. At 768 px, beside the side menu, that made each tile about 105 px wide, and the captions ran out of the tiles. Columns now start atlg(3/5/6 tiles too). Captions are clamped at three lines with the whole text on hover, so a project's long description keeps the tiles level, and they are hidden on a phone, where they pushed the list below the fold.FieldType.Date). With the time, German ran 57 px past the card and the dates sat under the sticky "Ansehen". German, French and Spanish now fit at 1280 px. The Incident column names its own floor (min-w-[4rem]), so the table's spare room goes to the title. Before, the first column took that room as the row's name: it stood 184 px wide around "INC-101" while titles wrapped to three lines. They now wrap to one or two.MarkdownUtil.getMarkdownCheatsheetappends its own full stop, and it was never translated. It is now a plain description, translated into 16 languages.md, where rows are a table.Tests added or changed in the review:
PostmortemsList: Declared is a date; the empty state's icon and words; the Incident column's floor, never a cap, and not the filling column; caption clamp, hover text andmax-sm:hidden(never the barehidden); nomd:columns.IncidentPostmortemPage: the viewer's single locked button and its note; button styles and the phone column; no Change owners for a viewer; the note hint with one full stop.IncidentStatesPageTable: the name cell keeps one line frommd.PostmortemStatusTiles: thelggrid, and nomd:columns at any count.CriteriaGroupsPostmortemsDocsFactsfollows the new source.Left as follow-ups:
A bug the review found and fixed: CI would have failed. The builder placed the data migration
AddDefaultPostmortemStatusesToExistingProjectsbetweenRemoveRunbookCredentialsand the last slot,AddAuditLogMcpClientColumns. That brokeRemoveRunbookCredentials.test.tsandMoveRunbookApprovalsBeforeTheirSteps.test.ts, which assert each neighbour. The migration has no ordering requirement among data migrations, so it now sits above the runbook moves, whereAddProcessColumnsToSecurityEventsits. Its own suite now pins that place.What the review ran (after merging master at a082b72; the conflicts were the migrations index and the Dashboard locales):
BILLING_ENABLED=true.npx jest Guardwith UpdateChecksReadHeldRows, DeleteChecksReadHeldRows, HookWritesCallersMayMake, ListOrderColumnModels and PermissionCatalogueCoverage: 166 suites.Locale: 37 suites.npm run compilein Common, App and E2E;compile-testsin Common;tsc --listFilesin each, which lists nothing outside the package and Common.i18n:extract(no change after the review's keys) andi18n:check(every locale sound, no translation lost).--checkand eslint over every file the branch changes; eslint's only errors are the fixture.jsfiles, whicheslint.config.jsignores, as for every fixture.CardDescriptionBoilerplateGuard, noted above as failing on master, passes on the merged tree.🤖 Generated with Claude Code