diff --git a/CHANGELOG.md b/CHANGELOG.md index 4204754b69..3e8c8b598f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ The types of changes are: ### Added - Added models for Privacy Center configuration (for plus users) [#4716](https://github.com/ethyca/fides/pull/4716) +### Changed +- Updated privacy notice & experience forms to hide translation UI when user doesn't have translation feature [#4728](https://github.com/ethyca/fides/pull/4728) + ### Fixed - Fixed responsive issues with the buttons on the integration screen [#4729](https://github.com/ethyca/fides/pull/4729) diff --git a/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts b/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts index 3cfacb3010..4b7e8c7dbc 100644 --- a/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts +++ b/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts @@ -1,4 +1,4 @@ -import { stubPlus } from "cypress/support/stubs"; +import { stubPlus, stubTranslationConfig } from "cypress/support/stubs"; import { PRIVACY_EXPERIENCE_ROUTE } from "~/features/common/nav/v2/routes"; import { RoleRegistryEnum } from "~/types/api"; @@ -192,4 +192,21 @@ describe("Privacy experiences", () => { }); }); }); + + describe("translation interface", () => { + it("shows the language interface when translations are enabled", () => { + stubTranslationConfig(true); + cy.visit(`${PRIVACY_EXPERIENCE_ROUTE}/new`); + cy.wait("@getTranslationConfig"); + cy.getByTestId("input-auto_detect_language").should("exist"); + }); + + it("shows an edit button instead when translations are disabled", () => { + stubTranslationConfig(false); + cy.visit(`${PRIVACY_EXPERIENCE_ROUTE}/new`); + cy.wait("@getTranslationConfig"); + cy.getByTestId("input-auto_detect_language").should("not.exist"); + cy.getByTestId("edit-experience-btn").should("exist"); + }); + }); }); diff --git a/clients/admin-ui/cypress/e2e/privacy-notices.cy.ts b/clients/admin-ui/cypress/e2e/privacy-notices.cy.ts index 9ba3dfd6e7..4cd7b21517 100644 --- a/clients/admin-ui/cypress/e2e/privacy-notices.cy.ts +++ b/clients/admin-ui/cypress/e2e/privacy-notices.cy.ts @@ -3,6 +3,7 @@ import { stubPlus, stubPrivacyNoticesCrud, stubTaxonomyEntities, + stubTranslationConfig, } from "cypress/support/stubs"; import { PRIVACY_NOTICES_ROUTE } from "~/features/common/nav/v2/routes"; @@ -307,6 +308,7 @@ describe("Privacy notices", () => { }); it("can create a new privacy notice", () => { + stubTranslationConfig(true); cy.visit(`${PRIVACY_NOTICES_ROUTE}/new`); cy.getByTestId("new-privacy-notice-page"); const notice = { @@ -382,4 +384,21 @@ describe("Privacy notices", () => { cy.getByTestId("input-notice_key").clear().type("custom_key"); }); }); + + describe("translation interface", () => { + it("shows the translation interface when translations are enabled", () => { + stubLanguages(); + stubTranslationConfig(true); + cy.visit(`${PRIVACY_NOTICES_ROUTE}/new`); + cy.wait("@getTranslationConfig"); + cy.getByTestId("add-language-btn").should("exist"); + }); + + it("doesn't show the translation interface when translations are disabled", () => { + stubTranslationConfig(false); + cy.visit(`${PRIVACY_NOTICES_ROUTE}/new`); + cy.wait("@getTranslationConfig"); + cy.getByTestId("add-language-btn").should("not.exist"); + }); + }); }); diff --git a/clients/admin-ui/cypress/support/stubs.ts b/clients/admin-ui/cypress/support/stubs.ts index f338ad12f6..d864b4fa64 100644 --- a/clients/admin-ui/cypress/support/stubs.ts +++ b/clients/admin-ui/cypress/support/stubs.ts @@ -247,3 +247,14 @@ export const stubSystemVendors = () => { fixture: "systems/system-vendors.json", }).as("getSystemVendors"); }; + +export const stubTranslationConfig = (enabled: boolean) => { + cy.intercept("GET", "/api/v1/config*", { + body: { + consent: { + enable_translations: enabled, + enable_oob_translations: enabled, + }, + }, + }).as("getTranslationConfig"); +}; diff --git a/clients/admin-ui/src/features/privacy-experience/ConfigurePrivacyExperience.tsx b/clients/admin-ui/src/features/privacy-experience/ConfigurePrivacyExperience.tsx index d370144c66..a54da83627 100644 --- a/clients/admin-ui/src/features/privacy-experience/ConfigurePrivacyExperience.tsx +++ b/clients/admin-ui/src/features/privacy-experience/ConfigurePrivacyExperience.tsx @@ -37,6 +37,7 @@ import { import { PrivacyExperienceForm } from "~/features/privacy-experience/PrivacyExperienceForm"; import PrivacyExperienceTranslationForm from "~/features/privacy-experience/PrivacyExperienceTranslationForm"; import { selectAllPrivacyNotices } from "~/features/privacy-notices/privacy-notices.slice"; +import { useGetConfigurationSettingsQuery } from "~/features/privacy-requests"; import { ComponentType, ExperienceConfigCreate, @@ -91,6 +92,11 @@ const ConfigurePrivacyExperience = ({ const router = useRouter(); + const { data: appConfig } = useGetConfigurationSettingsQuery({ + api_set: false, + }); + const translationsEnabled = appConfig?.consent?.enable_translations; + const languagePage = useAppSelector(selectLanguagePage); const languagePageSize = useAppSelector(selectLanguagePageSize); useGetAllLanguagesQuery({ page: languagePage, size: languagePageSize }); @@ -184,12 +190,14 @@ const ConfigurePrivacyExperience = ({ {translationToEdit ? ( ) : ( diff --git a/clients/admin-ui/src/features/privacy-experience/NewJavaScriptTag.tsx b/clients/admin-ui/src/features/privacy-experience/NewJavaScriptTag.tsx index 11b0c21399..ff632d8a6d 100644 --- a/clients/admin-ui/src/features/privacy-experience/NewJavaScriptTag.tsx +++ b/clients/admin-ui/src/features/privacy-experience/NewJavaScriptTag.tsx @@ -43,7 +43,7 @@ const NewJavaScriptTag = ({ property }: Props) => { const fidesJsScriptTag = useMemo(() => { const script = FIDES_JS_SCRIPT_TEMPLATE.replace( PROPERTY_UNIQUE_ID_TEMPLATE, - property.id.toString() + property.id!.toString() ); if (isFidesCloud && isSuccess && fidesCloudConfig?.privacy_center_url) { script.replace( diff --git a/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceForm.tsx b/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceForm.tsx index 91ad8ab5e2..7b4f209ecd 100644 --- a/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceForm.tsx +++ b/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceForm.tsx @@ -1,4 +1,5 @@ import { + ArrowForwardIcon, Box, Button, ButtonGroup, @@ -81,10 +82,12 @@ export const PrivacyExperienceConfigColumnLayout = ({ export const PrivacyExperienceForm = ({ allPrivacyNotices, + translationsEnabled, onSelectTranslation, onCreateTranslation, }: { allPrivacyNotices: LimitedPrivacyNoticeResponseSchema[]; + translationsEnabled?: boolean; onSelectTranslation: (t: ExperienceTranslation) => void; onCreateTranslation: (lang: SupportedLanguage) => ExperienceTranslation; }) => { @@ -241,34 +244,48 @@ export const PrivacyExperienceForm = ({ getItemLabel={(item) => PRIVACY_NOTICE_REGION_RECORD[item]} draggable /> - setFieldValue("translations", newValues)} - idField="language" - canDeleteItem={(item) => !item.is_default} - allItems={allLanguages - .slice() - .sort((a, b) => a.name.localeCompare(b.name)) - .map((lang) => ({ - language: lang.id as SupportedLanguage, - is_default: false, - }))} - getItemLabel={getTranslationDisplayName} - createNewValue={(opt) => - onCreateTranslation(opt.value as SupportedLanguage) - } - onRowClick={onSelectTranslation} - selectOnAdd - draggable - /> - + {translationsEnabled ? ( + <> + setFieldValue("translations", newValues)} + idField="language" + canDeleteItem={(item) => !item.is_default} + allItems={allLanguages + .slice() + .sort((a, b) => a.name.localeCompare(b.name)) + .map((lang) => ({ + language: lang.id as SupportedLanguage, + is_default: false, + }))} + getItemLabel={getTranslationDisplayName} + createNewValue={(opt) => + onCreateTranslation(opt.value as SupportedLanguage) + } + onRowClick={onSelectTranslation} + selectOnAdd + draggable + /> + + + ) : ( + + )} ); }; diff --git a/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceTranslationForm.tsx b/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceTranslationForm.tsx index 3efcf5e64a..1d95780cc1 100644 --- a/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceTranslationForm.tsx +++ b/clients/admin-ui/src/features/privacy-experience/PrivacyExperienceTranslationForm.tsx @@ -40,10 +40,12 @@ export const OOBTranslationNotice = ({ const PrivacyExperienceTranslationForm = ({ translation, + translationsEnabled, isOOB, onReturnToMainForm, }: { translation: TranslationWithLanguageName; + translationsEnabled?: boolean; isOOB?: boolean; onReturnToMainForm: () => void; }) => { @@ -148,46 +150,57 @@ const PrivacyExperienceTranslationForm = ({ ); + let unsavedChangesMessage; + if (!translationsEnabled) { + unsavedChangesMessage = + "You have unsaved changes to this experience text. Discard changes?"; + } else { + unsavedChangesMessage = isEditing + ? "You have unsaved changes to this translation. Discard changes?" + : "This translation has not been added to your experience. Discard translation?"; + } + return ( - {isEditing - ? "You have unsaved changes to this translation. Discard changes?" - : "This translation has not been added to your experience. Discard translation?"} - - } + title={translationsEnabled ? "Translation not saved" : "Text not saved"} + message={{unsavedChangesMessage}} confirmButtonText="Discard" handleConfirm={discardChanges} /> - - Are you sure you want to update the default language of this - experience? - - } - handleConfirm={() => setNewDefaultTranslation(translationIndex)} - /> - Edit {translation.name} translation + {translationsEnabled + ? `Edit ${translation.name} translation` + : "Edit experience text"} {isOOB ? : null} - + {translationsEnabled ? ( + <> + + + Are you sure you want to update the default language of this + experience? + + } + handleConfirm={() => setNewDefaultTranslation(translationIndex)} + /> + + ) : null} + ( + <> + + + +); + const TranslationFormBlock = ({ index, name, @@ -51,17 +68,7 @@ const TranslationFormBlock = ({ Edit {name} translation {isOOB ? : null} - - + ); @@ -111,6 +118,11 @@ const PrivacyNoticeTranslationForm = ({ const [translationIsOOB, setTranslationIsOOB] = useState(false); const [tabIndex, setTabIndex] = useState(0); + const { data: appConfig } = useGetConfigurationSettingsQuery({ + api_set: false, + }); + const translationsEnabled = appConfig?.consent?.enable_translations; + const languagePage = useAppSelector(selectPage); const languagePageSize = useAppSelector(selectPageSize); useGetAllLanguagesQuery({ page: languagePage, size: languagePageSize }); @@ -161,6 +173,14 @@ const PrivacyNoticeTranslationForm = ({ const getLanguageDisplayName = (lang: SupportedLanguage) => allLanguages.find((language) => language.id === lang)?.name ?? lang; + if (!translationsEnabled) { + return ( + + + + ); + } + return ( ; translations?: Array; - properties?: Array; + properties?: Array; }; diff --git a/clients/admin-ui/src/types/api/models/ExperienceConfigListViewResponse.ts b/clients/admin-ui/src/types/api/models/ExperienceConfigListViewResponse.ts index 5611175ea6..69897ac1a6 100644 --- a/clients/admin-ui/src/types/api/models/ExperienceConfigListViewResponse.ts +++ b/clients/admin-ui/src/types/api/models/ExperienceConfigListViewResponse.ts @@ -3,7 +3,7 @@ /* eslint-disable */ import type { ComponentType } from "./ComponentType"; -import { MinimalProperty } from "./MinimalProperty"; +import type { MinimalProperty } from "./MinimalProperty"; import type { PrivacyNoticeRegion } from "./PrivacyNoticeRegion"; /** @@ -13,8 +13,8 @@ export type ExperienceConfigListViewResponse = { id: string; name: string; regions: Array; - properties: Array; component: ComponentType; updated_at: string; disabled: boolean; + properties: Array; }; diff --git a/clients/admin-ui/src/types/api/models/ExperienceConfigResponse.ts b/clients/admin-ui/src/types/api/models/ExperienceConfigResponse.ts index 44f6b16563..bfae9cbda1 100644 --- a/clients/admin-ui/src/types/api/models/ExperienceConfigResponse.ts +++ b/clients/admin-ui/src/types/api/models/ExperienceConfigResponse.ts @@ -4,6 +4,7 @@ import type { ComponentType } from "./ComponentType"; import type { ExperienceTranslationResponse } from "./ExperienceTranslationResponse"; +import type { MinimalProperty } from "./MinimalProperty"; import type { PrivacyNoticeRegion } from "./PrivacyNoticeRegion"; import type { PrivacyNoticeResponse } from "./PrivacyNoticeResponse"; @@ -24,4 +25,5 @@ export type ExperienceConfigResponse = { component: ComponentType; privacy_notices?: Array; translations?: Array; + properties?: Array; }; diff --git a/clients/admin-ui/src/types/api/models/ExperienceConfigResponseNoNotices.ts b/clients/admin-ui/src/types/api/models/ExperienceConfigResponseNoNotices.ts index 3c7b2c7802..e2ab444c49 100644 --- a/clients/admin-ui/src/types/api/models/ExperienceConfigResponseNoNotices.ts +++ b/clients/admin-ui/src/types/api/models/ExperienceConfigResponseNoNotices.ts @@ -4,6 +4,7 @@ import type { ComponentType } from "./ComponentType"; import type { ExperienceTranslationResponse } from "./ExperienceTranslationResponse"; +import type { MinimalProperty } from "./MinimalProperty"; import type { PrivacyNoticeRegion } from "./PrivacyNoticeRegion"; import type { SupportedLanguage } from "./SupportedLanguage"; @@ -75,4 +76,5 @@ export type ExperienceConfigResponseNoNotices = { updated_at: string; component: ComponentType; translations?: Array; + properties?: Array; }; diff --git a/clients/admin-ui/src/types/api/models/ExperienceConfigUpdate.ts b/clients/admin-ui/src/types/api/models/ExperienceConfigUpdate.ts index d8140abcc2..883d9a48a6 100644 --- a/clients/admin-ui/src/types/api/models/ExperienceConfigUpdate.ts +++ b/clients/admin-ui/src/types/api/models/ExperienceConfigUpdate.ts @@ -3,6 +3,7 @@ /* eslint-disable */ import type { ExperienceTranslation } from "./ExperienceTranslation"; +import type { MinimalProperty } from "./MinimalProperty"; import type { PrivacyNoticeRegion } from "./PrivacyNoticeRegion"; /** @@ -24,4 +25,5 @@ export type ExperienceConfigUpdate = { regions: Array; translations: Array; privacy_notice_ids: Array; + properties: Array; }; diff --git a/clients/admin-ui/src/types/api/models/MinimalPrivacyExperience.ts b/clients/admin-ui/src/types/api/models/MinimalPrivacyExperience.ts index 578ea938ec..e74f286db6 100644 --- a/clients/admin-ui/src/types/api/models/MinimalPrivacyExperience.ts +++ b/clients/admin-ui/src/types/api/models/MinimalPrivacyExperience.ts @@ -2,6 +2,10 @@ /* tslint:disable */ /* eslint-disable */ +/** + * Minimal representation of a privacy experience, contains enough information + * to select experiences by name in the UI and an ID to link the selections in the database. + */ export type MinimalPrivacyExperience = { id: string; name: string; diff --git a/clients/admin-ui/src/types/api/models/MinimalProperty.ts b/clients/admin-ui/src/types/api/models/MinimalProperty.ts index 3408a59a48..f8abf03740 100644 --- a/clients/admin-ui/src/types/api/models/MinimalProperty.ts +++ b/clients/admin-ui/src/types/api/models/MinimalProperty.ts @@ -2,6 +2,9 @@ /* tslint:disable */ /* eslint-disable */ +/** + * A base template for all other Fides Schemas to inherit from. + */ export type MinimalProperty = { id: string; name: string; diff --git a/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts b/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts index e0e4277317..c87cf354b5 100644 --- a/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts +++ b/clients/admin-ui/src/types/api/models/PrivacyExperienceResponse.ts @@ -23,7 +23,8 @@ import type { TCFVendorRelationships } from "./TCFVendorRelationships"; * Notices are extracted from the shared Experience Config and placed at the top-level here * for backwards compatibility, and to reduce nesting due to notice translations. * - * Additionally, the notices on the ExperienceConfig are further filtered. + * Additionally, the notices may be a subset of the notices attached to the ExperienceConfig + * due to filtering */ export type PrivacyExperienceResponse = { id: string; diff --git a/clients/admin-ui/src/types/api/models/PrivacyNoticeResponse.ts b/clients/admin-ui/src/types/api/models/PrivacyNoticeResponse.ts index 1bac778bbb..b708418194 100644 --- a/clients/admin-ui/src/types/api/models/PrivacyNoticeResponse.ts +++ b/clients/admin-ui/src/types/api/models/PrivacyNoticeResponse.ts @@ -13,7 +13,7 @@ import type { UserConsentPreference } from "./UserConsentPreference"; /** * An API representation of a PrivacyNotice used for response payloads * - * Overrides fields from PriavcyNotice schema to indicate which ones + * Overrides fields from PrivacyNotice schema to indicate which ones * are guaranteed to be supplied */ export type PrivacyNoticeResponse = { diff --git a/clients/admin-ui/src/types/api/models/Property.ts b/clients/admin-ui/src/types/api/models/Property.ts index 8b8b46bbbe..45ac8823ec 100644 --- a/clients/admin-ui/src/types/api/models/Property.ts +++ b/clients/admin-ui/src/types/api/models/Property.ts @@ -2,12 +2,15 @@ /* tslint:disable */ /* eslint-disable */ -import { MinimalPrivacyExperience } from "./MinimalPrivacyExperience"; -import { PropertyType } from "./PropertyType"; +import type { MinimalPrivacyExperience } from "./MinimalPrivacyExperience"; +import type { PropertyType } from "./PropertyType"; +/** + * A base template for all other Fides Schemas to inherit from. + */ export type Property = { - id: string; name: string; type: PropertyType; + id?: string; experiences: Array; }; diff --git a/clients/admin-ui/src/types/api/models/SupportedLanguage.ts b/clients/admin-ui/src/types/api/models/SupportedLanguage.ts index 47ca71346b..d9f326e78e 100644 --- a/clients/admin-ui/src/types/api/models/SupportedLanguage.ts +++ b/clients/admin-ui/src/types/api/models/SupportedLanguage.ts @@ -16,6 +16,7 @@ export enum SupportedLanguage { EL = "el", EN = "en", ES = "es", + ES_MX = "es-MX", ET = "et", EU = "eu", FI = "fi",