Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/api/__tests__/errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ describe('processServerError', () => {
code: undefined,
message: 'Hi, you have an error',
statusCode: 400,
errorCode: undefined,
requestId: '1',
})
})

Expand All @@ -67,6 +69,7 @@ describe('processServerError', () => {
errorCode: 'ObjectAlreadyExists',
message: 'Instance name already exists',
statusCode: 400,
requestId: '2',
})
})

Expand All @@ -76,6 +79,7 @@ describe('processServerError', () => {
errorCode: 'ObjectAlreadyExists',
message: 'Thing name already exists',
statusCode: 400,
requestId: '2',
})
})
})
Expand All @@ -91,6 +95,7 @@ describe('processServerError', () => {
errorCode: 'ObjectNotFound',
message: 'Not found: whatever',
statusCode: 404,
requestId: '2',
})
})
})
Expand All @@ -101,6 +106,7 @@ describe('processServerError', () => {
errorCode: 'WeirdError',
message: 'Whatever',
statusCode: 400,
requestId: '2',
})
})
})
Expand Down
5 changes: 3 additions & 2 deletions app/api/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export type ApiError = {
message: string
errorCode?: string
statusCode?: number
requestId?: string
}

/**
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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,
}
}

Expand Down
23 changes: 22 additions & 1 deletion app/api/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand Down
4 changes: 3 additions & 1 deletion app/ui/lib/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
2 changes: 1 addition & 1 deletion mock-api/msw/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down