From 523c1ab716c666feeca4636b844cf195810e4ac2 Mon Sep 17 00:00:00 2001 From: Jason Gill Date: Fri, 15 Nov 2024 14:15:52 -0700 Subject: [PATCH] Migrate one-off Select inputs to Ant (#5475) --- CHANGELOG.md | 3 + clients/admin-ui/cypress/e2e/connectors.cy.ts | 7 +- .../cypress/e2e/discovery-detection.cy.ts | 15 +- clients/admin-ui/cypress/e2e/messaging.cy.ts | 16 +- .../cypress/e2e/privacy-experiences.cy.ts | 32 +-- .../cypress/e2e/privacy-notices.cy.ts | 16 +- .../e2e/system-integrations-plus.cy.ts | 22 +- .../admin-ui/cypress/support/ant-support.ts | 62 +++++ clients/admin-ui/cypress/support/e2e.ts | 1 + .../src/features/common/ScrollableList.tsx | 26 +- .../src/features/common/TaxonomiesPicker.tsx | 20 +- .../features/common/dropdown/FilterSelect.tsx | 16 ++ .../dropdown/TaxonomySelect.module.scss | 17 ++ .../common/dropdown/TaxonomySelect.tsx | 82 ++++++ .../dropdown/TaxonomySelectDropdown.tsx | 119 -------- .../src/features/common/hooks/index.ts | 1 - .../features/common/hooks/useOutsideClick.ts | 22 -- .../common/hooks/useTaxonomies.test.ts | 6 +- .../features/common/hooks/useTaxonomies.tsx | 71 +++-- .../tables/cells/EditCategoryCell.tsx | 27 +- .../datamap/modals/ReportExportModal.tsx | 23 +- .../add-connection/DatasetConfiguration.tsx | 104 ++++--- .../add-connection/forms/YamlEditorForm.tsx | 5 +- .../filters/ConnectionTypeFilter.tsx | 24 +- .../filters/DisabledStatusFilter.tsx | 24 +- .../filters/SystemTypeFilter.tsx | 24 +- .../filters/TestingStatusFilter.tsx | 24 +- .../ConsentAutomationForm.tsx | 42 +-- .../forms/SelectDataset.tsx | 19 +- .../AddMessagingTemplateModal.tsx | 13 +- .../privacy-notices/PrivacyNoticeForm.tsx | 34 +-- .../PrivacyNoticeTranslationForm.tsx | 37 ++- .../features/system/SystemInformationForm.tsx | 9 +- .../src/features/system/VendorSelector.tsx | 1 + .../dictionary-form/DictSuggestionInputs.tsx | 256 +++--------------- .../src/pages/datastore-connection/[id].tsx | 10 +- clients/fidesui/src/hoc/CustomSelect.tsx | 19 ++ clients/fidesui/src/hoc/index.tsx | 1 + clients/fidesui/src/index.ts | 47 ++-- 39 files changed, 567 insertions(+), 730 deletions(-) create mode 100644 clients/admin-ui/cypress/support/ant-support.ts create mode 100644 clients/admin-ui/src/features/common/dropdown/FilterSelect.tsx create mode 100644 clients/admin-ui/src/features/common/dropdown/TaxonomySelect.module.scss create mode 100644 clients/admin-ui/src/features/common/dropdown/TaxonomySelect.tsx delete mode 100644 clients/admin-ui/src/features/common/dropdown/TaxonomySelectDropdown.tsx delete mode 100644 clients/admin-ui/src/features/common/hooks/useOutsideClick.ts create mode 100644 clients/fidesui/src/hoc/CustomSelect.tsx create mode 100644 clients/fidesui/src/hoc/index.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 00816fca25..d133eeeae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,9 @@ The types of changes are: ### Added - Added namespace support for Snowflake [#5486](https://github.com/ethyca/fides/pull/5486) +### Developer Experience +- Migrated several instances of Chakra's Select component to use Ant's Select component [#5475](https://github.com/ethyca/fides/pull/5475) + ### Fixed - Fixed deletion of ConnectionConfigs that have related MonitorConfigs [#5478](https://github.com/ethyca/fides/pull/5478) diff --git a/clients/admin-ui/cypress/e2e/connectors.cy.ts b/clients/admin-ui/cypress/e2e/connectors.cy.ts index aad20f189d..10f4472f83 100644 --- a/clients/admin-ui/cypress/e2e/connectors.cy.ts +++ b/clients/admin-ui/cypress/e2e/connectors.cy.ts @@ -65,12 +65,13 @@ describe("Connectors", () => { // The dataset dropdown selector should have the value of the existing connected dataset cy.getByTestId("save-dataset-link-btn").should("be.enabled"); cy.getByTestId("dataset-selector").should( - "have.value", + "have.text", "postgres_example_test_dataset", ); // Change the linked dataset - cy.getByTestId("dataset-selector").select("demo_users_dataset_2"); + cy.getByTestId("dataset-selector").antSelect("demo_users_dataset_2"); + cy.getByTestId("save-dataset-link-btn").click(); cy.wait("@patchDatasetconfig").then((interception) => { @@ -95,7 +96,7 @@ describe("Connectors", () => { cy.wait("@getPostgresConnectorDatasetconfig"); // Unset the linked dataset, which should switch the save button enable-ness - cy.getByTestId("dataset-selector").select("Select"); + cy.getByTestId("dataset-selector").antClearSelect(); cy.getByTestId("save-dataset-link-btn").should("be.disabled"); // The monaco yaml editor takes a bit to load // eslint-disable-next-line cypress/no-unnecessary-waiting diff --git a/clients/admin-ui/cypress/e2e/discovery-detection.cy.ts b/clients/admin-ui/cypress/e2e/discovery-detection.cy.ts index 74cf1ec336..ca8ad8bd4d 100644 --- a/clients/admin-ui/cypress/e2e/discovery-detection.cy.ts +++ b/clients/admin-ui/cypress/e2e/discovery-detection.cy.ts @@ -389,19 +389,14 @@ describe("discovery and detection", () => { }); it("allows classifications to be changed using the dropdown", () => { - cy.intercept("GET", "/api/v1/data_category", [ - { fides_key: "system", active: true }, - { fides_key: "user.contact", active: true }, - ]); - cy.intercept("PATCH", "/api/v1/plus/discovery-monitor/*/results", { - response: 200, - }).as("patchClassification"); + stubTaxonomyEntities(); + cy.intercept("PATCH", "/api/v1/plus/discovery-monitor/*/results").as( + "patchClassification", + ); cy.getByTestId("classification-user.device.device_id").click({ force: true, }); - cy.get(".select-wrapper").within(() => { - cy.getByTestId("option-system").click({ force: true }); - }); + cy.getByTestId("taxonomy-select").antSelect("system"); cy.wait("@patchClassification"); }); diff --git a/clients/admin-ui/cypress/e2e/messaging.cy.ts b/clients/admin-ui/cypress/e2e/messaging.cy.ts index 908336db85..46e2abd292 100644 --- a/clients/admin-ui/cypress/e2e/messaging.cy.ts +++ b/clients/admin-ui/cypress/e2e/messaging.cy.ts @@ -1,8 +1,5 @@ import { stubPlus } from "cypress/support/stubs"; -const getSelectOptionList = (selectorId: string) => - cy.getByTestId(selectorId).click().find(`.custom-select__menu-list`); - describe("Messaging", () => { beforeEach(() => { cy.login(); @@ -77,9 +74,8 @@ describe("Messaging", () => { cy.getByTestId("add-messaging-template-modal").should("exist"); - getSelectOptionList("template-type-selector") - .find(".custom-select__option") - .should("have.length", customizableMessagesCount); + cy.getByTestId("template-type-selector").click(); + cy.get(".ant-select-item").should("have.length", customizableMessagesCount); }); it("should redirect to the add new page after selecting a message type", () => { @@ -88,7 +84,9 @@ describe("Messaging", () => { cy.getByTestId("add-message-btn").click(); - cy.selectOption("template-type-selector", "Access request completed"); + cy.getByTestId("template-type-selector") + .find(".ant-select") + .antSelect("Access request completed"); cy.getByTestId("confirm-btn").click(); @@ -131,9 +129,7 @@ describe("Messaging", () => { cy.getByTestId("submit-btn").should("be.disabled"); cy.getByTestId("add-property").click(); - cy.getByTestId("select-property") - .find(".select-property__input-container") - .click(); + cy.getByTestId("select-property").click(); cy.getByTestId("select-property").find("input").focus().type("{enter}"); cy.getByTestId("submit-btn").should("be.enabled"); cy.getByTestId("submit-btn").click(); diff --git a/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts b/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts index d53ac6f8f3..1df388b341 100644 --- a/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts +++ b/clients/admin-ui/cypress/e2e/privacy-experiences.cy.ts @@ -188,17 +188,10 @@ describe("Privacy experiences", () => { cy.getByTestId("input-name").type("Test experience name"); cy.selectOption("input-component", "Banner and modal"); cy.getByTestId("add-privacy-notice").click(); - cy.getByTestId("select-privacy-notice").click(); - cy.get(".select-privacy-notice__menu") - .find(".select-privacy-notice__option") - .first() - .click(); + cy.getByTestId("select-privacy-notice").antSelect(0); cy.getByTestId("add-location").click(); - cy.getByTestId("select-location").click(); - cy.get(".select-location__menu") - .find(".select-location__option") - .first() - .click(); + + cy.getByTestId("select-location").antSelect("France"); cy.intercept("POST", "/api/v1/experience-config", { statusCode: 200, }).as("postExperience"); @@ -256,11 +249,7 @@ describe("Privacy experiences", () => { "No privacy notices added", ); cy.getByTestId("add-privacy-notice").click(); - cy.getByTestId("select-privacy-notice").click(); - cy.get(".select-privacy-notice__menu") - .find(".select-privacy-notice__option") - .first() - .click(); + cy.getByTestId("select-privacy-notice").antSelect(0); cy.getByTestId("no-preview-notice").should("not.exist"); cy.get(`#${PREVIEW_CONTAINER_ID}`).should("be.visible"); }); @@ -269,12 +258,7 @@ describe("Privacy experiences", () => { cy.getByTestId("input-show_layer1_notices").should("not.be.visible"); cy.selectOption("input-component", "Banner and modal"); cy.getByTestId("add-privacy-notice").click(); - cy.getByTestId("select-privacy-notice").click(); - cy.get(".select-privacy-notice__menu") - .find(".select-privacy-notice__option") - .first() - .as("SelectedPrivacyNotice") - .click(); + cy.getByTestId("select-privacy-notice").antSelect(0); cy.getByTestId("input-show_layer1_notices").click(); cy.get("#preview-container") .find("#fides-banner") @@ -285,11 +269,7 @@ describe("Privacy experiences", () => { it("allows editing experience text and shows updated text in the preview", () => { cy.selectOption("input-component", "Banner and modal"); cy.getByTestId("add-privacy-notice").click(); - cy.getByTestId("select-privacy-notice").click(); - cy.get(".select-privacy-notice__menu") - .find(".select-privacy-notice__option") - .first() - .click(); + cy.getByTestId("select-privacy-notice").antSelect(0); cy.getByTestId("edit-experience-btn").click(); cy.getByTestId("input-translations.0.title") .clear() diff --git a/clients/admin-ui/cypress/e2e/privacy-notices.cy.ts b/clients/admin-ui/cypress/e2e/privacy-notices.cy.ts index b2288d33a8..53950f0d7b 100644 --- a/clients/admin-ui/cypress/e2e/privacy-notices.cy.ts +++ b/clients/admin-ui/cypress/e2e/privacy-notices.cy.ts @@ -242,13 +242,7 @@ describe("Privacy notices", () => { "Notice only", ); - cy.getByTestId("notice-locations").within(() => { - cy.get(".notice-locations--is-disabled"); - cy.get(".notice-locations__value-container").should( - "contain", - "United States", - ); - }); + cy.getByTestId("notice-locations").should("contain", "United States"); cy.getByTestId("input-has_gpc_flag").within(() => { cy.get("span").should("not.have.attr", "data-checked"); @@ -317,10 +311,7 @@ describe("Privacy notices", () => { cy.getByTestId("add-children").click(); cy.getByTestId("select-children").click(); - cy.get(".select-children__menu") - .find(".select-children__option") - .first() - .click(); + cy.get(".ant-select-dropdown").find(".ant-select-item").first().click(); cy.getByTestId("save-btn").click(); cy.wait("@patchNotices").then((interception) => { @@ -391,8 +382,7 @@ describe("Privacy notices", () => { // add a new translation cy.getByTestId("add-language-btn").click(); - cy.getByTestId("select-language").click(); - cy.get(".select-language__menu").find(".select-language__option").click(); + cy.getByTestId("select-language").antSelect("French"); cy.getByTestId("input-translations.1.title").type("Le titre"); cy.getByTestId("input-translations.1.description").type( "Un description français", diff --git a/clients/admin-ui/cypress/e2e/system-integrations-plus.cy.ts b/clients/admin-ui/cypress/e2e/system-integrations-plus.cy.ts index e3ee8373b6..0de1c76610 100644 --- a/clients/admin-ui/cypress/e2e/system-integrations-plus.cy.ts +++ b/clients/admin-ui/cypress/e2e/system-integrations-plus.cy.ts @@ -102,30 +102,10 @@ describe("System integrations", () => { cy.getByTestId("consentable-item-label").should("have.length", 5); cy.getByTestId("consentable-item-label-child").should("have.length", 6); cy.getByTestId("consentable-item-select").should("have.length", 11); - cy.getByTestId("consentable-item-select") - .first() - .within(() => { - cy.get(".custom-select__input").focus().realPress(" "); - }); - cy.get(".custom-select__menu").first().should("exist"); - cy.get(".custom-select__menu") - .first() - .within(() => { - cy.get(".custom-select__option").should("have.length", 5); - }); }); it("should save the consent automation settings", () => { cy.getByTestId("accordion-consent-automation").click(); - cy.getByTestId("consentable-item-select") - .first() - .within(() => { - cy.get(".custom-select__input").focus().realPress(" "); - }); - cy.get(".custom-select__menu") - .first() - .within(() => { - cy.get(".custom-select__option").first().click(); - }); + cy.getByTestId("consentable-item-select").antSelect(0); cy.getByTestId("save-consent-automation").click(); cy.wait("@putConsentableItems").then((interception) => { cy.fixture("connectors/consentable_items.json").then((expected) => { diff --git a/clients/admin-ui/cypress/support/ant-support.ts b/clients/admin-ui/cypress/support/ant-support.ts new file mode 100644 index 0000000000..f9f0569b62 --- /dev/null +++ b/clients/admin-ui/cypress/support/ant-support.ts @@ -0,0 +1,62 @@ +/// + +declare global { + namespace Cypress { + interface Chainable { + getAntSelectOption: (optionLabel: string | number) => Chainable; + /** + * Select an option from an Ant Design Select component + * @param option The label of the option to select or the index of the option + */ + antSelect: ( + option: string | number, + clickOptions?: { force?: boolean }, + ) => void; + /** + * Clear all options from an Ant Design Select component + */ + antClearSelect: () => void; + } + } +} + +Cypress.Commands.add("getAntSelectOption", (option: string | number) => + typeof option === "string" + ? cy.get(`.ant-select-item-option[title="${option}"]`) + : cy.get(`.ant-select-item-option`).eq(option), +); + +Cypress.Commands.add( + "antSelect", + { + prevSubject: "element", + }, + (subject, option, clickOptions) => { + cy.get(subject.selector).first().should("have.class", "ant-select"); + cy.get(subject.selector) + .first() + .invoke("attr", "class") + .then((classes) => { + if (classes.includes("ant-select-disabled")) { + throw new Error("Cannot select from a disabled Ant Select component"); + } + if (!classes.includes("ant-select-open")) { + cy.get(subject.selector).first().click(clickOptions); + } + }); + cy.getAntSelectOption(option).should("be.visible").click(clickOptions); + }, +); + +Cypress.Commands.add( + "antClearSelect", + { + prevSubject: "element", + }, + (subject) => { + cy.get(subject.selector).should("have.class", "ant-select-allow-clear"); + cy.get(subject.selector).find(".ant-select-clear").click({ force: true }); + }, +); + +export {}; diff --git a/clients/admin-ui/cypress/support/e2e.ts b/clients/admin-ui/cypress/support/e2e.ts index 6f56418768..4bfe584c46 100644 --- a/clients/admin-ui/cypress/support/e2e.ts +++ b/clients/admin-ui/cypress/support/e2e.ts @@ -15,6 +15,7 @@ // Import commands.js using ES2015 syntax: import "./commands"; +import "./ant-support"; // eslint-disable-next-line import/no-extraneous-dependencies import "cypress-real-events"; diff --git a/clients/admin-ui/src/features/common/ScrollableList.tsx b/clients/admin-ui/src/features/common/ScrollableList.tsx index a24751284c..26c2232123 100644 --- a/clients/admin-ui/src/features/common/ScrollableList.tsx +++ b/clients/admin-ui/src/features/common/ScrollableList.tsx @@ -1,6 +1,6 @@ -import { Select } from "chakra-react-select"; import { AntButton as Button, + AntSelect as Select, Box, ChakraProps, DeleteIcon, @@ -13,7 +13,7 @@ import { import { motion, Reorder, useDragControls } from "framer-motion"; import { useState } from "react"; -import { Label, Option, SELECT_STYLES } from "~/features/common/form/inputs"; +import { Label, Option } from "~/features/common/form/inputs"; import QuestionTooltip from "~/features/common/QuestionTooltip"; const ScrollableListItem = ({ @@ -112,24 +112,26 @@ const ScrollableListAdd = ({ const [isAdding, setIsAdding] = useState(false); const [selectValue, setSelectValue] = useState