Skip to content
This repository was archived by the owner on Nov 30, 2022. It is now read-only.
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
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ The types of changes are:
* Added `due_date` sorting [#1284](https://github.com/ethyca/fidesops/pull/1284)
* Added erasure endpoints for Shopify connector [#1289](https://github.com/ethyca/fidesops/pull/1289)
* Adds ability to send email notification upon privacy request completion [#1282](https://github.com/ethyca/fidesops/pull/1282)
* Enable new manual webhooks in privacy request execution [#1285](https://github.com/ethyca/fidesops/pull/1285)
* Added human readable label to ConnectionType endpoint [#1297](https://github.com/ethyca/fidesops/pull/1297)
* Enable new manual webhooks in privacy request execution [#1285](https://github.com/ethyca/fidesops/pull/1285)
* Add table for consent [#1301](https://github.com/ethyca/fidesops/pull/1301)
* Adds ability to send email notification upon privacy request receipt [#1303](https://github.com/ethyca/fidesops/pull/1303)
* Add table for consent (#1301)[https://github.com/ethyca/fidesops/pull/1301]
* Utility to update SaaS config instances based on template updates [#1307](https://github.com/ethyca/fidesops/pull/1307)
* Added generic request sorting button [#1320](https://github.com/ethyca/fidesops/pull/1320)


### Docs

Expand Down
171 changes: 171 additions & 0 deletions clients/ops/admin-ui/src/features/common/Icon/SortArrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { Icon } from "@fidesui/react";
import React from "react";

type SortArrowProps = {
up?: boolean;
};

const SortArrow: React.FC<SortArrowProps> = ({ up }) => {
if (up === undefined) {
return (
<Icon width="24px" height="26px" viewBox="0 0 24 26" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="26"
viewBox="0 0 24 26"
fill="none"
>
<path
d="M8.72726 15.129L10.9716 12.8145L12 13.875L7.99998 18L4 13.875L5.02837 12.8145L7.27271 15.129V6H8.72726V15.129Z"
fill="#2D3748"
/>
<path
d="M16.7523 8.871V18H15.3089V8.871L13.0205 11.25L12 10.1895L16.0306 6L20 10.125L18.9795 11.1855L16.7523 8.871Z"
fill="#2D3748"
/>
</svg>
</Icon>
);
}

if (up) {
return (
<Icon
width="24px"
height="26px"
viewBox="0 0 24 26"
fill="none"
style={{
transform: "rotate(180deg)",
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="26"
viewBox="0 0 24 26"
fill="none"
>
<g filter="url(#filter0_d_3399_48680)">
<path
d="M8.72726 15.129L10.9716 12.8145L12 13.875L7.99998 18L4 13.875L5.02837 12.8145L7.27271 15.129V6H8.72726V15.129Z"
fill="#2D3748"
/>
<path
d="M8.22726 15.129V16.3629L9.08621 15.4771L10.9716 13.5327L11.3035 13.875L7.99998 17.2818L4.69648 13.875L5.02837 13.5327L6.91375 15.4771L7.77271 16.3629V15.129V6.5H8.22726V15.129Z"
stroke="black"
/>
</g>
<path
d="M16.7523 8.871V18H15.3089V8.871L13.0205 11.25L12 10.1895L16.0306 6L20 10.125L18.9795 11.1855L16.7523 8.871Z"
fill="#2D3748"
/>
<defs>
<filter
id="filter0_d_3399_48680"
x="0"
y="6"
width="16"
height="20"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="4" />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_3399_48680"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_3399_48680"
result="shape"
/>
</filter>
</defs>
</svg>
</Icon>
);
}

return (
<Icon width="24px" height="26px" viewBox="0 0 24 26" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="26"
viewBox="0 0 24 26"
fill="none"
>
<g filter="url(#filter0_d_3399_48680)">
<path
d="M8.72726 15.129L10.9716 12.8145L12 13.875L7.99998 18L4 13.875L5.02837 12.8145L7.27271 15.129V6H8.72726V15.129Z"
fill="#2D3748"
/>
<path
d="M8.22726 15.129V16.3629L9.08621 15.4771L10.9716 13.5327L11.3035 13.875L7.99998 17.2818L4.69648 13.875L5.02837 13.5327L6.91375 15.4771L7.77271 16.3629V15.129V6.5H8.22726V15.129Z"
stroke="black"
/>
</g>
<path
d="M16.7523 8.871V18H15.3089V8.871L13.0205 11.25L12 10.1895L16.0306 6L20 10.125L18.9795 11.1855L16.7523 8.871Z"
fill="#2D3748"
/>
<defs>
<filter
id="filter0_d_3399_48680"
x="0"
y="6"
width="16"
height="20"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feColorMatrix
in="SourceAlpha"
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
result="hardAlpha"
/>
<feOffset dy="4" />
<feGaussianBlur stdDeviation="2" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix
type="matrix"
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"
/>
<feBlend
mode="normal"
in2="BackgroundImageFix"
result="effect1_dropShadow_3399_48680"
/>
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_dropShadow_3399_48680"
result="shape"
/>
</filter>
</defs>
</svg>
</Icon>
);
};

export default SortArrow;
1 change: 1 addition & 0 deletions clients/ops/admin-ui/src/features/common/Icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export { default as GearIcon } from "./Gear";
export { default as GreenCheckCircleIcon } from "./GreenCheckCircle";
export { default as MoreIcon } from "./More";
export { default as SearchLineIcon } from "./SearchLine";
export { default as SortArrowIcon } from "./SortArrow";
export { default as UserIcon } from "./User";
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Table, Tbody, Th, Thead, Tr } from "@fidesui/react";
import { Flex, Table, Tbody, Th, Thead, Tr } from "@fidesui/react";
import { debounce } from "common/utils";
import React, { useEffect, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
Expand All @@ -10,6 +10,7 @@ import {
useGetAllPrivacyRequestsQuery,
} from "./privacy-requests.slice";
import RequestRow from "./RequestRow";
import SortRequestButton from "./SortRequestButton";
import { PrivacyRequest, PrivacyRequestParams } from "./types";

interface RequestTableProps {
Expand Down Expand Up @@ -39,11 +40,13 @@ const useRequestTable = () => {
dispatch(setPage(filters.page + 1));
};

const { data, isLoading } = useGetAllPrivacyRequestsQuery(cachedFilters);
const { data, isLoading, isFetching } =
useGetAllPrivacyRequestsQuery(cachedFilters);
const { items: requests, total } = data || { items: [], total: 0 };
return {
...filters,
isLoading,
isFetching,
requests,
total,
handleNextPage,
Expand All @@ -52,16 +55,31 @@ const useRequestTable = () => {
};

const RequestTable: React.FC<RequestTableProps> = () => {
const { requests, total, page, size, handleNextPage, handlePreviousPage } =
useRequestTable();
const {
requests,
total,
page,
size,
handleNextPage,
handlePreviousPage,
isFetching,
} = useRequestTable();

return (
<>
<Table size="sm">
<Thead>
<Tr>
<Th pl={0}>Status</Th>
<Th>Days Left</Th>
<Th>
<Flex alignItems="center">
Days Left{" "}
<SortRequestButton
sortField="due_date"
isLoading={isFetching}
/>
</Flex>
</Th>
<Th>Request Type</Th>
<Th>Subject Identity</Th>
<Th>Time Received</Th>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Flex, IconButton } from "@fidesui/react";
import { SortArrowIcon } from "common/Icon";
import React, { useCallback, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";

import {
clearSortFields,
selectPrivacyRequestFilters,
setSortDirection,
setSortField,
} from "./privacy-requests.slice";

type UseSortRequestButtonParams = {
sortField: string;
isLoading: boolean;
};

export enum ButtonState {
ASC = "asc",
DESC = "desc",
UNSELECTED = "unselected",
}

const useSortRequestButton = ({
sortField,
isLoading,
}: UseSortRequestButtonParams) => {
const filters = useSelector(selectPrivacyRequestFilters);
const dispatch = useDispatch();
const [buttonState, setButtonState] = useState<ButtonState>(
ButtonState.UNSELECTED
);
const [wasButtonJustClicked, setWasButtonJustClicked] = useState(false);

useEffect(() => {
if (!isLoading) {
setWasButtonJustClicked(false);
}
}, [isLoading]);

useEffect(() => {
if (filters.sort_direction === undefined) {
setButtonState(ButtonState.UNSELECTED);
}
}, [filters]);

const handleButtonClick = useCallback(() => {
setWasButtonJustClicked(true);
dispatch(setSortField(sortField));

switch (buttonState) {
case ButtonState.UNSELECTED:
dispatch(setSortDirection(ButtonState.ASC));
setButtonState(ButtonState.ASC);
break;
case ButtonState.ASC:
dispatch(setSortDirection(ButtonState.DESC));
setButtonState(ButtonState.DESC);
break;
case ButtonState.DESC:
dispatch(clearSortFields());
setButtonState(ButtonState.UNSELECTED);
break;
default:
break;
}
}, [buttonState, setButtonState, dispatch, sortField]);

return {
handleButtonClick,
buttonState,
wasButtonJustClicked,
};
};

type SortRequestButtonProps = {
sortField: string;
isLoading: boolean;
};

const SortRequestButton: React.FC<SortRequestButtonProps> = ({
sortField,
isLoading,
}) => {
const { buttonState, handleButtonClick, wasButtonJustClicked } =
useSortRequestButton({ sortField, isLoading });

let icon = null;

switch (buttonState) {
case ButtonState.ASC:
icon = <SortArrowIcon up />;
break;
case ButtonState.DESC:
icon = <SortArrowIcon up={false} />;
break;
case ButtonState.UNSELECTED:
icon = <SortArrowIcon />;
break;
default:
icon = <SortArrowIcon />;
}

return (
<Flex paddingLeft="3px">
<IconButton
variant="ghost"
aria-label="Sort requests"
icon={icon}
isLoading={isLoading && wasButtonJustClicked}
onClick={handleButtonClick}
/>
</Flex>
);
};

export default SortRequestButton;
Loading