diff --git a/app/api/__tests__/errors.spec.ts b/app/api/__tests__/errors.spec.ts index 725d7ce430..a30ae58647 100644 --- a/app/api/__tests__/errors.spec.ts +++ b/app/api/__tests__/errors.spec.ts @@ -45,6 +45,8 @@ describe('processServerError', () => { code: undefined, message: 'Hi, you have an error', statusCode: 400, + errorCode: undefined, + requestId: '1', }) }) @@ -67,6 +69,7 @@ describe('processServerError', () => { errorCode: 'ObjectAlreadyExists', message: 'Instance name already exists', statusCode: 400, + requestId: '2', }) }) @@ -76,6 +79,7 @@ describe('processServerError', () => { errorCode: 'ObjectAlreadyExists', message: 'Thing name already exists', statusCode: 400, + requestId: '2', }) }) }) @@ -91,6 +95,7 @@ describe('processServerError', () => { errorCode: 'ObjectNotFound', message: 'Not found: whatever', statusCode: 404, + requestId: '2', }) }) }) @@ -101,6 +106,7 @@ describe('processServerError', () => { errorCode: 'WeirdError', message: 'Whatever', statusCode: 400, + requestId: '2', }) }) }) diff --git a/app/api/errors.ts b/app/api/errors.ts index a9a6c74476..a7c8b422c3 100644 --- a/app/api/errors.ts +++ b/app/api/errors.ts @@ -18,6 +18,7 @@ export type ApiError = { message: string errorCode?: string statusCode?: number + requestId?: string } /** @@ -42,11 +43,10 @@ export function processServerError(method: string, resp: ErrorResult): ApiError // client error is a JSON parse or processing error and is highly unlikely to // be end-user readable if (resp.type === 'client_error') { - // nice to log but don't clutter test output - if (process.env.NODE_ENV !== 'test') console.error(resp) return { message: 'Error reading API response', statusCode: resp.response.status, + requestId: undefined, } } @@ -77,6 +77,7 @@ export function processServerError(method: string, resp: ErrorResult): ApiError message, errorCode: resp.data.errorCode || undefined, statusCode: resp.response.status, + requestId: resp.data.requestId, } } diff --git a/app/api/hooks.ts b/app/api/hooks.ts index 2b0b9b7f1b..e66796c502 100644 --- a/app/api/hooks.ts +++ b/app/api/hooks.ts @@ -58,8 +58,29 @@ const handleResult = // action, e.g., polling or refetching when window regains focus navToLogin({ includeCurrent: true }) } + + const error = processServerError(method, result) + + // log to the console so it's there in case they open the dev tools, unlike + // network tab, which only records if dev tools are already open. but don't + // clutter test output + if (process.env.NODE_ENV !== 'test') { + const consolePage = window.location.pathname + window.location.search + // TODO: need to change oxide.ts to put the HTTP method on the result in + // order to log it here + console.error( + `More info about API ${error.statusCode || 'error'} on ${consolePage} + +API URL: ${result.response.url} +Request ID: ${error.requestId} +Error code: ${error.errorCode} +Error message: ${error.message} +` + ) + } + // we need to rethrow because that's how react-query knows it's an error - throw processServerError(method, result) + throw error } /** diff --git a/app/ui/lib/Toast.tsx b/app/ui/lib/Toast.tsx index 44119b2267..29b4251cca 100644 --- a/app/ui/lib/Toast.tsx +++ b/app/ui/lib/Toast.tsx @@ -75,9 +75,11 @@ export const Toast = ({ content, onClose, variant = 'success', - timeout = 5000, + timeout: timeoutArg, cta, }: ToastProps) => { + const defaultTimeout = variant === 'error' ? 15000 : 5000 + const timeout = timeoutArg === undefined ? defaultTimeout : timeoutArg // TODO: consider assertive announce for error toasts useEffect( () => announce((title || defaultTitle[variant]) + ' ' + content, 'polite'), diff --git a/mock-api/msw/util.ts b/mock-api/msw/util.ts index f3cd70db3e..234a72e367 100644 --- a/mock-api/msw/util.ts +++ b/mock-api/msw/util.ts @@ -93,7 +93,7 @@ export function getTimestamps() { } export const unavailableErr = () => - json({ error_code: 'ServiceUnavailable' }, { status: 503 }) + json({ error_code: 'ServiceUnavailable', request_id: 'fake-id' }, { status: 503 }) export const NotImplemented = () => { // This doesn't just return the response because it broadens the type to be usable