发布

  • [OPIK-7139] [BE] [FE] Surface diagnostics run failures with a reason in the UI (#7272)

    frostbyte_neo 发布于 2026-07-01 10:52:24 +00:00

    • feat(diagnostics): surface run failures with a reason in the UI

    A triggered Diagnostics run was fire-and-forget: if it crashed (e.g. out of
    LLM credits) or never started, nothing was recorded and the UI spun until the
    client timeout, indistinguishable from "completed, no issues".

    Capture the failure on the job and surface it:

    • Migration 000087 adds last_failure_reason/detail/last_failed_at to
      agent_insights_jobs; a successful report clears them.
    • New POST /v1/private/agent-insights/run-failure ingest endpoint + service.
    • The report subscriber records "did_not_start" when the trigger never reaches
      Ollie, so the run still ends.
    • FE ends the run on a failure signal and shows a reason-specific failed state
      (out_of_credits / rate_limited / provider_error / generic) with a Try again
      action; run timeout cut 12m -> 5m.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • fix(diagnostics): address review — derive failure from job, migration newline
    • Derive the failed state from the job's last_failed_at instead of a client-only
      flag, so the banner is correct after reload, in other tabs, and auto-clears on
      a later successful (incl. auto) run. The hook now owns only the "running" state.
    • Tolerate the pre-failBaseline localStorage shape so a run in flight across a
      deploy isn't dropped on reload.
    • Add the trailing blank line required for Liquibase formatted-SQL migrations.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • fix(diagnostics): backfill legacy run-state failBaseline to startedAt

    A legacy run-state blob (no failBaseline) backfilled to 0 made a reload treat
    any pre-existing last_failed_at as a fresh failure, flipping an in-flight run to
    failed. Backfill to the run's startedAt so only a failure recorded after the run
    began counts.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • feat(diagnostics): per-reason failure copy, details, stale-results banner
    • Distinct copy per failure code (out_of_credits / rate_limited /
      provider_error / did_not_start / internal_error) instead of one catch-all.
    • Surface the backend's failure detail: inline "Show details" toggle in the
      banner (issues present), always-shown plain text in the empty state.
    • New "Results might be outdated" banner when the last scan is older than 3
      days, showing how many last-24h traces a re-run would analyze.
    • Run-status banners (running / failed / stale) are suppressed on the Resolved
      view; precedence is running > failed > stale.
    • Issue list item: description is muted unless selected; stats always muted.
    • Failure surfaces use #F14668 (icon) / #F146681A (wash); Try again uses the
      rotate-ccw icon; severity badge corner radius 4px.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • fix(diagnostics): expose latest-day count alongside cross-day total

    A multi-day issue showed two contradicting numbers: the "Occurrences" stat is
    the cross-day SUM, while the description prose embeds the latest run's count.

    • BE: findIssues now returns latest_count (most recent report day's count) via
      window functions, keeping total_occurrences as the cross-day sum.
    • FE: the occurrence headline shows "N total · M latest" for multi-day issues
      (collapses to just the total for single-day), so the number matches the prose.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • fix(diagnostics): address review — stale-banner edge cases + run-failure validation test
    • Stale check derives scanAt from the displayed lastScan (which falls back to
      the latest issue update), so records without last_scan_at still resolve.
    • Last-24h cutoff is bucketed to the hour so it tracks the rolling window
      instead of freezing at mount, without churning the query key each render.
    • Add a parameterized 400/422 test for POST /run-failure covering missing
      projectId and missing/blank errorCode.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • fix(diagnostics): round stale-window cutoff up so it never exceeds 24h

    Bucketing the last-24h cutoff with floor() widened the window to ~25h, letting
    the count include traces slightly older than 24h. Round up instead — the window
    stays <=24h while the query key remains hour-stable.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • refactor(diagnostics): address review — hooks layering, traces-by-id hook, theme tokens

    Addresses andriidudar's suggestions from OPIK-6817:

    • Move useDiagnosticsRunState/useDiagnosticsSeen from pages/SignalsPage to
      @/hooks so the v2/layout sidebar badge no longer reaches into a page.
    • AffectedTracesSample now fetches via a new api/traces/useTracesByIds (reusing
      useTraceById's fetcher + query key, no double-fetch) and caps the sample to 5
      trace requests instead of firing one per example id.
    • Replace hardcoded hex with theme tokens: toggle-outline-active, light-slate,
      and the upload-chip-bg / upload-chip-icon-bg CSS variables.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • chore(diagnostics): trim verbose comments

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • refactor(diagnostics): make Resolved issues a route instead of local state

    Resolved issues is now its own route (/diagnostics/resolved) rendering
    SignalsPage with showResolved, instead of a local useState toggle. The header
    uses the shared BackButton (Link) to return to /diagnostics — matching how back
    navigation works elsewhere in the app — and the "Resolved issues" / "Back to
    open issues" actions navigate between the two routes.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • refactor(diagnostics): store run failures in a generic report_failures table

    Per review, move run failures off agent_insights_jobs (which only held the
    latest failure) into a dedicated, generic report_failures table keyed by
    (type, entity_id) so failure history is preserved and other features can reuse
    it. 000087 already shipped to dev, so this is forward-only: a new table + a
    column-drop migration rather than editing 000087.

    • 000088 creates report_failures (utf8mb4_unicode_ci to match the schema-wide
      collation, avoiding illegal-mix-of-collations on the join).
    • 000089 drops last_failure_reason/detail/at from agent_insights_jobs.
    • New generic ReportFailureDAO.insert; Agent Insights inserts with
      type="agent_insights", entity_id=project_id.
    • AgentInsightsJobDAO.findByProject LEFT JOINs the latest failure newer than
      last_scan_at, aliased to last_failure_* — so the job DTO/JSON and the FE are
      unchanged. markScanned just advances last_scan_at; markFailed removed.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • feat(diagnostics): generic report-failures endpoints; drop agent-insights run-failure path

    Add a generic POST/GET /v1/private/report-failures over the report_failures
    table (ReportFailure DTO, ReportFailureService, ReportFailuresResource, DAO
    find/count). Ollie now records run failures here with type="agent_insights".
    The BE still computes "currently failed" by joining the latest failure against
    the job's last_scan_at, so the job DTO/JSON and FE are unchanged.

    Remove the now-redundant POST /v1/private/agent-insights/run-failure endpoint,
    its AgentInsightsRunFailure DTO, and AgentInsightsIssueService.reportRunFailure.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • refactor(diagnostics): address review on report-failures endpoint
    • create now returns 201 Created with a Location header (was 204), so clients
      learn the new resource id (matches the create-endpoint convention elsewhere).
    • reject agent_insights failures for a non-existent project (per-type integrity
      guard via ProjectService) so orphan rows can't later surface on a job.
    • parameterize reportFailure__invalidBody__returns422.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • fix(diagnostics): record run failure when manual trigger fails to enqueue

    The manual trigger's enqueue-error path only logged + a metric, so a
    publisher-side failure (the run never reaches Ollie, which would otherwise
    report its own failure) left the UI spinning until the client timeout. Record
    a did_not_start failure in that path so the failure surfaces. Addresses
    review feedback that failures were only marked on the subscriber side.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • refactor(diagnostics): rename report_failures entity_id -> project_id, type enum

    Address review (BorisTkachenko): the failure log is project-scoped, so rename
    the column/field/param/query-param entity_id -> project_id across the migration,
    DTO, DAO, service, resource, the agent-insights job join, and tests; make type
    a MySQL ENUM('agent_insights'); reorder the lookup index to
    (workspace_id, project_id, type, created_at).

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • refactor(diagnostics): apply report_failures rename via forward migration 000090

    000088 is already deployed to dev, so editing it in place would break the
    liquibase checksum there (and CREATE TABLE IF NOT EXISTS wouldn't alter the
    existing table anyway). Restore 000088 to its deployed form and add a forward
    000090 that renames entity_id -> project_id, makes type ENUM('agent_insights'),
    and rebuilds the lookup index as (workspace_id, project_id, type, created_at).
    Same forward-only approach as 000089; no manual dev DB surgery needed.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • fix(diagnostics): validate report-failure type up front; migration EOF newline

    Now that report_failures.type is an ENUM, reject an unsupported type with a 400
    in ReportFailureService.create instead of letting it fail at the DB with a 500
    (addresses review). Add a test for the 400. Append the trailing newline to
    000090 per the Liquibase formatting convention.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • refactor(diagnostics): address review — type enum, faster issues query, clearer doc
    • ReportFailure.type is now a ReportFailureType enum (AGENT_INSIGHTS) with a
      @JsonCreator that returns 400 for unknown values; drops the manual type check.
    • AgentInsightsIssueDAO.findIssues: replace window functions with a GROUP BY
      aggregate subquery + a join to the latest report-day row (per BorisTkachenko),
      so the aggregation can use the details index instead of partitioning every row.
    • Clarify the latestCount @Schema description.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • fix(diagnostics): ReportFailureType.fromString returns null for missing input

    So an absent/null type falls through to @NotNull (422) instead of being
    reported as an unknown type (400); a present-but-unknown value still yields 400.
    Makes the documented validation explicit (addresses review).

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • refactor(diagnostics): consolidate report_failures migrations into one clean CREATE

    Staging/prod have never run this branch, so collapse the dev-driven incremental
    migrations (000087 add cols → 000088 create → 000089 drop cols → 000090 alter)
    into a single 000087_create_report_failures with the final schema (project_id,
    type ENUM('agent_insights'), lookup index). No agent_insights_jobs column churn.

    Dev, which already ran the incremental set, needs a one-time reset (drop the
    empty report_failures table + delete the old 000087-000090 changelog rows)
    before its next deploy.

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com

    • chore(diagnostics): trailing newline on 000087_create_report_failures

    Liquibase formatted-SQL files end with a blank line (addresses review).

    Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com


    Co-authored-by: Claude Opus 4.8 noreply@anthropic.com

    下载附件