Skip to content

feat: status-report impact - #2258

Merged
mxkaske merged 34 commits into
mainfrom
feat/status-report-impact
Jun 11, 2026
Merged

feat: status-report impact#2258
mxkaske merged 34 commits into
mainfrom
feat/status-report-impact

Conversation

@mxkaske

@mxkaske mxkaske commented Jun 11, 2026

Copy link
Copy Markdown
Member

No description provided.

mxkaske and others added 12 commits June 11, 2026 08:48
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Membership syncs from impact-named components; resolve writes explicit
operational rows; impacts ride in the update audit metadata.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… impacts

Events carry per-component impact intervals; manual-mode uptime weighs
major/partial as downtime, degraded/operational as up. Reports without
impact rows keep the legacy flat-orange, full-duration behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Additive only (buf breaking clean): PageComponentImpact enum, ComponentImpact
message, repeated fields on update/create/add-update. UNSPECIFIED means the
caller doesn't speak impact — never operational.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t column

Create form defaults every selected component to operational (fresh reports
are never legacy); add-update prefills current impacts; legacy reports stay
legacy unless the operator sets a non-operational impact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…line

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Statuspage maps affected_components.new_status per update; Betterstack maps
per-update affected_resources (richer than the report-level draft assumed);
Instatus stays legacy. Writer inserts updates one-by-one to pair impact rows
deterministically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openstatus-dashboard Canceled Canceled Jun 11, 2026 7:23pm
openstatus-status-page Canceled Canceled Jun 11, 2026 7:23pm
openstatus-web Canceled Canceled Jun 11, 2026 7:23pm

Request Review

mxkaske and others added 4 commits June 11, 2026 09:48
…ates

updateStatusReportUpdate accepts componentImpacts with replace-set semantics;
the per-update cards show a picker prefilled with the update's own rows and
'No change' for components it didn't touch. The dashboard only sends the set
when actually edited, so untouched (incl. legacy) updates keep their rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The list page's expanded-row sheets get the picker too: the edit sheet
prefills the update's own rows with 'No change' for untouched components
(replace-set sent only when edited); the create sheet prefills current
impacts with the same legacy guard as the report detail page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…snapshots

pageComponentIds on report create/update snapshots and componentImpacts on
update snapshots, sorted for stable diffs — the audit CHANGES UI only renders
before/after, so join-table changes were invisible. Also fixes membership-only
updateStatusReport edits being dropped entirely as empty-diff no-ops.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mxkaske and others added 2 commits June 11, 2026 10:40
currentImpactsFromUpdates/worstImpact now single-sourced in constants;
getEvents gets the (date, id) tie-break and defensive copy; rpc status-page
groups impact rows by update id; rename the relation-shaped update type to
avoid clashing with the service-side name; revert unused optionalMetadata.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Manual-mode day cards carry the worst report impact ('Partial outage',
'Major outage', ...) instead of the generic Degraded/Error label; legacy
reports and mixed days where legacy dominates keep the generic label.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 9 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/dashboard/src/components/forms/status-report/component-impact-field.tsx">

<violation number="1" location="apps/dashboard/src/components/forms/status-report/component-impact-field.tsx:62">
P2: `allowUnset` mode is not reversible: after selecting an impact, users cannot set the component back to "No change" (unset).</violation>
</file>

<file name="packages/ui/src/components/blocks/status-events.tsx">

<violation number="1" location="packages/ui/src/components/blocks/status-events.tsx:412">
P3: Use a stable item key here instead of the array index to avoid incorrect element reuse if the impact list changes.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread apps/server/src/routes/slack/agent.ts Outdated
@mxkaske

mxkaske commented Jun 11, 2026

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jun 11, 2026

Copy link
Copy Markdown

Claude finished @mxkaske's task in 2m 56s —— View job


Code Review

  • Gather context
  • Review key changed files
  • Analyze unresolved issues from prior review
  • Post findings

Summary

This PR introduces per-component impact tracking for status reports — a solid feature that correctly wires the data model (new join table), service layer, UI forms, and the status-page rendering. The architecture is clean and the batched query design in enrichReportsBatch avoids N+1 nicely.

Two issues from the prior review remain unresolved, and I found two additional ones.


Issues

P2 — allowUnset mode is not reversible

component-impact-field.tsx:43-85

Once a user selects any impact in allowUnset=true mode (used in the edit-update flow), there is no way to revert the component to "No change". The SelectContent only lists the four pageComponentImpact enum values — there is no unset option. The setImpact helper always inserts an entry; it never removes one.

Fix needed: When allowUnset=true, add a sentinel item to the top of SelectContent (e.g. value "__unset__" with label matching the placeholder), and handle it in setImpact by filtering the entry out instead of upserting it. Concretely:

function setImpact(id: number, next: string) {
  const rest = value.filter((v) => v.pageComponentId !== id);
  if (next === "__unset__") {
    onValueChange(rest);  // removes the entry → "No change"
  } else {
    onValueChange([...rest, { pageComponentId: id, impact: next as PageComponentImpact }]);
  }
}

And in the JSX:

<SelectContent>
  {allowUnset && (
    <SelectItem value="__unset__" className="text-muted-foreground font-mono">
      {placeholder}
    </SelectItem>
  )}
  {pageComponentImpact.map((option) => (
    <SelectItem key={option} value={option} ...>
  ))}
</SelectContent>

Fix this →


P3 — key={i} in StatusEventTimelineImpact

status-events.tsx:412

Using array index as key causes React to reuse the wrong DOM elements if the changes list is reordered or spliced. The items have a stable name field — use that instead.

// before
key={i}
// after
key={change.name}

Fix this →


P3 — In-place .sort() mutates row.updates

columns.tsx:162

accessorFn: (row) =>
  row.updates.sort((a, b) => a.date.getTime() - b.date.getTime())[0]
    ?.date ?? row.createdAt,

Array.prototype.sort sorts in-place. enrichReportsBatch returns updates ordered newest-first (it queries orderBy(desc(...date))). This accessor will silently reorder that array to oldest-first on first render, which may affect any other code that reads row.updates expecting newest-first ordering (e.g. worstCurrentImpact → currentImpactsFromUpdates).

currentImpactsFromUpdates re-sorts internally so it is OK, but it is still a latent hazard. Use a shallow copy:

accessorFn: (row) =>
  [...row.updates].sort((a, b) => a.date.getTime() - b.date.getTime())[0]
    ?.date ?? row.createdAt,

Fix this →


Nit — Comment discipline in new code

status-events.tsx:377-381

The new StatusEventTimelineImpact JSDoc block is 5 lines; CLAUDE.md caps new doc comments at 3 lines max ("one short line where possible, 3 lines max. Never write multi-paragraph JSDoc blocks"). The two-sentence description could be collapsed to one:

/** Hover-card label showing the worst impact among this update's component changes. */

What looks good

  • Service layer correctness: requireScope + emitAudit + withTransaction pattern followed throughout; the dedup refine on componentImpactsSchema prevents PK violations at the schema level before DB writes.
  • TOCTOU closed: validatePageComponentIds runs inside the caller's transaction, eliminating the race between validation and insert.
  • Batch loading: enrichReportsBatch loads updates, impact rows, and page components in 3 queries regardless of list size — solid fix for the N+1 pattern.
  • Resolve auto-clear: The logic in addStatusReportUpdate that synthesizes clears for still-non-operational components when status === "resolved" is correct. The named set properly prevents double-writing a component that appears in both componentImpacts and the auto-clear list.
  • accessorFn / getValue fix: columns.tsx now uses row.getValue<PageComponentImpact | null>("impact") instead of calling worstCurrentImpact a second time in the cell — the prior duplicate-computation complaint is resolved.

This reverts commit 8c4e945.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/test.yml">

<violation number="1">
P1: CI uses unbounded 'latest' Bun version instead of a pinned exact version, breaking build reproducibility.</violation>
</file>

<file name=".github/workflows/dx.yml">

<violation number="1">
P2: Using 'latest' for bun-version instead of a pinned version removes build reproducibility in CI</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

@mxkaske
mxkaske merged commit c203fbc into main Jun 11, 2026
12 of 15 checks passed
@mxkaske
mxkaske deleted the feat/status-report-impact branch June 11, 2026 19:23
@vercel
vercel Bot temporarily deployed to Preview – openstatus-status-page June 11, 2026 19:23 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-web June 11, 2026 19:23 Inactive
@vercel
vercel Bot temporarily deployed to Preview – openstatus-dashboard June 11, 2026 19:23 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant