From c1ef4ba9b95d4812d778cf5948295b8880f1525d Mon Sep 17 00:00:00 2001 From: Lucano Vera Date: Thu, 25 Jul 2024 11:32:35 -0300 Subject: [PATCH 01/11] PROD-2332 WIP --- .../src/features/common/nav/v2/routes.ts | 1 + .../src/features/dataset/dataset.slice.ts | 10 + .../src/features/system/system.slice.ts | 7 +- clients/admin-ui/src/pages/dataset/index.tsx | 232 ++++++++++++++++-- .../src/types/common/PaginationQueryParams.ts | 4 + 5 files changed, 231 insertions(+), 23 deletions(-) create mode 100644 clients/admin-ui/src/types/common/PaginationQueryParams.ts diff --git a/clients/admin-ui/src/features/common/nav/v2/routes.ts b/clients/admin-ui/src/features/common/nav/v2/routes.ts index d1f4955a39..dad72cee6e 100644 --- a/clients/admin-ui/src/features/common/nav/v2/routes.ts +++ b/clients/admin-ui/src/features/common/nav/v2/routes.ts @@ -11,6 +11,7 @@ export const SYSTEM_ROUTE = "/systems"; export const EDIT_SYSTEM_ROUTE = "/systems/configure/[id]"; export const CLASSIFY_SYSTEMS_ROUTE = "/classify-systems"; export const DATASET_ROUTE = "/dataset"; +export const DATASET_EDIT_ROUTE = "/dataset/:id"; // Detection and discovery export const DETECTION_DISCOVERY_ACTIVITY_ROUTE = "/data-discovery/activity"; diff --git a/clients/admin-ui/src/features/dataset/dataset.slice.ts b/clients/admin-ui/src/features/dataset/dataset.slice.ts index d123be032f..4144616465 100644 --- a/clients/admin-ui/src/features/dataset/dataset.slice.ts +++ b/clients/admin-ui/src/features/dataset/dataset.slice.ts @@ -8,6 +8,7 @@ import { GenerateRequestPayload, GenerateResponse, } from "~/types/api"; +import { PaginationQueryParams } from "~/types/common/PaginationQueryParams"; import { EditableType } from "./types"; @@ -29,6 +30,14 @@ interface DatasetDeleteResponse { const datasetApi = baseApi.injectEndpoints({ endpoints: (build) => ({ + getDatasets: build.query({ + query: (params) => ({ + method: "GET", + url: `dataset`, + params, + }), + providesTags: () => ["Datasets"], + }), getAllDatasets: build.query({ query: () => ({ url: `dataset` }), providesTags: () => ["Datasets"], @@ -99,6 +108,7 @@ const datasetApi = baseApi.injectEndpoints({ }); export const { + useGetDatasetsQuery, useGetAllDatasetsQuery, useGetAllFilteredDatasetsQuery, useGetDatasetByKeyQuery, diff --git a/clients/admin-ui/src/features/system/system.slice.ts b/clients/admin-ui/src/features/system/system.slice.ts index 41fc0a864a..7d2a5cd399 100644 --- a/clients/admin-ui/src/features/system/system.slice.ts +++ b/clients/admin-ui/src/features/system/system.slice.ts @@ -11,6 +11,7 @@ import { SystemResponse, TestStatusMessage, } from "~/types/api"; +import { PaginationQueryParams } from "~/types/common/PaginationQueryParams"; interface SystemDeleteResponse { message: string; @@ -23,10 +24,6 @@ interface UpsertResponse { updated: number; } -interface PaginationParams { - page: number; - size: number; -} interface SearchParams { search?: string; } @@ -42,7 +39,7 @@ const systemApi = baseApi.injectEndpoints({ endpoints: (build) => ({ getSystems: build.query< Page_BasicSystemResponse_, - PaginationParams & SearchParams + PaginationQueryParams & SearchParams >({ query: (params) => ({ method: "GET", diff --git a/clients/admin-ui/src/pages/dataset/index.tsx b/clients/admin-ui/src/pages/dataset/index.tsx index d3a4cddde5..bcda824367 100644 --- a/clients/admin-ui/src/pages/dataset/index.tsx +++ b/clients/admin-ui/src/pages/dataset/index.tsx @@ -1,35 +1,231 @@ -import { Box, Button, Spinner } from "fidesui"; +/* eslint-disable @typescript-eslint/no-use-before-define */ +/* eslint-disable react/no-unstable-nested-components */ +import { + createColumnHelper, + getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { + Box, + Button, + EditIcon, + HStack, + IconButton, + Text, + VStack, +} from "fidesui"; import type { NextPage } from "next"; import NextLink from "next/link"; +import { useRouter } from "next/router"; +import { useCallback, useMemo } from "react"; +import { useDispatch } from "react-redux"; import Layout from "~/features/common/Layout"; +import { DATASET_EDIT_ROUTE } from "~/features/common/nav/v2/routes"; import PageHeader from "~/features/common/PageHeader"; -import { useGetAllFilteredDatasetsQuery } from "~/features/dataset/dataset.slice"; -import DatasetsTable from "~/features/dataset/DatasetTable"; +import { + DefaultCell, + DefaultHeaderCell, + FidesTableV2, + GlobalFilterV2, + PaginationBar, + TableActionBar, + TableSkeletonLoader, + useServerSidePagination, +} from "~/features/common/table/v2"; +import { + setActiveDatasetFidesKey, + useGetAllFilteredDatasetsQuery, + useGetDatasetsQuery, +} from "~/features/dataset/dataset.slice"; +import { Dataset } from "~/types/api"; + +const columnHelper = createColumnHelper(); + +const EMPTY_RESPONSE = { + items: [], + total: 0, + page: 1, + size: 25, + pages: 1, +}; const DataSets: NextPage = () => { - const { isLoading } = useGetAllFilteredDatasetsQuery({ - onlyUnlinkedDatasets: false, + const dispatch = useDispatch(); + const router = useRouter(); + + const { + PAGE_SIZES, + pageSize, + setPageSize, + onPreviousPageClick, + isPreviousPageDisabled, + onNextPageClick, + isNextPageDisabled, + startRange, + endRange, + pageIndex, + setTotalPages, + resetPageIndexToDefault, + } = useServerSidePagination(); + + const { + data: datasetResponse, + isLoading, + isFetching, + } = useGetDatasetsQuery({ + page: pageIndex, + size: pageSize, + }); + + const { + items: data, + total: totalRows, + pages: totalPages, + } = useMemo(() => datasetResponse ?? EMPTY_RESPONSE, [datasetResponse]); + + const handleEdit = useCallback( + (dataset: Dataset) => { + dispatch(setActiveDatasetFidesKey(dataset.fides_key)); + router.push({ + pathname: DATASET_EDIT_ROUTE, + query: { + id: dataset.fides_key, + }, + }); + }, + [dispatch, router] + ); + + const columns = useMemo( + () => [ + columnHelper.accessor((row) => row.name, { + id: "name", + cell: (props) => , + header: (props) => ( + + ), + size: 200, + }), + columnHelper.accessor((row) => row.fides_key, { + id: "fides_key", + cell: (props) => , + header: (props) => , + size: 300, + }), + columnHelper.accessor((row) => row.description, { + id: "description", + cell: (props) => , + header: (props) => , + size: 300, + }), + columnHelper.display({ + id: "actions", + header: "Actions", + cell: ({ row }) => { + const system = row.original; + return ( + + } + onClick={() => handleEdit(system)} + /> + + ); + }, + meta: { + disableRowClick: true, + }, + }), + ], + [handleEdit] + ); + + const tableInstance = useReactTable({ + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getSortedRowModel: getSortedRowModel(), + columns, + data, }); return ( - - {isLoading ? : } - - + + + {isLoading ? ( + + ) : ( + + + {/* */} + + + } + onRowClick={handleEdit} + /> + + )} + ); }; +const EmptyTableNotice = () => ( + + + + No datasets found. + + + Click "Create new dataset" to add your first dataset to Fides. + + + +); + export default DataSets; diff --git a/clients/admin-ui/src/types/common/PaginationQueryParams.ts b/clients/admin-ui/src/types/common/PaginationQueryParams.ts new file mode 100644 index 0000000000..bfdeda1475 --- /dev/null +++ b/clients/admin-ui/src/types/common/PaginationQueryParams.ts @@ -0,0 +1,4 @@ +export interface PaginationQueryParams { + page: number; + size: number; +} From a81954e0ce7341912e7db78ab26b8c6d60aab2ea Mon Sep 17 00:00:00 2001 From: Lucano Vera Date: Fri, 26 Jul 2024 10:44:43 -0300 Subject: [PATCH 02/11] PROD-2332 WIP --- .../src/features/common/nav/v2/routes.ts | 2 +- .../src/features/dataset/dataset.slice.ts | 7 ++++- .../src/features/system/system.slice.ts | 7 ++--- clients/admin-ui/src/pages/dataset/index.tsx | 30 ++++++++++++------- .../src/types/common/SearchQueryParams.ts | 3 ++ 5 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 clients/admin-ui/src/types/common/SearchQueryParams.ts diff --git a/clients/admin-ui/src/features/common/nav/v2/routes.ts b/clients/admin-ui/src/features/common/nav/v2/routes.ts index dad72cee6e..5a49edc22f 100644 --- a/clients/admin-ui/src/features/common/nav/v2/routes.ts +++ b/clients/admin-ui/src/features/common/nav/v2/routes.ts @@ -11,7 +11,7 @@ export const SYSTEM_ROUTE = "/systems"; export const EDIT_SYSTEM_ROUTE = "/systems/configure/[id]"; export const CLASSIFY_SYSTEMS_ROUTE = "/classify-systems"; export const DATASET_ROUTE = "/dataset"; -export const DATASET_EDIT_ROUTE = "/dataset/:id"; +export const DATASET_EDIT_ROUTE = "/dataset/[id]"; // Detection and discovery export const DETECTION_DISCOVERY_ACTIVITY_ROUTE = "/data-discovery/activity"; diff --git a/clients/admin-ui/src/features/dataset/dataset.slice.ts b/clients/admin-ui/src/features/dataset/dataset.slice.ts index 4144616465..d17d80689c 100644 --- a/clients/admin-ui/src/features/dataset/dataset.slice.ts +++ b/clients/admin-ui/src/features/dataset/dataset.slice.ts @@ -7,8 +7,10 @@ import { Dataset, GenerateRequestPayload, GenerateResponse, + Page_Dataset_, } from "~/types/api"; import { PaginationQueryParams } from "~/types/common/PaginationQueryParams"; +import { SearchQueryParams } from "~/types/common/SearchQueryParams"; import { EditableType } from "./types"; @@ -30,7 +32,10 @@ interface DatasetDeleteResponse { const datasetApi = baseApi.injectEndpoints({ endpoints: (build) => ({ - getDatasets: build.query({ + getDatasets: build.query< + Page_Dataset_, + PaginationQueryParams & SearchQueryParams + >({ query: (params) => ({ method: "GET", url: `dataset`, diff --git a/clients/admin-ui/src/features/system/system.slice.ts b/clients/admin-ui/src/features/system/system.slice.ts index 7d2a5cd399..759a10e2ec 100644 --- a/clients/admin-ui/src/features/system/system.slice.ts +++ b/clients/admin-ui/src/features/system/system.slice.ts @@ -12,6 +12,7 @@ import { TestStatusMessage, } from "~/types/api"; import { PaginationQueryParams } from "~/types/common/PaginationQueryParams"; +import { SearchQueryParams } from "~/types/common/SearchQueryParams"; interface SystemDeleteResponse { message: string; @@ -24,10 +25,6 @@ interface UpsertResponse { updated: number; } -interface SearchParams { - search?: string; -} - export type ConnectionConfigSecretsRequest = { systemFidesKey: string; secrets: { @@ -39,7 +36,7 @@ const systemApi = baseApi.injectEndpoints({ endpoints: (build) => ({ getSystems: build.query< Page_BasicSystemResponse_, - PaginationQueryParams & SearchParams + PaginationQueryParams & SearchQueryParams >({ query: (params) => ({ method: "GET", diff --git a/clients/admin-ui/src/pages/dataset/index.tsx b/clients/admin-ui/src/pages/dataset/index.tsx index bcda824367..699b64146e 100644 --- a/clients/admin-ui/src/pages/dataset/index.tsx +++ b/clients/admin-ui/src/pages/dataset/index.tsx @@ -19,7 +19,7 @@ import { import type { NextPage } from "next"; import NextLink from "next/link"; import { useRouter } from "next/router"; -import { useCallback, useMemo } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { useDispatch } from "react-redux"; import Layout from "~/features/common/Layout"; @@ -37,7 +37,6 @@ import { } from "~/features/common/table/v2"; import { setActiveDatasetFidesKey, - useGetAllFilteredDatasetsQuery, useGetDatasetsQuery, } from "~/features/dataset/dataset.slice"; import { Dataset } from "~/types/api"; @@ -71,6 +70,12 @@ const DataSets: NextPage = () => { resetPageIndexToDefault, } = useServerSidePagination(); + const [globalFilter, setGlobalFilter] = useState(); + const updateGlobalFilter = (searchTerm: string) => { + resetPageIndexToDefault(); + setGlobalFilter(searchTerm); + }; + const { data: datasetResponse, isLoading, @@ -78,6 +83,7 @@ const DataSets: NextPage = () => { } = useGetDatasetsQuery({ page: pageIndex, size: pageSize, + search: globalFilter, }); const { @@ -86,6 +92,10 @@ const DataSets: NextPage = () => { pages: totalPages, } = useMemo(() => datasetResponse ?? EMPTY_RESPONSE, [datasetResponse]); + useEffect(() => { + setTotalPages(totalPages); + }, [totalPages, setTotalPages]); + const handleEdit = useCallback( (dataset: Dataset) => { dispatch(setActiveDatasetFidesKey(dataset.fides_key)); @@ -103,17 +113,17 @@ const DataSets: NextPage = () => { () => [ columnHelper.accessor((row) => row.name, { id: "name", - cell: (props) => , + cell: (props) => , header: (props) => ( ), - size: 200, + size: 180, }), columnHelper.accessor((row) => row.fides_key, { id: "fides_key", cell: (props) => , header: (props) => , - size: 300, + size: 150, }), columnHelper.accessor((row) => row.description, { id: "description", @@ -159,24 +169,24 @@ const DataSets: NextPage = () => { return ( - + {isLoading ? ( ) : ( - {/* */} + />