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

PROD-2481 Update manage datasets pages #5191

Merged
merged 34 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d48482b
PROD-2481 Dataset table: add edit column
Aug 1, 2024
968258b
PROD-2481 Add edit drawer to dataset page
Aug 2, 2024
76b3fb8
PROD-2481 Adjust dataset drawer styling. Remove data categories input.
Aug 5, 2024
ffa9ab6
PROD-2481 Adjust dataset drawer styling.
Aug 5, 2024
2c9be2c
PROD-2481 Update add dataset buttons styling
Aug 5, 2024
bd86904
PROD-2481 WIP
Aug 9, 2024
d15b840
PROD-2481 WIP
Aug 13, 2024
a2a9675
PROD-2481 WIP
Aug 13, 2024
bc6530f
Merge branch 'main' of github.com:ethyca/fides into PROD-2481-Update-…
Aug 13, 2024
d55bb93
PROD-2481 WIP
Aug 13, 2024
1a9890b
PROD-2481 WIP
Aug 14, 2024
21a5b57
PROD-2481 Add dataset breadcrumbs and icons
Aug 14, 2024
8538347
PROD-2481 Dataset Header styling
Aug 14, 2024
9f014ce
PROD_2481 Client side search
Aug 14, 2024
6cb04f4
PROD-2481 Fix bug with closing tray
Aug 14, 2024
c0df9d4
PROD-2481 Remove unused files after refactor
Aug 14, 2024
92832e8
Merge branch 'main' of github.com:ethyca/fides into PROD-2481-Update-…
Aug 14, 2024
d081341
PROD-2481 Fix lint issues
Aug 14, 2024
32b67cf
PROD-2481 Update changelog
Aug 14, 2024
0b21ec2
PROD-2481 fix build issuer
Aug 14, 2024
6e8301e
PROD-2481 Update tests
Aug 14, 2024
4937471
PROD-2481 Fix not being able to add data categories after removing al…
Aug 14, 2024
3e16c05
Merge branch 'main' of github.com:ethyca/fides into PROD-2481-Update-…
Aug 14, 2024
99bbd6c
Fixed datset breadcrumb icons size
Aug 14, 2024
54fb01f
Merge branch 'main' of github.com:ethyca/fides into PROD-2481-Update-…
Aug 14, 2024
de1e130
PROD-2481 Adjust D&D title and breadcrumbs for consistency. Remove du…
Aug 14, 2024
0551240
fix findResourceType
jpople Aug 14, 2024
6c08541
Eslint fix
Aug 14, 2024
752ed61
PROD-2481 Update tests
Aug 14, 2024
0ad2e9b
PROD-2481 Update tests
Aug 14, 2024
fbaa7ab
PROD-2481 Fix build
Aug 14, 2024
77e461b
PROD-2481 Fix build
Aug 14, 2024
88e1f39
PROD-2481 Delete test for Dataset classification that were skipped al…
Aug 14, 2024
48aea5d
Fix tests
Aug 14, 2024
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 @@ -40,6 +40,7 @@ The types of changes are:
- Removed PRIVACY_REQUEST_READ scope from Viewer role [#5184](https://github.com/ethyca/fides/pull/5184)
- Asynchronously load GVL translations in FidesJS instead of blocking UI rendering [#5187](https://github.com/ethyca/fides/pull/5187)
- Model changes to support consent signals (Fidesplus) [#5190](https://github.com/ethyca/fides/pull/5190)
- Updated Datasets page with new UI for better usability and consistency with Detection and Discovery UI [#5191](https://github.com/ethyca/fides/pull/5191)
- Updated the Yotpo Reviews integration to use email and phone number identities instead of external ID [#5169](https://github.com/ethyca/fides/pull/5169)


Expand Down
106 changes: 64 additions & 42 deletions clients/admin-ui/cypress/e2e/datasets.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,78 +13,100 @@ describe("Dataset", () => {
});

describe("List of datasets view", () => {
it("Can navigate to the datasets view via URL", () => {
cy.visit("/dataset");
cy.getByTestId("dataset-table");
});

it("Can navigate to the datasets list view", () => {
cy.visit("/");
cy.getByTestId("Manage datasets-nav-link").click();
cy.wait("@getFilteredDatasets");
cy.getByTestId("dataset-table");
cy.getByTestId("row-3");
});

it("Can edit a dataset from the list view / edit drawer", () => {
cy.visit("/dataset");
cy.wait("@getFilteredDatasets");
cy.getByTestId("row-0-col-actions").find("button").click();

cy.getByTestId("input-name").should("have.value", "Demo Users Dataset");
cy.getByTestId("input-description").should(
"have.value",
"Data collected about users for our analytics system.",
);

// The classifier toggle should not be available.
cy.get("input-classify").should("not.exist");
cy.getByTestId("input-name").clear().type("New dataset name");
cy.getByTestId("input-description").clear().type("New description");
cy.getByTestId("save-btn").click();

cy.getByTestId("dataset-table__status-table-header").should("not.exist");
cy.getByTestId("classification-status-badge").should("not.exist");
cy.wait("@putDataset").then((interception) => {
const { body } = interception.request;
expect(body.name).to.eql("New dataset name");
expect(body.description).to.eql("New description");
});
});

it("Can navigate to the datasets view via URL", () => {
it("Can delete a dataset from the list view / edit drawer", () => {
cy.visit("/dataset");
cy.wait("@getFilteredDatasets");
cy.getByTestId("row-0-col-actions").find("button").click();
cy.getByTestId("delete-btn").click();
cy.getByTestId("continue-btn").click();
cy.wait("@deleteDataset").then((interception) => {
expect(interception.request.url).to.contain("demo_users_dataset");
});
cy.getByTestId("toast-success-msg");
});

it("Can use the search bar to filter datasets", () => {
cy.visit("/dataset");
cy.wait("@getFilteredDatasets");
cy.getByTestId("dataset-table");
cy.getByTestId("row-3");

cy.getByTestId("dataset-search").type("postgres");
cy.wait("@getFilteredDatasets").then((interception) => {
const { url } = interception.request;
expect(url).to.contain("search=postgres");
});
});

it("Can load an individual dataset", () => {
it("Can click on a row to navigate an dataset detail page", () => {
cy.visit("/dataset");
cy.wait("@getFilteredDatasets");
cy.getByTestId("row-0").click();
// for some reason this is slow in CI, so add a timeout :(
cy.url({ timeout: 10000 }).should(
"contain",
"/dataset/demo_users_dataset",
);
cy.getByTestId("dataset-fields-table");
cy.url().should("contain", "/dataset/demo_users_dataset");
cy.getByTestId("collections-table");
});
});

describe("Dataset fields view", () => {
it("Can navigate to edit a dataset via URL", () => {
cy.visit("/dataset/demo_users_dataset");
cy.getByTestId("dataset-fields-table");
cy.getByTestId("collections-table");
});

it("Can choose different columns to view", () => {
const columnNames = [
"Field Name",
"Description",
"Personal Data Categories",
"Identifiability",
];
it("Displays a table with the dataset's collections", () => {
cy.visit("/dataset/demo_users_dataset");
// check we can remove a column
cy.getByTestId(`column-${columnNames[0]}`);
cy.getByTestId("column-dropdown").click();
cy.getByTestId(`checkbox-${columnNames[0]}`).click();
cy.getByTestId(`column-${columnNames[0]}`).should("not.exist");

// check we can clear all columns
cy.getByTestId("column-clear-btn").click();
columnNames.forEach((c) => {
cy.getByTestId(`column-${c}`).should("not.exist");
});

// check we can add a column back
cy.getByTestId(`checkbox-${columnNames[1]}`).click({ force: true });
cy.getByTestId(`column-${columnNames[1]}`);
cy.getByTestId("collections-table");
cy.getByTestId("row-0-col-name").contains("users");
cy.getByTestId("row-1-col-name").contains("products");
});

// clicking 'done' should close the modal
cy.getByTestId("column-done-btn").click();
cy.getByTestId(`checkbox-${columnNames[0]}`).should("not.be.visible");
it.only("Can use the search bar to filter collections", () => {
cy.visit("/dataset/demo_users_dataset");
cy.getByTestId("collections-table");
cy.getByTestId("collections-search").type("products");
cy.getByTestId("row-0-col-name").contains("products");
cy.getByTestId("row-1-col-name").should("not.exist");
});

it("Can choose a different collection", () => {
it("Can navigate to a collection's fields view", () => {
cy.visit("/dataset/demo_users_dataset");
cy.getByTestId("field-row-price").should("not.exist");
cy.getByTestId("collection-select").select("products");
cy.getByTestId("field-row-price").should("exist");
cy.getByTestId("row-0-col-name").click();
cy.getByTestId("fields-table");
});

it("Can render an edit form for a dataset field with existing values", () => {
Expand Down
11 changes: 4 additions & 7 deletions clients/admin-ui/cypress/support/stubs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,10 @@ export const stubDatasetCrud = () => {
cy.intercept("GET", "/api/v1/dataset", { fixture: "datasets.json" }).as(
"getDatasets",
);
cy.intercept(
"GET",
"/api/v1/dataset?page=1&size=25&exclude_saas_datasets=true",
{
fixture: "datasets_paginated.json",
},
).as("getFilteredDatasets");
cy.intercept("GET", "/api/v1/dataset?page*", {
fixture: "datasets_paginated.json",
}).as("getFilteredDatasets");

cy.intercept("GET", "/api/v1/dataset/*", { fixture: "dataset.json" }).as(
"getDataset",
);
Expand Down
54 changes: 41 additions & 13 deletions clients/admin-ui/src/features/common/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink } from "fidesui";
import {
Box,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the breadcrumbs component. Added support for icons, expanded Link to be able to receive query params, added prop optionally be able to override some styles. Remove isOpaque props since it was too specific and it won't be used anymore. Removed onClick since it wasn't working nor being used.

Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbProps as ChakraBreadcrumbProps,
HTMLChakraProps,
} from "fidesui";
import { Url } from "next/dist/shared/lib/router/router";
import NextLink from "next/link";

export interface BreadcrumbsProps {
export interface BreadcrumbsProps extends ChakraBreadcrumbProps {
breadcrumbs: {
title: string;
link?: string;
link?: Url; // Next.js link url. It can be a string or an URL object (accepts query params)
onClick?: () => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't know this existed, neat.

isOpaque?: boolean;
icon?: React.ReactNode;
}[];
fontSize?: string;
fontWeight?: string;
separator?: string;
lastItemStyles?: HTMLChakraProps<"li">;
normalItemStyles?: HTMLChakraProps<"li">;
}

/**
Expand All @@ -16,27 +29,42 @@ export interface BreadcrumbsProps {
* @param breadcrumbs - array of breadcrumbs
* @param breadcrumbs.title - title of the breadcrumb
* @param breadcrumbs.link - (optional) link to the page
* @param breadcrumbs.onClick - (optional) function to call when the breadcrumb is clicked
* @param breadcrumbs.isOpaque - (optional) if true, the breadcrumb will be black, otherwise gray
* @param breadcrumbs.icon - (optional) icon to show before the title
*/
const Breadcrumbs = ({ breadcrumbs }: BreadcrumbsProps) => (
const Breadcrumbs = ({
breadcrumbs,
fontSize = "2xl",
fontWeight = "semibold",
separator = "->",
lastItemStyles = {
color: "black",
},
normalItemStyles = {
color: "gray.500",
},
...otherChakraBreadcrumbProps
}: BreadcrumbsProps) => (
<Breadcrumb
separator="->"
fontSize="2xl"
fontWeight="semibold"
separator={separator}
fontSize={fontSize}
fontWeight={fontWeight}
data-testid="breadcrumbs"
{...otherChakraBreadcrumbProps}
>
{breadcrumbs.map((breadcumbItem, index) => {
const isLast = index + 1 === breadcrumbs.length;
const hasLink = !!breadcumbItem.link || !!breadcumbItem.onClick;

return (
<BreadcrumbItem
color={isLast || breadcumbItem.isOpaque ? "black" : "gray.500"}
{...normalItemStyles}
{...(isLast ? lastItemStyles : {})}
key={breadcumbItem.title}
>
{hasLink ? (
{breadcumbItem?.icon && <Box mr={2}>{breadcumbItem.icon}</Box>}
{breadcumbItem.link ? (
<BreadcrumbLink
as={NextLink}
// @ts-ignore - href for chakra expects string, but can also pass a URL Object because we're using as={NextLink}.
href={breadcumbItem.link}
isCurrentPage={isLast}
>
Expand Down
55 changes: 36 additions & 19 deletions clients/admin-ui/src/features/common/EditDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated edit drawer component with some new styling.

Box,
Button,
CloseSolidIcon,
CloseIcon,
Drawer,
DrawerBody,
DrawerContent,
Expand All @@ -10,10 +10,11 @@ import {
DrawerOverlay,
IconButton,
Text,
TrashCanSolidIcon,
} from "fidesui";
import { ReactNode } from "react";

import { TrashCanOutlineIcon } from "~/features/common/Icon/TrashCanOutlineIcon";

interface Props {
header?: ReactNode;
description?: string;
Expand All @@ -30,13 +31,16 @@ export const EditDrawerHeader = ({
title: string;
onDelete?: () => void;
}) => (
<DrawerHeader py={2} display="flex" alignItems="center">
<Text mr="2">{title}</Text>
<DrawerHeader py={0} display="flex" alignItems="flex-start">
<Text mr="2" color="gray.700" fontSize="lg" lineHeight={1.8}>
{title}
</Text>
{onDelete ? (
<IconButton
variant="outline"
aria-label="delete"
icon={<TrashCanSolidIcon />}
size="xs"
icon={<TrashCanOutlineIcon fontSize="small" />}
size="sm"
onClick={onDelete}
data-testid="delete-btn"
/>
Expand All @@ -56,7 +60,7 @@ export const EditDrawerFooter = ({
formId?: string;
isSaving?: boolean;
} & Pick<Props, "onClose">) => (
<DrawerFooter justifyContent="flex-start">
<DrawerFooter justifyContent="space-between">
<Button onClick={onClose} mr={2} size="sm" variant="outline">
Cancel
</Button>
Expand All @@ -81,22 +85,35 @@ const EditDrawer = ({
children,
footer,
}: Props) => (
<Drawer placement="right" isOpen={isOpen} onClose={onClose} size="lg">
<Drawer placement="right" isOpen={isOpen} onClose={onClose} size="md">
<DrawerOverlay />
<DrawerContent data-testid="edit-drawer-content" py={2}>
<Box display="flex" justifyContent="flex-end" mr={2}>
<Button
variant="ghost"
onClick={onClose}
data-testid="close-drawer-btn"
>
<CloseSolidIcon width="17px" />
</Button>
<Box
display="flex"
justifyContent="space-between"
alignItems="top"
mr={2}
py={2}
gap={2}
>
<Box flex={1} minH={8}>
{header}
</Box>
<Box display="flex" justifyContent="flex-end" mr={2}>
<IconButton
aria-label="Close editor"
variant="outline"
onClick={onClose}
data-testid="close-drawer-btn"
size="sm"
icon={<CloseIcon fontSize="smaller" />}
/>
</Box>
</Box>
{header}
<DrawerBody>

<DrawerBody pt={1}>
{description ? (
<Text fontSize="sm" mb={4}>
<Text fontSize="sm" mb={4} color="gray.600">
{description}
</Text>
) : null}
Expand Down
Loading
Loading