Skip to content

UN-3444 [FEAT] Propagate frontend request ID and surface it on error notifications#1955

Open
Deepak-Kesavan wants to merge 3 commits into
mainfrom
UN-3444-implement-and-propagate-request-id-from-frontend-to-backend
Open

UN-3444 [FEAT] Propagate frontend request ID and surface it on error notifications#1955
Deepak-Kesavan wants to merge 3 commits into
mainfrom
UN-3444-implement-and-propagate-request-id-from-frontend-to-backend

Conversation

@Deepak-Kesavan
Copy link
Copy Markdown
Contributor

Summary

  • Adds an axios request interceptor that injects X-Request-ID: <uuidv4> on every outbound API call (both on the global axios default instance and on useAxiosPrivate instances). Django + Flask backend middleware already honor an incoming X-Request-ID, so backend logs now share the same correlation ID the frontend generated.
  • useExceptionHandler pulls the request ID out of err.response.headers['x-request-id'] with a fallback to err.config.headers['X-Request-ID'] (covers ERR_NETWORK / ERR_CANCELED paths where there is no response).
  • The alert store carries requestId, and the notification description now renders a Request ID: <id> line with antd's Typography.Text copyable (built-in copy icon) whenever an error alert has a request ID.

Why

Support and on-call needed an easy way for users to share the correlation ID that ties a UI error back to backend/worker logs. Backend already understood X-Request-ID but the frontend was neither sending one nor surfacing it.

Files changed

  • frontend/src/helpers/requestId.js (new) — attachRequestIdInterceptor, getRequestIdFromError, REQUEST_ID_HEADER constant.
  • frontend/src/App.jsx — registers interceptor on global axios; renders the copyable request ID inline in error notifications.
  • frontend/src/hooks/useAxiosPrivate.js — registers + ejects the interceptor on the private axios instance.
  • frontend/src/hooks/useExceptionHandler.jsx — threads requestId through every buildAlert return path.
  • frontend/src/store/alert-store.js — adds requestId to the default alert shape.
  • frontend/src/index.css.notification-request-id BEM styling.

Notes

  • Cross-origin caveat: if FE and BE are on different origins, response.headers['x-request-id'] won't be readable in the browser unless the backend sets CORS_EXPOSE_HEADERS = ["X-Request-ID"]. The fallback to err.config.headers keeps the feature working regardless because the backend reuses whatever ID we send. Adding the expose header is a small follow-up if desired.

Test plan

  • Verify a successful request: confirm X-Request-ID is present on the outgoing request in the Network tab.
  • Verify the same UUID appears in backend logs for that request.
  • Trigger an API error (e.g. invalid payload) and confirm the notification shows Request ID: <uuid> with a working copy icon.
  • Disconnect network, trigger a request, confirm the offline notification still displays the request ID we sent.
  • Cancel an in-flight request and confirm the ID is still rendered.
  • Confirm non-error alerts (success/warning) do NOT show the request ID line.

…notifications

- Add `X-Request-ID` request interceptor (uuidv4) on the global axios instance
  and on each `useAxiosPrivate` axios instance so every API call carries a
  client-generated correlation ID. Django/Flask backends already honor
  incoming `X-Request-ID`, so backend logs reuse the same value.
- Extract the request ID in `useExceptionHandler` from response headers with
  a fallback to the outgoing request headers (covers network/cancel errors).
- Thread `requestId` through the alert store and render it in the error
  notification with an antd `Typography.Text copyable` for a one-click copy.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7a1a1426-1c4a-4274-9b5b-3243c9a6eaf4

📥 Commits

Reviewing files that changed from the base of the PR and between d242835 and 0fa515b.

📒 Files selected for processing (2)
  • frontend/src/App.jsx
  • frontend/src/helpers/requestId.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/helpers/requestId.js
  • frontend/src/App.jsx

Summary by CodeRabbit

  • New Features
    • Error notifications now show a copyable Request ID when available, helping support troubleshoot issues faster.
    • API requests are automatically assigned a unique Request ID so errors can be traced more reliably.
  • Style
    • Notification UI updated with styles that display Request ID labels and wrap long IDs for readability.

Walkthrough

Adds X-Request-ID support: a helper injects/generates request IDs on Axios requests and extracts them from errors; global and private Axios instances attach the interceptor; exception handler threads requestId into alerts; alert store holds requestId; notifications show a copyable Request ID for error alerts.

Changes

Request ID Tracking

Layer / File(s) Summary
Request ID helpers
frontend/src/helpers/requestId.js
Introduce REQUEST_ID_HEADER = "X-Request-ID", attachRequestIdInterceptor(axiosInstance) to add/generate the header on outbound requests, and getRequestIdFromError(err) to read the header from err.response.headers or err.config.headers.
Global Axios wiring
frontend/src/App.jsx
Module-level export const globalRequestIdInterceptor = attachRequestIdInterceptor(axios) attaches the request-id interceptor to the global Axios instance at startup; notification rendering refactored to compute a description that conditionally includes a Request ID block for error alerts.
Private Axios instance wiring
frontend/src/hooks/useAxiosPrivate.js
useAxiosPrivate registers the request-id interceptor on axiosPrivate and captures/ejects the interceptor in cleanup alongside existing interceptors.
Exception handling and alert shape
frontend/src/hooks/useExceptionHandler.jsx, frontend/src/store/alert-store.js
useExceptionHandler extracts requestId via getRequestIdFromError(err) and passes it into buildAlert for all code paths; alert objects now include requestId. useAlertStore default alertDetails adds requestId: null.
Notification UI and styling
frontend/src/App.jsx (notification block), frontend/src/index.css
Notification description always renders CustomMarkdown(alertDetails?.content) and appends a styled "Request ID" block (copyable value) when alertDetails.type === "error" and requestId exists; new CSS classes .notification-request-id, __label, __value added for layout and word-break.

Sequence Diagram

sequenceDiagram
    participant App
    participant Axios
    participant ReqInterceptor as Request Interceptor
    participant Server
    participant ErrHandler as Error Handler
    participant AlertStore as Alert Store
    participant Notification

    App->>Axios: attachRequestIdInterceptor(axios)
    Note over Axios: global interceptor installed

    App->>Axios: send HTTP request
    Axios->>ReqInterceptor: outgoing request
    ReqInterceptor->>ReqInterceptor: ensure/generate X-Request-ID
    ReqInterceptor->>Server: request + X-Request-ID

    Server-->>Axios: error response (+ X-Request-ID header)
    Axios->>ErrHandler: error caught
    ErrHandler->>ErrHandler: getRequestIdFromError(err)
    ErrHandler->>AlertStore: setAlertDetails(..., requestId)
    AlertStore->>Notification: notify with requestId
    Notification->>Notification: render error + Request ID block
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature: propagating a frontend request ID and surfacing it on error notifications, directly matching the changeset's primary objective.
Description check ✅ Passed The PR description comprehensively covers all template sections with clear explanations of what, why, how, files changed, notes on cross-origin considerations, and a detailed test plan.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch UN-3444-implement-and-propagate-request-id-from-frontend-to-backend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR closes the correlation-ID gap between the frontend and backend: a new attachRequestIdInterceptor injects a uuidv4 X-Request-ID header on every outbound Axios request (both the global instance and useAxiosPrivate instances), and useExceptionHandler now extracts that ID from the error and threads it through to the notification layer, where Ant Design's copyable Typography.Text makes it trivial for users to copy and paste to support.

  • requestId.js: Single-responsibility helper exporting the interceptor factory and getRequestIdFromError, which correctly uses ?? with lowercase response-header lookup and original-case config-header fallback to cover CORS-opaque responses and network/cancel errors alike.
  • useAxiosPrivate.js: Registers and ejects the request-ID interceptor symmetrically alongside the existing response interceptor, preventing accumulation on re-renders.
  • App.jsx / useExceptionHandler.jsx / alert-store.js: requestId is plumbed from error extraction → alert shape → notification description, and the UI only surfaces the ID for type === "error" alerts, leaving success/warning notifications unchanged.

Confidence Score: 5/5

This PR is safe to merge — it is additive only, touching no existing logic paths beyond threading a new nullable field through buildAlert and the notification renderer.

All changes are additive. The interceptor pattern matches the existing response-interceptor pattern in useAxiosPrivate (register + eject in the same effect). The getRequestIdFromError fallback chain is correct: ?? for the response-header lookup and a config-header fallback for cases where no response exists. Optional chaining throughout means falsy or missing err values are handled safely. The notification gate (type === "error" && requestId) ensures non-error alerts are unaffected.

No files require special attention.

Important Files Changed

Filename Overview
frontend/src/helpers/requestId.js New helper module exporting the request-ID header constant, interceptor factory, and error extractor; fallback chain correctly uses ?? and lowercase for response headers vs original case for config headers
frontend/src/App.jsx Registers global axios interceptor at module scope and conditionally renders a copyable Request ID in error notifications; interceptor ID is exported but never ejected (only matters for HMR dev flow, already noted in prior review)
frontend/src/hooks/useAxiosPrivate.js Correctly registers the request-ID interceptor inside the same useEffect as the response interceptor and ejects both on cleanup, matching the existing pattern
frontend/src/hooks/useExceptionHandler.jsx Threads requestId through all buildAlert return paths; getRequestIdFromError is called before the !err guard but optional chaining makes this safe
frontend/src/store/alert-store.js Adds requestId: null to the default alert shape; setAlertDetails spreads caller-supplied details so requestId flows through correctly
frontend/src/index.css Adds BEM-style CSS for the request-ID notification row; straightforward layout rules with no issues

Sequence Diagram

sequenceDiagram
    participant Browser
    participant AxiosInterceptor as Axios Request Interceptor
    participant Backend
    participant ExceptionHandler as useExceptionHandler
    participant AlertStore as alert-store
    participant AppUI as App.jsx Notification

    Browser->>AxiosInterceptor: outgoing request (no X-Request-ID)
    AxiosInterceptor->>AxiosInterceptor: inject X-Request-ID: uuidv4()
    AxiosInterceptor->>Backend: request with X-Request-ID header
    Backend-->>Browser: response

    alt HTTP error
        Backend-->>Browser: error response
        Browser->>ExceptionHandler: handleException(err)
        ExceptionHandler->>ExceptionHandler: getRequestIdFromError(err)
        ExceptionHandler->>AlertStore: buildAlert(..., requestId)
        AlertStore->>AppUI: alertDetails with requestId
        AppUI-->>Browser: notification with copyable Request ID
    else Network or Canceled error
        Browser->>ExceptionHandler: handleException(err)
        ExceptionHandler->>ExceptionHandler: fallback to err.config.headers
        ExceptionHandler->>AlertStore: buildAlert(..., requestId)
        AlertStore->>AppUI: alertDetails with requestId
        AppUI-->>Browser: notification with copyable Request ID
    end
Loading

Reviews (3): Last reviewed commit: "Merge branch 'main' into UN-3444-impleme..." | Re-trigger Greptile

Comment thread frontend/src/helpers/requestId.js
Comment thread frontend/src/App.jsx Outdated
Comment thread frontend/src/helpers/requestId.js
- Use nullish coalescing in `getRequestIdFromError` so an empty-string
  backend-echoed header isn't silently shadowed by the request-side header.
- Drop the redundant mixed-case response-header lookup; browser/axios
  response headers are always lower-cased.
- Export the global axios interceptor ID so HMR-friendly cleanup is
  possible (and the chain doesn't leak across module re-evals).
@Deepak-Kesavan Deepak-Kesavan self-assigned this May 12, 2026
@github-actions
Copy link
Copy Markdown
Contributor

Frontend Lint Report (Biome)

All checks passed! No linting or formatting issues found.

@sonarqubecloud
Copy link
Copy Markdown

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