Skip to content

Commit

Permalink
Refactor to use common system table and column dropdown (#1590)
Browse files Browse the repository at this point in the history
* Refactor to use common ColumnDropdown component

* Add common SystemsCheckboxTable

* Use SystemsCheckboxTable in ScanResultsForm

* Configure columns

* Fix styling to allow for larger table

* Update changelog

* Fix padding on SuccessPage
  • Loading branch information
allisonking authored Nov 1, 2022
1 parent bdb8773 commit 819ebf7
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 240 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ The types of changes are:
* Bulk select and reprocess DSRs that have errored [#1205](https://github.com/ethyca/fides/pull/1489)
* Config Wizard: AWS scan results populate in system review forms. [#1454](https://github.com/ethyca/fides/pull/1454)
* Integrate rate limiter with Saas Connectors. [#1433](https://github.com/ethyca/fides/pull/1433)
* Added a column selector to the scan results page of the config wizard [#1590](https://github.com/ethyca/fides/pull/1590)

### Changed
* Updated mypy to version 0.981 and Python to version 3.10.7 [#1448](https://github.com/ethyca/fides/pull/1448)
Expand Down
6 changes: 2 additions & 4 deletions clients/admin-ui/cypress/e2e/config-wizard.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,8 @@ describe.skip("Config Wizard", () => {
cy.getByTestId("submit-btn").click();
cy.wait("@postGenerate");

cy.getByTestId("scan-results-form");
cy.getByTestId(`scan-result-row-example-system-1`).within(() => {
cy.getByTestId("checkbox").click();
});
cy.getByTestId("scan-results");
cy.getByTestId(`checkbox-example-system-1`).click();
cy.getByTestId("register-btn").click();

// The request while editing the form should match the generated system's body.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,42 @@ import {
MenuList,
Stack,
} from "@fidesui/react";
import { useMemo } from "react";
import React, { useMemo } from "react";

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

import { ColumnMetadata } from "./types";
export interface ColumnMetadata<T = Record<string, unknown>> {
name: string;
attribute: keyof T;
}

interface Props {
allColumns: ColumnMetadata[];
selectedColumns: ColumnMetadata[];
onChange: (columns: ColumnMetadata[]) => void;
interface Props<T> {
allColumns: ColumnMetadata<T>[];
selectedColumns: ColumnMetadata<T>[];
onChange: (columns: ColumnMetadata<T>[]) => void;
}
const ColumnDropdown = ({ allColumns, selectedColumns, onChange }: Props) => {
export const ColumnDropdown = <T extends Record<string, unknown>>({
allColumns,
selectedColumns,
onChange,
}: Props<T>) => {
const nameToColumnInfo = useMemo(() => {
const info = new Map<string, boolean>();
allColumns.forEach((c) => info.set(c.name, true));
allColumns.forEach((c) =>
info.set(
c.name,
!!selectedColumns.find((selected) => selected.name === c.name)
)
);
return info;
}, [allColumns]);
}, [allColumns, selectedColumns]);

const handleClear = () => {
nameToColumnInfo.forEach((value, key) => nameToColumnInfo.set(key, false));
onChange([]);
};

const handleChange = (column: ColumnMetadata) => {
const handleChange = (column: ColumnMetadata<T>) => {
// Toggle the column
const prevInfo = nameToColumnInfo.get(column.name) ?? false;
nameToColumnInfo.set(column.name, !prevInfo);
Expand Down
149 changes: 149 additions & 0 deletions clients/admin-ui/src/features/common/SystemsCheckboxTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import {
Box,
Checkbox,
Table,
TableHeadProps,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
} from "@fidesui/react";

import type { ColumnMetadata } from "~/features/common/ColumnDropdown";
import { System } from "~/types/api";

/**
* Index into an object with possibility of nesting
*
* Ex:
* obj = {
* a : {
* b: 'hi'
* }
* }
* resolvePath(obj, 'a') --> { b: 'hi' }
* resolvePath(obj, 'a.b') --> 'hi'
*
* @param object The object to index into
* @param path String path to use as a key
* @returns
*/
export const resolvePath = (object: Record<string, any>, path: string) =>
path.split(".").reduce((o, p) => (o ? o[p] : undefined), object);

// This component is used within a Chakra Td element. Chakra requires a
// JSX.Element in that context, so all returns in this component need to be wrapped in a fragment.
/* eslint-disable react/jsx-no-useless-fragment */
const SystemTableCell = ({
system,
attribute,
}: {
system: System;
attribute: string;
}) => {
if (attribute === "name") {
return (
<label htmlFor={`checkbox-${system.fides_key}`}>{system.name}</label>
);
}
if (attribute === "fidesctl_meta.resource_id") {
return (
<Box
whiteSpace="nowrap"
overflow="hidden"
textOverflow="ellipsis"
title={system.fidesctl_meta?.resource_id}
>
{system.fidesctl_meta?.resource_id}
</Box>
);
}
return <>{resolvePath(system, attribute)}</>;
};

interface Props {
onChange: (systems: System[]) => void;
allSystems: System[];
checked: System[];
columns: ColumnMetadata<System>[];
tableHeadProps?: TableHeadProps;
}
export const SystemsCheckboxTable = ({
allSystems,
checked,
onChange,
columns,
tableHeadProps,
}: Props) => {
const handleChangeAll = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.checked) {
onChange(allSystems);
} else {
onChange([]);
}
};
const onCheck = (system: System) => {
const exists = checked.indexOf(system) >= 0;
if (!exists) {
onChange([...checked, system]);
} else {
onChange(checked.filter((c) => c.fides_key !== system.fides_key));
}
};

const allChecked = allSystems.length === checked.length;

if (columns.length === 0) {
return <Text>No columns selected to display</Text>;
}

return (
<Table
size="sm"
/* https://github.com/chakra-ui/chakra-ui/issues/6822 */
sx={{
tableLayout: "fixed",
}}
>
<Thead {...tableHeadProps}>
<Tr>
<Th width="15px">
<Checkbox
colorScheme="complimentary"
title="Select All"
isChecked={allChecked}
onChange={handleChangeAll}
/>
</Th>
{columns.map((c) => (
<Th key={c.attribute}>{c.name}</Th>
))}
</Tr>
</Thead>
<Tbody>
{allSystems.map((system) => (
<Tr key={system.fides_key}>
<Td>
<Checkbox
colorScheme="complimentary"
value={system.fides_key}
isChecked={checked.indexOf(system) >= 0}
onChange={() => onCheck(system)}
data-testid={`checkbox-${system.fides_key}`}
/>
</Td>
{columns.map((c) => (
<Td key={c.attribute}>
<SystemTableCell system={system} attribute={c.attribute} />
</Td>
))}
</Tr>
))}
</Tbody>
</Table>
);
};

export default SystemsCheckboxTable;
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const ConfigWizardWalkthrough = () => {
</Box>
<Divider orientation="horizontal" />
<Stack direction={["column", "row"]}>
<Stack bg="white" height="100vh" maxW="60%">
<Stack bg="white" height="100vh">
<Stack mt={10} mb={10} direction="row" spacing="24px">
<Box flexShrink={0}>
<Stepper
Expand All @@ -67,50 +67,56 @@ const ConfigWizardWalkthrough = () => {
steps={STEPS}
/>
</Box>
{step === 1 ? <OrganizationInfoForm /> : null}
{step === 2 ? <AddSystemForm /> : null}
{step === 3 ? <AuthenticateScanner /> : null}
{step === 4 ? <ScanResultsForm /> : null}
{step === 5 ? (
<Stack direction="column">
{reviewStep <= 3 ? (
<HorizontalStepper
activeStep={reviewStep}
steps={HORIZONTAL_STEPS}
/>
) : null}
{reviewStep === 1 && (
<DescribeSystemStep
system={system}
onSuccess={handleSuccess}
abridged
/>
)}
{reviewStep === 2 && system && (
<PrivacyDeclarationStep
system={system}
onSuccess={handleSuccess}
abridged
/>
)}
{reviewStep === 3 && system && (
<ReviewSystemStep
system={system}
onSuccess={() => dispatch(changeReviewStep())}
abridged
/>
)}
{reviewStep === 4 && system && (
<SuccessPage
systemInReview={system}
systemsForReview={systemsForReview}
onAddNextSystem={() => {
dispatch(reviewManualSystem());
}}
/>
)}
</Stack>
) : null}
<Box w={step === 4 ? "100%" : "40%"}>
{step === 1 ? <OrganizationInfoForm /> : null}
{step === 2 ? <AddSystemForm /> : null}
{step === 3 ? <AuthenticateScanner /> : null}
{step === 4 ? (
<Box pr={10}>
<ScanResultsForm />
</Box>
) : null}
{step === 5 ? (
<Stack direction="column">
{reviewStep <= 3 ? (
<HorizontalStepper
activeStep={reviewStep}
steps={HORIZONTAL_STEPS}
/>
) : null}
{reviewStep === 1 && (
<DescribeSystemStep
system={system}
onSuccess={handleSuccess}
abridged
/>
)}
{reviewStep === 2 && system && (
<PrivacyDeclarationStep
system={system}
onSuccess={handleSuccess}
abridged
/>
)}
{reviewStep === 3 && system && (
<ReviewSystemStep
system={system}
onSuccess={() => dispatch(changeReviewStep())}
abridged
/>
)}
{reviewStep === 4 && system && (
<SuccessPage
systemInReview={system}
systemsForReview={systemsForReview}
onAddNextSystem={() => {
dispatch(reviewManualSystem());
}}
/>
)}
</Stack>
) : null}
</Box>
</Stack>
</Stack>
</Stack>
Expand Down
Loading

0 comments on commit 819ebf7

Please sign in to comment.