Skip to content

ENG-2879: Relocate delete and generate report buttons to page header#7564

Merged
kruulik merged 3 commits intomainfrom
2879-relocate-buttons
Mar 4, 2026
Merged

ENG-2879: Relocate delete and generate report buttons to page header#7564
kruulik merged 3 commits intomainfrom
2879-relocate-buttons

Conversation

@kruulik
Copy link
Copy Markdown
Contributor

@kruulik kruulik commented Mar 4, 2026

Ticket ENG-2879

Description Of Changes

Relocates the trash icon (delete assessment) and Generate report button from the inline title row in AssessmentDetail to the sticky PageHeader via its rightContent prop. This matches the existing pattern used by the assessments list page and keeps action buttons consistently in the upper header area.

The delete handler and isComplete computation are lifted to the page component ([id].tsx) where the PageHeader is rendered.

image

Code Changes

  • clients/admin-ui/src/pages/privacy-assessments/[id].tsx - Add delete mutation, isComplete computation, handleDelete, and render buttons as PageHeader rightContent
  • clients/admin-ui/src/features/privacy-assessments/AssessmentDetail.tsx - Remove delete button, generate report button, and associated hooks/handlers; simplify title row to a plain div

Steps to Confirm

  1. Navigate to a privacy assessment detail page
  2. Confirm the trash icon and Generate report button appear in the sticky header (right side, same row as the page title/breadcrumbs)
  3. Confirm the title row in the page body only shows the assessment name, status tag, and system name — no action buttons
  4. Confirm Generate report is disabled with a tooltip when the assessment is in progress
  5. Confirm clicking the trash icon opens the delete confirmation modal and successfully deletes the assessment
  6. Confirm the Request input from team button still appears correctly in the second row when the assessment is incomplete

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:
    • No migrations
  • Documentation:
    • No documentation updates required

Summary by CodeRabbit

Release Notes

  • Changed
    • Relocated Delete and Generate Report buttons to the sticky page header for easier access.
    • Added confirmation dialog when deleting assessments for added safety.
    • Generate Report button now requires all assessment questions to be completed before enabling.

kruulik and others added 2 commits March 4, 2026 14:29
Move the trash icon and Generate report button from the AssessmentDetail
title row into the sticky PageHeader rightContent slot, matching the
pattern used by the list page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 4, 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 4, 2026 7:42pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
fides-privacy-center Ignored Ignored Mar 4, 2026 7:42pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

The PR relocates delete and generate report buttons from the AssessmentDetail component to the page header of the privacy-assessments detail page. The refactoring adds completion validation for the generate report action and implements a confirmation modal for the delete operation with centralized error handling.

Changes

Cohort / File(s) Summary
Changelog
changelog/7564-relocate-assessment-action-buttons.yaml
Changelog entry documenting the UI change to relocate delete and generate report buttons to the sticky page header.
Button Relocation
clients/admin-ui/src/features/privacy-assessments/AssessmentDetail.tsx
Removed delete button, generate report button, and associated imports (useDeletePrivacyAssessmentMutation, useRouter, useModal). Simplified header to display only name, status tag, and system name.
Header Actions Integration
clients/admin-ui/src/pages/privacy-assessments/[id].tsx
Added delete button with modal confirmation dialog, error handling, and success messaging. Integrated completion-check logic to validate all assessment questions have answers before enabling generate report button, with disabled state and tooltip for incomplete assessments.

Sequence Diagram

sequenceDiagram
    actor User
    participant PageHeader as Page Header
    participant Modal
    participant Assessment as Assessment API
    participant MessageUI as Message UI

    User->>PageHeader: Click Delete Button
    PageHeader->>Modal: Open Confirmation Dialog
    User->>Modal: Confirm Deletion
    Modal->>Assessment: Execute Delete Mutation
    
    alt Success
        Assessment-->>Modal: Delete Complete
        Modal->>MessageUI: Show Success Message
        MessageUI-->>User: Navigate to List
    else Error
        Assessment-->>Modal: Error Response
        Modal->>MessageUI: Display Error Message
        MessageUI-->>User: Show Error Notification
    end

    User->>PageHeader: Hover Generate Report Button
    alt Assessment Complete
        PageHeader-->>User: Button Enabled
    else Assessment Incomplete
        PageHeader-->>User: Tooltip: Complete All Questions
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Buttons once tucked in the component deep,
Now dance in headers for a cleaner sweep!
With modals that ask and checks that ensure,
This relocation makes the workflow pure! ✨
— Your friendly rabbit reviewer 🐇

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: relocating delete and generate report buttons to the page header, which is the primary objective of this PR.
Description check ✅ Passed The description comprehensively covers the changes, includes code file modifications, clear confirmation steps, and a completed pre-merge checklist with appropriate items checked off.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2879-relocate-buttons

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

@kruulik kruulik marked this pull request as ready for review March 4, 2026 19:44
@kruulik kruulik requested a review from a team as a code owner March 4, 2026 19:44
@kruulik kruulik requested review from lucanovera and removed request for a team March 4, 2026 19:44
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 4, 2026

Greptile Summary

This PR cleanly relocates the delete and generate-report action buttons from the inline title row in AssessmentDetail to the sticky PageHeader via its rightContent prop, matching the pattern already used on the assessments list page. The refactor is well-scoped and straightforward.

Key changes:

  • [id].tsx: useDeletePrivacyAssessmentMutation, isComplete computation, and handleDelete are added to the page component; both buttons are rendered inside PageHeader's rightContent.
  • AssessmentDetail.tsx: Corresponding hooks, handlers, and button UI are removed; the outer Flex wrapper is simplified to a plain div grouping container.
  • changelog/7564-relocate-assessment-action-buttons.yaml: Changelog entry added.

Issue identified:

  • isComplete is computed twice — once in [id].tsx (without useMemo) and once in AssessmentDetail.tsx (with useMemo). The unmemoized computation on the page causes unnecessary recomputation on every render and introduces a maintenance risk if the completeness criterion changes.

Confidence Score: 4/5

  • This PR is safe to merge; the refactor is straightforward UI relocation with no logic changes affecting business rules.
  • The refactoring is correct and matches existing patterns. The flagged issue—isComplete computed twice without memoization in the page component—is a code quality/performance concern rather than a correctness issue, and is straightforward to fix.
  • clients/admin-ui/src/pages/privacy-assessments/[id].tsx — memoize the isComplete computation to avoid duplicate work and maintenance risk.

Last reviewed commit: 4cda64c

Comment on lines +47 to +51
const isComplete =
!!assessment &&
(assessment.question_groups ?? [])
.flatMap((g) => g.questions)
.every((q) => q.answer_text.trim().length > 0);
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.

The isComplete computation is now present in both this page component and in AssessmentDetail.tsx (lines 58–61 of that file, where it's memoized). This creates two separate traversals of the same question_groups data structure on every render of the page component, and introduces a maintenance risk — if the completeness criterion ever changes, both places need to be updated.

Consider wrapping this in useMemo to match the memoization pattern already used in the child component:

Suggested change
const isComplete =
!!assessment &&
(assessment.question_groups ?? [])
.flatMap((g) => g.questions)
.every((q) => q.answer_text.trim().length > 0);
const isComplete = useMemo(
() =>
!!assessment &&
(assessment.question_groups ?? [])
.flatMap((g) => g.questions)
.every((q) => q.answer_text.trim().length > 0),
[assessment],
);

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
clients/admin-ui/src/pages/privacy-assessments/[id].tsx (1)

47-51: Consider extracting or memoizing the isComplete computation.

This logic is duplicated in AssessmentDetail.tsx (lines 58-61) where it's memoized with useMemo. Here it's computed inline on every render.

Options:

  1. Extract to a shared utility function and memoize in both places
  2. Compute it once here and pass isComplete as a prop to AssessmentDetail

Given that AssessmentDetail uses isComplete for the status tag and this page uses it for the "Generate report" button, consolidating would reduce duplication and ensure consistency.

♻️ Option 2: Pass isComplete as prop
+ import { useMemo } from "react";
...
- const isComplete =
-   !!assessment &&
-   (assessment.question_groups ?? [])
-     .flatMap((g) => g.questions)
-     .every((q) => q.answer_text.trim().length > 0);
+ const isComplete = useMemo(
+   () =>
+     !!assessment &&
+     (assessment.question_groups ?? [])
+       .flatMap((g) => g.questions)
+       .every((q) => q.answer_text.trim().length > 0),
+   [assessment],
+ );
...
- <AssessmentDetail assessment={assessment} />
+ <AssessmentDetail assessment={assessment} isComplete={isComplete} />

Then update AssessmentDetail to accept and use the prop instead of recomputing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/admin-ui/src/pages/privacy-assessments/`[id].tsx around lines 47 -
51, Extract the inline completion logic into a shared utility (e.g.,
computeIsComplete(assessment)) that encapsulates the current check (flatten
question_groups -> questions -> answer_text.trim().length > 0), then replace the
inline computation in this page ([id].tsx) and in AssessmentDetail.tsx with
calls to that utility; in components, wrap the call in useMemo (e.g., useMemo(()
=> computeIsComplete(assessment), [assessment])) or alternatively compute once
here and pass the resulting isComplete boolean into AssessmentDetail via a prop
instead of recomputing there so both places use the same memoized/shared logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clients/admin-ui/src/pages/privacy-assessments/`[id].tsx:
- Around line 68-72: The onOk async callback currently uses the non-null
assertion assessment! when calling deleteAssessment(assessment!.id) which can
throw if assessment becomes undefined; add a defensive guard at the start of the
onOk handler to check assessment exists and bail out gracefully (e.g., show
message.error('Assessment not found') and return) before calling
deleteAssessment/unwrap and router.push(PRIVACY_ASSESSMENTS_ROUTE), so
deleteAssessment, assessment.id, and subsequent logic only run when assessment
is defined.

---

Nitpick comments:
In `@clients/admin-ui/src/pages/privacy-assessments/`[id].tsx:
- Around line 47-51: Extract the inline completion logic into a shared utility
(e.g., computeIsComplete(assessment)) that encapsulates the current check
(flatten question_groups -> questions -> answer_text.trim().length > 0), then
replace the inline computation in this page ([id].tsx) and in
AssessmentDetail.tsx with calls to that utility; in components, wrap the call in
useMemo (e.g., useMemo(() => computeIsComplete(assessment), [assessment])) or
alternatively compute once here and pass the resulting isComplete boolean into
AssessmentDetail via a prop instead of recomputing there so both places use the
same memoized/shared logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6e726d42-a8b0-4bb7-a977-585628161f2d

📥 Commits

Reviewing files that changed from the base of the PR and between bb5b67f and 4cda64c.

📒 Files selected for processing (3)
  • changelog/7564-relocate-assessment-action-buttons.yaml
  • clients/admin-ui/src/features/privacy-assessments/AssessmentDetail.tsx
  • clients/admin-ui/src/pages/privacy-assessments/[id].tsx

Comment on lines +68 to +72
onOk: async () => {
try {
await deleteAssessment(assessment!.id).unwrap();
message.success("Assessment deleted.");
router.push(PRIVACY_ASSESSMENTS_ROUTE);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add a guard for the assessment reference in the async callback.

The assessment! non-null assertion assumes assessment remains defined between opening the modal and clicking confirm. While unlikely, if a background refetch fails during this window, assessment could become undefined, causing a runtime error.

🛡️ Proposed defensive guard
      onOk: async () => {
+       if (!assessment) {
+         message.error("Assessment no longer available.");
+         return;
+       }
        try {
-         await deleteAssessment(assessment!.id).unwrap();
+         await deleteAssessment(assessment.id).unwrap();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onOk: async () => {
try {
await deleteAssessment(assessment!.id).unwrap();
message.success("Assessment deleted.");
router.push(PRIVACY_ASSESSMENTS_ROUTE);
onOk: async () => {
if (!assessment) {
message.error("Assessment no longer available.");
return;
}
try {
await deleteAssessment(assessment.id).unwrap();
message.success("Assessment deleted.");
router.push(PRIVACY_ASSESSMENTS_ROUTE);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/admin-ui/src/pages/privacy-assessments/`[id].tsx around lines 68 -
72, The onOk async callback currently uses the non-null assertion assessment!
when calling deleteAssessment(assessment!.id) which can throw if assessment
becomes undefined; add a defensive guard at the start of the onOk handler to
check assessment exists and bail out gracefully (e.g., show
message.error('Assessment not found') and return) before calling
deleteAssessment/unwrap and router.push(PRIVACY_ASSESSMENTS_ROUTE), so
deleteAssessment, assessment.id, and subsequent logic only run when assessment
is defined.

Copy link
Copy Markdown
Contributor

@lucanovera lucanovera left a comment

Choose a reason for hiding this comment

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

UI and code changes look good. Approved

@kruulik kruulik enabled auto-merge March 4, 2026 20:21
@kruulik kruulik added this pull request to the merge queue Mar 4, 2026
Merged via the queue into main with commit 1451a9f Mar 4, 2026
47 checks passed
@kruulik kruulik deleted the 2879-relocate-buttons branch March 4, 2026 21:21
mfbrown pushed a commit that referenced this pull request Mar 12, 2026
…7564)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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