Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
34b2a34
chore: point fideslang to branch with data_purposes support
Mar 16, 2026
9c0240e
fix: allow direct references in hatch metadata for fideslang git dep
Mar 17, 2026
5662d04
fix: move fideslang git ref to requirements.txt to avoid hatchling di…
Mar 17, 2026
0f403a6
Merge branch 'main' into feat/dataset-data-purposes
galvana Mar 18, 2026
9dace64
feat: PBAC UI — data purposes, data consumers, and query log config
Mar 19, 2026
a41d177
feat: add Seed Data developer page for triggering seed scenarios
Mar 19, 2026
13850c0
fix: disable query logging via update instead of delete
Mar 19, 2026
79e6e5d
fix: use valid page size for data purposes query in consumer form
Mar 19, 2026
3f17cde
fix: use white text on in-progress badge in seed data page
Mar 19, 2026
d065c10
Adding PBAC UI plan
Mar 19, 2026
1a5159a
Merge branch 'main' into feat/dataset-data-purposes
galvana Mar 23, 2026
83da9ad
Merge branch 'feat/dataset-data-purposes' into feat/pbac-ui-management
galvana Mar 23, 2026
2fabb3f
chore: move seed data UI to separate branch (ENG-3100)
Mar 23, 2026
8b03868
feat: add Seed Data developer page (ENG-3100)
Mar 23, 2026
cf1cf65
Merge branch 'main' into feat/pbac-ui-management
galvana Mar 24, 2026
4fc8a7d
Add DashboardSnapshot model for trend sparklines
Mar 24, 2026
8fc427c
Add Dashboard scenario to Seed Data page
Mar 24, 2026
d1b88ed
Rename table to dashboard_snapshot and fix migration chain
Mar 24, 2026
4fbea88
Rename GPS Score to Governance Posture in TrendCard
Mar 24, 2026
9d78644
Add is_overdue filter, dashboard UI polish, and tests
Mar 25, 2026
50f43cb
Merge branch 'feat/pbac-ui-management' into feat/seed-data-ui
galvana Mar 25, 2026
230d07f
Misc fixes
Mar 25, 2026
b471019
Address PR review feedback on SeedDataPanel
Mar 25, 2026
16bccfc
Merge branch 'main' into feat/pbac-ui-management
galvana Mar 25, 2026
05f3ac7
Fix mypy and TypeScript type errors
Mar 25, 2026
2e48576
Merge branch 'feat/pbac-ui-management' into feat/seed-data-ui
galvana Mar 25, 2026
fdd7654
Register tests/unit/ in CI test coverage mapping
Mar 25, 2026
0bf5f22
Address PR review feedback
Mar 25, 2026
b20e8f7
Merge branch 'main' into feat/pbac-ui-management
galvana Mar 25, 2026
27a7087
Merge branch 'main' into feat/pbac-ui-management
galvana Mar 25, 2026
c2011c9
Merge branch 'main' into feat/pbac-ui-management
galvana Mar 25, 2026
8561032
Merge branch 'feat/pbac-ui-management' into feat/seed-data-ui
galvana Mar 25, 2026
f0ed284
Fix TrashCanOutlineIcon imports and add changelog entry
Mar 25, 2026
44b9e31
Merge remote feat/seed-data-ui into local
Mar 25, 2026
4f263a7
Fix import sorting in DataPurposeActionsCell
Mar 25, 2026
8805514
Fix TypeScript errors and Alembic multiple heads
Mar 25, 2026
0a9859c
Revert "Fix TypeScript errors and Alembic multiple heads"
Mar 25, 2026
a28baab
Revert "Merge remote feat/seed-data-ui into local"
Mar 25, 2026
6842ec4
Reapply "Merge remote feat/seed-data-ui into local"
Mar 25, 2026
ea203cb
Reapply "Fix TypeScript errors and Alembic multiple heads"
Mar 25, 2026
7fa54fc
Fix curly brace lint errors in StackedBarChart and constants
Mar 25, 2026
3d25be4
Fix useless fragment lint error in DaysLeft component
Mar 25, 2026
1455fc3
Add missing id index to dashboard_snapshot migration
Mar 25, 2026
3190bf6
Add dashboard_snapshot data category annotations
Mar 25, 2026
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
17 changes: 17 additions & 0 deletions .fides/db_dataset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1246,6 +1246,23 @@ dataset:
- name: replaceable
description: 'Fides Generated Description for Column: replaceable'
data_categories: [system.operations]
- name: dashboard_snapshot
data_categories: [system.operations]
fields:
- name: created_at
data_categories: [system.operations]
- name: id
data_categories: [system.operations]
- name: metadata
data_categories: [system.operations]
- name: metric_key
data_categories: [system.operations]
- name: snapshot_date
data_categories: [system.operations]
- name: updated_at
data_categories: [system.operations]
- name: value
data_categories: [system.operations]
- name: datasetconfig
data_categories: []
fields:
Expand Down
4 changes: 4 additions & 0 deletions changelog/7700-pbac-ui-management.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
type: Added
description: Add PBAC management UI for data purposes, consumers, and query log configuration
pr: 7700
labels: []
3 changes: 3 additions & 0 deletions clients/admin-ui/src/features/common/api.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ export const baseApi = createApi({
"Property",
"Property-Specific Messaging Templates",
"Purpose",
"DataPurpose",
"DataConsumer",
"QueryLogConfig",
"Shared Monitor Configs",
"System",
"System Assets",
Expand Down
20 changes: 20 additions & 0 deletions clients/admin-ui/src/features/common/nav/nav-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,20 @@ export const NAV_CONFIG: NavConfigGroup[] = [
ScopeRegistryEnum.CONFIG_UPDATE,
],
},
{
title: "Data purposes",
path: routes.DATA_PURPOSES_ROUTE,
requiresPlus: true,
requiresFlag: "alphaPurposeBasedAccessControl",
scopes: [ScopeRegistryEnum.DATA_PURPOSE_READ],
},
{
title: "Data consumers",
path: routes.DATA_CONSUMERS_ROUTE,
requiresPlus: true,
requiresFlag: "alphaPurposeBasedAccessControl",
scopes: [ScopeRegistryEnum.DATA_CONSUMER_READ],
},
{
title: "Access policies",
path: routes.ACCESS_POLICIES_ROUTE,
Expand Down Expand Up @@ -392,6 +406,12 @@ if (process.env.NEXT_PUBLIC_APP_ENV === "development") {
scopes: [ScopeRegistryEnum.DEVELOPER_READ],
requiresPlus: true,
},
{
title: "Seed Data",
path: routes.SEED_DATA_ROUTE,
scopes: [ScopeRegistryEnum.DEVELOPER_READ],
requiresPlus: true,
},
{
title: "Test monitors",
path: routes.TEST_MONITORS_ROUTE,
Expand Down
11 changes: 11 additions & 0 deletions clients/admin-ui/src/features/common/nav/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export const ERRORS_POC_ROUTE = "/poc/error";
export const TABLE_MIGRATION_POC_ROUTE = "/poc/table-migration";
export const FIDES_JS_DOCS = "/fides-js-docs";
export const PROMPT_EXPLORER_ROUTE = "/poc/prompt-explorer";
export const SEED_DATA_ROUTE = "/poc/seed-data";
export const TEST_MONITORS_ROUTE = "/poc/test-monitors";

// RBAC routes
Expand All @@ -120,6 +121,16 @@ export const SANDBOX_PRIVACY_NOTICES_ROUTE = "/sandbox/privacy-notices";
export const PRIVACY_ASSESSMENTS_ROUTE = "/privacy-assessments";
export const PRIVACY_ASSESSMENTS_DETAIL_ROUTE = "/privacy-assessments/[id]";

// Data Purposes (Core Configuration)
export const DATA_PURPOSES_ROUTE = "/data-purposes";
export const DATA_PURPOSES_NEW_ROUTE = "/data-purposes/new";
export const DATA_PURPOSES_EDIT_ROUTE = "/data-purposes/[fidesKey]";

// Data Consumers (Core Configuration)
export const DATA_CONSUMERS_ROUTE = "/data-consumers";
export const DATA_CONSUMERS_NEW_ROUTE = "/data-consumers/new";
export const DATA_CONSUMERS_EDIT_ROUTE = "/data-consumers/[id]";

// Access Policies (Core Configuration)
export const ACCESS_POLICIES_ROUTE = "/access-policies";
export const ACCESS_POLICIES_ONBOARDING_ROUTE = "/access-policies/onboarding";
Expand Down
11 changes: 9 additions & 2 deletions clients/admin-ui/src/features/dashboard/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,15 @@ export const ACTION_CTA: Record<
},
[ActionType.DSR_ACTION]: {
label: "View request",
route: (d) =>
d.request_id ? `/privacy-requests/${d.request_id}` : "/privacy-requests",
route: (d) => {
if (d.request_id) {
return `/privacy-requests/${d.request_id}`;
}
if (d.is_overdue) {
return "/privacy-requests?is_overdue=true";
}
return "/privacy-requests";
},
},
[ActionType.SYSTEM_REVIEW]: {
label: "Review system",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Button, Flex, Icons } from "fidesui";
import { useRouter } from "next/router";

import { DATA_CONSUMERS_ROUTE } from "~/features/common/nav/routes";
import Restrict from "~/features/common/Restrict";
import { ScopeRegistryEnum } from "~/types/api";

import { DataConsumer } from "./data-consumer.slice";
import DeleteDataConsumerModal from "./DeleteDataConsumerModal";

interface Props {
consumer: DataConsumer;
}

const DataConsumerActionsCell = ({ consumer }: Props) => {
const router = useRouter();

const handleEdit = () => {
router.push(`${DATA_CONSUMERS_ROUTE}/${consumer.id}`);
};

return (
<Flex gap="small">
<Restrict scopes={[ScopeRegistryEnum.DATA_CONSUMER_UPDATE]}>
<Button
aria-label="Edit data consumer"
data-testid="edit-data-consumer-button"
size="small"
icon={<Icons.Edit />}
onClick={handleEdit}
/>
</Restrict>
<Restrict scopes={[ScopeRegistryEnum.DATA_CONSUMER_DELETE]}>
<DeleteDataConsumerModal consumer={consumer} />
</Restrict>
</Flex>
);
};

export default DataConsumerActionsCell;
169 changes: 169 additions & 0 deletions clients/admin-ui/src/features/data-consumers/DataConsumerForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { Button, Flex, Form, Input, Select, Spin } from "fidesui";
import { useRouter } from "next/router";
import { useCallback, useMemo } from "react";

import { DATA_CONSUMERS_ROUTE } from "~/features/common/nav/routes";
import { useGetAllDataPurposesQuery } from "~/features/data-purposes/data-purpose.slice";

import { CONSUMER_TYPE_OPTIONS } from "./constants";
import { DataConsumer } from "./data-consumer.slice";

export interface DataConsumerFormValues {
name: string;
type: string;
contact_email: string;
description: string;
tags: string[];
purposeFidesKeys: string[];
}

interface DataConsumerFormProps {
consumer?: DataConsumer;
handleSubmit: (values: DataConsumerFormValues) => Promise<void>;
}

const DataConsumerForm = ({
consumer,
handleSubmit,
}: DataConsumerFormProps) => {
const [form] = Form.useForm<DataConsumerFormValues>();
const router = useRouter();

const { data: purposesData, isLoading: purposesLoading } =
useGetAllDataPurposesQuery({ size: 200 });

const purposeOptions = useMemo(
Comment on lines +34 to +35
Copy link
Contributor

Choose a reason for hiding this comment

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

P2 Hardcoded purpose list truncation

The purposes query is capped at size: 100. If there are more than 100 data purposes in the system, the select will silently show an incomplete list and the user will be unable to assign purposes beyond that threshold. Since the purpose count is unbounded, this will break purpose assignment at scale.

Consider fetching all pages or implementing a debounced search-as-you-type select (e.g., Ant Design's Select with showSearch and a server-side filter query) to avoid loading all items at once:

Suggested change
const purposeOptions = useMemo(
const { data: purposesData, isLoading: purposesLoading } =
useGetAllDataPurposesQuery({ size: 200 });

Or, better, use a paginated/searchable select that queries the API on user input rather than loading everything upfront.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Bumped to 200 as a safety margin. In practice, data purposes are a bounded set, but the extra headroom is sensible.

() =>
(purposesData?.items ?? []).map((p) => ({
value: p.fides_key,
label: p.name || p.fides_key,
})),
[purposesData],
);

const initialValues = useMemo<DataConsumerFormValues>(
() => ({
name: consumer?.name ?? "",
type: consumer?.type ?? "",
contact_email: consumer?.contact_email ?? "",
description: consumer?.description ?? "",
tags: consumer?.tags ?? [],
purposeFidesKeys: consumer?.purpose_fides_keys ?? [],
}),
[consumer],
);

const handleCancel = useCallback(() => {
router.push(DATA_CONSUMERS_ROUTE);
}, [router]);

if (purposesLoading) {
return (
<Flex justify="center" align="center" className="py-12">
<Spin />
</Flex>
);
}

return (
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={initialValues}
key={consumer?.id ?? "create"}
data-testid="data-consumer-form"
style={{ maxWidth: 720 }}
>
<Form.Item
name="name"
label="Name"
rules={[{ required: true, message: "Name is required" }]}
>
<Input
placeholder="Enter consumer name"
data-testid="data-consumer-name-input"
/>
</Form.Item>

<Form.Item
name="type"
label="Type"
rules={[{ required: true, message: "Type is required" }]}
tooltip="The type of data consumer (service, application, group, or user)"
>
<Select
placeholder="Select consumer type"
options={CONSUMER_TYPE_OPTIONS}
aria-label="Type"
data-testid="data-consumer-type-select"
/>
</Form.Item>

<Form.Item
name="contact_email"
label="Contact email"
tooltip="The email address used to identify this consumer in query logs"
>
<Input
placeholder="Enter contact email"
data-testid="data-consumer-contact-input"
/>
</Form.Item>

<Form.Item
name="purposeFidesKeys"
label="Assigned purposes"
tooltip="Which data purposes is this consumer authorized for?"
>
<Select
mode="multiple"
placeholder="Select purposes"
options={purposeOptions}
aria-label="Assigned purposes"
data-testid="data-consumer-purposes-select"
/>
</Form.Item>

<Form.Item
name="description"
label="Description"
tooltip="An optional description of this data consumer"
>
<Input.TextArea
placeholder="Enter a description"
rows={3}
data-testid="data-consumer-description-input"
/>
</Form.Item>

<Form.Item
name="tags"
label="Tags"
tooltip="Optional tags for organizing consumers"
>
<Select
mode="tags"
placeholder="Add tags"
aria-label="Tags"
data-testid="data-consumer-tags-select"
/>
</Form.Item>

<Flex justify="space-between" className="pt-4">
<Button onClick={handleCancel} data-testid="cancel-button">
Cancel
</Button>
<Button
type="primary"
htmlType="submit"
data-testid="save-data-consumer-button"
>
Save
</Button>
</Flex>
</Form>
);
};

export default DataConsumerForm;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Button, Flex, Table } from "fidesui";
import { useRouter } from "next/router";

import { DebouncedSearchInput } from "~/features/common/DebouncedSearchInput";
import { DATA_CONSUMERS_NEW_ROUTE } from "~/features/common/nav/routes";
import Restrict from "~/features/common/Restrict";
import { ScopeRegistryEnum } from "~/types/api";

import useDataConsumersTable from "./useDataConsumersTable";

const DataConsumersTable = () => {
const router = useRouter();
const { tableProps, columns, searchQuery, updateSearch } =
useDataConsumersTable();

return (
<Flex vertical gap="middle">
<Flex justify="space-between" className="mb-2">
<DebouncedSearchInput
value={searchQuery}
onChange={updateSearch}
placeholder="Search data consumers..."
/>
<Restrict scopes={[ScopeRegistryEnum.DATA_CONSUMER_CREATE]}>
<Button
type="primary"
onClick={() => router.push(DATA_CONSUMERS_NEW_ROUTE)}
data-testid="add-data-consumer-button"
>
Add data consumer
</Button>
</Restrict>
</Flex>
<Table
{...tableProps}
columns={columns}
data-testid="data-consumers-table"
/>
</Flex>
);
};

export default DataConsumersTable;
Loading
Loading