-
Notifications
You must be signed in to change notification settings - Fork 91
ENG-3098: PBAC management UI — data purposes, consumers, query log config #7700
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+3,410
−32
Merged
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
9c0240e
fix: allow direct references in hatch metadata for fideslang git dep
5662d04
fix: move fideslang git ref to requirements.txt to avoid hatchling di…
0f403a6
Merge branch 'main' into feat/dataset-data-purposes
galvana 9dace64
feat: PBAC UI — data purposes, data consumers, and query log config
a41d177
feat: add Seed Data developer page for triggering seed scenarios
13850c0
fix: disable query logging via update instead of delete
79e6e5d
fix: use valid page size for data purposes query in consumer form
3f17cde
fix: use white text on in-progress badge in seed data page
d065c10
Adding PBAC UI plan
1a5159a
Merge branch 'main' into feat/dataset-data-purposes
galvana 83da9ad
Merge branch 'feat/dataset-data-purposes' into feat/pbac-ui-management
galvana 2fabb3f
chore: move seed data UI to separate branch (ENG-3100)
8b03868
feat: add Seed Data developer page (ENG-3100)
cf1cf65
Merge branch 'main' into feat/pbac-ui-management
galvana 4fc8a7d
Add DashboardSnapshot model for trend sparklines
8fc427c
Add Dashboard scenario to Seed Data page
d1b88ed
Rename table to dashboard_snapshot and fix migration chain
4fbea88
Rename GPS Score to Governance Posture in TrendCard
9d78644
Add is_overdue filter, dashboard UI polish, and tests
50f43cb
Merge branch 'feat/pbac-ui-management' into feat/seed-data-ui
galvana 230d07f
Misc fixes
b471019
Address PR review feedback on SeedDataPanel
16bccfc
Merge branch 'main' into feat/pbac-ui-management
galvana 05f3ac7
Fix mypy and TypeScript type errors
2e48576
Merge branch 'feat/pbac-ui-management' into feat/seed-data-ui
galvana fdd7654
Register tests/unit/ in CI test coverage mapping
0bf5f22
Address PR review feedback
b20e8f7
Merge branch 'main' into feat/pbac-ui-management
galvana 27a7087
Merge branch 'main' into feat/pbac-ui-management
galvana c2011c9
Merge branch 'main' into feat/pbac-ui-management
galvana 8561032
Merge branch 'feat/pbac-ui-management' into feat/seed-data-ui
galvana f0ed284
Fix TrashCanOutlineIcon imports and add changelog entry
44b9e31
Merge remote feat/seed-data-ui into local
4f263a7
Fix import sorting in DataPurposeActionsCell
8805514
Fix TypeScript errors and Alembic multiple heads
0a9859c
Revert "Fix TypeScript errors and Alembic multiple heads"
a28baab
Revert "Merge remote feat/seed-data-ui into local"
6842ec4
Reapply "Merge remote feat/seed-data-ui into local"
ea203cb
Reapply "Fix TypeScript errors and Alembic multiple heads"
7fa54fc
Fix curly brace lint errors in StackedBarChart and constants
3d25be4
Fix useless fragment lint error in DaysLeft component
1455fc3
Add missing id index to dashboard_snapshot migration
3190bf6
Add dashboard_snapshot data category annotations
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: [] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
clients/admin-ui/src/features/data-consumers/DataConsumerActionsCell.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
169
clients/admin-ui/src/features/data-consumers/DataConsumerForm.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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( | ||
| () => | ||
| (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; | ||
43 changes: 43 additions & 0 deletions
43
clients/admin-ui/src/features/data-consumers/DataConsumersTable.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
SelectwithshowSearchand a server-side filter query) to avoid loading all items at once:Or, better, use a paginated/searchable select that queries the API on user input rather than loading everything upfront.
There was a problem hiding this comment.
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.