Skip to content

ENG-2647: Privacy request detail — Jira ticket section#7742

Merged
jpople merged 13 commits intomainfrom
jpople/eng-2647/request-detail-jira-view
Mar 25, 2026
Merged

ENG-2647: Privacy request detail — Jira ticket section#7742
jpople merged 13 commits intomainfrom
jpople/eng-2647/request-detail-jira-view

Conversation

@jpople
Copy link
Copy Markdown
Contributor

@jpople jpople commented Mar 24, 2026

Ticket ENG-2647

Description Of Changes

Adds a Jira ticket section to the privacy request detail view. Operators can see all linked Jira tickets, click through to the ticket in Jira, retry failed ticket creation, refresh ticket status, and link an existing ticket by key.

Note: Retry and refresh require the backend to include instance_id (ManualTaskInstance.id) in the GET .../jira-tickets response. Until that is in place, those actions will not work end-to-end.

Code Changes

  • New RequestJiraTickets component displaying linked tickets with status badges and retry/refresh actions
  • New LinkJiraTicketModal for linking an existing Jira ticket by key (e.g. PRIV-123)
  • RTK Query endpoints for listing, linking, retrying, and refreshing Jira tickets

Steps to Confirm

  1. Navigate to a privacy request detail page — a "Jira tickets" section should appear
  2. If tickets are linked, each row shows the ticket key as an external link and a status badge
  3. Click the refresh icon on a ticket row — status should update and a success toast should appear
  4. Click "Link ticket", enter a ticket key, and confirm — the ticket should appear in the list
  5. For a ticket in failed state, click the retry icon — expect a success toast or an appropriate error message

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
    • Add a db-migration This indicates that a change includes a database migration label to the entry if your change includes a DB migration
    • Add a high-risk This issue suggests changes that have a high-probability of breaking existing code label to the entry if your change includes a high-risk change (i.e. potential for performance impact or unexpected regression) that should be flagged
    • Updates unreleased work already in Changelog, no new entry necessary
  • UX feedback:
    • All UX related changes have been reviewed by a designer
    • No UX review needed
  • Followup issues:
    • Followup issues created
    • No followup issues
  • Database migrations:
    • Ensure that your downrev is up to date with the latest revision on main
    • Ensure that your downgrade() migration is correct and works
      • If a downgrade migration is not possible for this change, please call this out in the PR description!
    • No migrations
  • Documentation:
    • Documentation complete, PR opened in fidesdocs
    • Documentation issue created in fidesdocs
    • If there are any new client scopes created as part of the pull request, remember to update public-facing documentation that references our scope registry
    • No documentation updates required

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 24, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
fides-plus-nightly Ready Ready Preview, Comment Mar 25, 2026 5:18pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
fides-privacy-center Ignored Ignored Mar 25, 2026 5:18pm

Request Review

@jpople jpople marked this pull request as ready for review March 25, 2026 04:49
@jpople jpople requested a review from a team as a code owner March 25, 2026 04:49
@jpople jpople requested review from eastandwestwind and speaker-ender and removed request for a team and speaker-ender March 25, 2026 04:49
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is clean, well-structured frontend work. The component decomposition (RequestJiraTicketsJiraTicketRow + LinkJiraTicketModal), per-ticket loading state tracking via originalArgs, and the RTK Query slice pattern all look solid. A few things to address:

Suggestions (worth fixing before merge):

  • ticket_url: string in types.ts should be string | null — it's used with || undefined in the component, indicating the API can return an empty/falsy value.
  • The getButtonVisibility parameterized test in helpers.test.ts doesn't include a case for the newly added PENDING_EXTERNAL status. It should.
  • The permanently disabled "Force close" button with a "not yet available" tooltip should be removed until the feature is ready — it's confusing as dead UI.

Nice to have:

  • A light format validation in LinkJiraTicketModal (e.g. /^[A-Z]+-\d+$/) would improve UX since the submit button currently only checks for non-empty input.
  • The instance_id/instance_status gap noted in the PR description warrants a guard: if instance_id is null, the refresh URL would be .../jira-tickets/null/refresh. A null check before calling either mutation would prevent a confusing API error.
  • No component-level tests for RequestJiraTickets or LinkJiraTicketModal are included. Given that tests exist for other parts of this feature area, adding at minimum a smoke test for the happy path rendering and the link modal submit flow would be consistent with the codebase.

instance_id: string;
instance_status: string | null;
ticket_key: string;
ticket_url: string;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ticket_url is typed as string (non-nullable), but in RequestJiraTickets.tsx it's used as ticket.ticket_url || undefined, which implies it can be falsy (empty string or null from the API). This type should be string | null to match the actual backend response and the defensive usage in the component — otherwise TypeScript won't catch cases where ticket_url is unexpectedly empty.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — updated ticket_url to string | null to match the defensive usage in the component.

</Button>
<Tooltip title="Force close not yet available">
<Button danger disabled data-testid="force-close-btn">
Force close
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Force close" button is permanently disabled with a placeholder tooltip. Shipping disabled UI with "not yet available" messaging creates a confusing experience — users will see a dead button with no way to understand when or if it will ever work. Consider removing this button entirely and adding it back when the feature is ready, rather than keeping it as a non-functional placeholder.

icon={<Icons.Undo />}
onClick={onRetry}
loading={isRetrying}
disabled={ticket.instance_status !== "failed" || isRefreshing}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The retry button is disabled when instance_status !== "failed". The PR description notes that instance_id is not yet populated by the backend, which means instance_status will also be null for all existing tickets — so retry will always be disabled in practice. This is fine as a temporary guard, but worth making explicit with a comment so the next developer knows why the button appears inert. Also consider: if instance_id is missing/null, calling retry/refresh will still hit the API with a null instance_id in the URL (e.g. .../jira-tickets/null/retry). It may be worth guarding against a null instance_id more explicitly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — typed instance_id as string | null and added a null guard in the retry/refresh handlers.

error as RTKErrorResult["error"],
"Failed to link Jira ticket.",
),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The submit button is only gated on !ticketKey.trim() (non-empty), but there's no format validation for the ticket key. A user could submit arbitrary strings like "hello" or "123" which would hit the backend and produce an error. Even a simple regex check (e.g. /^[A-Z]+-\d+$/) would give instant feedback and avoid a round-trip. This is a nice-to-have if validation also happens server-side, but worth considering for UX.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skipping for now — the backend will error if the key doesn't exist in Jira, so the user gets feedback either way. Can add client-side format validation in a follow-up if needed.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 25, 2026

Greptile Summary

This PR adds a Jira ticket section to the privacy request detail view, including a new RequestJiraTickets component (with per-ticket retry/refresh actions and per-row loading state), a LinkJiraTicketModal for linking tickets by key, and the RTK Query slice wiring up four new API endpoints. It also introduces the new PENDING_EXTERNAL privacy request status throughout the frontend.

Key findings:

  • Logic bug in LinkJiraTicketModal: onPressEnter on the ticket key input calls handleSubmit unconditionally. The submit button is correctly disabled when the input is empty/whitespace, but pressing Enter bypasses that guard and fires the API with ticket_key: "". A if (!ticketKey.trim()) return; guard at the top of handleSubmit is the fix.
  • Magic strings: The hardcoded "failed" value compared against instance_status in RequestJiraTickets.tsx, and the Jira status category strings ("done", "in progress", "to do") in statusColorMap, should be extracted as named constants per the project's convention.
  • The RTK Query slice correctly wires providesTags/invalidatesTags on all endpoints, keeping the ticket list fresh after mutations.
  • The hasPlus feature flag gating in RequestDetails.tsx is consistent with other Plus-only sections.

Confidence Score: 4/5

  • Safe to merge after fixing the onPressEnter empty-key bypass in LinkJiraTicketModal.
  • One concrete logic bug (Enter key bypasses empty-key validation) needs addressing before merge. The remaining comments are style/convention suggestions. The overall architecture is clean and consistent with the rest of the codebase.
  • clients/admin-ui/src/features/privacy-requests/jira-tickets/LinkJiraTicketModal.tsx — empty ticketKey guard missing from handleSubmit.

Important Files Changed

Filename Overview
clients/admin-ui/src/features/privacy-requests/jira-tickets/LinkJiraTicketModal.tsx New modal for linking existing Jira tickets by key. Has a logic bug where onPressEnter bypasses the empty-key guard on the submit button, potentially sending an API request with an empty ticket_key.
clients/admin-ui/src/features/privacy-requests/jira-tickets/RequestJiraTickets.tsx New component displaying linked Jira tickets with retry/refresh/link actions. Per-ticket loading state via originalArgs is well-implemented. Uses magic strings for Jira status names and instance_status comparison that should be named constants.
clients/admin-ui/src/features/privacy-requests/jira-tickets/privacy-request-jira-tickets.slice.ts RTK Query slice for Jira ticket endpoints. All four mutations/queries are correctly defined with proper providesTags/invalidatesTags to keep the list in sync.
clients/admin-ui/src/features/privacy-requests/jira-tickets/types.ts Type definitions for JiraTicketResult. Clean and straightforward; instance_id is typed as non-nullable string, consistent with how it is used in the retry/refresh URL params.
clients/admin-ui/src/features/privacy-requests/RequestDetails.tsx Correctly gates RequestJiraTickets behind the hasPlus feature flag, consistent with how RequestAttachments and other plus-only features are handled.

Reviews (1): Last reviewed commit: "format" | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@eastandwestwind eastandwestwind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small UX-facing things, nice work so far!

data-testid="force-close-modal"
>
<div className="mb-2 text-sm text-gray-500">
Force closing will complete all pending Jira gates for this request,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit on customer-facing messages like this one - I don't think it's obvious what is a Jira "gate" / "DSR runner"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this actually complete the JIRA tickets? e.g. move them to done?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call-- yep, I've updated to use clearer language and specify that tickets won't be closed (which is the actual behavior).

privacy_request_id: privacyRequestId,
reason: reason.trim() || null,
}).unwrap();
message.success("Privacy request Jira gates force-closed successfully.");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as below

message.error(
getErrorMessage(
error as RTKErrorResult["error"],
"Failed to force close Jira gates.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as below

return (
<div className="mt-6">
<div className="mb-4">
<Title level={3}>Jira tickets</Title>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will customers who have never set up the "Jira for Ticketing" integration see these jira-related components on every privacy request?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's probably wrong. Adding a check for the integration itself is a bit awkward so for now I've just stuck this behind the alphaJiraIntegration flag.

@jpople jpople enabled auto-merge March 25, 2026 17:09
@jpople jpople added this pull request to the merge queue Mar 25, 2026
Merged via the queue into main with commit 27b9461 Mar 25, 2026
45 of 46 checks passed
@jpople jpople deleted the jpople/eng-2647/request-detail-jira-view branch March 25, 2026 17:27
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.

2 participants