Skip to content

Access policies list page#7817

Merged
lucanovera merged 39 commits intomainfrom
ENG-2956-FE-Policies-list
Apr 7, 2026
Merged

Access policies list page#7817
lucanovera merged 39 commits intomainfrom
ENG-2956-FE-Policies-list

Conversation

@lucanovera
Copy link
Copy Markdown
Contributor

@lucanovera lucanovera commented Apr 2, 2026

Ticket ENG-2956

Description Of Changes

Implements the access policies list page with full CRUD-adjacent interactions. Users can browse policies in a table or card view, search and filter by control group or enabled state, reorder policies via drag-and-drop (which updates priority), and toggle a policy enabled/disabled inline. Policies are grouped by control group in the card view. The page is gated behind the alphaPurposeBasedAccessControl feature flag and requires a Fides Plus license.

Code Changes

  • Added PoliciesContainer — top-level orchestrator: fetches policies and control groups, applies search/filter state, and delegates to table or card view
  • Added PoliciesToolbar — search input, control group select, enabled/disabled filter, and table/card view toggle (persisted as ?view= query param)
  • Added PoliciesTable — Ant Design table with drag-and-drop row reordering, inline editable priority cell, and an enabled/disabled toggle per row
  • Added PoliciesGrid and PolicyCard — card view with policies grouped by control group category, each card showing name, description, enabled state, and associated controls
  • Added PolicyCategoryGroup — section header component for grouping cards by control group in grid view
  • Added policy-yaml.ts — utilities for parsing and patching YAML fields (enabled, priority) without a round-trip to the backend schema
  • Added hooks: useAccessPoliciesList, useAccessPolicyGroups, usePoliciesFilters, useReorderPolicies, useTogglePolicyEnabled, useUpdatePolicyPriority
  • Extended access-policies.slice.ts with reorderAccessPolicy and updateAccessPolicy RTK Query mutations
  • Added MSW handlers and mock data for all access policy endpoints (list, get, create, update, delete, reorder, control groups)
  • Extended CustomTypography in fidesui to support a LinkText variant

Steps to Confirm

  1. Start the admin UI with mock API:

    cd clients/admin-ui
    npm run dev:mock

    Navigate to http://localhost:3000 and log in

  2. Enable the feature flag: Go to Settings → About and enable alphaPurposeBasedAccessControl. The Access policies nav item should appear in the sidebar.

  3. Navigate to /access-policies and confirm the list loads with mock policies.

  4. Card view (default):

    • Confirm policies render as cards grouped by control group
    • Confirm "Recommended" badge appears on the appropriate policies
  5. Table view:

    • Click the table view toggle in the toolbar — confirm policies render with name, description, priority, enabled toggle, and controls columns
    • Toggle a policy's enabled switch — confirm optimistic update and success toast
    • Click the priority cell — confirm it becomes an editable input; change the value and press Enter to save
    • Drag a row handle to reorder — confirm rows reorder visually and priorities update after drop
  6. Search and filtering:

    • Type in the search box — confirm the list filters by policy name in both views
    • Select a control group from the dropdown — confirm only policies belonging to that group are shown
    • Select Enabled / Disabled from the status filter — confirm the list narrows correctly
    • Combine search + control filter + status filter simultaneously to confirm they compose
  7. Empty state:

    • Apply a filter that matches nothing — confirm the empty state renders without errors
  8. "New policy" button:

    • Click New policy in the page header — confirm navigation to /access-policies/new

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 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

@vercel
Copy link
Copy Markdown
Contributor

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

Request Review

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 2, 2026

Title Lines Statements Branches Functions
admin-ui Coverage: 7%
5.83% (2500/42880) 4.71% (1146/24299) 3.89% (495/12712)
fides-js Coverage: 78%
78.98% (1962/2484) 65.55% (1214/1852) 72.57% (336/463)
privacy-center Coverage: 86%
84.05% (290/345) 79.18% (156/197) 76.56% (49/64)

@lucanovera lucanovera marked this pull request as ready for review April 2, 2026 06:43
@lucanovera lucanovera requested a review from a team as a code owner April 2, 2026 06:43
@lucanovera lucanovera requested review from jpople and kruulik and removed request for a team and jpople April 2, 2026 06:43
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.

Code Review — Access Policies List View

Good overall structure. The feature is well-decomposed: hooks own their data/mutation concerns, the container wires things together, and the table/grid split is clean. The drag-and-drop implementation follows the standard react-dnd pattern correctly, including the visual-only local state during a drag and the single API call on drop. The nuqs integration for URL-synced filters is a nice touch. Tests for formatRelativeTime, extractPolicyFields, and updateYamlField are thorough.

A few things to address before merging:

Bug

  • EditablePriorityCell — Escape does not reset inputValue (inline comment on line 160 of PoliciesTable.tsx). After pressing Escape, re-opening the edit input shows the previously typed value instead of the current priority. One-line fix: reset inputValue before calling setIsEditing(false).

Correctness concerns

  • updateYamlField drops YAML formatting (policy-yaml.ts). The load+dump round-trip strips comments, re-orders keys, and may reformat multi-line strings. Worth addressing before the policy YAML schema gets more complex.
  • Multi-control policies appear in multiple card groups (useAccessPolicyGroups.ts). Same card renders once per matching control. Intentional or not, it should be explicit.
  • Policies missing priority in YAML sort to the top (useAccessPoliciesList.ts). Default of 0 may produce a confusing ordering for legacy policies.

Minor / nits

  • Misleading doc comment in extractPolicyFields (says it avoids a full parse but delegates to parseYaml).
  • ViewMode re-exported through PoliciesToolbar rather than imported directly from types.
  • Unused type field in the DragItem interface.
  • No range validation on the priority <Input> — backend errors surface via toast but the cell shows no inline error state.

size="small"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onBlur={commit}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Escape key doesn't reset inputValue to the current value.

When the user types a new value and then presses Escape, setIsEditing(false) is called but inputValue is left as whatever the user typed. The next time they click the edit button, the stale typed value reappears instead of the current priority.

} else if (e.key === "Escape") {
  setInputValue(String(value)); // reset first
  setIsEditing(false);
}

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 catch, fixed!

setIsEditing(false);
}
};

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 priority input has no range validation — a user can submit -1, 0, or an arbitrarily large number. If the backend enforces constraints (e.g. priority >= 1), the API call will fail silently (the error is caught and surfaced via message.error in useUpdatePolicyPriority, but the cell shows no error state). Consider adding a min and/or max attribute on the <Input> and validating before calling onEdit:

const parsed = parseInt(inputValue, 10);
if (!Number.isNaN(parsed) && parsed > 0 && parsed !== value) {
  onEdit(parsed);
}

): string => {
try {
const parsed = yaml.load(yamlString) as Record<string, unknown> | null;
if (!parsed || typeof parsed !== "object") {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

updateYamlField does not preserve YAML formatting or key order.

yaml.load + yaml.dump round-trips the document through a plain JS object, which means:

  • Key insertion order is not preserved across JS engines
  • Comments are stripped
  • Multi-line string scalars may be reformatted
  • Anchors/aliases are lost

For the current use-cases (toggling enabled, updating priority) this is likely fine today, but as policy YAML grows more complex this could silently corrupt the document shape. A safer approach is a targeted regex/string replacement for scalar fields, or using a YAML AST library that preserves document structure (e.g. yaml's Document API with doc.set()).

* Extract top-level fields from a policy YAML string for list display.
* Avoids building the full node graph — just reads the scalar fields.
*/
export const extractPolicyFields = (
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 doc comment says "Avoids building the full node graph — just reads the scalar fields", but the implementation calls parseYaml which does a full yaml.load of the document. The comment is misleading and should be updated or removed.


import { ControlGroup } from "./access-policies.slice";
import { ViewMode } from "./types";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ViewMode is defined in types.ts but re-exported from PoliciesToolbar.tsx, and then PoliciesContainer.tsx imports it from PoliciesToolbar. This indirect chain (types → Toolbar → Container) is confusing for future readers. Consider importing ViewMode directly from ./types in PoliciesContainer and removing the re-export here.

if (!controlGroups) {
return [];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A policy with multiple controls (e.g. controls: ["eea_uk_gdpr", "us_glba_ccpa"]) will appear in every group whose key it matches. In the card view this means the same policy card renders multiple times — once per matching control group. Is that intentional? If so, it's worth a comment to make it explicit. If not, you'll want to de-duplicate, e.g. by assigning each policy to only its first matching group or showing it only in the group the user is currently viewing.

if (!data?.items) {
return [];
}
return data.items.map(toListItem).sort((a, b) => a.priority - b.priority);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorting by priority client-side is fine, but policies without an explicit priority in their YAML all default to 0 and end up clustered at the top of the list in an arbitrary order. If there are many legacy policies without the field, this could produce a confusing default ordering. It may be worth treating a missing priority as Infinity (sorted to the end) rather than 0, or documenting the intent if placing unprioritized policies first is deliberate.

id: string;
type: 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.

The type field on DragItem is declared but never read in the implementation — useDrag handles the type internally via its type option. This can be removed to avoid confusion:

interface DragItem {
  index: number;
  originalIndex: number;
  id: string;
}

Copy link
Copy Markdown
Contributor

@kruulik kruulik left a comment

Choose a reason for hiding this comment

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

Checked out the branch and fiddled around with the table - super performant! Couple nits in the code but nothing critical. I think the table component itself could be split up a little bit, but that's up to your discretion.

Comment thread clients/admin-ui/src/features/access-policies/hooks/useAccessPolicyGroups.ts Outdated
Comment thread clients/admin-ui/src/features/access-policies/PoliciesContainer.tsx
Comment thread clients/admin-ui/src/features/access-policies/PolicyCard.tsx
Comment thread clients/admin-ui/src/features/access-policies/PoliciesContainer.tsx Outdated
Comment thread clients/admin-ui/src/features/access-policies/PoliciesGrid.tsx Outdated
<Segmented
value={viewMode}
onChange={(value) => onViewModeChange(value as ViewMode)}
options={[
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.

Not totally related to your PR, but the these icons look reversed in the context of this page. The left one looks like a list of items or a table, the right one looks like a card. But it's the opposite.

Image

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 callout! I've updated the icons, hopefully they're more recognizable now.
Captura de pantalla 2026-04-07 a la(s) 12 56 38 p  m

@lucanovera lucanovera enabled auto-merge April 7, 2026 16:29
@lucanovera lucanovera added this pull request to the merge queue Apr 7, 2026
Merged via the queue into main with commit ef33099 Apr 7, 2026
51 of 54 checks passed
@lucanovera lucanovera deleted the ENG-2956-FE-Policies-list branch April 7, 2026 16:47
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