From dba96f6902e0046fbfdb6e6299ff1f54d7b2a3a2 Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Thu, 27 Apr 2023 15:28:21 -0400 Subject: [PATCH 01/12] Update types --- clients/admin-ui/src/types/api/index.ts | 6 + .../src/types/api/models/ComponentType.ts | 11 ++ .../src/types/api/models/DeliveryMechanism.ts | 11 ++ .../models/Page_PrivacyExperienceResponse_.ts | 12 ++ .../api/models/PrivacyDeclarationResponse.ts | 45 +++++++ .../api/models/PrivacyExperienceResponse.ts | 33 ++++++ .../src/types/api/models/ScopeRegistryEnum.ts | 3 + .../src/types/api/models/SystemResponse.ts | 112 ++++++++++++++++++ 8 files changed, 233 insertions(+) create mode 100644 clients/admin-ui/src/types/api/models/ComponentType.ts create mode 100644 clients/admin-ui/src/types/api/models/DeliveryMechanism.ts create mode 100644 clients/admin-ui/src/types/api/models/Page_PrivacyExperienceResponse_.ts create mode 100644 clients/admin-ui/src/types/api/models/PrivacyDeclarationResponse.ts create mode 100644 clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts create mode 100644 clients/admin-ui/src/types/api/models/SystemResponse.ts diff --git a/clients/admin-ui/src/types/api/index.ts b/clients/admin-ui/src/types/api/index.ts index c6ba3b3fac..00bda557a7 100644 --- a/clients/admin-ui/src/types/api/index.ts +++ b/clients/admin-ui/src/types/api/index.ts @@ -53,6 +53,7 @@ export { ClusterHealth } from "./models/ClusterHealth"; export type { CollectionAddressResponse } from "./models/CollectionAddressResponse"; export type { CollectionMeta } from "./models/CollectionMeta"; export { ColumnSort } from "./models/ColumnSort"; +export { ComponentType } from "./models/ComponentType"; export type { ConnectionConfigurationResponse } from "./models/ConnectionConfigurationResponse"; export type { ConnectionSystemTypeMap } from "./models/ConnectionSystemTypeMap"; export { ConnectionTestStatus } from "./models/ConnectionTestStatus"; @@ -98,6 +99,7 @@ export { DataSubjectRightsEnum } from "./models/DataSubjectRightsEnum"; export type { DataUpload } from "./models/DataUpload"; export type { DataUse } from "./models/DataUse"; export { DBActions } from "./models/DBActions"; +export { DeliveryMechanism } from "./models/DeliveryMechanism"; export type { DenyPrivacyRequests } from "./models/DenyPrivacyRequests"; export { DrpAction } from "./models/DrpAction"; export type { DrpDataRightsResponse } from "./models/DrpDataRightsResponse"; @@ -182,6 +184,7 @@ export type { Page_ExecutionLogDetailResponse_ } from "./models/Page_ExecutionLo export type { Page_MessagingConfigResponse_ } from "./models/Page_MessagingConfigResponse_"; export type { Page_PolicyResponse_ } from "./models/Page_PolicyResponse_"; export type { Page_PolicyWebhookResponse_ } from "./models/Page_PolicyWebhookResponse_"; +export type { Page_PrivacyExperienceResponse_ } from "./models/Page_PrivacyExperienceResponse_"; export type { Page_PrivacyNoticeResponse_ } from "./models/Page_PrivacyNoticeResponse_"; export type { Page_RuleResponseWithTargets_ } from "./models/Page_RuleResponseWithTargets_"; export type { Page_RuleTarget_ } from "./models/Page_RuleTarget_"; @@ -200,6 +203,8 @@ export type { PolicyWebhookUpdate } from "./models/PolicyWebhookUpdate"; export type { PolicyWebhookUpdateResponse } from "./models/PolicyWebhookUpdateResponse"; export type { PostgreSQLDocsSchema } from "./models/PostgreSQLDocsSchema"; export type { PrivacyDeclaration } from "./models/PrivacyDeclaration"; +export type { PrivacyDeclarationResponse } from "./models/PrivacyDeclarationResponse"; +export type { PrivacyExperienceResponse } from "./models/PrivacyExperienceResponse"; export type { PrivacyNoticeCreation } from "./models/PrivacyNoticeCreation"; export type { PrivacyNoticeHistorySchema } from "./models/PrivacyNoticeHistorySchema"; export { PrivacyNoticeRegion } from "./models/PrivacyNoticeRegion"; @@ -262,6 +267,7 @@ export { StorageTypeApiAccepted } from "./models/StorageTypeApiAccepted"; export type { Strategy } from "./models/Strategy"; export type { System } from "./models/System"; export type { SystemMetadata } from "./models/SystemMetadata"; +export type { SystemResponse } from "./models/SystemResponse"; export type { SystemScanHistory } from "./models/SystemScanHistory"; export type { SystemScannerStatus } from "./models/SystemScannerStatus"; export type { SystemScanResponse } from "./models/SystemScanResponse"; diff --git a/clients/admin-ui/src/types/api/models/ComponentType.ts b/clients/admin-ui/src/types/api/models/ComponentType.ts new file mode 100644 index 0000000000..9a6a1b4c87 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/ComponentType.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * The component type - not formalized in the db + */ +export enum ComponentType { + OVERLAY = "overlay", + PRIVACY_CENTER = "privacy_center", +} diff --git a/clients/admin-ui/src/types/api/models/DeliveryMechanism.ts b/clients/admin-ui/src/types/api/models/DeliveryMechanism.ts new file mode 100644 index 0000000000..99b24cab60 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/DeliveryMechanism.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * The delivery mechanism - not formalized in the db + */ +export enum DeliveryMechanism { + BANNER = "banner", + LINK = "link", +} diff --git a/clients/admin-ui/src/types/api/models/Page_PrivacyExperienceResponse_.ts b/clients/admin-ui/src/types/api/models/Page_PrivacyExperienceResponse_.ts new file mode 100644 index 0000000000..42fdad2450 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/Page_PrivacyExperienceResponse_.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { PrivacyExperienceResponse } from "./PrivacyExperienceResponse"; + +export type Page_PrivacyExperienceResponse_ = { + items: Array; + total: number; + page: number; + size: number; +}; diff --git a/clients/admin-ui/src/types/api/models/PrivacyDeclarationResponse.ts b/clients/admin-ui/src/types/api/models/PrivacyDeclarationResponse.ts new file mode 100644 index 0000000000..dbf5327824 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/PrivacyDeclarationResponse.ts @@ -0,0 +1,45 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Extension of base pydantic model to include DB `id` field in the response + */ +export type PrivacyDeclarationResponse = { + /** + * The name of the privacy declaration on the system. + */ + name?: string; + /** + * An array of data categories describing a system in a privacy declaration. + */ + data_categories: Array; + /** + * The Data Use describing a system in a privacy declaration. + */ + data_use: string; + /** + * The fides key of the data qualifier describing a system in a privacy declaration. + */ + data_qualifier?: string; + /** + * An array of data subjects describing a system in a privacy declaration. + */ + data_subjects: Array; + /** + * Referenced Dataset fides keys used by the system. + */ + dataset_references?: Array; + /** + * The resources to which data is sent. Any `fides_key`s included in this list reference `DataFlow` entries in the `egress` array of any `System` resources to which this `PrivacyDeclaration` is applied. + */ + egress?: Array; + /** + * The resources from which data is received. Any `fides_key`s included in this list reference `DataFlow` entries in the `ingress` array of any `System` resources to which this `PrivacyDeclaration` is applied. + */ + ingress?: Array; + /** + * The database-assigned ID of the privacy declaration on the system. This is meant to be a read-only field, returned only in API responses + */ + id: string; +}; diff --git a/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts b/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts new file mode 100644 index 0000000000..bea7841f57 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts @@ -0,0 +1,33 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ComponentType } from "./ComponentType"; +import type { DeliveryMechanism } from "./DeliveryMechanism"; +import type { PrivacyNoticeRegion } from "./PrivacyNoticeRegion"; +import type { PrivacyNoticeResponse } from "./PrivacyNoticeResponse"; + +/** + * An API representation of a PrivacyExperience used for response payloads + */ +export type PrivacyExperienceResponse = { + 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; + created_at: string; + updated_at: string; + version: number; + privacy_experience_history_id: string; + privacy_experience_template_id?: string; + privacy_notices?: Array; +}; diff --git a/clients/admin-ui/src/types/api/models/ScopeRegistryEnum.ts b/clients/admin-ui/src/types/api/models/ScopeRegistryEnum.ts index 1696b1c325..8d0ec00c1d 100644 --- a/clients/admin-ui/src/types/api/models/ScopeRegistryEnum.ts +++ b/clients/admin-ui/src/types/api/models/ScopeRegistryEnum.ts @@ -88,6 +88,9 @@ export enum ScopeRegistryEnum { POLICY_CREATE_OR_UPDATE = "policy:create_or_update", POLICY_DELETE = "policy:delete", POLICY_READ = "policy:read", + PRIVACY_EXPERIENCE_CREATE = "privacy-experience:create", + PRIVACY_EXPERIENCE_READ = "privacy-experience:read", + PRIVACY_EXPERIENCE_UPDATE = "privacy-experience:update", PRIVACY_NOTICE_CREATE = "privacy-notice:create", PRIVACY_NOTICE_READ = "privacy-notice:read", PRIVACY_NOTICE_UPDATE = "privacy-notice:update", diff --git a/clients/admin-ui/src/types/api/models/SystemResponse.ts b/clients/admin-ui/src/types/api/models/SystemResponse.ts new file mode 100644 index 0000000000..fcb538e896 --- /dev/null +++ b/clients/admin-ui/src/types/api/models/SystemResponse.ts @@ -0,0 +1,112 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { ContactDetails } from "./ContactDetails"; +import type { DataFlow } from "./DataFlow"; +import type { DataProtectionImpactAssessment } from "./DataProtectionImpactAssessment"; +import type { DataResponsibilityTitle } from "./DataResponsibilityTitle"; +import type { PrivacyDeclarationResponse } from "./PrivacyDeclarationResponse"; +import type { SystemMetadata } from "./SystemMetadata"; + +/** + * Extension of base pydantic model to include `privacy_declarations.id` fields in responses + */ +export type SystemResponse = { + /** + * A unique key used to identify this resource. + */ + fides_key: string; + /** + * Defines the Organization that this resource belongs to. + */ + organization_fides_key?: string; + tags?: Array; + /** + * Human-Readable name for this resource. + */ + name?: string; + /** + * A detailed description of what this resource is. + */ + description?: string; + /** + * The id of the system registry, if used. + */ + registry_id?: number; + /** + * An optional property to store any extra information for a system. Not used by fidesctl. + */ + meta?: Record; + /** + * + * The SystemMetadata resource model. + * + * Object used to hold application specific metadata for a system + * + */ + fidesctl_meta?: SystemMetadata; + /** + * A required value to describe the type of system being modeled, examples include: Service, Application, Third Party, etc. + */ + system_type: string; + /** + * + * The model defining the responsibility or role over + * the system that processes personal data. + * + * Used to identify whether the organization is a + * Controller, Processor, or Sub-Processor of the data + * + */ + data_responsibility_title?: DataResponsibilityTitle; + /** + * The resources to which the System sends data. + */ + egress?: Array; + /** + * The resources from which the System receives data. + */ + ingress?: Array; + /** + * Extension of base pydantic model to include DB `id` field in the response + */ + privacy_declarations: Array; + /** + * A list of fides keys to model dependencies. + */ + system_dependencies?: Array; + /** + * + * The contact details information model. + * + * Used to capture contact information for controllers, used + * as part of exporting a data map / ROPA. + * + * This model is nested under an Organization and + * potentially under a system/dataset. + * + */ + joint_controller?: ContactDetails; + /** + * An optional array to identify any third countries where data is transited to. For consistency purposes, these fields are required to follow the Alpha-3 code set in ISO 3166-1. + */ + third_country_transfers?: Array; + /** + * An optional value to identify the owning department or group of the system within your organization + */ + administrating_department?: string; + /** + * + * The DataProtectionImpactAssessment (DPIA) resource model. + * + * Contains information in regard to the data protection + * impact assessment exported on a data map or Record of + * Processing Activities (RoPA). + * + * A legal requirement under GDPR for any project that + * introduces a high risk to personal information. + * + */ + data_protection_impact_assessment?: DataProtectionImpactAssessment; +}; From dda6873560c1f79a205e4f8e3b787951330f4f86 Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Mon, 1 May 2023 15:06:58 -0400 Subject: [PATCH 02/12] Nearly finish forms --- .../features/common/custom-fields/hooks.ts | 28 +++++- .../PrivacyDeclarationAccordion.tsx | 8 +- .../PrivacyDeclarationForm.tsx | 85 ++++++++++++++++--- .../PrivacyDeclarationManager.tsx | 21 ++++- .../PrivacyDeclarationStep.tsx | 8 +- 5 files changed, 126 insertions(+), 24 deletions(-) diff --git a/clients/admin-ui/src/features/common/custom-fields/hooks.ts b/clients/admin-ui/src/features/common/custom-fields/hooks.ts index 2a5495ad0b..c1272e35e9 100644 --- a/clients/admin-ui/src/features/common/custom-fields/hooks.ts +++ b/clients/admin-ui/src/features/common/custom-fields/hooks.ts @@ -47,6 +47,8 @@ export const useCustomFields = ({ skip: queryFidesKey !== "" && !(isEnabled && queryFidesKey), }); + // console.log("response from getting data", data, resourceFidesKey) + // The `fixedCacheKey` options will ensure that components referencing the same resource will // share mutation info. That won't be too useful, though, because `upsertCustomField` can issue // multiple requests: one for each field associated with the resource. @@ -130,29 +132,49 @@ export const useCustomFields = ({ */ const upsertCustomFields = useCallback( async (formValues: CustomFieldsFormValues) => { + console.log(1, "isEnabled", isEnabled); if (!isEnabled) { return; } // When creating an resource, the fides key may have initially been blank. But by the time the // form is submitted it must not be blank (not undefined, not an empty string). - const fidesKey = formValues.fides_key || resourceFidesKey; + console.log( + "formValue Fides key: ", + formValues.fides_key, + " resourceFidesKey: ", + resourceFidesKey + ); + console.log( + "fides_key" in formValues, + formValues.fides_key !== "", + resourceFidesKey + ); + const fidesKey = + "fides_key" in formValues && formValues.fides_key !== "" + ? formValues.fides_key + : resourceFidesKey; + console.log("fidesKey", fidesKey); if (!fidesKey) { return; } + console.log(2); const { customFieldValues: customFieldValuesFromForm } = formValues; // This will be undefined if the form never rendered a `CustomFieldList` that would assign // form values. + if (!customFieldValuesFromForm) { return; } + console.log("about to save custom fields"); + try { // This would be a lot simpler (and more efficient) if the API had an endpoint for updating // all the metadata associated with a field, including deleting options that weren't passed. - await Promise.allSettled( + const res = await Promise.allSettled( sortedCustomFieldDefinitionIds.map((definitionId) => { const customField = definitionIdToCustomField.get(definitionId); const value = customFieldValuesFromForm[definitionId]; @@ -181,9 +203,11 @@ export const useCustomFields = ({ }) ); + console.log("res", res); successAlert( `Custom field(s) successfully saved and added to this ${resourceType} form.` ); + return res; } catch (e) { errorAlert( `One or more custom fields have failed to save, please try again.` 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 079667ea45..4d8249dc19 100644 --- a/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationAccordion.tsx +++ b/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationAccordion.tsx @@ -6,13 +6,14 @@ import { AccordionPanel, Stack, } from "@fidesui/react"; -import { Form, Formik } from "formik"; +import { Form, Formik, FormikHelpers } from "formik"; import { DataProps, PrivacyDeclarationFormComponents, usePrivacyDeclarationForm, ValidationSchema, + FormValues, } from "./PrivacyDeclarationForm"; import { PrivacyDeclarationWithId } from "./types"; @@ -34,13 +35,13 @@ const PrivacyDeclarationAccordionItem = ({ AccordionProps, "privacyDeclarations" >) => { - const handleEdit = (newValues: PrivacyDeclarationWithId) => - onEdit(privacyDeclaration, newValues); + const handleEdit = (values: FormValues) => onEdit(privacyDeclaration, values); const { initialValues, renderHeader, handleSubmit } = usePrivacyDeclarationForm({ initialValues: privacyDeclaration, onSubmit: handleEdit, + privacyDeclarationId: privacyDeclaration.id, ...dataProps, }); @@ -71,6 +72,7 @@ const PrivacyDeclarationAccordionItem = ({ 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 ade499eb3e..a7980f95a3 100644 --- a/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationForm.tsx +++ b/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationForm.tsx @@ -13,6 +13,12 @@ import { Text, useDisclosure, } from "@fidesui/react"; +import { + CustomFieldsList, + CustomFieldValues, + useCustomFields, + CustomFieldsFormValues, +} from "common/custom-fields"; import { Form, Formik, FormikHelpers, useFormikContext } from "formik"; import { useMemo, useState } from "react"; import * as Yup from "yup"; @@ -25,6 +31,7 @@ import { DataSubject, DataUse, PrivacyDeclaration, + ResourceTypes, } from "~/types/api"; import { PrivacyDeclarationWithId } from "./types"; @@ -39,21 +46,27 @@ export const ValidationSchema = Yup.object().shape({ .label("Data subjects"), }); -const defaultInitialValues: PrivacyDeclaration = { +// type FormValues = typeof defaultInitialValues; +export type FormValues = PrivacyDeclarationWithId & + CustomFieldsFormValues & { + customFieldValues: CustomFieldValues; + }; +const defaultInitialValues: FormValues = { data_categories: [], data_subjects: [], data_use: "", dataset_references: [], + customFieldValues: {}, + id: "", + fides_key: "", }; -type FormValues = typeof defaultInitialValues; - const transformPrivacyDeclarationToHaveId = ( - privacyDeclaration: PrivacyDeclaration + privacyDeclaration: PrivacyDeclarationWithId ) => ({ ...privacyDeclaration, - id: privacyDeclaration.name - ? `${privacyDeclaration.data_use} - ${privacyDeclaration.name}` + id: privacyDeclaration.id + ? privacyDeclaration.id : privacyDeclaration.data_use, }); @@ -75,7 +88,8 @@ export const PrivacyDeclarationFormComponents = ({ allDataSubjects, allDatasets, onDelete, -}: DataProps & Pick) => { + privacyDeclarationId, +}: DataProps & Pick & { privacyDeclarationId?: string }) => { const { dirty, isSubmitting, isValid, initialValues } = useFormikContext(); const deleteModal = useDisclosure(); @@ -107,6 +121,7 @@ export const PrivacyDeclarationFormComponents = ({ 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={!!privacyDeclarationId} /> ) : null} +