Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Classify data category dropdown #1110

Merged
merged 17 commits into from
Oct 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ The types of changes are:
* Added toggle for enabling classify during generation. [#1057](https://github.com/ethyca/fides/pull/1057)
* Initial implementation of API request to kick off classify, with confirmation modal. [#1069](https://github.com/ethyca/fides/pull/1069)
* Initial Classification & Review status for generated datasets. [#1074](https://github.com/ethyca/fides/pull/1074)
* Component for choosing data categories based on classification results. [#1110](https://github.com/ethyca/fides/pull/1110)
* The dataset fields table shows data categories from the classifier (if available). [#1088](https://github.com/ethyca/fides/pull/1088)
* The "Approve" button can be used to update the dataset with the classifier's suggestions. [#1129](https://github.com/ethyca/fides/pull/1129)
* System management UI:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import * as React from "react";

import ClassifiedDataCategoryDropdown from "~/features/dataset/ClassifiedDataCategoryDropdown";
import { MOCK_DATA_CATEGORIES } from "~/mocks/data";
import { DataCategory } from "~/types/api";

const CATEGORIES_WITH_CONFIDENCE = MOCK_DATA_CATEGORIES.map((dc) => ({
...dc,
confidence: Math.floor(Math.random() * 100),
}));
const MOST_LIKELY_CATEGORIES = CATEGORIES_WITH_CONFIDENCE.slice(0, 5);

describe("ClassifiedDataCategoryDropdown", () => {
it("can render checked categories", () => {
const onCheckedSpy = cy.spy().as("onCheckedSpy");
const selectedClassifiedCategory = MOST_LIKELY_CATEGORIES[0];
cy.mount(
<ClassifiedDataCategoryDropdown
dataCategories={MOCK_DATA_CATEGORIES as DataCategory[]}
ssangervasi marked this conversation as resolved.
Show resolved Hide resolved
// check one "most likely" and one regular one
checked={[
selectedClassifiedCategory.fides_key,
"system.authentication",
]}
onChecked={onCheckedSpy}
mostLikelyCategories={MOST_LIKELY_CATEGORIES}
/>
);

// check that the classified one is selected
cy.getByTestId("classified-select").should(
"contain",
selectedClassifiedCategory.fides_key
);
// check that the regular one is selected
cy.getByTestId("data-category-dropdown").click();
cy.get("[data-testid='checkbox-Authentication Data'] > span").should(
"have.attr",
"data-checked"
);
cy.get(
`[data-testid='checkbox-${selectedClassifiedCategory.name}'] > span`
).should("have.attr", "data-checked");
});

it("can render all categories even if not recommended", () => {
const onCheckedSpy = cy.spy().as("onCheckedSpy");
const selectedClassifiedCategory = MOST_LIKELY_CATEGORIES[0];
const otherCategory = MOCK_DATA_CATEGORIES[6];
cy.mount(
<ClassifiedDataCategoryDropdown
dataCategories={MOCK_DATA_CATEGORIES as DataCategory[]}
// check one "most likely" and one regular one
checked={[
selectedClassifiedCategory.fides_key,
"system.authentication",
]}
onChecked={onCheckedSpy}
mostLikelyCategories={MOST_LIKELY_CATEGORIES}
/>
);
cy.getByTestId("classified-select").contains("system.authentication (N/A)");
cy.getByTestId("classified-select")
.click()
.within(() => {
cy.contains(`${otherCategory.fides_key} (N/A)`);
});
});

it("can select from classified select without overriding taxonomy dropdown", () => {
const onCheckedSpy = cy.spy().as("onCheckedSpy");
const toSelect = MOST_LIKELY_CATEGORIES[0];
cy.mount(
<ClassifiedDataCategoryDropdown
dataCategories={MOCK_DATA_CATEGORIES as DataCategory[]}
checked={["system.authentication"]}
onChecked={onCheckedSpy}
mostLikelyCategories={MOST_LIKELY_CATEGORIES}
/>
);
cy.getByTestId("classified-select")
.click()
.type(`${toSelect.fides_key}{enter}`);
cy.get("@onCheckedSpy").should("have.been.calledWith", [
"system.authentication",
toSelect.fides_key,
]);
});

it("can select from taxonomy dropdown without overriding classified select", () => {
const onCheckedSpy = cy.spy().as("onCheckedSpy");
const selectedClassifiedCategory = MOST_LIKELY_CATEGORIES[0];
cy.mount(
<ClassifiedDataCategoryDropdown
dataCategories={MOCK_DATA_CATEGORIES as DataCategory[]}
checked={[selectedClassifiedCategory.fides_key]}
onChecked={onCheckedSpy}
mostLikelyCategories={MOST_LIKELY_CATEGORIES}
/>
);
cy.getByTestId("data-category-dropdown").click();
cy.getByTestId("expand-System Data").click();
cy.getByTestId("checkbox-Authentication Data").click();
cy.get("@onCheckedSpy").should("have.been.calledWith", [
selectedClassifiedCategory.fides_key,
"system.authentication",
]);
});

it("can remove items from classified select", () => {
const onCheckedSpy = cy.spy().as("onCheckedSpy");
const selectedClassifiedCategory = MOST_LIKELY_CATEGORIES[0];
cy.mount(
<ClassifiedDataCategoryDropdown
dataCategories={MOCK_DATA_CATEGORIES as DataCategory[]}
checked={[
selectedClassifiedCategory.fides_key,
"system.authentication",
]}
onChecked={onCheckedSpy}
mostLikelyCategories={MOST_LIKELY_CATEGORIES}
/>
);
// delete the selected category
cy.getByTestId("classified-select").click().type("{backspace}");
cy.get("@onCheckedSpy").should("have.been.calledWith", [
selectedClassifiedCategory.fides_key,
]);
});

it("can remove items from taxonomy select", () => {
const onCheckedSpy = cy.spy().as("onCheckedSpy");
const selectedClassifiedCategory = MOST_LIKELY_CATEGORIES[0];
cy.mount(
<ClassifiedDataCategoryDropdown
dataCategories={MOCK_DATA_CATEGORIES as DataCategory[]}
checked={[
selectedClassifiedCategory.fides_key,
"system.authentication",
]}
onChecked={onCheckedSpy}
mostLikelyCategories={MOST_LIKELY_CATEGORIES}
/>
);
// uncheck system authentication
cy.getByTestId("data-category-dropdown").click();
cy.getByTestId("checkbox-Authentication Data").click();
cy.get("@onCheckedSpy").should("have.been.calledWith", [
selectedClassifiedCategory.fides_key,
]);
});

it("playground", () => {
// it's useful when developing to be able to play with the component with actual react state
const ClassifiedDataCategoryDropdownWithState = () => {
const [checked, setChecked] = React.useState([]);
return (
<ClassifiedDataCategoryDropdown
dataCategories={MOCK_DATA_CATEGORIES as DataCategory[]}
checked={checked}
onChecked={setChecked}
mostLikelyCategories={MOST_LIKELY_CATEGORIES}
/>
);
};
cy.mount(<ClassifiedDataCategoryDropdownWithState />);
});
});
18 changes: 18 additions & 0 deletions clients/ctl/admin-ui/cypress/components/DataCategoryInput.cy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import DataCategoryInput from "~/features/dataset/DataCategoryInput";
import { MOCK_DATA_CATEGORIES } from "~/mocks/data";
import { DataCategory } from "~/types/api";

import { stubPlus } from "../support/stubs";

describe("DataCategoryInput", () => {
it("can check a category", () => {
const onCheckedSpy = cy.spy().as("onCheckedSpy");
Expand All @@ -15,6 +17,8 @@ describe("DataCategoryInput", () => {
/>
);

cy.getByTestId("classified-select").should("not.exist");

cy.getByTestId("selected-categories").should("contain", "user");
cy.getByTestId("data-category-dropdown").click();
cy.getByTestId("expand-System Data").click();
Expand All @@ -25,4 +29,18 @@ describe("DataCategoryInput", () => {
"system.authentication",
]);
});

it("can render the classified version", () => {
stubPlus(true);

const onCheckedSpy = cy.spy().as("onCheckedSpy");
cy.mount(
<DataCategoryInput
dataCategories={MOCK_DATA_CATEGORIES as DataCategory[]}
checked={["user"]}
onChecked={onCheckedSpy}
/>
);
cy.getByTestId("classified-select");
});
});
36 changes: 27 additions & 9 deletions clients/ctl/admin-ui/cypress/support/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ import "@fontsource/inter/500.css";
import "@fontsource/inter/700.css";

import { ChakraProvider } from "@chakra-ui/react";
import { EnhancedStore } from "@reduxjs/toolkit";
// Alternatively you can use CommonJS syntax:
// require('./commands')
// eslint-disable-next-line import/no-extraneous-dependencies
import { mount } from "cypress/react";
import { mount, MountOptions, MountReturn } from "cypress/react";
import * as React from "react";
import { Provider } from "react-redux";

import theme from "../../src/theme";
import { AppState, makeStore } from "~/app/store";
import theme from "~/theme";

// Augment the Cypress namespace to include type definitions for
// your custom command.
Expand All @@ -35,14 +38,29 @@ import theme from "../../src/theme";
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount;
/**
* Mounts a React node
* @param component React Node to mount
* @param options Additional options to pass into mount
*/
mount(
component: React.ReactNode,
options?: MountOptions & { reduxStore?: EnhancedStore<AppState> }
): Cypress.Chainable<MountReturn>;
}
}
}

Cypress.Commands.add("mount", (jsx, options) =>
mount(React.createElement(ChakraProvider, { theme }, jsx), options)
);

// Example use:
// cy.mount(<MyComponent />)
/**
* Wrap the default mount in Redux and Chakra
*/
Cypress.Commands.add("mount", (component, options = {}) => {
const { reduxStore = makeStore(), ...mountOptions } = options;
const wrapChakra = React.createElement(ChakraProvider, { theme }, component);
const wrapRedux = React.createElement(
Provider,
{ store: reduxStore },
wrapChakra
);
return mount(wrapRedux, mountOptions);
});
2 changes: 1 addition & 1 deletion clients/ctl/admin-ui/src/app/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { reducer as systemReducer, systemApi } from "~/features/system";
import { reducer as taxonomyReducer, taxonomyApi } from "~/features/taxonomy";
import { reducer as userReducer } from "~/features/user";

const makeStore = () => {
export const makeStore = () => {
const store = configureStore({
reducer: {
configWizard: configWizardReducer,
Expand Down
Loading