mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
feat(web): prefer backend-enriched errorCodeInfo, fall back to local catalog
Adds getEnrichedOrLookup() helper that returns the backend-provided
{description, confidence, source} sibling when present (set by the
backend ErrorCodeEnricher on event responses), otherwise looks up via
the local catalog. EventTimeline event badges now read the enriched
field first — older API responses without enrichment fall through to
the existing local-catalog path so there is no regression.
InstallProgress + apps/[appName] pages consume UI-model rows (not raw
event.Data) and are not touched — they continue to use the local
catalog which already covers their inputs. 5 new helper tests; 409/409
web tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
import { useState, useMemo } from "react";
|
||||
import { EnrollmentEvent, Session } from "@/types";
|
||||
import { normalizeEventDataForDisplay, shortenBuildHashInMessage } from "../utils/eventHelpers";
|
||||
import { getErrorCodeEntry, formatErrorCode } from "@/utils/errorCodeMap";
|
||||
import { getErrorCodeEntry, getEnrichedOrLookup, formatErrorCode } from "@/utils/errorCodeMap";
|
||||
|
||||
interface EventTimelineProps {
|
||||
filteredEvents: EnrollmentEvent[];
|
||||
@@ -447,8 +447,10 @@ function EventRow({ event, showScriptOutput }: { event: EnrollmentEvent; showScr
|
||||
const hr = event.data?.hresultFromWin32 ?? event.data?.hresult_from_win32;
|
||||
const hasNonZero = (ec && String(ec) !== "0") || (hr && String(hr) !== "0");
|
||||
if (!hasNonZero) return null;
|
||||
const ecEntry = ec ? getErrorCodeEntry(String(ec)) : null;
|
||||
const hrEntry = hr ? getErrorCodeEntry(String(hr)) : null;
|
||||
// Prefer backend-enriched *Info sibling, fall back to local lookup for older
|
||||
// responses that pre-date the backend ErrorCodeEnricher.
|
||||
const ecEntry = ec ? getEnrichedOrLookup(event.data?.exitCodeInfo, String(ec)) : null;
|
||||
const hrEntry = hr ? getEnrichedOrLookup(event.data?.hresultFromWin32Info, String(hr)) : null;
|
||||
return (
|
||||
<div className="mt-1 flex flex-wrap items-center gap-2 text-xs">
|
||||
{ec && String(ec) !== "0" && (
|
||||
@@ -489,7 +491,7 @@ function EventRow({ event, showScriptOutput }: { event: EnrollmentEvent; showScr
|
||||
const isAdvisory = event.eventType === "esp_failure_advisory";
|
||||
if (!code && !isAdvisory) return null;
|
||||
const codeStr = code ? String(code) : null;
|
||||
const entry = codeStr ? getErrorCodeEntry(codeStr) : null;
|
||||
const entry = codeStr ? getEnrichedOrLookup(event.data?.errorCodeInfo, codeStr) : null;
|
||||
// Advisory path uses warning-color palette (amber); the device continued past the
|
||||
// failure via ContinueAnyway, so this is not a hard error. PR1 Session 4fa5a2d4.
|
||||
const badgeBg = isAdvisory ? "bg-amber-100 text-amber-800" : "bg-red-100 text-red-800";
|
||||
|
||||
@@ -2,6 +2,7 @@ import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
getErrorCodeDescription,
|
||||
getErrorCodeEntry,
|
||||
getEnrichedOrLookup,
|
||||
formatErrorCode,
|
||||
} from "../errorCodeMap";
|
||||
import catalogFile from "../error-codes.json";
|
||||
@@ -67,6 +68,43 @@ describe("errorCodeMap", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getEnrichedOrLookup", () => {
|
||||
it("prefers backend-enriched sibling when present", () => {
|
||||
const enriched = {
|
||||
description: "Backend-provided description",
|
||||
confidence: "high" as const,
|
||||
source: "Backend Catalog v2",
|
||||
};
|
||||
const result = getEnrichedOrLookup(enriched, "0x80070005");
|
||||
expect(result).toBe(enriched);
|
||||
expect(result?.description).toBe("Backend-provided description");
|
||||
});
|
||||
|
||||
it("falls back to local catalog when info is null", () => {
|
||||
const result = getEnrichedOrLookup(null, "0x80070005");
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.description).toContain("Access denied");
|
||||
});
|
||||
|
||||
it("falls back to local catalog when info is undefined", () => {
|
||||
const result = getEnrichedOrLookup(undefined, "0x80070005");
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.description).toContain("Access denied");
|
||||
});
|
||||
|
||||
it("returns null when both info and lookup miss", () => {
|
||||
const result = getEnrichedOrLookup(null, "0xDEADBEEF");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("ignores malformed info objects and falls back to lookup", () => {
|
||||
// Backend would never emit this but guard against bad clients.
|
||||
const malformed = { description: 42 } as unknown as { description: string; confidence: "high"; source: string };
|
||||
const result = getEnrichedOrLookup(malformed, "0x80070005");
|
||||
expect(result?.description).toContain("Access denied");
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatErrorCode", () => {
|
||||
it("uppercases hex codes", () => {
|
||||
expect(formatErrorCode("0x80070005")).toBe("0X80070005");
|
||||
|
||||
@@ -95,6 +95,26 @@ export function getErrorCodeEntry(code: string | number | null | undefined): Err
|
||||
return lookupEntry(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Backend-enriched info entry. The backend ErrorCodeEnricher injects sibling
|
||||
* <code>{codeKey}Info</code> properties into event Data containing the same shape as
|
||||
* <see cref="ErrorCodeEntry"/>. When present we prefer the server-side value (uses the
|
||||
* authoritative catalog version + Confidence-level), and only fall back to the local
|
||||
* lookup for older responses that don't carry the enrichment yet.
|
||||
*
|
||||
* Example: an event with <code>{ errorCode: "0x80070005", errorCodeInfo: {...} }</code>
|
||||
* passes the info through without a second lookup.
|
||||
*/
|
||||
export function getEnrichedOrLookup(
|
||||
info: ErrorCodeEntry | null | undefined,
|
||||
code: string | number | null | undefined
|
||||
): ErrorCodeEntry | null {
|
||||
if (info && typeof info === "object" && typeof info.description === "string") {
|
||||
return info;
|
||||
}
|
||||
return lookupEntry(code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a raw numeric error code for display.
|
||||
* Signed-decimal HRESULTs are converted to hex notation.
|
||||
|
||||
Reference in New Issue
Block a user