diff --git a/CHANGELOG.md b/CHANGELOG.md index 820305736c..02ff8cba67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ The types of changes are: - Update custom field definition uniqueness to be case insensitive name per resource type [#3215](https://github.com/ethyca/fides/pull/3215) - Restrict where privacy notices of certain consent mechanisms must be displayed [#3195](https://github.com/ethyca/fides/pull/3195) - Merged the `lib` submodule into the `api.ops` submodule [#3134](https://github.com/ethyca/fides/pull/3134) +- Merged duplicate privacy declaration components [#3254](https://github.com/ethyca/fides/pull/3254) ### Fixed - Prevent ability to unintentionally show "default" Privacy Center configuration, styles, etc. [#3242](https://github.com/ethyca/fides/pull/3242) diff --git a/clients/admin-ui/cypress/e2e/systems.cy.ts b/clients/admin-ui/cypress/e2e/systems.cy.ts index c7423ceaf0..60127a0eee 100644 --- a/clients/admin-ui/cypress/e2e/systems.cy.ts +++ b/clients/admin-ui/cypress/e2e/systems.cy.ts @@ -530,6 +530,12 @@ describe("System management page", () => { }); cy.visit(`${SYSTEM_ROUTE}/configure/fidesctl_system`); cy.wait("@getFidesctlSystemWithDataUses"); + cy.fixture("systems/system.json").then((system) => { + const newSystem = { ...system, fides_key: "fidesctl_system" }; + cy.intercept("PUT", "/api/v1/system*", { body: newSystem }).as( + "putFidesSystem" + ); + }); cy.getByTestId("tab-Data uses").click(); }); @@ -545,13 +551,13 @@ describe("System management page", () => { cy.getByTestId("input-data_subjects").type(`anonymous{enter}`); cy.getByTestId("delete-btn").should("be.disabled"); cy.getByTestId("save-btn").click(); - cy.wait("@putSystem"); + cy.wait("@putFidesSystem"); cy.getByTestId("delete-btn").should("be.enabled"); // now go through delete flow cy.getByTestId("delete-btn").click(); }); cy.getByTestId("continue-btn").click(); - cy.wait("@putSystem"); + cy.wait("@putFidesSystem"); cy.getByTestId("toast-success-msg").contains("Data use deleted"); }); @@ -561,7 +567,7 @@ describe("System management page", () => { cy.getByTestId("delete-btn").click(); }); cy.getByTestId("continue-btn").click(); - cy.wait("@putSystem").then((interception) => { + cy.wait("@putFidesSystem").then((interception) => { const { body } = interception.request; expect(body.privacy_declarations.length).to.eql(1); expect(body.privacy_declarations[0].data_use !== "improve.system"); diff --git a/clients/admin-ui/src/features/datamap/datamap-drawer/DatamapDrawer.tsx b/clients/admin-ui/src/features/datamap/datamap-drawer/DatamapDrawer.tsx index 70fa30aa7a..f5970af643 100644 --- a/clients/admin-ui/src/features/datamap/datamap-drawer/DatamapDrawer.tsx +++ b/clients/admin-ui/src/features/datamap/datamap-drawer/DatamapDrawer.tsx @@ -6,23 +6,13 @@ import { Slide, Spacer, Text, - useToast, } from "@fidesui/react"; -import { SerializedError } from "@reduxjs/toolkit"; -import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query/fetchBaseQuery"; import { DataFlowAccordion } from "common/system-data-flow/DataFlowAccordion"; import React, { useMemo } from "react"; -import { getErrorMessage, isErrorResult } from "~/features/common/helpers"; -import { errorToastParams, successToastParams } from "~/features/common/toast"; -import { useTaxonomyData } from "~/features/datamap/privacy-declarations/PrivacyDeclarationForm"; -import PrivacyDeclarationManager from "~/features/datamap/privacy-declarations/PrivacyDeclarationManager"; -import { - useGetSystemByFidesKeyQuery, - useUpdateSystemMutation, -} from "~/features/system/system.slice"; -import { PrivacyDeclaration } from "~/types/api/models/PrivacyDeclaration"; -import { System } from "~/types/api/models/System"; +import { usePrivacyDeclarationData } from "~/features/system/privacy-declarations/hooks"; +import PrivacyDeclarationManager from "~/features/system/privacy-declarations/PrivacyDeclarationManager"; +import { useGetSystemByFidesKeyQuery } from "~/features/system/system.slice"; import SystemInfo from "./SystemInfo"; @@ -36,58 +26,14 @@ const DatamapDrawer = ({ resetSelectedSystemId, }: DatamapDrawerProps) => { const isOpen = useMemo(() => Boolean(selectedSystemId), [selectedSystemId]); - const { isLoading, ...dataProps } = useTaxonomyData(); - const toast = useToast(); + const { isLoading, ...dataProps } = usePrivacyDeclarationData({ + includeDatasets: false, + }); - const [updateSystemMutationTrigger] = useUpdateSystemMutation(); const { data: system } = useGetSystemByFidesKeyQuery(selectedSystemId!, { skip: !selectedSystemId, }); - const handleSave = async ( - updatedDeclarations: PrivacyDeclaration[], - isDelete?: boolean - ) => { - const systemBodyWithDeclaration = { - ...system!, - privacy_declarations: updatedDeclarations, - }; - - const handleResult = ( - result: - | { data: System } - | { error: FetchBaseQueryError | SerializedError } - ) => { - if (isErrorResult(result)) { - const errorMsg = getErrorMessage( - result.error, - "An unexpected error occurred while updating the system. Please try again." - ); - - toast(errorToastParams(errorMsg)); - return false; - } - toast.closeAll(); - toast( - successToastParams(isDelete ? "Data use deleted" : "Data use saved") - ); - return true; - }; - - const updateSystemResult = await updateSystemMutationTrigger( - systemBodyWithDeclaration - ); - - return handleResult(updateSystemResult); - }; - const collisionWarning = () => { - toast( - errorToastParams( - "A declaration already exists with that data use in this system. Please supply a different data use." - ) - ); - }; - return ( - + + + Promise; - onDelete: (declaration: PrivacyDeclarationWithId) => Promise; -} - -const PrivacyDeclarationAccordionItem = ({ - privacyDeclaration, - onEdit, - onDelete, - ...dataProps -}: { privacyDeclaration: PrivacyDeclarationWithId } & Omit< - AccordionProps, - "privacyDeclarations" ->) => { - const handleEdit = (newValues: PrivacyDeclarationWithId) => - onEdit(privacyDeclaration, newValues); - - const { initialValues, renderHeader, handleSubmit } = - usePrivacyDeclarationForm({ - initialValues: privacyDeclaration, - onSubmit: handleEdit, - ...dataProps, - }); - - return ( - - {({ isExpanded }) => ( - - {({ dirty }) => ( -
- - - {renderHeader({ - dirty, - boxProps: { flex: "1", textAlign: "left" }, - hideSaved: !isExpanded, - })} - - - - - - - - - )} -
- )} -
- ); -}; - -const PrivacyDeclarationAccordion = ({ - privacyDeclarations, - ...props -}: AccordionProps) => ( - - {privacyDeclarations.map((dec) => ( - - ))} - -); - -export default PrivacyDeclarationAccordion; diff --git a/clients/admin-ui/src/features/datamap/privacy-declarations/PrivacyDeclarationForm.tsx b/clients/admin-ui/src/features/datamap/privacy-declarations/PrivacyDeclarationForm.tsx deleted file mode 100644 index dbc3955c8b..0000000000 --- a/clients/admin-ui/src/features/datamap/privacy-declarations/PrivacyDeclarationForm.tsx +++ /dev/null @@ -1,325 +0,0 @@ -/** - * Exports various parts of the privacy declaration form for flexibility - */ - -import { - Box, - BoxProps, - Button, - ButtonGroup, - ErrorWarningIcon, - GreenCheckCircleIcon, - Heading, - Stack, - Text, - useDisclosure, -} from "@fidesui/react"; -import { Form, Formik, FormikHelpers, useFormikContext } from "formik"; -import { useMemo, useState } from "react"; -import * as Yup from "yup"; - -import { useAppSelector } from "~/app/hooks"; -import ConfirmationModal from "~/features/common/ConfirmationModal"; -import { CustomSelect, CustomTextInput } from "~/features/common/form/inputs"; -import { FormGuard } from "~/features/common/hooks/useIsAnyFormDirty"; -import { - selectDataSubjects, - useGetAllDataSubjectsQuery, -} from "~/features/data-subjects/data-subject.slice"; -import { - selectDataUses, - useGetAllDataUsesQuery, -} from "~/features/data-use/data-use.slice"; -import { PrivacyDeclarationWithId } from "~/features/datamap/privacy-declarations/types"; -import { - selectDataCategories, - useGetAllDataCategoriesQuery, -} from "~/features/taxonomy"; -import { - DataCategory, - DataSubject, - DataUse, - PrivacyDeclaration, -} from "~/types/api"; - -const transformPrivacyDeclarationToHaveId = ( - privacyDeclaration: PrivacyDeclaration -) => ({ - ...privacyDeclaration, - id: privacyDeclaration.name - ? `${privacyDeclaration.data_use} - ${privacyDeclaration.name}` - : privacyDeclaration.data_use, -}); - -export const transformPrivacyDeclarationsToHaveId = ( - privacyDeclarations: PrivacyDeclaration[] -): PrivacyDeclarationWithId[] => - privacyDeclarations.map(transformPrivacyDeclarationToHaveId); - -export const ValidationSchema = Yup.object().shape({ - data_categories: Yup.array(Yup.string()) - .min(1, "Must assign at least one data category") - .label("Data categories"), - data_use: Yup.string().required().label("Data use"), - data_subjects: Yup.array(Yup.string()) - .min(1, "Must assign at least one data subject") - .label("Data subjects"), -}); - -const defaultInitialValues: PrivacyDeclaration = { - data_categories: [], - data_subjects: [], - data_use: "", - name: "", -}; - -export type PrivacyDeclarationFormValues = typeof defaultInitialValues; - -export interface DataProps { - allDataCategories: DataCategory[]; - allDataUses: DataUse[]; - allDataSubjects: DataSubject[]; - isEditing?: boolean; -} - -export const PrivacyDeclarationFormComponents = ({ - allDataUses, - allDataCategories, - allDataSubjects, - onDelete, - isEditing, -}: DataProps & Pick) => { - const { dirty, isSubmitting, isValid, initialValues } = - useFormikContext(); - const deleteModal = useDisclosure(); - - const handleDelete = async () => { - await onDelete(transformPrivacyDeclarationToHaveId(initialValues)); - deleteModal.onClose(); - }; - - const deleteDisabled = initialValues.data_use === ""; - return ( - - ({ - value: data.fides_key, - label: data.fides_key, - }))} - tooltip="What is the system using the data for. For example, is it for third party advertising or perhaps simply providing system operations." - variant="stacked" - singleValueBlock - isDisabled={isEditing} - /> - - ({ - value: data.fides_key, - label: data.fides_key, - }))} - tooltip="What type of data is your system processing? This could be various types of user or system data." - isMulti - variant="stacked" - /> - ({ - value: data.fides_key, - label: data.fides_key, - }))} - tooltip="Whose data are you processing? This could be customers, employees or any other type of user in your system." - isMulti - variant="stacked" - /> - - - - - - - - ); -}; - -/** - * Set up subscriptions to all taxonomy resources - */ -export const useTaxonomyData = () => { - // Query subscriptions: - const { isLoading: isLoadingDataCategories } = useGetAllDataCategoriesQuery(); - const { isLoading: isLoadingDataSubjects } = useGetAllDataSubjectsQuery(); - const { isLoading: isLoadingDataUses } = useGetAllDataUsesQuery(); - - const allDataCategories = useAppSelector(selectDataCategories); - const allDataSubjects = useAppSelector(selectDataSubjects); - const allDataUses = useAppSelector(selectDataUses); - - const isLoading = - isLoadingDataCategories || isLoadingDataSubjects || isLoadingDataUses; - - return { allDataCategories, allDataSubjects, allDataUses, isLoading }; -}; - -/** - * Hook to supply all data needed for the privacy declaration form - * Purposefully excludes redux queries so that this can be used across apps - */ -export const usePrivacyDeclarationForm = ({ - onSubmit, - initialValues: passedInInitialValues, - allDataUses, -}: Pick & - Pick) => { - const initialValues = passedInInitialValues ?? defaultInitialValues; - const [showSaved, setShowSaved] = useState(false); - - const title = useMemo(() => { - const thisDataUse = allDataUses.filter( - (du) => du.fides_key === initialValues.data_use - )[0]; - if (thisDataUse) { - return initialValues.name - ? `${thisDataUse.name} - ${initialValues.name}` - : thisDataUse.name; - } - return undefined; - }, [allDataUses, initialValues]); - - const handleSubmit = async ( - values: PrivacyDeclarationFormValues, - formikHelpers: FormikHelpers - ) => { - const success = await onSubmit( - transformPrivacyDeclarationToHaveId(values), - formikHelpers - ); - if (success) { - // Reset state such that isDirty will be checked again before next save - formikHelpers.resetForm({ values }); - setShowSaved(true); - } - }; - - const renderHeader = ({ - dirty, - boxProps, - /** Allow overriding showing the saved indicator */ - hideSaved, - }: { - dirty: boolean; - hideSaved?: boolean; - boxProps?: BoxProps; - }) => ( - - {title ? ( - - {title} - - ) : null} - {!hideSaved && showSaved && !dirty && initialValues.data_use ? ( - - Saved - - ) : null} - - {dirty && hideSaved ? ( - - Unsaved Changes - - ) : null} - - ); - - return { handleSubmit, renderHeader, initialValues }; -}; - -interface Props { - onSubmit: ( - values: PrivacyDeclarationWithId, - formikHelpers: FormikHelpers - ) => Promise; - initialValues?: PrivacyDeclarationWithId; - onDelete: (declaration: PrivacyDeclarationWithId) => Promise; -} - -export const PrivacyDeclarationForm = ({ - onSubmit, - initialValues: passedInInitialValues, - onDelete, - ...dataProps -}: Props & DataProps) => { - const { handleSubmit, renderHeader, initialValues } = - usePrivacyDeclarationForm({ - onSubmit, - initialValues: passedInInitialValues, - allDataUses: dataProps.allDataUses, - }); - - return ( - - {({ dirty }: { dirty: boolean }) => ( -
- - - - {renderHeader({ dirty })} - - - - - )} -
- ); -}; diff --git a/clients/admin-ui/src/features/datamap/privacy-declarations/PrivacyDeclarationManager.tsx b/clients/admin-ui/src/features/datamap/privacy-declarations/PrivacyDeclarationManager.tsx deleted file mode 100644 index d614b5ce56..0000000000 --- a/clients/admin-ui/src/features/datamap/privacy-declarations/PrivacyDeclarationManager.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import { Box, Button, Stack, Tooltip, useToast } from "@fidesui/react"; -import { useEffect, useMemo, useState } from "react"; - -import { PrivacyDeclarationWithId } from "~/features/datamap/privacy-declarations/types"; -import { PrivacyDeclaration, System } from "~/types/api"; - -import PrivacyDeclarationAccordion from "./PrivacyDeclarationAccordion"; -import { - DataProps, - PrivacyDeclarationForm, - transformPrivacyDeclarationsToHaveId, -} from "./PrivacyDeclarationForm"; - -const transformDeclarationForSubmission = ( - formValues: PrivacyDeclarationWithId -): PrivacyDeclaration => { - // Remove the id which is only a frontend artifact - const { id, ...values } = formValues; - return { - ...values, - // Fill in an empty string for name because of https://github.com/ethyca/fideslang/issues/98 - name: values.name ?? "", - }; -}; - -interface Props { - system: System; - onCollision: () => void; - onSave: ( - privacyDeclarations: PrivacyDeclaration[], - isDelete?: boolean - ) => Promise; -} - -const PrivacyDeclarationManager = ({ - system, - onCollision, - onSave, - ...dataProps -}: Props & DataProps) => { - const toast = useToast(); - - const [showNewForm, setShowNewForm] = useState(false); - const [newDeclaration, setNewDeclaration] = useState< - PrivacyDeclarationWithId | undefined - >(undefined); - - const accordionDeclarations = useMemo(() => { - const declarations = transformPrivacyDeclarationsToHaveId( - system.privacy_declarations - ); - if (!newDeclaration) { - return declarations; - } - return declarations.filter((pd) => pd.id !== newDeclaration.id); - }, [newDeclaration, system]); - - const checkAlreadyExists = (values: PrivacyDeclaration) => { - if ( - accordionDeclarations.filter( - (d) => d.data_use === values.data_use && d.name === values.name - ).length > 0 - ) { - onCollision(); - return true; - } - return false; - }; - - const handleSave = async ( - updatedDeclarations: PrivacyDeclarationWithId[], - isDelete?: boolean - ) => { - const transformedDeclarations = updatedDeclarations.map((d) => - transformDeclarationForSubmission(d) - ); - const success = await onSave(transformedDeclarations, isDelete); - return success; - }; - - const handleEditDeclaration = async ( - oldDeclaration: PrivacyDeclarationWithId, - updatedDeclaration: PrivacyDeclarationWithId - ) => { - // Do not allow editing a privacy declaration to have the same data use as one that already exists - if ( - updatedDeclaration.id !== oldDeclaration.id && - checkAlreadyExists(updatedDeclaration) - ) { - return false; - } - // Because the data use can change, we also need a reference to the old declaration in order to - // make sure we are replacing the proper one - const updatedDeclarations = accordionDeclarations.map((dec) => - dec.id === oldDeclaration.id ? updatedDeclaration : dec - ); - return handleSave(updatedDeclarations); - }; - - const saveNewDeclaration = async (values: PrivacyDeclarationWithId) => { - if (checkAlreadyExists(values)) { - return false; - } - - toast.closeAll(); - setNewDeclaration(values); - const updatedDeclarations = [...accordionDeclarations, values]; - return handleSave(updatedDeclarations); - }; - - const handleShowNewForm = () => { - setShowNewForm(true); - setNewDeclaration(undefined); - }; - - const handleDelete = async ( - declarationToDelete: PrivacyDeclarationWithId - ) => { - const updatedDeclarations = transformPrivacyDeclarationsToHaveId( - system.privacy_declarations - ).filter((dec) => dec.id !== declarationToDelete.id); - return handleSave(updatedDeclarations, true); - }; - - const handleDeleteNew = async ( - declarationToDelete: PrivacyDeclarationWithId - ) => { - const success = await handleDelete(declarationToDelete); - if (success) { - setShowNewForm(false); - setNewDeclaration(undefined); - } - return success; - }; - - useEffect(() => { - setShowNewForm(false); - }, [system.fides_key]); - - const showAddDataUseButton = - system.privacy_declarations.length > 0 || - (system.privacy_declarations.length === 0 && !showNewForm); - - return ( - - - {showNewForm ? ( - - - - ) : null} - {showAddDataUseButton ? ( - - - - - - ) : null} - - ); -}; - -export default PrivacyDeclarationManager; diff --git a/clients/admin-ui/src/features/datamap/privacy-declarations/types.ts b/clients/admin-ui/src/features/datamap/privacy-declarations/types.ts deleted file mode 100644 index b16500a316..0000000000 --- a/clients/admin-ui/src/features/datamap/privacy-declarations/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { PrivacyDeclaration } from "~/types/api"; - -/** - * This is because privacy declarations do not have an ID on the backend. - * It is very useful for React rendering to have a stable ID. We currently - * make this the composite of data_use - name, but even better may be to - * give it a UUID (or to have the backend actually enforce this!) - */ -export interface PrivacyDeclarationWithId extends PrivacyDeclaration { - id: string; -} diff --git a/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationAccordion.tsx b/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationAccordion.tsx index 626125a426..a08df39a7d 100644 --- a/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationAccordion.tsx +++ b/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationAccordion.tsx @@ -8,6 +8,8 @@ import { } from "@fidesui/react"; import { Form, Formik } from "formik"; +import { FormGuard } from "~/features/common/hooks/useIsAnyFormDirty"; + import { DataProps, PrivacyDeclarationFormComponents, @@ -25,12 +27,14 @@ interface AccordionProps extends DataProps { onDelete: ( declaration: PrivacyDeclarationWithId ) => Promise; + includeCustomFields?: boolean; } const PrivacyDeclarationAccordionItem = ({ privacyDeclaration, onEdit, onDelete, + includeCustomFields, ...dataProps }: { privacyDeclaration: PrivacyDeclarationWithId } & Omit< AccordionProps, @@ -58,6 +62,10 @@ const PrivacyDeclarationAccordionItem = ({ > {({ dirty }) => (
+ diff --git a/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationForm.tsx b/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationForm.tsx index c48781c3d9..e19c757382 100644 --- a/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationForm.tsx +++ b/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationForm.tsx @@ -24,6 +24,7 @@ import * as Yup from "yup"; import ConfirmationModal from "~/features/common/ConfirmationModal"; import { CustomSelect, CustomTextInput } from "~/features/common/form/inputs"; +import { FormGuard } from "~/features/common/hooks/useIsAnyFormDirty"; import { DataCategory, Dataset, @@ -94,7 +95,12 @@ export const PrivacyDeclarationFormComponents = ({ allDatasets, onDelete, privacyDeclarationId, -}: DataProps & Pick & { privacyDeclarationId?: string }) => { + includeCustomFields, +}: DataProps & + Pick & { + privacyDeclarationId?: string; + includeCustomFields?: boolean; + }) => { const { dirty, isSubmitting, isValid, initialValues } = useFormikContext(); const deleteModal = useDisclosure(); @@ -168,10 +174,12 @@ export const PrivacyDeclarationFormComponents = ({ variant="stacked" /> ) : null} - + {includeCustomFields ? ( + + ) : null} diff --git a/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationStep.tsx b/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationStep.tsx index 05d09cd35d..dd7d947f01 100644 --- a/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationStep.tsx +++ b/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationStep.tsx @@ -1,14 +1,9 @@ -import { Heading, Spinner, Stack, Text, useToast } from "@fidesui/react"; -import { SerializedError } from "@reduxjs/toolkit"; -import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query/fetchBaseQuery"; +import { Heading, Spinner, Stack, Text } from "@fidesui/react"; import NextLink from "next/link"; import { useAppDispatch } from "~/app/hooks"; -import { getErrorMessage, isErrorResult } from "~/features/common/helpers"; -import { errorToastParams, successToastParams } from "~/features/common/toast"; -import { setActiveSystem, useUpdateSystemMutation } from "~/features/system"; -import { PrivacyDeclarationWithId } from "~/features/system/privacy-declarations/types"; -import { PrivacyDeclaration, System } from "~/types/api"; +import { setActiveSystem } from "~/features/system"; +import { System } from "~/types/api"; import { usePrivacyDeclarationData } from "./hooks"; import PrivacyDeclarationManager from "./PrivacyDeclarationManager"; @@ -18,55 +13,14 @@ interface Props { } const PrivacyDeclarationStep = ({ system }: Props) => { - const toast = useToast(); const dispatch = useAppDispatch(); - const [updateSystemMutationTrigger] = useUpdateSystemMutation(); - const { isLoading, ...dataProps } = usePrivacyDeclarationData(); - const handleSave = async ( - updatedDeclarations: PrivacyDeclaration[], - isDelete?: boolean - ): Promise => { - const systemBodyWithDeclaration = { - ...system, - privacy_declarations: updatedDeclarations, - }; + const { isLoading, ...dataProps } = usePrivacyDeclarationData({ + includeDatasets: true, + }); - const handleResult = ( - result: - | { data: System } - | { error: FetchBaseQueryError | SerializedError } - ) => { - if (isErrorResult(result)) { - const errorMsg = getErrorMessage( - result.error, - "An unexpected error occurred while updating the system. Please try again." - ); - - toast(errorToastParams(errorMsg)); - return undefined; - } - toast.closeAll(); - toast( - successToastParams(isDelete ? "Data use deleted" : "Data use saved") - ); - dispatch(setActiveSystem(result.data)); - return result.data.privacy_declarations as PrivacyDeclarationWithId[]; - }; - - const updateSystemResult = await updateSystemMutationTrigger( - systemBodyWithDeclaration - ); - - return handleResult(updateSystemResult); - }; - - const collisionWarning = () => { - toast( - errorToastParams( - "A declaration already exists with that data use in this system. Please supply a different data use." - ) - ); + const onSave = (savedSystem: System) => { + dispatch(setActiveSystem(savedSystem)); }; return ( @@ -92,8 +46,8 @@ const PrivacyDeclarationStep = ({ system }: Props) => { ) : ( )} diff --git a/clients/admin-ui/src/features/system/privacy-declarations/hooks.ts b/clients/admin-ui/src/features/system/privacy-declarations/hooks.ts index a2954f5eea..aad7c99d3b 100644 --- a/clients/admin-ui/src/features/system/privacy-declarations/hooks.ts +++ b/clients/admin-ui/src/features/system/privacy-declarations/hooks.ts @@ -14,14 +14,22 @@ import { } from "~/features/taxonomy"; /** - * Set up subscriptions to all taxonomy resources + * Set up subscriptions to all taxonomy resources. + * + * Conditionally subscribe to datasets, as not all privacy declarations need this field. */ -export const usePrivacyDeclarationData = () => { +export const usePrivacyDeclarationData = ({ + includeDatasets, +}: { + includeDatasets?: boolean; +}) => { // Query subscriptions: const { isLoading: isLoadingDataCategories } = useGetAllDataCategoriesQuery(); const { isLoading: isLoadingDataSubjects } = useGetAllDataSubjectsQuery(); const { isLoading: isLoadingDataUses } = useGetAllDataUsesQuery(); - const { isLoading: isLoadingDatasets } = useGetAllDatasetsQuery(); + const { isLoading: isLoadingDatasets } = useGetAllDatasetsQuery(undefined, { + skip: !includeDatasets, + }); const allDataCategories = useAppSelector(selectDataCategories); const allDataSubjects = useAppSelector(selectDataSubjects); @@ -38,7 +46,7 @@ export const usePrivacyDeclarationData = () => { allDataCategories, allDataSubjects, allDataUses, - allDatasets, + allDatasets: includeDatasets ? allDatasets : undefined, isLoading, }; };