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

UI to delete a system #1085

Merged
merged 6 commits into from
Sep 19, 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 @@ -27,6 +27,7 @@ The types of changes are:
* New page to add a system via yaml [#1062](https://github.com/ethyca/fides/pull/1062)
* Skeleton of page to add a system manually [#1068](https://github.com/ethyca/fides/pull/1068)
* Refactor config wizard system forms to be reused for system management [#1072](https://github.com/ethyca/fides/pull/1072)
* Delete a system through the UI [#1085](https://github.com/ethyca/fides/pull/1085)

### Changed

Expand Down
63 changes: 57 additions & 6 deletions clients/ctl/admin-ui/cypress/e2e/systems.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ describe("System management page", () => {

it("Can render system cards", () => {
cy.getByTestId("system-fidesctl_system");
// Uncomment when we enable the more actions button
// cy.getByTestId("system-fidesctl_system").within(() => {
// cy.getByTestId("more-btn").click();
// cy.getByTestId("edit-btn");
// cy.getByTestId("delete-btn");
// });

cy.getByTestId("system-fidesctl_system").within(() => {
cy.getByTestId("more-btn").click();
// Uncomment when we enable the edit button
// cy.getByTestId("edit-btn");
cy.getByTestId("delete-btn");
});
cy.getByTestId("system-demo_analytics_system");
cy.getByTestId("system-demo_marketing_system");
});
Expand Down Expand Up @@ -228,4 +229,54 @@ describe("System management page", () => {
});
});
});

describe("Can delete a system", () => {
beforeEach(() => {
cy.fixture("system.json").then((system) => {
cy.intercept("DELETE", "/api/v1/system/*", {
body: {
message: "resource deleted",
resource: system,
},
}).as("deleteSystem");
});
});

it("Can delete a system from its card", () => {
cy.visit("/system");
cy.getByTestId("system-fidesctl_system").within(() => {
cy.getByTestId("more-btn").click();
cy.getByTestId("delete-btn").click();
});
cy.getByTestId("confirmation-modal");
cy.getByTestId("continue-btn").click();
cy.wait("@deleteSystem").then((interception) => {
const { url } = interception.request;
expect(url).to.contain("fidesctl_system");
});
cy.getByTestId("toast-success-msg");
});

it.only("Can render an error on delete", () => {
cy.intercept("DELETE", "/api/v1/system/*", {
statusCode: 404,
body: {
detail: {
error: "resource does not exist",
fides_key: "key",
resource_type: "System",
},
},
}).as("deleteSystemError");
cy.visit("/system");
cy.getByTestId("system-fidesctl_system").within(() => {
cy.getByTestId("more-btn").click();
cy.getByTestId("delete-btn").click();
});
cy.getByTestId("confirmation-modal");
cy.getByTestId("continue-btn").click();
cy.wait("@deleteSystemError");
cy.getByTestId("toast-error-msg").contains("resource does not exist");
});
});
});
4 changes: 4 additions & 0 deletions clients/ctl/admin-ui/src/features/common/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
isAPIError,
isDetailStringErrorData,
isHTTPValidationErrorData,
isNotFoundError,
isParsingError,
RTKErrorResult,
} from "~/types/errors/api";
Expand All @@ -32,6 +33,9 @@ export const getErrorMessage = (
if (error.status === 409 && isAlreadyExistsErrorData(error.data)) {
return `${error.data.detail.error} (${error.data.detail.fides_key})`;
}
if (error.status === 404 && isNotFoundError(error.data)) {
return `${error.data.detail.error} (${error.data.detail.fides_key})`;
}
}

return defaultMsg;
Expand Down
92 changes: 69 additions & 23 deletions clients/ctl/admin-ui/src/features/system/SystemCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,96 @@ import {
MenuItem,
MenuList,
Text,
useDisclosure,
useToast,
} from "@fidesui/react";

import ConfirmationModal from "~/features/common/ConfirmationModal";
import { MoreIcon } from "~/features/common/Icon";
import { System } from "~/types/api";

import { getErrorMessage, isErrorResult } from "../common/helpers";
import { errorToastParams, successToastParams } from "../common/toast";
import { useDeleteSystemMutation } from "./system.slice";

interface SystemCardProps {
system: System;
}
const SystemCard = ({ system }: SystemCardProps) => {
// TODO fides#1035, fides#1036
const showMoreActions = false; // disable while feature is not implemented yet
const {
isOpen: deleteIsOpen,
onOpen: onDeleteOpen,
onClose: onDeleteClose,
} = useDisclosure();
const toast = useToast();

const [deleteSystem] = useDeleteSystemMutation();

// TODO fides#1035
const showEditButton = false; // disable while feature is not implemented yet
const handleEdit = () => {};
const handleDelete = () => {};
const handleDelete = async () => {
const result = await deleteSystem(system.fides_key);
if (isErrorResult(result)) {
toast(errorToastParams(getErrorMessage(result.error)));
} else {
toast(successToastParams("Successfully deleted system"));
}
onDeleteClose();
};

const systemName =
system.name === "" || system.name == null ? system.fides_key : system.name;

return (
<Box display="flex" p={4} data-testid={`system-${system.fides_key}`}>
<Box flexGrow={1}>
<Box display="flex" data-testid={`system-${system.fides_key}`}>
<Box flexGrow={1} p={4}>
<Heading as="h2" fontSize="16px" mb={2}>
{system.name}
{systemName}
</Heading>
<Box color="gray.600" fontSize="14px">
<Text>{system.description}</Text>
</Box>
</Box>
{showMoreActions ? (
<Menu>
<MenuButton
as={IconButton}
icon={<MoreIcon />}
aria-label="more actions"
variant="unstyled"
size="sm"
data-testid="more-btn"
/>
<MenuList>
<Menu>
<MenuButton
as={IconButton}
icon={<MoreIcon />}
aria-label="more actions"
variant="unstyled"
size="sm"
data-testid="more-btn"
m={1}
/>
<MenuList>
{showEditButton && (
<MenuItem onClick={handleEdit} data-testid="edit-btn">
Edit
</MenuItem>
<MenuItem onClick={handleDelete} data-testid="delete-btn">
Delete
</MenuItem>
</MenuList>
</Menu>
) : null}
)}
<MenuItem onClick={onDeleteOpen} data-testid="delete-btn">
Delete
</MenuItem>
</MenuList>
</Menu>
<ConfirmationModal
isOpen={deleteIsOpen}
onClose={onDeleteClose}
onConfirm={handleDelete}
title={`Delete ${systemName}`}
message={
<>
<Text>
You are about to permanently delete the system{" "}
<Text color="complimentary.500" as="span" fontWeight="bold">
{systemName}
</Text>
.
</Text>
<Text>Are you sure you would like to continue?</Text>
</>
}
/>
</Box>
);
};
Expand Down
14 changes: 14 additions & 0 deletions clients/ctl/admin-ui/src/features/system/system.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

import { System } from "~/types/api";

interface SystemDeleteResponse {
message: string;
resource: System;
}

export const systemApi = createApi({
reducerPath: "systemApi",
baseQuery: fetchBaseQuery({
Expand All @@ -28,6 +33,14 @@ export const systemApi = createApi({
}),
invalidatesTags: () => ["System"],
}),
deleteSystem: build.mutation<SystemDeleteResponse, string>({
query: (key) => ({
url: `system/${key}`,
params: { resource_type: "system" },
method: "DELETE",
}),
invalidatesTags: ["System"],
}),
updateSystem: build.mutation<
System,
Partial<System> & Pick<System, "fides_key">
Expand Down Expand Up @@ -73,6 +86,7 @@ export const {
useGetSystemByFidesKeyQuery,
useCreateSystemMutation,
useUpdateSystemMutation,
useDeleteSystemMutation,
} = systemApi;

export interface State {}
Expand Down
13 changes: 13 additions & 0 deletions clients/ctl/admin-ui/src/types/errors/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DetailStringError,
HTTPException,
HTTPValidationError,
NotFoundError,
} from "./models";

/**
Expand Down Expand Up @@ -90,6 +91,18 @@ export const isAlreadyExistsErrorData = (
data
);

export const isNotFoundError = (data: unknown): data is NotFoundError =>
narrow(
{
detail: {
error: "string",
resource_type: "string",
fides_key: "string",
},
},
data
);

export const isHTTPValidationErrorData = (
data: unknown
): data is HTTPValidationError =>
Expand Down
8 changes: 6 additions & 2 deletions clients/ctl/admin-ui/src/types/errors/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ export interface DetailStringError {
}

/**
* Source: https://github.com/ethyca/fides/blob/main/src/fidesapi/utils/errors.py#L4
* Source: https://github.com/ethyca/fides/blob/main/src/fidesctl/api/ctl/utils/errors.py#L4
*/
export interface AlreadyExistsError {
interface ErrorDetails {
detail: {
error: string;
resource_type: string;
fides_key: string;
};
}

export type AlreadyExistsError = ErrorDetails;

export type NotFoundError = ErrorDetails;