From f35bd2b07a2c9118b8acc1a2c83daaaaa2dfafaa Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 27 Apr 2023 15:47:25 -0400 Subject: [PATCH 01/10] Configure nav for new page --- .../src/features/common/nav/v2/NavSideBar.tsx | 12 ++--- .../features/common/nav/v2/nav-config.test.ts | 23 +++++++++ .../src/features/common/nav/v2/nav-config.ts | 15 ++++-- .../src/features/common/nav/v2/routes.ts | 2 + clients/admin-ui/src/flags.json | 7 +++ clients/admin-ui/src/pages/consent/index.tsx | 24 ++++++++++ .../consent/privacy-experience/index.tsx | 47 +++++++++++++++++++ 7 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 clients/admin-ui/src/pages/consent/index.tsx create mode 100644 clients/admin-ui/src/pages/consent/privacy-experience/index.tsx diff --git a/clients/admin-ui/src/features/common/nav/v2/NavSideBar.tsx b/clients/admin-ui/src/features/common/nav/v2/NavSideBar.tsx index f39d92cd9b..3184c8a5a9 100644 --- a/clients/admin-ui/src/features/common/nav/v2/NavSideBar.tsx +++ b/clients/admin-ui/src/features/common/nav/v2/NavSideBar.tsx @@ -1,5 +1,5 @@ import { NavList } from "@fidesui/components"; -import { Heading, UnorderedList, VStack } from "@fidesui/react"; +import { Heading, ListItem, UnorderedList, VStack } from "@fidesui/react"; import { useRouter } from "next/router"; import React from "react"; @@ -21,13 +21,11 @@ const NavListItem = ({ {title} {children.length ? ( - + {children.map((childRoute) => ( - + + + ))} ) : null} diff --git a/clients/admin-ui/src/features/common/nav/v2/nav-config.test.ts b/clients/admin-ui/src/features/common/nav/v2/nav-config.test.ts index 652c7766be..fb04f77367 100644 --- a/clients/admin-ui/src/features/common/nav/v2/nav-config.test.ts +++ b/clients/admin-ui/src/features/common/nav/v2/nav-config.test.ts @@ -19,6 +19,8 @@ const ALL_SCOPES = [ ScopeRegistryEnum.DATA_CATEGORY_CREATE, ScopeRegistryEnum.ORGANIZATION_READ, ScopeRegistryEnum.ORGANIZATION_UPDATE, + ScopeRegistryEnum.PRIVACY_NOTICE_READ, + ScopeRegistryEnum.PRIVACY_EXPERIENCE_READ, ]; describe("configureNavGroups", () => { @@ -201,6 +203,7 @@ describe("findActiveNav", () => { config: NAV_CONFIG, hasPlus: true, userScopes: ALL_SCOPES, + flags: { privacyNotices: true, privacyExperience: true }, }); const testCases = [ @@ -241,6 +244,26 @@ describe("findActiveNav", () => { path: routes.DATASTORE_CONNECTION_ROUTE, }, }, + // Nested side nav child + { + path: routes.PRIVACY_EXPERIENCE_ROUTE, + expected: { + title: "Privacy requests", + // this _might_ not be the right thing to expect, but it at least works intuitively + // since then both the Consent route and the Privacy experience route will be marked as "active" + // since they both start with "/consent". if we see weird behavior with which nav is active + // we may need to revisit the logic in `findActiveNav` + path: routes.CONSENT_ROUTE, + }, + }, + // Parent side nav + { + path: routes.CONSENT_ROUTE, + expected: { + title: "Privacy requests", + path: routes.CONSENT_ROUTE, + }, + }, ] as const; testCases.forEach(({ path, expected }) => { diff --git a/clients/admin-ui/src/features/common/nav/v2/nav-config.ts b/clients/admin-ui/src/features/common/nav/v2/nav-config.ts index 367fa47bb1..8c9fca5ed0 100644 --- a/clients/admin-ui/src/features/common/nav/v2/nav-config.ts +++ b/clients/admin-ui/src/features/common/nav/v2/nav-config.ts @@ -53,11 +53,13 @@ export const NAV_CONFIG: NavConfigGroup[] = [ }, { title: "Consent", - // For now, we don't have a full Consent page, so just use the privacy notice route - path: routes.PRIVACY_NOTICES_ROUTE, + path: routes.CONSENT_ROUTE, requiresFlag: "privacyNotices", requiresPlus: true, - scopes: [ScopeRegistryEnum.PRIVACY_NOTICE_READ], + scopes: [ + ScopeRegistryEnum.PRIVACY_NOTICE_READ, + ScopeRegistryEnum.PRIVACY_EXPERIENCE_READ, + ], routes: [ { title: "Privacy notices", @@ -66,6 +68,13 @@ export const NAV_CONFIG: NavConfigGroup[] = [ requiresPlus: true, scopes: [ScopeRegistryEnum.PRIVACY_NOTICE_READ], }, + { + title: "Privacy experience", + path: routes.PRIVACY_EXPERIENCE_ROUTE, + requiresFlag: "privacyExperience", + requiresPlus: true, + scopes: [ScopeRegistryEnum.PRIVACY_EXPERIENCE_READ], + }, ], }, ], diff --git a/clients/admin-ui/src/features/common/nav/v2/routes.ts b/clients/admin-ui/src/features/common/nav/v2/routes.ts index d359d7b287..c6831c6549 100644 --- a/clients/admin-ui/src/features/common/nav/v2/routes.ts +++ b/clients/admin-ui/src/features/common/nav/v2/routes.ts @@ -7,7 +7,9 @@ export const CLASSIFY_SYSTEMS_ROUTE = "/classify-systems"; export const DATASET_ROUTE = "/dataset"; // Privacy requests group +export const CONSENT_ROUTE = "/consent"; export const DATASTORE_CONNECTION_ROUTE = "/datastore-connection"; +export const PRIVACY_EXPERIENCE_ROUTE = "/consent/privacy-experience"; export const PRIVACY_NOTICES_ROUTE = "/consent/privacy-notices"; export const PRIVACY_REQUESTS_ROUTE = "/privacy-requests"; export const PRIVACY_REQUESTS_CONFIGURATION_ROUTE = `${PRIVACY_REQUESTS_ROUTE}/configure`; diff --git a/clients/admin-ui/src/flags.json b/clients/admin-ui/src/flags.json index 0fbb97ced2..5b415c8a0d 100644 --- a/clients/admin-ui/src/flags.json +++ b/clients/admin-ui/src/flags.json @@ -23,5 +23,12 @@ "development": true, "test": true, "production": false + }, + "privacyExperience": { + "description": "Page to configure privacy experience", + "development": true, + "test": true, + "production": false, + "userCannotModify": true } } diff --git a/clients/admin-ui/src/pages/consent/index.tsx b/clients/admin-ui/src/pages/consent/index.tsx new file mode 100644 index 0000000000..0826ed76e2 --- /dev/null +++ b/clients/admin-ui/src/pages/consent/index.tsx @@ -0,0 +1,24 @@ +import { Center, Spinner } from "@fidesui/react"; +import { useRouter } from "next/router"; +import { useEffect } from "react"; + +import Layout from "~/features/common/Layout"; +import { PRIVACY_NOTICES_ROUTE } from "~/features/common/nav/v2/routes"; + +const ConsentPage = () => { + const router = useRouter(); + + useEffect(() => { + router.push(PRIVACY_NOTICES_ROUTE); + }, [router]); + + return ( + +
+ +
+
+ ); +}; + +export default ConsentPage; diff --git a/clients/admin-ui/src/pages/consent/privacy-experience/index.tsx b/clients/admin-ui/src/pages/consent/privacy-experience/index.tsx new file mode 100644 index 0000000000..136c465425 --- /dev/null +++ b/clients/admin-ui/src/pages/consent/privacy-experience/index.tsx @@ -0,0 +1,47 @@ +import { PRIVACY_REQUESTS_ROUTE } from "@fidesui/components"; +import { Box, Breadcrumb, BreadcrumbItem, Heading, Text } from "@fidesui/react"; +import NextLink from "next/link"; +import React from "react"; + +import Layout from "~/features/common/Layout"; +import { PRIVACY_EXPERIENCE_ROUTE } from "~/features/common/nav/v2/routes"; + +const PrivacyExperiencePage = () => ( + + + + Privacy experience + + + + + Privacy requests + + {/* TODO: Add Consent breadcrumb once the page exists */} + + + Privacy experience + + + + + + + Based on your privacy notices, Fides has created the overlay and privacy + experience configuration below. Your privacy notices will be presented by + region in these components. Edit each component to adjust the text that + displays in the privacy center, overlay, and banners that show your + notices. When you’re ready to include these privacy notices on your + website, copy the javascript using the button on this page and place it on + your website. + + Work in progress! + +); + +export default PrivacyExperiencePage; From ef348e624dd1abdb4230d0369b41c5751405c0cd Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 27 Apr 2023 17:07:15 -0400 Subject: [PATCH 02/10] Set up privacy experience slice --- clients/admin-ui/src/app/store.ts | 2 + .../admin-ui/src/features/common/api.slice.ts | 1 + .../privacy-experience.slice.ts | 77 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts diff --git a/clients/admin-ui/src/app/store.ts b/clients/admin-ui/src/app/store.ts index 7c17e22deb..78452b5f9b 100644 --- a/clients/admin-ui/src/app/store.ts +++ b/clients/admin-ui/src/app/store.ts @@ -32,6 +32,7 @@ import { datamapSlice } from "~/features/datamap"; import { datasetSlice } from "~/features/dataset"; import { datastoreConnectionSlice } from "~/features/datastore-connections"; import { organizationSlice } from "~/features/organization"; +import { privacyExperienceSlice } from "~/features/privacy-experience/privacy-experience.slice"; import { privacyNoticesSlice } from "~/features/privacy-notices/privacy-notices.slice"; import { subjectRequestsSlice } from "~/features/privacy-requests"; import { systemSlice } from "~/features/system"; @@ -79,6 +80,7 @@ const reducer = { [featuresSlice.name]: featuresSlice.reducer, [organizationSlice.name]: organizationSlice.reducer, [privacyNoticesSlice.name]: privacyNoticesSlice.reducer, + [privacyExperienceSlice.name]: privacyExperienceSlice.reducer, [subjectRequestsSlice.name]: subjectRequestsSlice.reducer, [systemSlice.name]: systemSlice.reducer, [taxonomySlice.name]: taxonomySlice.reducer, diff --git a/clients/admin-ui/src/features/common/api.slice.ts b/clients/admin-ui/src/features/common/api.slice.ts index 798c4ab09c..4735ca3601 100644 --- a/clients/admin-ui/src/features/common/api.slice.ts +++ b/clients/admin-ui/src/features/common/api.slice.ts @@ -37,6 +37,7 @@ export const baseApi = createApi({ "Notification", "Organization", "Plus", + "Privacy Experiences", "Privacy Notices", "System", "Request", diff --git a/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts b/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts new file mode 100644 index 0000000000..630750ee40 --- /dev/null +++ b/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts @@ -0,0 +1,77 @@ +import { createSelector, createSlice } from "@reduxjs/toolkit"; + +import type { RootState } from "~/app/store"; +import { baseApi } from "~/features/common/api.slice"; +import { + Page_PrivacyExperienceResponse_, + PrivacyExperienceResponse, + PrivacyNoticeRegion, +} from "~/types/api"; + +export interface State { + activePrivacyNoticeId?: string; + page?: number; + pageSize?: number; +} + +const initialState: State = { + page: 1, + pageSize: 10, +}; + +interface PrivacyExperienceParams { + show_disabled?: boolean; + region?: PrivacyNoticeRegion; + page?: number; + size?: number; +} + +const privacyExperienceApi = baseApi.injectEndpoints({ + endpoints: (build) => ({ + getAllPrivacyExperiences: build.query< + Page_PrivacyExperienceResponse_, + PrivacyExperienceParams + >({ + query: (params) => ({ + url: `privacy-experience/`, + params: { ...params, show_disabled: true }, + }), + providesTags: () => ["Privacy Experiences"], + }), + }), +}); + +export const { useGetAllPrivacyExperiencesQuery } = privacyExperienceApi; + +export const privacyExperienceSlice = createSlice({ + name: "privacyExperience", + initialState, + reducers: {}, +}); + +export const { reducer } = privacyExperienceSlice; + +const selectPrivacyExperiences = (state: RootState) => state.privacyExperience; +export const selectPage = createSelector( + selectPrivacyExperiences, + (state) => state.page +); + +export const selectPageSize = createSelector( + selectPrivacyExperiences, + (state) => state.pageSize +); + +const emptyPrivacyNotices: PrivacyExperienceResponse[] = []; +export const selectAllPrivacyExperiences = createSelector( + [(RootState) => RootState, selectPage, selectPageSize], + (RootState, page, pageSize) => { + const data = privacyExperienceApi.endpoints.getAllPrivacyExperiences.select( + { + page, + size: pageSize, + } + )(RootState)?.data; + return data ? data.items : emptyPrivacyNotices; + } +); From bca681e2bbc1792d73444811468b74a8c20a1a8b Mon Sep 17 00:00:00 2001 From: Allison King Date: Thu, 27 Apr 2023 17:07:37 -0400 Subject: [PATCH 03/10] Initial components for privacy experience --- .../PrivacyExperiencesTable.tsx | 100 ++++++++++++++++++ .../src/features/privacy-experience/cells.tsx | 43 ++++++++ .../pages/consent/privacy-experience/[id].tsx | 100 ++++++++++++++++++ .../consent/privacy-experience/index.tsx | 7 +- 4 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 clients/admin-ui/src/features/privacy-experience/PrivacyExperiencesTable.tsx create mode 100644 clients/admin-ui/src/features/privacy-experience/cells.tsx create mode 100644 clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx diff --git a/clients/admin-ui/src/features/privacy-experience/PrivacyExperiencesTable.tsx b/clients/admin-ui/src/features/privacy-experience/PrivacyExperiencesTable.tsx new file mode 100644 index 0000000000..6a7bf6232e --- /dev/null +++ b/clients/admin-ui/src/features/privacy-experience/PrivacyExperiencesTable.tsx @@ -0,0 +1,100 @@ +import { Button, Flex, Spinner } from "@fidesui/react"; +import { PRIVACY_EXPERIENCE_ROUTE, SYSTEM_ROUTE } from "common/nav/v2/routes"; +import { useHasPermission } from "common/Restrict"; +import { DateCell, FidesTable, MultiTagCell } from "common/table"; +import EmptyTableState from "common/table/EmptyTableState"; +import NextLink from "next/link"; +import { useRouter } from "next/router"; +import React, { useMemo } from "react"; +import { Column } from "react-table"; + +import { useAppSelector } from "~/app/hooks"; +import { EnablePrivacyExperienceCell } from "~/features/privacy-experience/cells"; +import { + selectAllPrivacyExperiences, + selectPage, + selectPageSize, + useGetAllPrivacyExperiencesQuery, +} from "~/features/privacy-experience/privacy-experience.slice"; +import { PrivacyExperienceResponse, ScopeRegistryEnum } from "~/types/api"; + +const PrivacyExperiencesTable = () => { + const router = useRouter(); + // Subscribe to get all privacy experiences + const page = useAppSelector(selectPage); + const pageSize = useAppSelector(selectPageSize); + const { isLoading } = useGetAllPrivacyExperiencesQuery({ + page, + size: pageSize, + }); + + const privacyExperiences = useAppSelector(selectAllPrivacyExperiences); + // Permissions + const userCanUpdate = useHasPermission([ + ScopeRegistryEnum.PRIVACY_EXPERIENCE_UPDATE, + ]); + const handleRowClick = ({ id }: PrivacyExperienceResponse) => { + if (userCanUpdate) { + router.push(`${PRIVACY_EXPERIENCE_ROUTE}/${id}`); + } + }; + + const columns: Column[] = useMemo( + () => [ + { + Header: "Component", + accessor: "component", + // Cell: MechanismCell, + }, + { Header: "Location", accessor: "regions", Cell: MultiTagCell }, + { Header: "Last update", accessor: "updated_at", Cell: DateCell }, + { + Header: "Enable", + accessor: "disabled", + disabled: !userCanUpdate, + Cell: EnablePrivacyExperienceCell, + }, + ], + [userCanUpdate] + ); + + if (isLoading) { + return ( + + + + ); + } + + if (privacyExperiences.length === 0) { + return ( + + Set up data uses + + } + /> + ); + } + return ( + + columns={columns} + data={privacyExperiences} + onRowClick={userCanUpdate ? handleRowClick : undefined} + /> + ); +}; + +export default PrivacyExperiencesTable; diff --git a/clients/admin-ui/src/features/privacy-experience/cells.tsx b/clients/admin-ui/src/features/privacy-experience/cells.tsx new file mode 100644 index 0000000000..c0a016210e --- /dev/null +++ b/clients/admin-ui/src/features/privacy-experience/cells.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import { CellProps } from "react-table"; + +import { EnableCell } from "~/features/common/table/"; +import { PrivacyExperienceResponse } from "~/types/api"; + +export const EnablePrivacyExperienceCell = ( + cellProps: CellProps +) => { + // TODO: replace with real API call once it is ready + const mockPatch = (payload: any) => { + console.log("patch", payload); + }; + + const { row } = cellProps; + const onToggle = async (toggle: boolean) => { + await mockPatch([ + { + id: row.original.id, + disabled: !toggle, + }, + ]); + }; + + const { regions } = row.original; + const multipleRegions = regions ? regions.length > 1 : false; + + const title = multipleRegions + ? "Disabling multiple states" + : "Disabling experience"; + const message = multipleRegions + ? "Warning, you are about to disable this privacy experience for multiple states. If you continue, your privacy notices will not be accessible to users in these locations." + : "Warning, you are about to disable this privacy experience. If you continue, your privacy notices will not be accessible to users in this location."; + + return ( + + {...cellProps} + onToggle={onToggle} + title={title} + message={message} + /> + ); +}; diff --git a/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx b/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx new file mode 100644 index 0000000000..0cb35863d0 --- /dev/null +++ b/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx @@ -0,0 +1,100 @@ +import { PRIVACY_REQUESTS_ROUTE } from "@fidesui/components"; +import { + Box, + Breadcrumb, + BreadcrumbItem, + Center, + Heading, + Spinner, + Text, +} from "@fidesui/react"; +import NextLink from "next/link"; +import { useRouter } from "next/router"; + +import Layout from "~/features/common/Layout"; +import { PRIVACY_EXPERIENCE_ROUTE } from "~/features/common/nav/v2/routes"; +import { useGetPrivacyNoticeByIdQuery } from "~/features/privacy-notices/privacy-notices.slice"; + +const PrivacyExperienceDetailPage = () => { + const router = useRouter(); + + let experienceId = ""; + if (router.query.id) { + experienceId = Array.isArray(router.query.id) + ? router.query.id[0] + : router.query.id; + } + + // TODO: replace with the proper call when it is available + const { data, isLoading } = useGetPrivacyNoticeByIdQuery(experienceId); + + if (isLoading) { + return ( + +
+ +
+
+ ); + } + + if (!data) { + return ( + + No privacy experience with id {experienceId} found. + + ); + } + + return ( + + + + {/* TODO: fill in actual component name here */} + Configure your consent overlay + + + + + + Privacy requests + + + {/* TODO: Add Consent breadcrumb once the page exists */} + + + Privacy experience + + + + {data.name} + + + + + + + Configure the text of your privacy overlay, privacy banner, and the + text of the buttons which users will click to accept, reject, manage, + and save their preferences. This privacy overlay contains opt-in + privacy notices and must be delivered with a banner. + + + Work in progress! + + + + ); +}; + +export default PrivacyExperienceDetailPage; diff --git a/clients/admin-ui/src/pages/consent/privacy-experience/index.tsx b/clients/admin-ui/src/pages/consent/privacy-experience/index.tsx index 136c465425..585d86a8b0 100644 --- a/clients/admin-ui/src/pages/consent/privacy-experience/index.tsx +++ b/clients/admin-ui/src/pages/consent/privacy-experience/index.tsx @@ -5,6 +5,7 @@ import React from "react"; import Layout from "~/features/common/Layout"; import { PRIVACY_EXPERIENCE_ROUTE } from "~/features/common/nav/v2/routes"; +import PrivacyExperiencesTable from "~/features/privacy-experience/PrivacyExperiencesTable"; const PrivacyExperiencePage = () => ( @@ -31,7 +32,7 @@ const PrivacyExperiencePage = () => ( - + Based on your privacy notices, Fides has created the overlay and privacy experience configuration below. Your privacy notices will be presented by region in these components. Edit each component to adjust the text that @@ -40,7 +41,9 @@ const PrivacyExperiencePage = () => ( website, copy the javascript using the button on this page and place it on your website. - Work in progress! + + + ); From ad9c81a9a4729ceda079e42bc41298118e84862f Mon Sep 17 00:00:00 2001 From: Allison King Date: Fri, 28 Apr 2023 13:30:08 -0400 Subject: [PATCH 04/10] Update TS types --- clients/admin-ui/src/types/api/index.ts | 2 ++ .../src/types/api/models/PrivacyExperience.ts | 25 +++++++++++++++++ .../api/models/PrivacyExperienceResponse.ts | 2 +- .../api/models/PrivacyExperienceWithId.ts | 27 +++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 clients/admin-ui/src/types/api/models/PrivacyExperience.ts create mode 100644 clients/admin-ui/src/types/api/models/PrivacyExperienceWithId.ts diff --git a/clients/admin-ui/src/types/api/index.ts b/clients/admin-ui/src/types/api/index.ts index 00bda557a7..da56a8270e 100644 --- a/clients/admin-ui/src/types/api/index.ts +++ b/clients/admin-ui/src/types/api/index.ts @@ -204,7 +204,9 @@ export type { PolicyWebhookUpdateResponse } from "./models/PolicyWebhookUpdateRe export type { PostgreSQLDocsSchema } from "./models/PostgreSQLDocsSchema"; export type { PrivacyDeclaration } from "./models/PrivacyDeclaration"; export type { PrivacyDeclarationResponse } from "./models/PrivacyDeclarationResponse"; +export type { PrivacyExperience } from "./models/PrivacyExperience"; export type { PrivacyExperienceResponse } from "./models/PrivacyExperienceResponse"; +export type { PrivacyExperienceWithId } from "./models/PrivacyExperienceWithId"; export type { PrivacyNoticeCreation } from "./models/PrivacyNoticeCreation"; export type { PrivacyNoticeHistorySchema } from "./models/PrivacyNoticeHistorySchema"; export { PrivacyNoticeRegion } from "./models/PrivacyNoticeRegion"; diff --git a/clients/admin-ui/src/types/api/models/PrivacyExperience.ts b/clients/admin-ui/src/types/api/models/PrivacyExperience.ts new file mode 100644 index 0000000000..4093912a7f --- /dev/null +++ b/clients/admin-ui/src/types/api/models/PrivacyExperience.ts @@ -0,0 +1,25 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ComponentType } from "./ComponentType"; +import type { DeliveryMechanism } from "./DeliveryMechanism"; +import type { PrivacyNoticeRegion } from "./PrivacyNoticeRegion"; + +/** + * Base for PrivacyExperience API objects + */ +export type PrivacyExperience = { + disabled?: boolean; + component: ComponentType; + delivery_mechanism: DeliveryMechanism; + regions: Array; + component_title?: string; + component_description?: string; + banner_title?: string; + banner_description?: string; + link_label?: string; + confirmation_button_label?: string; + reject_button_label?: string; + acknowledgement_button_label?: string; +}; diff --git a/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts b/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts index bea7841f57..7fdb87afa9 100644 --- a/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts +++ b/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts @@ -14,7 +14,7 @@ export type PrivacyExperienceResponse = { disabled?: boolean; component: ComponentType; delivery_mechanism: DeliveryMechanism; - regions?: Array; + regions: Array; component_title?: string; component_description?: string; banner_title?: string; diff --git a/clients/admin-ui/src/types/api/models/PrivacyExperienceWithId.ts b/clients/admin-ui/src/types/api/models/PrivacyExperienceWithId.ts new file mode 100644 index 0000000000..9036752119 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/PrivacyExperienceWithId.ts @@ -0,0 +1,27 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ComponentType } from "./ComponentType"; +import type { DeliveryMechanism } from "./DeliveryMechanism"; +import type { PrivacyNoticeRegion } from "./PrivacyNoticeRegion"; + +/** + * An API representation of a PrivacyExperience that includes an `id` field. + * Used to help model API responses and update payloads + */ +export type PrivacyExperienceWithId = { + disabled?: boolean; + component: ComponentType; + delivery_mechanism: DeliveryMechanism; + regions: Array; + component_title?: string; + component_description?: string; + banner_title?: string; + banner_description?: string; + link_label?: string; + confirmation_button_label?: string; + reject_button_label?: string; + acknowledgement_button_label?: string; + id: string; +}; From d9872351d25de6db22a473cd5da98f64f51227c3 Mon Sep 17 00:00:00 2001 From: Allison King Date: Fri, 28 Apr 2023 13:44:09 -0400 Subject: [PATCH 05/10] Add new privacy experience endpoints --- .../privacy-experience.slice.ts | 40 +++++++++++++++++-- .../pages/consent/privacy-experience/[id].tsx | 16 +++++--- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts b/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts index 630750ee40..5d69917956 100644 --- a/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts +++ b/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts @@ -4,12 +4,12 @@ import type { RootState } from "~/app/store"; import { baseApi } from "~/features/common/api.slice"; import { Page_PrivacyExperienceResponse_, + PrivacyExperience, PrivacyExperienceResponse, PrivacyNoticeRegion, } from "~/types/api"; export interface State { - activePrivacyNoticeId?: string; page?: number; pageSize?: number; } @@ -38,10 +38,42 @@ const privacyExperienceApi = baseApi.injectEndpoints({ }), providesTags: () => ["Privacy Experiences"], }), + patchPrivacyExperience: build.mutation< + PrivacyExperienceResponse[], + Partial[] + >({ + query: (payload) => ({ + method: "PATCH", + url: `privacy-experience/`, + body: payload, + }), + invalidatesTags: () => ["Privacy Experiences"], + }), + getPrivacyExperienceById: build.query({ + query: (id) => ({ + url: `privacy-experience/${id}`, + }), + providesTags: (result, error, arg) => [ + { type: "Privacy Experiences", id: arg }, + ], + }), + postPrivacyExperience: build.mutation({ + query: (payload) => ({ + method: "POST", + url: `privacy-experience/`, + body: payload, + }), + invalidatesTags: () => ["Privacy Experiences"], + }), }), }); -export const { useGetAllPrivacyExperiencesQuery } = privacyExperienceApi; +export const { + useGetAllPrivacyExperiencesQuery, + usePatchPrivacyExperienceMutation, + useGetPrivacyExperienceByIdQuery, + usePostPrivacyExperienceMutation, +} = privacyExperienceApi; export const privacyExperienceSlice = createSlice({ name: "privacyExperience", @@ -62,7 +94,7 @@ export const selectPageSize = createSelector( (state) => state.pageSize ); -const emptyPrivacyNotices: PrivacyExperienceResponse[] = []; +const emptyPrivacyExperiences: PrivacyExperienceResponse[] = []; export const selectAllPrivacyExperiences = createSelector( [(RootState) => RootState, selectPage, selectPageSize], (RootState, page, pageSize) => { @@ -72,6 +104,6 @@ export const selectAllPrivacyExperiences = createSelector( size: pageSize, } )(RootState)?.data; - return data ? data.items : emptyPrivacyNotices; + return data ? data.items : emptyPrivacyExperiences; } ); diff --git a/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx b/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx index 0cb35863d0..008b0eff24 100644 --- a/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx +++ b/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx @@ -13,7 +13,8 @@ import { useRouter } from "next/router"; import Layout from "~/features/common/Layout"; import { PRIVACY_EXPERIENCE_ROUTE } from "~/features/common/nav/v2/routes"; -import { useGetPrivacyNoticeByIdQuery } from "~/features/privacy-notices/privacy-notices.slice"; +import { useGetPrivacyExperienceByIdQuery } from "~/features/privacy-experience/privacy-experience.slice"; +import { ComponentType } from "~/types/api"; const PrivacyExperienceDetailPage = () => { const router = useRouter(); @@ -25,8 +26,7 @@ const PrivacyExperienceDetailPage = () => { : router.query.id; } - // TODO: replace with the proper call when it is available - const { data, isLoading } = useGetPrivacyNoticeByIdQuery(experienceId); + const { data, isLoading } = useGetPrivacyExperienceByIdQuery(experienceId); if (isLoading) { return ( @@ -46,8 +46,13 @@ const PrivacyExperienceDetailPage = () => { ); } + const header = + data.component === ComponentType.OVERLAY + ? "Configure your consent overlay" + : "Configure your privacy center"; + return ( - + { mb={2} data-testid="header" > - {/* TODO: fill in actual component name here */} - Configure your consent overlay + {header} Date: Mon, 1 May 2023 10:49:02 -0400 Subject: [PATCH 06/10] Handle experience table cells --- .../PrivacyExperiencesTable.tsx | 10 ++++++---- .../src/features/privacy-experience/cells.tsx | 16 ++++++++++------ .../src/features/privacy-experience/constants.ts | 4 ++++ 3 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 clients/admin-ui/src/features/privacy-experience/constants.ts diff --git a/clients/admin-ui/src/features/privacy-experience/PrivacyExperiencesTable.tsx b/clients/admin-ui/src/features/privacy-experience/PrivacyExperiencesTable.tsx index 6a7bf6232e..d83a23e979 100644 --- a/clients/admin-ui/src/features/privacy-experience/PrivacyExperiencesTable.tsx +++ b/clients/admin-ui/src/features/privacy-experience/PrivacyExperiencesTable.tsx @@ -9,7 +9,10 @@ import React, { useMemo } from "react"; import { Column } from "react-table"; import { useAppSelector } from "~/app/hooks"; -import { EnablePrivacyExperienceCell } from "~/features/privacy-experience/cells"; +import { + ComponentCell, + EnablePrivacyExperienceCell, +} from "~/features/privacy-experience/cells"; import { selectAllPrivacyExperiences, selectPage, @@ -27,7 +30,6 @@ const PrivacyExperiencesTable = () => { page, size: pageSize, }); - const privacyExperiences = useAppSelector(selectAllPrivacyExperiences); // Permissions const userCanUpdate = useHasPermission([ @@ -41,12 +43,12 @@ const PrivacyExperiencesTable = () => { const columns: Column[] = useMemo( () => [ + { Header: "Location", accessor: "regions", Cell: MultiTagCell }, { Header: "Component", accessor: "component", - // Cell: MechanismCell, + Cell: ComponentCell, }, - { Header: "Location", accessor: "regions", Cell: MultiTagCell }, { Header: "Last update", accessor: "updated_at", Cell: DateCell }, { Header: "Enable", diff --git a/clients/admin-ui/src/features/privacy-experience/cells.tsx b/clients/admin-ui/src/features/privacy-experience/cells.tsx index c0a016210e..b97b968220 100644 --- a/clients/admin-ui/src/features/privacy-experience/cells.tsx +++ b/clients/admin-ui/src/features/privacy-experience/cells.tsx @@ -1,20 +1,24 @@ import React from "react"; import { CellProps } from "react-table"; -import { EnableCell } from "~/features/common/table/"; +import { EnableCell, MapCell } from "~/features/common/table/"; import { PrivacyExperienceResponse } from "~/types/api"; +import { COMPONENT_MAP } from "./constants"; +import { usePatchPrivacyExperienceMutation } from "./privacy-experience.slice"; + +export const ComponentCell = ( + cellProps: CellProps +) => ; + export const EnablePrivacyExperienceCell = ( cellProps: CellProps ) => { - // TODO: replace with real API call once it is ready - const mockPatch = (payload: any) => { - console.log("patch", payload); - }; + const [patchExperienceMutationTrigger] = usePatchPrivacyExperienceMutation(); const { row } = cellProps; const onToggle = async (toggle: boolean) => { - await mockPatch([ + await patchExperienceMutationTrigger([ { id: row.original.id, disabled: !toggle, diff --git a/clients/admin-ui/src/features/privacy-experience/constants.ts b/clients/admin-ui/src/features/privacy-experience/constants.ts new file mode 100644 index 0000000000..d9427900c4 --- /dev/null +++ b/clients/admin-ui/src/features/privacy-experience/constants.ts @@ -0,0 +1,4 @@ +export const COMPONENT_MAP = new Map([ + ["overlay", "Overlay"], + ["privacy_center", "Privacy center"], +]); From 34dd2bf4c1606a9bdd3cc578d8656378fd6d1604 Mon Sep 17 00:00:00 2001 From: Allison King Date: Tue, 2 May 2023 11:59:12 -0400 Subject: [PATCH 07/10] Fix breadcrumb and component cell --- .../src/features/privacy-experience/cells.tsx | 11 +++++++---- .../src/pages/consent/privacy-experience/[id].tsx | 5 ++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/clients/admin-ui/src/features/privacy-experience/cells.tsx b/clients/admin-ui/src/features/privacy-experience/cells.tsx index b97b968220..53715a3ec8 100644 --- a/clients/admin-ui/src/features/privacy-experience/cells.tsx +++ b/clients/admin-ui/src/features/privacy-experience/cells.tsx @@ -1,15 +1,18 @@ +import { Text } from "@fidesui/react"; import React from "react"; import { CellProps } from "react-table"; -import { EnableCell, MapCell } from "~/features/common/table/"; +import { EnableCell } from "~/features/common/table/"; import { PrivacyExperienceResponse } from "~/types/api"; import { COMPONENT_MAP } from "./constants"; import { usePatchPrivacyExperienceMutation } from "./privacy-experience.slice"; -export const ComponentCell = ( - cellProps: CellProps -) => ; +export const ComponentCell = ({ + value, +}: CellProps) => ( + {COMPONENT_MAP.get(value) ?? value} +); export const EnablePrivacyExperienceCell = ( cellProps: CellProps diff --git a/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx b/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx index 008b0eff24..5998bb7132 100644 --- a/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx +++ b/clients/admin-ui/src/pages/consent/privacy-experience/[id].tsx @@ -13,6 +13,7 @@ import { useRouter } from "next/router"; import Layout from "~/features/common/Layout"; import { PRIVACY_EXPERIENCE_ROUTE } from "~/features/common/nav/v2/routes"; +import { COMPONENT_MAP } from "~/features/privacy-experience/constants"; import { useGetPrivacyExperienceByIdQuery } from "~/features/privacy-experience/privacy-experience.slice"; import { ComponentType } from "~/types/api"; @@ -81,7 +82,9 @@ const PrivacyExperienceDetailPage = () => { - {data.name} + + {COMPONENT_MAP.get(data.component) ?? data.component} + From ec81d724034d4f6a33f79e321a7a7f975689cad3 Mon Sep 17 00:00:00 2001 From: Allison King Date: Tue, 2 May 2023 12:42:30 -0400 Subject: [PATCH 08/10] Add privacy experience tests --- .../cypress/e2e/privacy-experiences.cy.ts | 147 ++++ .../privacy-experiences/experience.json | 21 + .../fixtures/privacy-experiences/list.json | 172 +++++ .../fixtures/scopes/roles-to-scopes.json | 716 +++++++++--------- .../fixtures/user-management/permissions.json | 285 +++---- .../src/features/common/table/FidesTable.tsx | 3 +- 6 files changed, 850 insertions(+), 494 deletions(-) create mode 100644 clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts create mode 100644 clients/admin-ui/cypress/fixtures/privacy-experiences/experience.json create mode 100644 clients/admin-ui/cypress/fixtures/privacy-experiences/list.json diff --git a/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts b/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts new file mode 100644 index 0000000000..22a6402771 --- /dev/null +++ b/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts @@ -0,0 +1,147 @@ +import { stubPlus } from "cypress/support/stubs"; + +import { PRIVACY_EXPERIENCE_ROUTE } from "~/features/common/nav/v2/routes"; +import { RoleRegistryEnum } from "~/types/api"; + +const OVERLAY_EXPERIENCE_ID = "pri_4076d6dd-a728-4f2d-9a5c-98f35d7a5a86"; +const DISABLED_EXPERIENCE_ID = "pri_75d2b3dc-6f7c-4a36-95ee-3088bd1b4572"; + +describe("Privacy experiences", () => { + beforeEach(() => { + cy.login(); + cy.intercept("GET", "/api/v1/privacy-experience*", { + fixture: "privacy-experiences/list.json", + }).as("getExperiences"); + stubPlus(true); + }); + + describe("permissions", () => { + it("should not be viewable for approvers", () => { + cy.assumeRole(RoleRegistryEnum.APPROVER); + cy.visit(PRIVACY_EXPERIENCE_ROUTE); + // should be redirected to the home page + cy.getByTestId("home-content"); + }); + + it("should be visible to everyone else", () => { + [ + RoleRegistryEnum.CONTRIBUTOR, + RoleRegistryEnum.OWNER, + RoleRegistryEnum.VIEWER, + ].forEach((role) => { + cy.assumeRole(role); + cy.visit(PRIVACY_EXPERIENCE_ROUTE); + cy.getByTestId("privacy-experience-page"); + }); + }); + + it("viewers and approvers cannot click into an experience to edit", () => { + [RoleRegistryEnum.VIEWER, RoleRegistryEnum.VIEWER_AND_APPROVER].forEach( + (role) => { + cy.assumeRole(role); + cy.visit(PRIVACY_EXPERIENCE_ROUTE); + cy.wait("@getExperiences"); + cy.getByTestId(`row-${OVERLAY_EXPERIENCE_ID}`).click(); + // we should still be on the same page + cy.getByTestId("privacy-experience-detail-page").should("not.exist"); + cy.getByTestId("privacy-experience-page"); + } + ); + }); + + it("viewers and approvers cannot toggle the enable toggle", () => { + [RoleRegistryEnum.VIEWER, RoleRegistryEnum.VIEWER_AND_APPROVER].forEach( + (role) => { + cy.assumeRole(role); + cy.visit(PRIVACY_EXPERIENCE_ROUTE); + cy.wait("@getExperiences"); + cy.getByTestId("toggle-Enable") + .first() + .within(() => { + cy.get("span").should("have.attr", "data-disabled"); + }); + } + ); + }); + }); + + it("can show an empty state", () => { + cy.intercept("GET", "/api/v1/privacy-experience*", { + body: { items: [], page: 1, size: 10, total: 0 }, + }).as("getEmptyExperiences"); + cy.visit(PRIVACY_EXPERIENCE_ROUTE); + cy.wait("@getEmptyExperiences"); + cy.getByTestId("empty-state"); + }); + + describe("table", () => { + beforeEach(() => { + cy.visit(PRIVACY_EXPERIENCE_ROUTE); + cy.wait("@getExperiences"); + }); + + it("should render a row for each privacy experience", () => { + cy.fixture("privacy-experiences/list.json").then((data) => { + data.items + .map((item) => item.id) + .forEach((id) => { + cy.getByTestId(`row-${id}`); + }); + }); + }); + + it("can click a row to go to the experience page", () => { + cy.intercept("GET", "/api/v1/privacy-experience/pri*", { + fixture: "privacy-experiences/experience.json", + }).as("getExperienceDetail"); + cy.getByTestId(`row-${OVERLAY_EXPERIENCE_ID}`).click(); + cy.wait("@getExperienceDetail"); + cy.getByTestId("privacy-experience-detail-page"); + cy.url().should("contain", OVERLAY_EXPERIENCE_ID); + }); + + describe("enabling and disabling", () => { + beforeEach(() => { + cy.intercept("PATCH", "/api/v1/privacy-experience*", { + fixture: "privacy-experiences/list.json", + }).as("patchExperiences"); + }); + + it("can enable an experience", () => { + cy.getByTestId(`row-${DISABLED_EXPERIENCE_ID}`).within(() => { + cy.getByTestId("toggle-Enable").within(() => { + cy.get("span").should("not.have.attr", "data-checked"); + }); + cy.getByTestId("toggle-Enable").click(); + }); + + cy.wait("@patchExperiences").then((interception) => { + const { body } = interception.request; + expect(body).to.eql([ + { id: DISABLED_EXPERIENCE_ID, disabled: false }, + ]); + }); + // redux should requery after invalidation + cy.wait("@getExperiences"); + }); + + it("can disable an experience with a warning", () => { + cy.getByTestId(`row-${OVERLAY_EXPERIENCE_ID}`).within(() => { + cy.getByTestId("toggle-Enable").within(() => { + cy.get("span").should("have.attr", "data-checked"); + }); + cy.getByTestId("toggle-Enable").click(); + }); + + cy.getByTestId("confirmation-modal"); + cy.getByTestId("continue-btn").click(); + cy.wait("@patchExperiences").then((interception) => { + const { body } = interception.request; + expect(body).to.eql([{ id: OVERLAY_EXPERIENCE_ID, disabled: true }]); + }); + // redux should requery after invalidation + cy.wait("@getExperiences"); + }); + }); + }); +}); diff --git a/clients/admin-ui/cypress/fixtures/privacy-experiences/experience.json b/clients/admin-ui/cypress/fixtures/privacy-experiences/experience.json new file mode 100644 index 0000000000..c950edbe7a --- /dev/null +++ b/clients/admin-ui/cypress/fixtures/privacy-experiences/experience.json @@ -0,0 +1,21 @@ +{ + "disabled": false, + "component": "overlay", + "delivery_mechanism": "link", + "regions": ["eu_de"], + "component_title": null, + "component_description": null, + "banner_title": null, + "banner_description": null, + "link_label": null, + "confirmation_button_label": null, + "reject_button_label": null, + "acknowledgement_button_label": null, + "id": "pri_4076d6dd-a728-4f2d-9a5c-98f35d7a5a86", + "created_at": "2023-05-02T15:32:41.253621+00:00", + "updated_at": "2023-05-02T15:32:41.253621+00:00", + "version": 1.0, + "privacy_experience_history_id": "pri_fa40ddc5-ba8d-4efe-b83f-643b2595b191", + "privacy_experience_template_id": null, + "privacy_notices": [] +} diff --git a/clients/admin-ui/cypress/fixtures/privacy-experiences/list.json b/clients/admin-ui/cypress/fixtures/privacy-experiences/list.json new file mode 100644 index 0000000000..5967d09700 --- /dev/null +++ b/clients/admin-ui/cypress/fixtures/privacy-experiences/list.json @@ -0,0 +1,172 @@ +{ + "items": [ + { + "disabled": false, + "component": "overlay", + "delivery_mechanism": "link", + "regions": ["eu_de"], + "component_title": null, + "component_description": null, + "banner_title": null, + "banner_description": null, + "link_label": null, + "confirmation_button_label": null, + "reject_button_label": null, + "acknowledgement_button_label": null, + "id": "pri_4076d6dd-a728-4f2d-9a5c-98f35d7a5a86", + "created_at": "2023-05-02T15:32:41.253621+00:00", + "updated_at": "2023-05-02T15:32:41.253621+00:00", + "version": 1.0, + "privacy_experience_history_id": "pri_fa40ddc5-ba8d-4efe-b83f-643b2595b191", + "privacy_experience_template_id": null, + "privacy_notices": [] + }, + { + "disabled": true, + "component": "privacy_center", + "delivery_mechanism": "link", + "regions": ["us_ca"], + "component_title": null, + "component_description": null, + "banner_title": null, + "banner_description": null, + "link_label": null, + "confirmation_button_label": null, + "reject_button_label": null, + "acknowledgement_button_label": null, + "id": "pri_75d2b3dc-6f7c-4a36-95ee-3088bd1b4572", + "created_at": "2023-05-02T15:32:41.237150+00:00", + "updated_at": "2023-05-02T15:32:41.237150+00:00", + "version": 1.0, + "privacy_experience_history_id": "pri_422201ba-59c6-48df-992a-f20af701e999", + "privacy_experience_template_id": null, + "privacy_notices": [ + { + "name": "Data Sales", + "description": "Provide opt-out consent for the use of data in ways that may be considered “data sales” under US state privacy regulations.", + "internal_description": null, + "origin": null, + "regions": ["us_ca", "us_co"], + "consent_mechanism": "opt_out", + "data_uses": ["advertising.third_party.personalized"], + "enforcement_level": "frontend", + "disabled": false, + "has_gpc_flag": false, + "displayed_in_privacy_center": true, + "displayed_in_overlay": false, + "displayed_in_api": false, + "id": "pri_8ad94105-6142-4f20-bb56-9644a6f16917", + "created_at": "2023-05-02T15:19:36.733208+00:00", + "updated_at": "2023-05-02T15:19:36.733208+00:00", + "version": 1.0, + "privacy_notice_history_id": "pri_b524bbec-22e2-4364-9e39-5399208976d8" + } + ] + }, + { + "disabled": false, + "component": "overlay", + "delivery_mechanism": "banner", + "regions": ["eu_fr", "eu_ie"], + "component_title": null, + "component_description": null, + "banner_title": null, + "banner_description": null, + "link_label": null, + "confirmation_button_label": null, + "reject_button_label": null, + "acknowledgement_button_label": null, + "id": "pri_759df3b9-57c7-40aa-8252-0a12456d81cb", + "created_at": "2023-05-02T15:32:41.219842+00:00", + "updated_at": "2023-05-02T15:32:41.219842+00:00", + "version": 1.0, + "privacy_experience_history_id": "pri_438e7d81-0ad0-43e5-8c1e-2c45fe8d6177", + "privacy_experience_template_id": null, + "privacy_notices": [ + { + "name": "Advertising", + "description": "Ensures you are correctly notifying the user about your advertising practices and for appropriate locations, collecting the users consent preferences.", + "internal_description": null, + "origin": null, + "regions": ["eu_fr", "eu_ie"], + "consent_mechanism": "opt_in", + "data_uses": ["advertising.first_party.contextual"], + "enforcement_level": "system_wide", + "disabled": false, + "has_gpc_flag": false, + "displayed_in_privacy_center": false, + "displayed_in_overlay": true, + "displayed_in_api": false, + "id": "pri_82efe831-b134-434f-90eb-e4b3204a5e97", + "created_at": "2023-05-02T15:19:36.730578+00:00", + "updated_at": "2023-05-02T15:19:36.730578+00:00", + "version": 1.0, + "privacy_notice_history_id": "pri_f19c3323-7281-4043-b964-a103835cab4b" + }, + { + "name": "Analytics", + "description": "Ensures you are correctly notifying the user about your advertising practices and for appropriate locations, collecting the users consent preferences.", + "internal_description": null, + "origin": null, + "regions": ["eu_fr", "eu_ie"], + "consent_mechanism": "opt_in", + "data_uses": ["collect"], + "enforcement_level": "system_wide", + "disabled": false, + "has_gpc_flag": false, + "displayed_in_privacy_center": false, + "displayed_in_overlay": true, + "displayed_in_api": false, + "id": "pri_1e98649f-9294-4217-a51a-2d2781f19052", + "created_at": "2023-05-02T15:19:36.727792+00:00", + "updated_at": "2023-05-02T15:19:36.727792+00:00", + "version": 1.0, + "privacy_notice_history_id": "pri_18e70ccb-af22-4761-bf77-4095ee19118f" + }, + { + "name": "Functional", + "description": "This is for data processing activities that enhance the capability or features of your site but may not be strictly necessary.", + "internal_description": null, + "origin": null, + "regions": ["eu_fr", "eu_ie"], + "consent_mechanism": "opt_in", + "data_uses": ["improve.system"], + "enforcement_level": "system_wide", + "disabled": false, + "has_gpc_flag": false, + "displayed_in_privacy_center": false, + "displayed_in_overlay": true, + "displayed_in_api": false, + "id": "pri_97f45baf-298e-4ea0-a22e-cf80543b573b", + "created_at": "2023-05-02T15:19:36.723747+00:00", + "updated_at": "2023-05-02T15:19:36.723747+00:00", + "version": 1.0, + "privacy_notice_history_id": "pri_595eef41-9848-4709-be5c-cf924d6e5968" + }, + { + "name": "Essential", + "description": "Notify the user about data processing activities that are essential to your services functionality. Typically consent is not required for this.", + "internal_description": null, + "origin": null, + "regions": ["eu_fr", "eu_ie"], + "consent_mechanism": "notice_only", + "data_uses": ["provide.service"], + "enforcement_level": "system_wide", + "disabled": false, + "has_gpc_flag": false, + "displayed_in_privacy_center": false, + "displayed_in_overlay": true, + "displayed_in_api": false, + "id": "pri_e8d8c006-5753-4b13-afc6-4f26caf6eb61", + "created_at": "2023-05-02T15:19:36.703598+00:00", + "updated_at": "2023-05-02T15:19:36.703598+00:00", + "version": 1.0, + "privacy_notice_history_id": "pri_79eba2d7-d6f2-4bd4-94b9-f02dbc2f7621" + } + ] + } + ], + "total": 3, + "page": 1, + "size": 10 +} diff --git a/clients/admin-ui/cypress/fixtures/scopes/roles-to-scopes.json b/clients/admin-ui/cypress/fixtures/scopes/roles-to-scopes.json index aac1cf2ed9..fe99fd21e2 100644 --- a/clients/admin-ui/cypress/fixtures/scopes/roles-to-scopes.json +++ b/clients/admin-ui/cypress/fixtures/scopes/roles-to-scopes.json @@ -1,353 +1,365 @@ { - "owner": [ - "allow_list:create", - "allow_list:delete", - "allow_list:read", - "allow_list:update", - "classify_instance:create", - "classify_instance:read", - "classify_instance:update", - "cli-objects:create", - "cli-objects:delete", - "cli-objects:read", - "cli-objects:update", - "client:create", - "client:delete", - "client:read", - "client:update", - "config:read", - "config:update", - "connection:authorize", - "connection:create_or_update", - "connection:delete", - "connection:instantiate", - "connection:read", - "connection_type:read", - "connector_template:register", - "consent:read", - "ctl_dataset:create", - "ctl_dataset:delete", - "ctl_dataset:read", - "ctl_dataset:update", - "ctl_policy:create", - "ctl_policy:delete", - "ctl_policy:read", - "ctl_policy:update", - "custom_field:create", - "custom_field:delete", - "custom_field:read", - "custom_field:update", - "custom_field_definition:create", - "custom_field_definition:delete", - "custom_field_definition:read", - "custom_field_definition:update", - "data_category:create", - "data_category:delete", - "data_category:read", - "data_category:update", - "data_qualifier:create", - "data_qualifier:delete", - "data_qualifier:read", - "data_qualifier:update", - "data_subject:create", - "data_subject:delete", - "data_subject:read", - "data_subject:update", - "data_use:create", - "data_use:delete", - "data_use:read", - "data_use:update", - "database:reset", - "datamap:read", - "dataset:create_or_update", - "dataset:delete", - "dataset:read", - "encryption:exec", - "evaluation:create", - "evaluation:delete", - "evaluation:read", - "evaluation:update", - "fides_taxonomy:update", - "generate:exec", - "masking:exec", - "masking:read", - "messaging:create_or_update", - "messaging:delete", - "messaging:read", - "organization:create", - "organization:delete", - "organization:read", - "organization:update", - "policy:create_or_update", - "policy:delete", - "policy:read", - "privacy-notice:create", - "privacy-notice:read", - "privacy-notice:update", - "privacy-request-notifications:create_or_update", - "privacy-request-notifications:read", - "privacy-request:create", - "privacy-request:delete", - "privacy-request:read", - "privacy-request:resume", - "privacy-request:review", - "privacy-request:transfer", - "privacy-request:upload_data", - "privacy-request:view_data", - "registry:create", - "registry:delete", - "registry:read", - "registry:update", - "rule:create_or_update", - "rule:delete", - "rule:read", - "saas_config:create_or_update", - "saas_config:delete", - "saas_config:read", - "scope:read", - "storage:create_or_update", - "storage:delete", - "storage:read", - "system:create", - "system:delete", - "system:read", - "system:update", - "system_manager:delete", - "system_manager:read", - "system_manager:update", - "system_scan:create", - "system_scan:read", - "taxonomy:create", - "taxonomy:delete", - "taxonomy:update", - "user-permission:assign_owners", - "user-permission:create", - "user-permission:read", - "user-permission:update", - "user:create", - "user:delete", - "user:password-reset", - "user:read", - "user:update", - "validate:exec", - "webhook:create_or_update", - "webhook:delete", - "webhook:read" - ], - "viewer_and_approver": [ - "allow_list:read", - "classify_instance:read", - "cli-objects:read", - "client:read", - "config:read", - "connection:read", - "connection_type:read", - "consent:read", - "ctl_dataset:read", - "ctl_policy:read", - "custom_field:read", - "custom_field_definition:read", - "data_category:read", - "data_qualifier:read", - "data_subject:read", - "data_use:read", - "datamap:read", - "dataset:read", - "evaluation:read", - "masking:exec", - "masking:read", - "messaging:read", - "organization:read", - "policy:read", - "privacy-notice:read", - "privacy-request-notifications:read", - "privacy-request:read", - "privacy-request:resume", - "privacy-request:review", - "privacy-request:upload_data", - "privacy-request:view_data", - "registry:read", - "rule:read", - "saas_config:read", - "scope:read", - "storage:read", - "system:read", - "system_manager:read", - "system_scan:read", - "user:read", - "webhook:read" - ], - "viewer": [ - "allow_list:read", - "classify_instance:read", - "cli-objects:read", - "client:read", - "config:read", - "connection:read", - "connection_type:read", - "consent:read", - "ctl_dataset:read", - "ctl_policy:read", - "custom_field:read", - "custom_field_definition:read", - "data_category:read", - "data_qualifier:read", - "data_subject:read", - "data_use:read", - "datamap:read", - "dataset:read", - "evaluation:read", - "masking:exec", - "masking:read", - "messaging:read", - "organization:read", - "policy:read", - "privacy-notice:read", - "privacy-request-notifications:read", - "privacy-request:read", - "registry:read", - "rule:read", - "saas_config:read", - "scope:read", - "storage:read", - "system:read", - "system_manager:read", - "system_scan:read", - "user:read", - "webhook:read" - ], - "approver": [ - "privacy-request:read", - "privacy-request:resume", - "privacy-request:review", - "privacy-request:upload_data", - "privacy-request:view_data" - ], - "contributor": [ - "allow_list:create", - "allow_list:delete", - "allow_list:read", - "allow_list:update", - "classify_instance:create", - "classify_instance:read", - "classify_instance:update", - "cli-objects:create", - "cli-objects:delete", - "cli-objects:read", - "cli-objects:update", - "client:create", - "client:delete", - "client:read", - "client:update", - "config:read", - "connection:authorize", - "connection:create_or_update", - "connection:delete", - "connection:instantiate", - "connection:read", - "connection_type:read", - "consent:read", - "ctl_dataset:create", - "ctl_dataset:delete", - "ctl_dataset:read", - "ctl_dataset:update", - "ctl_policy:create", - "ctl_policy:delete", - "ctl_policy:read", - "ctl_policy:update", - "custom_field:create", - "custom_field:delete", - "custom_field:read", - "custom_field:update", - "custom_field_definition:create", - "custom_field_definition:delete", - "custom_field_definition:read", - "custom_field_definition:update", - "data_category:create", - "data_category:delete", - "data_category:read", - "data_category:update", - "data_qualifier:create", - "data_qualifier:delete", - "data_qualifier:read", - "data_qualifier:update", - "data_subject:create", - "data_subject:delete", - "data_subject:read", - "data_subject:update", - "data_use:create", - "data_use:delete", - "data_use:read", - "data_use:update", - "database:reset", - "datamap:read", - "dataset:create_or_update", - "dataset:delete", - "dataset:read", - "encryption:exec", - "evaluation:create", - "evaluation:delete", - "evaluation:read", - "evaluation:update", - "fides_taxonomy:update", - "generate:exec", - "masking:exec", - "masking:read", - "messaging:read", - "organization:create", - "organization:delete", - "organization:read", - "organization:update", - "policy:create_or_update", - "policy:delete", - "policy:read", - "privacy-notice:create", - "privacy-notice:read", - "privacy-notice:update", - "privacy-request-notifications:read", - "privacy-request:create", - "privacy-request:delete", - "privacy-request:read", - "privacy-request:resume", - "privacy-request:review", - "privacy-request:transfer", - "privacy-request:upload_data", - "privacy-request:view_data", - "registry:create", - "registry:delete", - "registry:read", - "registry:update", - "rule:create_or_update", - "rule:delete", - "rule:read", - "saas_config:create_or_update", - "saas_config:delete", - "saas_config:read", - "scope:read", - "storage:read", - "system:create", - "system:delete", - "system:read", - "system:update", - "system_manager:delete", - "system_manager:read", - "system_manager:update", - "system_scan:create", - "system_scan:read", - "taxonomy:create", - "taxonomy:delete", - "taxonomy:update", - "user-permission:create", - "user-permission:read", - "user-permission:update", - "user:create", - "user:delete", - "user:password-reset", - "user:read", - "user:update", - "validate:exec", - "webhook:create_or_update", - "webhook:delete", - "webhook:read" - ] -} \ No newline at end of file + "owner": [ + "allow_list:create", + "allow_list:delete", + "allow_list:read", + "allow_list:update", + "classify_instance:create", + "classify_instance:read", + "classify_instance:update", + "cli-objects:create", + "cli-objects:delete", + "cli-objects:read", + "cli-objects:update", + "client:create", + "client:delete", + "client:read", + "client:update", + "config:read", + "config:update", + "connection:authorize", + "connection:create_or_update", + "connection:delete", + "connection:instantiate", + "connection:read", + "connection_type:read", + "connector_template:register", + "consent:read", + "ctl_dataset:create", + "ctl_dataset:delete", + "ctl_dataset:read", + "ctl_dataset:update", + "ctl_policy:create", + "ctl_policy:delete", + "ctl_policy:read", + "ctl_policy:update", + "current-privacy-preference:read", + "custom_field:create", + "custom_field:delete", + "custom_field:read", + "custom_field:update", + "custom_field_definition:create", + "custom_field_definition:delete", + "custom_field_definition:read", + "custom_field_definition:update", + "data_category:create", + "data_category:delete", + "data_category:read", + "data_category:update", + "data_qualifier:create", + "data_qualifier:delete", + "data_qualifier:read", + "data_qualifier:update", + "data_subject:create", + "data_subject:delete", + "data_subject:read", + "data_subject:update", + "data_use:create", + "data_use:delete", + "data_use:read", + "data_use:update", + "database:reset", + "datamap:read", + "dataset:create_or_update", + "dataset:delete", + "dataset:read", + "encryption:exec", + "evaluation:create", + "evaluation:delete", + "evaluation:read", + "evaluation:update", + "fides_taxonomy:update", + "generate:exec", + "masking:exec", + "masking:read", + "messaging:create_or_update", + "messaging:delete", + "messaging:read", + "organization:create", + "organization:delete", + "organization:read", + "organization:update", + "policy:create_or_update", + "policy:delete", + "policy:read", + "privacy-experience:create", + "privacy-experience:read", + "privacy-experience:update", + "privacy-notice:create", + "privacy-notice:read", + "privacy-notice:update", + "privacy-preference-history:read", + "privacy-request-notifications:create_or_update", + "privacy-request-notifications:read", + "privacy-request:create", + "privacy-request:delete", + "privacy-request:read", + "privacy-request:resume", + "privacy-request:review", + "privacy-request:transfer", + "privacy-request:upload_data", + "privacy-request:view_data", + "registry:create", + "registry:delete", + "registry:read", + "registry:update", + "rule:create_or_update", + "rule:delete", + "rule:read", + "saas_config:create_or_update", + "saas_config:delete", + "saas_config:read", + "scope:read", + "storage:create_or_update", + "storage:delete", + "storage:read", + "system:create", + "system:delete", + "system:read", + "system:update", + "system_manager:delete", + "system_manager:read", + "system_manager:update", + "system_scan:create", + "system_scan:read", + "taxonomy:create", + "taxonomy:delete", + "taxonomy:update", + "user-permission:assign_owners", + "user-permission:create", + "user-permission:read", + "user-permission:update", + "user:create", + "user:delete", + "user:password-reset", + "user:read", + "user:update", + "validate:exec", + "webhook:create_or_update", + "webhook:delete", + "webhook:read" + ], + "viewer_and_approver": [ + "allow_list:read", + "classify_instance:read", + "cli-objects:read", + "client:read", + "config:read", + "connection:read", + "connection_type:read", + "consent:read", + "ctl_dataset:read", + "ctl_policy:read", + "custom_field:read", + "custom_field_definition:read", + "data_category:read", + "data_qualifier:read", + "data_subject:read", + "data_use:read", + "datamap:read", + "dataset:read", + "evaluation:read", + "masking:exec", + "masking:read", + "messaging:read", + "organization:read", + "policy:read", + "privacy-experience:read", + "privacy-notice:read", + "privacy-request-notifications:read", + "privacy-request:read", + "privacy-request:resume", + "privacy-request:review", + "privacy-request:upload_data", + "privacy-request:view_data", + "registry:read", + "rule:read", + "saas_config:read", + "scope:read", + "storage:read", + "system:read", + "system_manager:read", + "system_scan:read", + "user:read", + "webhook:read" + ], + "viewer": [ + "allow_list:read", + "classify_instance:read", + "cli-objects:read", + "client:read", + "config:read", + "connection:read", + "connection_type:read", + "consent:read", + "ctl_dataset:read", + "ctl_policy:read", + "custom_field:read", + "custom_field_definition:read", + "data_category:read", + "data_qualifier:read", + "data_subject:read", + "data_use:read", + "datamap:read", + "dataset:read", + "evaluation:read", + "masking:exec", + "masking:read", + "messaging:read", + "organization:read", + "policy:read", + "privacy-experience:read", + "privacy-notice:read", + "privacy-request-notifications:read", + "privacy-request:read", + "registry:read", + "rule:read", + "saas_config:read", + "scope:read", + "storage:read", + "system:read", + "system_manager:read", + "system_scan:read", + "user:read", + "webhook:read" + ], + "approver": [ + "privacy-request:read", + "privacy-request:resume", + "privacy-request:review", + "privacy-request:upload_data", + "privacy-request:view_data" + ], + "contributor": [ + "allow_list:create", + "allow_list:delete", + "allow_list:read", + "allow_list:update", + "classify_instance:create", + "classify_instance:read", + "classify_instance:update", + "cli-objects:create", + "cli-objects:delete", + "cli-objects:read", + "cli-objects:update", + "client:create", + "client:delete", + "client:read", + "client:update", + "config:read", + "connection:authorize", + "connection:create_or_update", + "connection:delete", + "connection:instantiate", + "connection:read", + "connection_type:read", + "consent:read", + "ctl_dataset:create", + "ctl_dataset:delete", + "ctl_dataset:read", + "ctl_dataset:update", + "ctl_policy:create", + "ctl_policy:delete", + "ctl_policy:read", + "ctl_policy:update", + "current-privacy-preference:read", + "custom_field:create", + "custom_field:delete", + "custom_field:read", + "custom_field:update", + "custom_field_definition:create", + "custom_field_definition:delete", + "custom_field_definition:read", + "custom_field_definition:update", + "data_category:create", + "data_category:delete", + "data_category:read", + "data_category:update", + "data_qualifier:create", + "data_qualifier:delete", + "data_qualifier:read", + "data_qualifier:update", + "data_subject:create", + "data_subject:delete", + "data_subject:read", + "data_subject:update", + "data_use:create", + "data_use:delete", + "data_use:read", + "data_use:update", + "database:reset", + "datamap:read", + "dataset:create_or_update", + "dataset:delete", + "dataset:read", + "encryption:exec", + "evaluation:create", + "evaluation:delete", + "evaluation:read", + "evaluation:update", + "fides_taxonomy:update", + "generate:exec", + "masking:exec", + "masking:read", + "messaging:read", + "organization:create", + "organization:delete", + "organization:read", + "organization:update", + "policy:create_or_update", + "policy:delete", + "policy:read", + "privacy-experience:create", + "privacy-experience:read", + "privacy-experience:update", + "privacy-notice:create", + "privacy-notice:read", + "privacy-notice:update", + "privacy-preference-history:read", + "privacy-request-notifications:read", + "privacy-request:create", + "privacy-request:delete", + "privacy-request:read", + "privacy-request:resume", + "privacy-request:review", + "privacy-request:transfer", + "privacy-request:upload_data", + "privacy-request:view_data", + "registry:create", + "registry:delete", + "registry:read", + "registry:update", + "rule:create_or_update", + "rule:delete", + "rule:read", + "saas_config:create_or_update", + "saas_config:delete", + "saas_config:read", + "scope:read", + "storage:read", + "system:create", + "system:delete", + "system:read", + "system:update", + "system_manager:delete", + "system_manager:read", + "system_manager:update", + "system_scan:create", + "system_scan:read", + "taxonomy:create", + "taxonomy:delete", + "taxonomy:update", + "user-permission:create", + "user-permission:read", + "user-permission:update", + "user:create", + "user:delete", + "user:password-reset", + "user:read", + "user:update", + "validate:exec", + "webhook:create_or_update", + "webhook:delete", + "webhook:read" + ] +} diff --git a/clients/admin-ui/cypress/fixtures/user-management/permissions.json b/clients/admin-ui/cypress/fixtures/user-management/permissions.json index 31a3e94510..ac24589852 100644 --- a/clients/admin-ui/cypress/fixtures/user-management/permissions.json +++ b/clients/admin-ui/cypress/fixtures/user-management/permissions.json @@ -1,142 +1,145 @@ { - "roles": [ - "owner" - ], - "id": "fidesadmin", - "user_id": "fidesadmin", - "total_scopes": [ - "allow_list:create", - "allow_list:delete", - "allow_list:read", - "allow_list:update", - "classify_instance:create", - "classify_instance:read", - "classify_instance:update", - "cli-objects:create", - "cli-objects:delete", - "cli-objects:read", - "cli-objects:update", - "client:create", - "client:delete", - "client:read", - "client:update", - "config:read", - "config:update", - "connection:authorize", - "connection:create_or_update", - "connection:delete", - "connection:instantiate", - "connection:read", - "connection_type:read", - "connector_template:register", - "consent:read", - "ctl_dataset:create", - "ctl_dataset:delete", - "ctl_dataset:read", - "ctl_dataset:update", - "ctl_policy:create", - "ctl_policy:delete", - "ctl_policy:read", - "ctl_policy:update", - "custom_field:create", - "custom_field:delete", - "custom_field:read", - "custom_field:update", - "custom_field_definition:create", - "custom_field_definition:delete", - "custom_field_definition:read", - "custom_field_definition:update", - "data_category:create", - "data_category:delete", - "data_category:read", - "data_category:update", - "data_qualifier:create", - "data_qualifier:delete", - "data_qualifier:read", - "data_qualifier:update", - "data_subject:create", - "data_subject:delete", - "data_subject:read", - "data_subject:update", - "data_use:create", - "data_use:delete", - "data_use:read", - "data_use:update", - "database:reset", - "datamap:read", - "dataset:create_or_update", - "dataset:delete", - "dataset:read", - "encryption:exec", - "evaluation:create", - "evaluation:delete", - "evaluation:read", - "evaluation:update", - "fides_taxonomy:update", - "generate:exec", - "masking:exec", - "masking:read", - "messaging:create_or_update", - "messaging:delete", - "messaging:read", - "organization:create", - "organization:delete", - "organization:read", - "organization:update", - "policy:create_or_update", - "policy:delete", - "policy:read", - "privacy-notice:create", - "privacy-notice:read", - "privacy-notice:update", - "privacy-request-notifications:create_or_update", - "privacy-request-notifications:read", - "privacy-request:create", - "privacy-request:delete", - "privacy-request:read", - "privacy-request:resume", - "privacy-request:review", - "privacy-request:transfer", - "privacy-request:upload_data", - "privacy-request:view_data", - "registry:create", - "registry:delete", - "registry:read", - "registry:update", - "rule:create_or_update", - "rule:delete", - "rule:read", - "saas_config:create_or_update", - "saas_config:delete", - "saas_config:read", - "scope:read", - "storage:create_or_update", - "storage:delete", - "storage:read", - "system:create", - "system:delete", - "system:read", - "system:update", - "system_manager:delete", - "system_manager:read", - "system_manager:update", - "system_scan:create", - "system_scan:read", - "taxonomy:create", - "taxonomy:delete", - "taxonomy:update", - "user-permission:assign_owners", - "user-permission:create", - "user-permission:read", - "user-permission:update", - "user:create", - "user:delete", - "user:password-reset", - "user:read", - "user:update", - "validate:exec", - "webhook:create_or_update", - "webhook:delete", - "webhook:read" - ] -} \ No newline at end of file + "roles": ["owner"], + "id": "fidesadmin", + "user_id": "fidesadmin", + "total_scopes": [ + "allow_list:create", + "allow_list:delete", + "allow_list:read", + "allow_list:update", + "classify_instance:create", + "classify_instance:read", + "classify_instance:update", + "cli-objects:create", + "cli-objects:delete", + "cli-objects:read", + "cli-objects:update", + "client:create", + "client:delete", + "client:read", + "client:update", + "config:read", + "config:update", + "connection:authorize", + "connection:create_or_update", + "connection:delete", + "connection:instantiate", + "connection:read", + "connection_type:read", + "connector_template:register", + "consent:read", + "ctl_dataset:create", + "ctl_dataset:delete", + "ctl_dataset:read", + "ctl_dataset:update", + "ctl_policy:create", + "ctl_policy:delete", + "ctl_policy:read", + "ctl_policy:update", + "current-privacy-preference:read", + "custom_field:create", + "custom_field:delete", + "custom_field:read", + "custom_field:update", + "custom_field_definition:create", + "custom_field_definition:delete", + "custom_field_definition:read", + "custom_field_definition:update", + "data_category:create", + "data_category:delete", + "data_category:read", + "data_category:update", + "data_qualifier:create", + "data_qualifier:delete", + "data_qualifier:read", + "data_qualifier:update", + "data_subject:create", + "data_subject:delete", + "data_subject:read", + "data_subject:update", + "data_use:create", + "data_use:delete", + "data_use:read", + "data_use:update", + "database:reset", + "datamap:read", + "dataset:create_or_update", + "dataset:delete", + "dataset:read", + "encryption:exec", + "evaluation:create", + "evaluation:delete", + "evaluation:read", + "evaluation:update", + "fides_taxonomy:update", + "generate:exec", + "masking:exec", + "masking:read", + "messaging:create_or_update", + "messaging:delete", + "messaging:read", + "organization:create", + "organization:delete", + "organization:read", + "organization:update", + "policy:create_or_update", + "policy:delete", + "policy:read", + "privacy-experience:create", + "privacy-experience:read", + "privacy-experience:update", + "privacy-notice:create", + "privacy-notice:read", + "privacy-notice:update", + "privacy-preference-history:read", + "privacy-request-notifications:create_or_update", + "privacy-request-notifications:read", + "privacy-request:create", + "privacy-request:delete", + "privacy-request:read", + "privacy-request:resume", + "privacy-request:review", + "privacy-request:transfer", + "privacy-request:upload_data", + "privacy-request:view_data", + "registry:create", + "registry:delete", + "registry:read", + "registry:update", + "rule:create_or_update", + "rule:delete", + "rule:read", + "saas_config:create_or_update", + "saas_config:delete", + "saas_config:read", + "scope:read", + "storage:create_or_update", + "storage:delete", + "storage:read", + "system:create", + "system:delete", + "system:read", + "system:update", + "system_manager:delete", + "system_manager:read", + "system_manager:update", + "system_scan:create", + "system_scan:read", + "taxonomy:create", + "taxonomy:delete", + "taxonomy:update", + "user-permission:assign_owners", + "user-permission:create", + "user-permission:read", + "user-permission:update", + "user:create", + "user:delete", + "user:password-reset", + "user:read", + "user:update", + "validate:exec", + "webhook:create_or_update", + "webhook:delete", + "webhook:read" + ] +} diff --git a/clients/admin-ui/src/features/common/table/FidesTable.tsx b/clients/admin-ui/src/features/common/table/FidesTable.tsx index faa2911b04..182d55424c 100644 --- a/clients/admin-ui/src/features/common/table/FidesTable.tsx +++ b/clients/admin-ui/src/features/common/table/FidesTable.tsx @@ -124,6 +124,7 @@ export const FidesTable = ({ prepareRow(row); const { key: rowKey, ...rowProps } = row.getRowProps(); const rowName = row.original.name; + const rowId = row.original.id; return ( ({ ? { backgroundColor: "gray.50", cursor: "pointer" } : undefined } - data-testid={`row-${rowName}`} + data-testid={`row-${rowName ?? rowId}`} > {row.cells.map((cell) => { const { key: cellKey, ...cellProps } = cell.getCellProps(); From f90a54a4b1a48e922e748abbf2171d9bfc63b634 Mon Sep 17 00:00:00 2001 From: Allison King Date: Tue, 2 May 2023 14:26:16 -0400 Subject: [PATCH 09/10] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e4f3e1b20..94cd65471b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ The types of changes are: - Access and erasure support for Amplitude [#2569](https://github.com/ethyca/fides/pull/2569) - Access and erasure support for Gorgias [#2444](https://github.com/ethyca/fides/pull/2444) - Privacy Experience Bulk Create, Bulk Update, and Detail Endpoints [#3185](https://github.com/ethyca/fides/pull/3185) +- Initial privacy experience UI [#3186](https://github.com/ethyca/fides/pull/3186) ### Changed From 75ed98db414b4c396a3fab3c0fb72d1c576a81d0 Mon Sep 17 00:00:00 2001 From: Allison King Date: Fri, 5 May 2023 11:50:25 -0400 Subject: [PATCH 10/10] Bump page size --- .../src/features/privacy-experience/privacy-experience.slice.ts | 2 +- .../src/features/privacy-notices/privacy-notices.slice.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts b/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts index 5d69917956..9b51350830 100644 --- a/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts +++ b/clients/admin-ui/src/features/privacy-experience/privacy-experience.slice.ts @@ -16,7 +16,7 @@ export interface State { const initialState: State = { page: 1, - pageSize: 10, + pageSize: 50, }; interface PrivacyExperienceParams { diff --git a/clients/admin-ui/src/features/privacy-notices/privacy-notices.slice.ts b/clients/admin-ui/src/features/privacy-notices/privacy-notices.slice.ts index 4a0882243c..cecb584ccf 100644 --- a/clients/admin-ui/src/features/privacy-notices/privacy-notices.slice.ts +++ b/clients/admin-ui/src/features/privacy-notices/privacy-notices.slice.ts @@ -17,7 +17,7 @@ export interface State { const initialState: State = { page: 1, - pageSize: 10, + pageSize: 50, }; interface PrivacyNoticesParams {