From c6b88200156d329167686582dc276224f6c67772 Mon Sep 17 00:00:00 2001 From: SteveDMurphy Date: Wed, 1 Mar 2023 09:17:11 +0000 Subject: [PATCH 01/16] allow template for sendgrid if exists --- .../messaging/message_dispatch_service.py | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/fides/api/ops/service/messaging/message_dispatch_service.py b/src/fides/api/ops/service/messaging/message_dispatch_service.py index e6f7673f77..afd65c1fc9 100644 --- a/src/fides/api/ops/service/messaging/message_dispatch_service.py +++ b/src/fides/api/ops/service/messaging/message_dispatch_service.py @@ -6,7 +6,7 @@ import requests import sendgrid from loguru import logger -from sendgrid.helpers.mail import Content, Email, Mail, To +from sendgrid.helpers.mail import Content, Email, Mail, Personalization, TemplateId, To from sqlalchemy.orm import Session from twilio.base.exceptions import TwilioRestException from twilio.rest import Client @@ -45,6 +45,7 @@ from fides.core.config.config_proxy import ConfigProxy EMAIL_JOIN_STRING = ", " +EMAIL_TEMPLATE_NAME = "fides" def check_and_dispatch_error_notifications(db: Session) -> None: @@ -378,7 +379,7 @@ def _mailgun_dispatcher( try: # Check if a fides template exists template_test = requests.get( - f"{base_url}/{messaging_config.details[MessagingServiceDetails.API_VERSION.value]}/{domain}/templates/fides", + f"{base_url}/{messaging_config.details[MessagingServiceDetails.API_VERSION.value]}/{domain}/templates/{EMAIL_TEMPLATE_NAME}", auth=( "api", messaging_config.secrets[MessagingServiceSecrets.MAILGUN_API_KEY.value], @@ -392,7 +393,7 @@ def _mailgun_dispatcher( } if template_test.status_code == 200: - data["template"] = "fides" + data["template"] = EMAIL_TEMPLATE_NAME data["h:X-Mailgun-Variables"] = json.dumps( {"fides_email_body": message.body} ) @@ -456,18 +457,27 @@ def _twilio_email_dispatcher( ) try: + sg = sendgrid.SendGridAPIClient( api_key=messaging_config.secrets[ MessagingServiceSecrets.TWILIO_API_KEY.value ] ) + template_test = _get_template_id_if_exists(sg, EMAIL_TEMPLATE_NAME) + from_email = Email( messaging_config.details[MessagingServiceDetails.TWILIO_EMAIL_FROM.value] ) to_email = To(to.strip()) subject = message.subject - content = Content("text/html", message.body) - mail = Mail(from_email, to_email, subject, content) + if template_test: + personalization = Personalization() + personalization.dynamic_template_data = {"fides_email_body": message.body} + template_id = TemplateId(template_test) + mail = Mail(from_email, to_email, personalization, template_id) + else: + content = Content("text/html", message.body) + mail = Mail(from_email, to_email, subject, content) response = sg.client.mail.send.post(request_body=mail.get()) if response.status_code >= 400: logger.error( @@ -526,3 +536,22 @@ def _twilio_sms_dispatcher( except TwilioRestException as e: logger.error("Twilio SMS failed to send: {}", Pii(str(e))) raise MessageDispatchException(f"Twilio SMS failed to send due to: {Pii(e)}") + + +def _get_template_id_if_exists( + sg: sendgrid.SendGridAPIClient, template_name: str +) -> Optional[str]: + """ + Checks to see if a SendGrid template exists for Fides, returning the id if so + """ + + params = {"generations": "legacy,dynamic", "page_size": 18} + + response = sg.client.templates.get(query_params=params) + + response_body_dict = json.loads(response.body) + + for template in response_body_dict["result"]: + if template["name"].lower() == template_name.lower(): + return template["id"] + return None From ab4ddc7b39b01a364b0b61f659227e62381f7ab3 Mon Sep 17 00:00:00 2001 From: SteveDMurphy Date: Wed, 1 Mar 2023 14:58:59 +0000 Subject: [PATCH 02/16] reconfigure sending from a template, dynamic only --- .../api/ops/service/messaging/message_dispatch_service.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/fides/api/ops/service/messaging/message_dispatch_service.py b/src/fides/api/ops/service/messaging/message_dispatch_service.py index afd65c1fc9..ef143edcb9 100644 --- a/src/fides/api/ops/service/messaging/message_dispatch_service.py +++ b/src/fides/api/ops/service/messaging/message_dispatch_service.py @@ -471,10 +471,12 @@ def _twilio_email_dispatcher( to_email = To(to.strip()) subject = message.subject if template_test: + mail = Mail(from_email=from_email, subject=subject) + mail.template_id = TemplateId(template_test) personalization = Personalization() personalization.dynamic_template_data = {"fides_email_body": message.body} - template_id = TemplateId(template_test) - mail = Mail(from_email, to_email, personalization, template_id) + personalization.add_email(to_email) + mail.add_personalization(personalization) else: content = Content("text/html", message.body) mail = Mail(from_email, to_email, subject, content) @@ -545,7 +547,7 @@ def _get_template_id_if_exists( Checks to see if a SendGrid template exists for Fides, returning the id if so """ - params = {"generations": "legacy,dynamic", "page_size": 18} + params = {"generations": "dynamic", "page_size": 18} response = sg.client.templates.get(query_params=params) From c6df2577bd74414e894acff6bc1514b2c587d624 Mon Sep 17 00:00:00 2001 From: SteveDMurphy Date: Wed, 1 Mar 2023 18:14:37 +0000 Subject: [PATCH 03/16] use max page limit due to broken client pagination --- .../api/ops/service/messaging/message_dispatch_service.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/fides/api/ops/service/messaging/message_dispatch_service.py b/src/fides/api/ops/service/messaging/message_dispatch_service.py index ef143edcb9..97fb676578 100644 --- a/src/fides/api/ops/service/messaging/message_dispatch_service.py +++ b/src/fides/api/ops/service/messaging/message_dispatch_service.py @@ -547,10 +547,11 @@ def _get_template_id_if_exists( Checks to see if a SendGrid template exists for Fides, returning the id if so """ - params = {"generations": "dynamic", "page_size": 18} - + # the pagination via the client actually doesn't work + # in lieu of over-engineering this we can manually call + # the next page if/when we hit the limit here + params = {"generations": "dynamic", "page_size": 200} response = sg.client.templates.get(query_params=params) - response_body_dict = json.loads(response.body) for template in response_body_dict["result"]: From 5368c054adddc97cac07411a64a8a7ca93761271 Mon Sep 17 00:00:00 2001 From: Sebastian Sangervasi <2236777+ssangervasi@users.noreply.github.com> Date: Wed, 1 Mar 2023 11:19:16 -0800 Subject: [PATCH 04/16] [2460]: Change "manual webhook" to "manual process" in FE (#2717) --- CHANGELOG.md | 1 + .../cypress/fixtures/connectors/connection_types.json | 2 +- clients/admin-ui/src/constants.ts | 6 +++--- .../manual-processing/ManualProcessingList.tsx | 2 +- src/fides/api/ops/models/connectionconfig.py | 2 +- .../api/v1/endpoints/test_connection_template_endpoints.py | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d3916ed0b..26c8ad03d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ The types of changes are: * Add flow for selecting system types when manually creating a system [#2530](https://github.com/ethyca/fides/pull/2530) * Updated forms for privacy declarations [#2648](https://github.com/ethyca/fides/pull/2648) * Delete flow for privacy declarations [#2664](https://github.com/ethyca/fides/pull/2664) + * "Manual Webhook" has been renamed to "Manual Process". [#2717](https://github.com/ethyca/fides/pull/2717) * Convert all config values to Pydantic `Field` objects [#2613](https://github.com/ethyca/fides/pull/2613) * Add warning to 'fides deploy' when installed outside of a virtual environment [#2641](https://github.com/ethyca/fides/pull/2641) * Redesigned the default/init config file to be auto-documented. Also updates the `fides init` logic and analytics consent logic [#2694](https://github.com/ethyca/fides/pull/2694) diff --git a/clients/admin-ui/cypress/fixtures/connectors/connection_types.json b/clients/admin-ui/cypress/fixtures/connectors/connection_types.json index 5804649ea6..96490a753e 100644 --- a/clients/admin-ui/cypress/fixtures/connectors/connection_types.json +++ b/clients/admin-ui/cypress/fixtures/connectors/connection_types.json @@ -57,7 +57,7 @@ { "identifier": "manual_webhook", "type": "manual", - "human_readable": "Manual Webhook", + "human_readable": "Manual Process", "encoded_icon": null }, { diff --git a/clients/admin-ui/src/constants.ts b/clients/admin-ui/src/constants.ts index 8ff6e55b7d..4861d42447 100644 --- a/clients/admin-ui/src/constants.ts +++ b/clients/admin-ui/src/constants.ts @@ -109,15 +109,15 @@ export const USER_PRIVILEGES: UserPrivileges[] = [ scope: "privacy-request:view_data", }, { - privilege: "Create manual webhooks", + privilege: "Create manual processes", scope: "webhook:create_or_update", }, { - privilege: "Read manual webhooks", + privilege: "Read manual processes", scope: "webhook:read", }, { - privilege: "Delete manual webhooks", + privilege: "Delete manual processes", scope: "webhook:delete", }, ]; diff --git a/clients/admin-ui/src/features/privacy-requests/manual-processing/ManualProcessingList.tsx b/clients/admin-ui/src/features/privacy-requests/manual-processing/ManualProcessingList.tsx index efb0aa28cf..3b1340c867 100644 --- a/clients/admin-ui/src/features/privacy-requests/manual-processing/ManualProcessingList.tsx +++ b/clients/admin-ui/src/features/privacy-requests/manual-processing/ManualProcessingList.tsx @@ -229,7 +229,7 @@ const ManualProcessingList: React.FC = ({
- You don‘t have any Manual Webhook connections + You don‘t have any Manual Process connections set up yet.
diff --git a/src/fides/api/ops/models/connectionconfig.py b/src/fides/api/ops/models/connectionconfig.py index ef9a6a78e9..07095ef52d 100644 --- a/src/fides/api/ops/models/connectionconfig.py +++ b/src/fides/api/ops/models/connectionconfig.py @@ -70,7 +70,7 @@ def human_readable(self) -> str: ConnectionType.bigquery.value: "BigQuery", ConnectionType.manual.value: "Manual Connector", ConnectionType.email.value: "Email Connector", - ConnectionType.manual_webhook.value: "Manual Webhook", + ConnectionType.manual_webhook.value: "Manual Process", ConnectionType.timescale.value: "TimescaleDB", ConnectionType.fides.value: "Fides Connector", ConnectionType.sovrn.value: "Sovrn", diff --git a/tests/ops/api/v1/endpoints/test_connection_template_endpoints.py b/tests/ops/api/v1/endpoints/test_connection_template_endpoints.py index 074466a062..66e12fa31a 100644 --- a/tests/ops/api/v1/endpoints/test_connection_template_endpoints.py +++ b/tests/ops/api/v1/endpoints/test_connection_template_endpoints.py @@ -310,7 +310,7 @@ def test_search_manual_system_type(self, api_client, generate_auth_header, url): { "identifier": "manual_webhook", "type": "manual", - "human_readable": "Manual Webhook", + "human_readable": "Manual Process", "encoded_icon": None, } ] From 0e7d3f78c47daa5d9ffad4610d8f58bbae96eb5f Mon Sep 17 00:00:00 2001 From: Allison King Date: Wed, 1 Mar 2023 15:54:56 -0500 Subject: [PATCH 05/16] Privacy declaration updates (#2723) --- clients/admin-ui/cypress/e2e/systems.cy.ts | 15 ++++- .../src/features/common/form/inputs.tsx | 7 +- .../src/features/config-wizard/AddSystem.tsx | 4 +- .../features/config-wizard/SystemOption.tsx | 2 +- .../src/features/dataset/dataset.slice.ts | 7 ++ .../features/system/SystemInformationForm.tsx | 1 + .../PrivacyDeclarationAccordion.tsx | 1 + .../PrivacyDeclarationForm.tsx | 67 +++++++++---------- .../PrivacyDeclarationManager.tsx | 1 + .../PrivacyDeclarationStep.tsx | 7 +- .../system/privacy-declarations/hooks.ts | 44 ++++++++++++ clients/admin-ui/src/flags.json | 6 ++ 12 files changed, 121 insertions(+), 41 deletions(-) create mode 100644 clients/admin-ui/src/features/system/privacy-declarations/hooks.ts diff --git a/clients/admin-ui/cypress/e2e/systems.cy.ts b/clients/admin-ui/cypress/e2e/systems.cy.ts index c4e5e732b0..e6b9647bde 100644 --- a/clients/admin-ui/cypress/e2e/systems.cy.ts +++ b/clients/admin-ui/cypress/e2e/systems.cy.ts @@ -56,6 +56,9 @@ describe("System management page", () => { beforeEach(() => { stubTaxonomyEntities(); stubSystemCrud(); + cy.intercept("GET", "/api/v1/dataset", { fixture: "datasets.json" }).as( + "getDatasets" + ); cy.intercept("GET", "/api/v1/connection_type*", { fixture: "connectors/connection_types.json", }).as("getConnectionTypes"); @@ -128,7 +131,12 @@ describe("System management page", () => { // Fill in the privacy declaration form cy.getByTestId("tab-Data uses").click(); cy.getByTestId("add-btn").click(); - cy.wait(["@getDataCategories", "@getDataSubjects", "@getDataUses"]); + cy.wait([ + "@getDataCategories", + "@getDataSubjects", + "@getDataUses", + "@getDatasets", + ]); cy.getByTestId("new-declaration-form"); const declaration = system.privacy_declarations[0]; cy.getByTestId("input-data_use").click(); @@ -141,6 +149,10 @@ describe("System management page", () => { declaration.data_subjects.forEach((ds) => { cy.getByTestId("input-data_subjects").type(`${ds}{enter}`); }); + cy.getByTestId("input-dataset_references").click(); + cy.getByTestId("input-dataset_references").within(() => { + cy.contains("Demo Users Dataset 2").click(); + }); cy.getByTestId("save-btn").click(); cy.wait("@putSystem").then((interception) => { @@ -150,6 +162,7 @@ describe("System management page", () => { data_use: declaration.data_use, data_categories: declaration.data_categories, data_subjects: declaration.data_subjects, + dataset_references: ["demo_users_dataset_2"], }); }); cy.getByTestId("new-declaration-form").within(() => { diff --git a/clients/admin-ui/src/features/common/form/inputs.tsx b/clients/admin-ui/src/features/common/form/inputs.tsx index a6186266e6..2cd2de5810 100644 --- a/clients/admin-ui/src/features/common/form/inputs.tsx +++ b/clients/admin-ui/src/features/common/form/inputs.tsx @@ -80,7 +80,12 @@ const TextInput = ({ return ( - + {isPassword ? ( { Manually add systems - + } @@ -67,7 +67,7 @@ const AddSystem = () => { Automated infrastructure scanning - + - + {icon} {label} diff --git a/clients/admin-ui/src/features/dataset/dataset.slice.ts b/clients/admin-ui/src/features/dataset/dataset.slice.ts index c927e706ed..296ee8ac9d 100644 --- a/clients/admin-ui/src/features/dataset/dataset.slice.ts +++ b/clients/admin-ui/src/features/dataset/dataset.slice.ts @@ -153,6 +153,13 @@ export const { reducer } = datasetSlice; const selectDataset = (state: RootState) => state.dataset; +const emptyDatasets: Dataset[] = []; +export const selectAllDatasets: (state: RootState) => Dataset[] = + createSelector( + datasetApi.endpoints.getAllDatasets.select(), + ({ data }) => data ?? emptyDatasets + ); + export const selectActiveDatasetFidesKey = createSelector( selectDataset, (state) => state.activeDatasetFidesKey diff --git a/clients/admin-ui/src/features/system/SystemInformationForm.tsx b/clients/admin-ui/src/features/system/SystemInformationForm.tsx index 37f6ab1dbb..0bbec56b8c 100644 --- a/clients/admin-ui/src/features/system/SystemInformationForm.tsx +++ b/clients/admin-ui/src/features/system/SystemInformationForm.tsx @@ -35,6 +35,7 @@ import SystemInformationFormExtension from "~/features/system/SystemInformationF import { ResourceTypes, System } from "~/types/api"; const ValidationSchema = Yup.object().shape({ + name: Yup.string().required().label("System name"), fides_key: Yup.string().required().label("System key"), }); 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 12c442086b..32f2919d3c 100644 --- a/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationAccordion.tsx +++ b/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationAccordion.tsx @@ -24,6 +24,7 @@ interface AccordionProps extends DataProps { newDeclaration: PrivacyDeclaration ) => Promise; onDelete: (declaration: PrivacyDeclaration) => Promise; + includeDeprecatedFields?: boolean; } 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 ea70fb4160..7b0f780f10 100644 --- a/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationForm.tsx +++ b/clients/admin-ui/src/features/system/privacy-declarations/PrivacyDeclarationForm.tsx @@ -17,23 +17,11 @@ 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 } from "~/features/common/form/inputs"; -import { - selectDataSubjects, - useGetAllDataSubjectsQuery, -} from "~/features/data-subjects/data-subject.slice"; -import { - selectDataUses, - useGetAllDataUsesQuery, -} from "~/features/data-use/data-use.slice"; -import { - selectDataCategories, - useGetAllDataCategoriesQuery, -} from "~/features/taxonomy/taxonomy.slice"; +import { CustomSelect, CustomTextInput } from "~/features/common/form/inputs"; import { DataCategory, + Dataset, DataSubject, DataUse, PrivacyDeclaration, @@ -53,6 +41,7 @@ const defaultInitialValues: PrivacyDeclaration = { data_categories: [], data_subjects: [], data_use: "", + dataset_references: [], }; type FormValues = typeof defaultInitialValues; @@ -61,18 +50,28 @@ export interface DataProps { allDataCategories: DataCategory[]; allDataUses: DataUse[]; allDataSubjects: DataSubject[]; + allDatasets?: Dataset[]; } export const PrivacyDeclarationFormComponents = ({ allDataUses, allDataCategories, allDataSubjects, + allDatasets, onDelete, -}: DataProps & Pick) => { + includeDeprecatedFields, +}: DataProps & Pick) => { const { dirty, isSubmitting, isValid, initialValues } = useFormikContext(); const deleteModal = useDisclosure(); + const datasetOptions = allDatasets + ? allDatasets.map((d) => ({ + label: d.name ?? d.fides_key, + value: d.fides_key, + })) + : []; + const handleDelete = async () => { await onDelete(initialValues); deleteModal.onClose(); @@ -94,6 +93,14 @@ export const PrivacyDeclarationFormComponents = ({ variant="stacked" singleValueBlock /> + {includeDeprecatedFields ? ( + + ) : null} + {allDatasets ? ( + + ) : null} - + + + + )} diff --git a/clients/admin-ui/src/features/privacy-requests/configuration/MessagingConfiguration.tsx b/clients/admin-ui/src/features/privacy-requests/configuration/MessagingConfiguration.tsx index 1a50d8f717..8b9259e51b 100644 --- a/clients/admin-ui/src/features/privacy-requests/configuration/MessagingConfiguration.tsx +++ b/clients/admin-ui/src/features/privacy-requests/configuration/MessagingConfiguration.tsx @@ -10,14 +10,16 @@ import { Text, } from "@fidesui/react"; import NextLink from "next/link"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { isErrorResult } from "~/features/common/helpers"; import { useAlert, useAPIHelper } from "~/features/common/hooks"; import Layout from "~/features/common/Layout"; +import { messagingProviders } from "~/features/privacy-requests/constants"; import { useCreateConfigurationSettingsMutation, useCreateMessagingConfigurationMutation, + useGetActiveMessagingProviderQuery, } from "~/features/privacy-requests/privacy-requests.slice"; import MailgunEmailConfiguration from "./MailgunEmailConfiguration"; @@ -31,34 +33,41 @@ const MessagingConfiguration = () => { const [createMessagingConfiguration] = useCreateMessagingConfigurationMutation(); const [saveActiveConfiguration] = useCreateConfigurationSettingsMutation(); + const { data: activeMessagingProvider } = + useGetActiveMessagingProviderQuery(); + + useEffect(() => { + if (activeMessagingProvider) { + setMessagingValue(activeMessagingProvider?.service_type); + } + }, [activeMessagingProvider]); const handleChange = async (value: string) => { const result = await saveActiveConfiguration({ - fides: { - notifications: { - notification_service_type: value, - send_request_completion_notification: true, - send_request_receipt_notification: true, - send_request_review_notification: true, - subject_identity_verification_required: true, - }, + notifications: { + notification_service_type: value, + send_request_completion_notification: true, + send_request_receipt_notification: true, + send_request_review_notification: true, + }, + execution: { + subject_identity_verification_required: true, }, }); if (isErrorResult(result)) { handleError(result.error); - } else if (value !== "twilio_text") { - successAlert(`Configured storage type successfully.`); + } else if (value !== messagingProviders.twilio_text) { setMessagingValue(value); } else { const twilioTextResult = await createMessagingConfiguration({ - type: "twilio_text", + service_type: messagingProviders.twilio_text, }); if (isErrorResult(twilioTextResult)) { handleError(twilioTextResult.error); } else { - successAlert(`Configure messaging provider saved successfully.`); + successAlert(`Messaging provider saved successfully.`); setMessagingValue(value); } } @@ -118,36 +127,38 @@ const MessagingConfiguration = () => { > - Mailgun email + Mailgun Email - Twilio email + Twilio Email - Twilio sms + Twilio SMS - {messagingValue === "mailgun-email" ? ( + {messagingValue === messagingProviders.mailgun ? ( ) : null} - {messagingValue === "twilio-email" ? ( + {messagingValue === messagingProviders.twilio_email ? ( ) : null} - {messagingValue === "twilio-text" ? : null} + {messagingValue === messagingProviders.twilio_text ? ( + + ) : null} ); diff --git a/clients/admin-ui/src/features/privacy-requests/configuration/S3StorageConfiguration.tsx b/clients/admin-ui/src/features/privacy-requests/configuration/S3StorageConfiguration.tsx index 1aa2083c15..6f71480d34 100644 --- a/clients/admin-ui/src/features/privacy-requests/configuration/S3StorageConfiguration.tsx +++ b/clients/admin-ui/src/features/privacy-requests/configuration/S3StorageConfiguration.tsx @@ -1,31 +1,38 @@ -import { Button, Divider, Heading, Stack } from "@fidesui/react"; +import { Box, Button, Divider, Heading, Stack } from "@fidesui/react"; import { Form, Formik } from "formik"; import { useState } from "react"; import { CustomSelect, CustomTextInput } from "~/features/common/form/inputs"; import { isErrorResult } from "~/features/common/helpers"; import { useAlert, useAPIHelper } from "~/features/common/hooks"; +import { storageTypes } from "~/features/privacy-requests/constants"; import { useCreateStorageMutation, useCreateStorageSecretsMutation, } from "~/features/privacy-requests/privacy-requests.slice"; +interface SavedStorageDetails { + storageDetails: { + details: { + auth_method: string; + bucket: string; + }; + format: string; + }; +} interface StorageDetails { - type: string; - details: { + storageDetails: { auth_method: string; bucket: string; + format: string; }; - format: string; } interface SecretsStorageData { aws_access_key_id: string; aws_secret_access_key: string; } -const S3StorageConfiguration = ({ - storageDetails: { auth_method, bucket, format }, -}: any) => { +const S3StorageConfiguration = ({ storageDetails }: SavedStorageDetails) => { const [authMethod, setAuthMethod] = useState(""); const [saveStorageDetails] = useCreateStorageMutation(); const [setStorageSecrets] = useCreateStorageSecretsMutation(); @@ -34,12 +41,10 @@ const S3StorageConfiguration = ({ const { successAlert } = useAlert(); const initialValues = { - type: "s3", - details: { - auth_method: auth_method ?? "", - bucket: bucket ?? "", - }, - format: format ?? "", + type: storageTypes.s3, + auth_method: storageDetails?.details?.auth_method ?? "", + bucket: storageDetails?.details?.bucket ?? "", + format: storageDetails?.format ?? "", }; const initialSecretValues = { @@ -48,13 +53,13 @@ const S3StorageConfiguration = ({ }; const handleSubmitStorageConfiguration = async ( - newValues: StorageDetails + newValues: StorageDetails["storageDetails"] ) => { const result = await saveStorageDetails({ - type: "s3", + type: storageTypes.s3, details: { - auth_method: newValues.details.auth_method, - bucket: newValues.details.bucket, + auth_method: newValues.auth_method, + bucket: newValues.bucket, }, format: newValues.format, }); @@ -62,15 +67,18 @@ const S3StorageConfiguration = ({ if (isErrorResult(result)) { handleError(result.error); } else { - setAuthMethod(newValues.details.auth_method); + setAuthMethod(newValues.auth_method); successAlert(`S3 storage credentials successfully updated.`); } }; const handleSubmitStorageSecrets = async (newValues: SecretsStorageData) => { const result = await setStorageSecrets({ - aws_access_key_id: newValues.aws_access_key_id, - aws_secret_access_key: newValues.aws_secret_access_key, + details: { + aws_access_key_id: newValues.aws_access_key_id, + aws_secret_access_key: newValues.aws_secret_access_key, + }, + type: storageTypes.s3, }); if (isErrorResult(result)) { @@ -89,8 +97,9 @@ const S3StorageConfiguration = ({ - {({ isSubmitting, resetForm }) => ( + {({ isSubmitting, handleReset }) => (
- + + + +
)}
diff --git a/clients/admin-ui/src/features/privacy-requests/configuration/StorageConfiguration.tsx b/clients/admin-ui/src/features/privacy-requests/configuration/StorageConfiguration.tsx index 49ed5bee75..0a3a2d75c6 100644 --- a/clients/admin-ui/src/features/privacy-requests/configuration/StorageConfiguration.tsx +++ b/clients/admin-ui/src/features/privacy-requests/configuration/StorageConfiguration.tsx @@ -10,14 +10,16 @@ import { Text, } from "@fidesui/react"; import NextLink from "next/link"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { isErrorResult } from "~/features/common/helpers"; import { useAlert, useAPIHelper } from "~/features/common/hooks"; import Layout from "~/features/common/Layout"; +import { storageTypes } from "~/features/privacy-requests/constants"; import { useCreateConfigurationSettingsMutation, useCreateStorageMutation, + useGetActiveStorageQuery, useGetStorageDetailsQuery, } from "~/features/privacy-requests/privacy-requests.slice"; @@ -27,37 +29,43 @@ const StorageConfiguration = () => { const { successAlert } = useAlert(); const { handleError } = useAPIHelper(); const [storageValue, setStorageValue] = useState(""); - const [saveStorageType, { isLoading }] = useCreateStorageMutation(); - const [saveActiveStorage] = useCreateConfigurationSettingsMutation(); + + const { data: activeStorage } = useGetActiveStorageQuery(); const { data: storageDetails } = useGetStorageDetailsQuery({ type: storageValue, }); + const [saveStorageType, { isLoading }] = useCreateStorageMutation(); + const [saveActiveStorage] = useCreateConfigurationSettingsMutation(); + + useEffect(() => { + if (activeStorage) { + setStorageValue(activeStorage.type); + } + }, [activeStorage]); const handleChange = async (value: string) => { - if (value === "local") { + if (value === storageTypes.local) { const storageDetailsResult = await saveStorageType({ type: value, + details: {}, format: "json", }); if (isErrorResult(storageDetailsResult)) { handleError(storageDetailsResult.error); } else { - successAlert(`Configure storage type details saved successfully.`); + successAlert(`Configured storage details successfully.`); } } const activeStorageResults = await saveActiveStorage({ - fides: { - storage: { - active_default_storage_type: value, - }, + storage: { + active_default_storage_type: value, }, }); if (isErrorResult(activeStorageResults)) { handleError(activeStorageResults.error); } else { - successAlert(`Configure active storage type saved successfully.`); setStorageValue(value); } }; diff --git a/clients/admin-ui/src/features/privacy-requests/configuration/TwilioEmailConfiguration.tsx b/clients/admin-ui/src/features/privacy-requests/configuration/TwilioEmailConfiguration.tsx index ce828e184c..94f3adb9e3 100644 --- a/clients/admin-ui/src/features/privacy-requests/configuration/TwilioEmailConfiguration.tsx +++ b/clients/admin-ui/src/features/privacy-requests/configuration/TwilioEmailConfiguration.tsx @@ -5,6 +5,7 @@ import { useState } from "react"; import { CustomTextInput } from "~/features/common/form/inputs"; import { isErrorResult } from "~/features/common/helpers"; import { useAlert, useAPIHelper } from "~/features/common/hooks"; +import { messagingProviders } from "~/features/privacy-requests/constants"; import { useCreateMessagingConfigurationMutation, useCreateMessagingConfigurationSecretsMutation, @@ -16,7 +17,7 @@ const TwilioEmailConfiguration = () => { const { successAlert } = useAlert(); const { handleError } = useAPIHelper(); const { data: messagingDetails } = useGetMessagingConfigurationDetailsQuery({ - type: "twilio_email", + type: messagingProviders.twilio_email, }); const [createMessagingConfiguration] = useCreateMessagingConfigurationMutation(); @@ -25,7 +26,7 @@ const TwilioEmailConfiguration = () => { const handleTwilioEmailConfiguration = async (value: { email: string }) => { const result = await createMessagingConfiguration({ - type: "twilio_email", + service_type: messagingProviders.twilio_email, details: { twilio_email_from: value.email, }, @@ -45,7 +46,10 @@ const TwilioEmailConfiguration = () => { api_key: string; }) => { const result = await createMessagingConfigurationSecrets({ - twilio_api_key: value.api_key, + details: { + twilio_api_key: value.api_key, + }, + service_type: messagingProviders.twilio_email, }); if (isErrorResult(result)) { @@ -57,11 +61,11 @@ const TwilioEmailConfiguration = () => { }; const initialValues = { - email: messagingDetails?.email ?? "", + email: messagingDetails?.details.twilio_email_from ?? "", }; const initialAPIKeyValues = { - api_key: messagingDetails?.key ?? "", + api_key: "", }; return ( @@ -73,19 +77,21 @@ const TwilioEmailConfiguration = () => { - {({ isSubmitting, resetForm }) => ( + {({ isSubmitting, handleReset }) => (
- + + + + )}
diff --git a/clients/admin-ui/src/features/privacy-requests/configuration/TwilioSMS.tsx b/clients/admin-ui/src/features/privacy-requests/configuration/TwilioSMS.tsx index be088e52d8..97e5191380 100644 --- a/clients/admin-ui/src/features/privacy-requests/configuration/TwilioSMS.tsx +++ b/clients/admin-ui/src/features/privacy-requests/configuration/TwilioSMS.tsx @@ -4,17 +4,12 @@ import { Form, Formik } from "formik"; import { CustomTextInput } from "~/features/common/form/inputs"; import { isErrorResult } from "~/features/common/helpers"; import { useAlert, useAPIHelper } from "~/features/common/hooks"; -import { - useCreateMessagingConfigurationSecretsMutation, - useGetMessagingConfigurationDetailsQuery, -} from "~/features/privacy-requests/privacy-requests.slice"; +import { messagingProviders } from "~/features/privacy-requests/constants"; +import { useCreateMessagingConfigurationSecretsMutation } from "~/features/privacy-requests/privacy-requests.slice"; const TwilioSMSConfiguration = () => { const { successAlert } = useAlert(); const { handleError } = useAPIHelper(); - const { data: messagingDetails } = useGetMessagingConfigurationDetailsQuery({ - type: "twilio_text", - }); const [createMessagingConfigurationSecrets] = useCreateMessagingConfigurationSecretsMutation(); @@ -25,10 +20,13 @@ const TwilioSMSConfiguration = () => { phone: string; }) => { const result = await createMessagingConfigurationSecrets({ - twilio_account_sid: value.account_sid, - twilio_auth_token: value.auth_token, - twilio_messaging_service_sid: value.messaging_service_sid, - twilio_sender_phone_number: value.phone, + details: { + twilio_account_sid: value.account_sid, + twilio_auth_token: value.auth_token, + twilio_messaging_service_sid: value.messaging_service_sid, + twilio_sender_phone_number: value.phone, + }, + service_type: messagingProviders.twilio_text, }); if (isErrorResult(result)) { @@ -39,10 +37,10 @@ const TwilioSMSConfiguration = () => { }; const initialValues = { - account_sid: messagingDetails?.twilio_account_sid ?? "", - auth_token: messagingDetails?.twilio_auth_token ?? "", - messaging_service_sid: messagingDetails?.twilio_messaging_service_sid ?? "", - phone: messagingDetails?.twilio_sender_phone_number ?? "", + account_sid: "", + auth_token: "", + messaging_service_sid: "", + phone: "", }; return ( @@ -54,20 +52,23 @@ const TwilioSMSConfiguration = () => { - {({ isSubmitting, resetForm }) => ( + {({ isSubmitting, handleReset }) => (
{ + + +
+ )} +
+ + + ); +}; + +export default TestMessagingProviderConnectionButton; diff --git a/clients/admin-ui/src/features/privacy-requests/configuration/TwilioEmailConfiguration.tsx b/clients/admin-ui/src/features/privacy-requests/configuration/TwilioEmailConfiguration.tsx index 94f3adb9e3..a62f007096 100644 --- a/clients/admin-ui/src/features/privacy-requests/configuration/TwilioEmailConfiguration.tsx +++ b/clients/admin-ui/src/features/privacy-requests/configuration/TwilioEmailConfiguration.tsx @@ -12,6 +12,8 @@ import { useGetMessagingConfigurationDetailsQuery, } from "~/features/privacy-requests/privacy-requests.slice"; +import TestMessagingProviderConnectionButton from "./TestMessagingProviderConnectionButton"; + const TwilioEmailConfiguration = () => { const [configurationStep, setConfigurationStep] = useState(""); const { successAlert } = useAlert(); @@ -56,7 +58,7 @@ const TwilioEmailConfiguration = () => { handleError(result.error); } else { successAlert(`Twilio email secrets successfully updated.`); - setConfigurationStep("configureTwilioEmailSecrets"); + setConfigurationStep("testConnection"); } }; @@ -112,7 +114,8 @@ const TwilioEmailConfiguration = () => { )} - {configurationStep === "configureTwilioEmailSecrets" ? ( + {configurationStep === "configureTwilioEmailSecrets" || + configurationStep === "testConnection" ? ( <> @@ -158,6 +161,11 @@ const TwilioEmailConfiguration = () => { ) : null} + {configurationStep === "testConnection" ? ( + + ) : null}
); }; diff --git a/clients/admin-ui/src/features/privacy-requests/configuration/TwilioSMS.tsx b/clients/admin-ui/src/features/privacy-requests/configuration/TwilioSMS.tsx index 97e5191380..82fe82f096 100644 --- a/clients/admin-ui/src/features/privacy-requests/configuration/TwilioSMS.tsx +++ b/clients/admin-ui/src/features/privacy-requests/configuration/TwilioSMS.tsx @@ -1,15 +1,25 @@ import { Box, Button, Heading, Stack } from "@fidesui/react"; import { Form, Formik } from "formik"; +import { useState } from "react"; import { CustomTextInput } from "~/features/common/form/inputs"; import { isErrorResult } from "~/features/common/helpers"; import { useAlert, useAPIHelper } from "~/features/common/hooks"; import { messagingProviders } from "~/features/privacy-requests/constants"; -import { useCreateMessagingConfigurationSecretsMutation } from "~/features/privacy-requests/privacy-requests.slice"; +import { + useCreateMessagingConfigurationSecretsMutation, + useGetMessagingConfigurationDetailsQuery, +} from "~/features/privacy-requests/privacy-requests.slice"; + +import TestMessagingProviderConnectionButton from "./TestMessagingProviderConnectionButton"; const TwilioSMSConfiguration = () => { const { successAlert } = useAlert(); const { handleError } = useAPIHelper(); + const [configurationStep, setConfigurationStep] = useState(""); + const { data: messagingDetails } = useGetMessagingConfigurationDetailsQuery({ + type: "twilio_text", + }); const [createMessagingConfigurationSecrets] = useCreateMessagingConfigurationSecretsMutation(); @@ -33,6 +43,7 @@ const TwilioSMSConfiguration = () => { handleError(result.error); } else { successAlert(`Twilio text secrets successfully updated.`); + setConfigurationStep("testConnection"); } }; @@ -104,6 +115,11 @@ const TwilioSMSConfiguration = () => { )} + {configurationStep === "testConnection" ? ( + + ) : null}
); }; diff --git a/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts b/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts index d4b67a75dc..db385c62ef 100644 --- a/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts +++ b/clients/admin-ui/src/features/privacy-requests/privacy-requests.slice.ts @@ -416,6 +416,13 @@ export const privacyRequestApi = createApi({ body: params.details, }), }), + createTestConnectionMessage: build.mutation({ + query: (params) => ({ + url: `messaging/config/test`, + method: "POST", + body: params, + }), + }), uploadManualWebhookData: build.mutation< any, PatchUploadManualWebhookDataRequest @@ -449,4 +456,5 @@ export const { useGetActiveStorageQuery, useCreateMessagingConfigurationMutation, useCreateMessagingConfigurationSecretsMutation, + useCreateTestConnectionMessageMutation, } = privacyRequestApi;