Skip to content

Commit bfff3d3

Browse files
authored
Fix data catalog breadcrumb navigation (#5717)
1 parent 50f44b8 commit bfff3d3

File tree

11 files changed

+236
-32
lines changed

11 files changed

+236
-32
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Changes can also be flagged with a GitHub label for tracking purposes. The URL o
2626

2727
### Fixed
2828
- Fixed Bigquery flakey tests. [#5713](LJ-278-fix-failed-big-query-enterprise-tests)
29+
- Fixed breadcrumb navigation issues in data catalog view [#5717](https://github.com/ethyca/fides/pull/5717)
2930

3031
## [2.53.0](https://github.com/ethyca/fides/compare/2.53.0...2.54.0)
3132

clients/admin-ui/cypress/e2e/data-catalog.cy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ describe("data catalog", () => {
9797
beforeEach(() => {
9898
stubStagedResourceActions();
9999
cy.visit(
100-
`${DATA_CATALOG_ROUTE}/bigquery_system/monitor.project.test_dataset_1`,
100+
`${DATA_CATALOG_ROUTE}/bigquery_system/projects/monitor.project/monitor.project.test_dataset_1`,
101101
);
102102
});
103103

clients/admin-ui/src/features/data-catalog/staged-resources/CatalogResourcesTable.tsx

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ import {
66
useReactTable,
77
} from "@tanstack/react-table";
88
import { Box, Flex } from "fidesui";
9-
import { useRouter } from "next/router";
109
import { useEffect, useMemo, useState } from "react";
1110

12-
import { DATA_CATALOG_ROUTE } from "~/features/common/nav/v2/routes";
1311
import {
1412
FidesTableV2,
1513
PaginationBar,
@@ -24,11 +22,7 @@ import { SearchInput } from "~/features/data-discovery-and-detection/SearchInput
2422
import { StagedResourceType } from "~/features/data-discovery-and-detection/types/StagedResourceType";
2523
import { findResourceType } from "~/features/data-discovery-and-detection/utils/findResourceType";
2624
import resourceHasChildren from "~/features/data-discovery-and-detection/utils/resourceHasChildren";
27-
import {
28-
DiffStatus,
29-
StagedResourceAPIResponse,
30-
SystemResponse,
31-
} from "~/types/api";
25+
import { DiffStatus, StagedResourceAPIResponse } from "~/types/api";
3226

3327
// everything except muted
3428
const DIFF_STATUS_FILTERS = [
@@ -53,13 +47,11 @@ const EMPTY_RESPONSE = {
5347

5448
const CatalogResourcesTable = ({
5549
resourceUrn,
56-
system,
50+
onRowClick,
5751
}: {
5852
resourceUrn: string;
59-
system: SystemResponse;
53+
onRowClick: (row: StagedResourceAPIResponse) => void;
6054
}) => {
61-
const router = useRouter();
62-
6355
const {
6456
PAGE_SIZES,
6557
pageSize,
@@ -135,9 +127,7 @@ const CatalogResourcesTable = ({
135127
tableInstance={tableInstance}
136128
emptyTableNotice={<EmptyCatalogTableNotice />}
137129
getRowIsClickable={(row) => resourceHasChildren(row)}
138-
onRowClick={(row) =>
139-
router.push(`${DATA_CATALOG_ROUTE}/${system.fides_key}/${row.urn}`)
140-
}
130+
onRowClick={onRowClick}
141131
/>
142132
<PaginationBar
143133
totalRows={totalRows || 0}

clients/admin-ui/src/features/data-catalog/systems/CatalogSystemsTable.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const EMPTY_RESPONSE = {
3939

4040
const columnHelper = createColumnHelper<SystemWithMonitorKeys>();
4141

42-
const SystemsTable = () => {
42+
const CatalogSystemsTable = () => {
4343
const [rowSelectionState, setRowSelectionState] = useState<RowSelectionState>(
4444
{},
4545
);
@@ -92,7 +92,7 @@ const SystemsTable = () => {
9292
"monitor_config_ids",
9393
);
9494

95-
const url = `${DATA_CATALOG_ROUTE}/${row.fides_key}${hasProjects ? "/projects" : ""}?${queryString}`;
95+
const url = `${DATA_CATALOG_ROUTE}/${row.fides_key}/${hasProjects ? "projects" : "resources"}?${queryString}`;
9696
router.push(url);
9797
};
9898

@@ -183,4 +183,4 @@ const SystemsTable = () => {
183183
);
184184
};
185185

186-
export default SystemsTable;
186+
export default CatalogSystemsTable;
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {
2+
parseResourceBreadcrumbsNoProject,
3+
parseResourceBreadcrumbsWithProject,
4+
} from "~/features/data-catalog/utils/urnParsing";
5+
6+
const URL_PREFIX = "/url-prefix";
7+
8+
describe(parseResourceBreadcrumbsWithProject.name, () => {
9+
const URN = "monitor.project.dataset.table.field";
10+
const EXPECTED_TITLES = ["project", "dataset", "table", "field"];
11+
const EXPECTED_HREFS = [
12+
"/url-prefix/monitor.project",
13+
"/url-prefix/monitor.project/monitor.project.dataset",
14+
"/url-prefix/monitor.project/monitor.project.dataset.table",
15+
undefined,
16+
];
17+
18+
it("should return no breadcrumbs without a URN", () => {
19+
const result = parseResourceBreadcrumbsWithProject(undefined, URL_PREFIX);
20+
expect(result).toEqual([]);
21+
});
22+
23+
it("should return no breadcrumbs with a short URN", () => {
24+
const result = parseResourceBreadcrumbsWithProject("monitor", URL_PREFIX);
25+
expect(result).toEqual([]);
26+
});
27+
28+
it("should return the correct breadcrumbs when URN is provided", () => {
29+
const result = parseResourceBreadcrumbsWithProject(URN, URL_PREFIX);
30+
result.forEach((breadcrumb, idx) => {
31+
expect(breadcrumb.title).toEqual(EXPECTED_TITLES[idx]);
32+
expect(breadcrumb.href).toEqual(EXPECTED_HREFS[idx]);
33+
});
34+
});
35+
});
36+
37+
describe(parseResourceBreadcrumbsNoProject.name, () => {
38+
const URN = "monitor.dataset.table.field";
39+
const EXPECTED_TITLES = ["dataset", "table", "field"];
40+
const EXPECTED_HREFS = [
41+
"/url-prefix/monitor.dataset",
42+
"/url-prefix/monitor.dataset.table",
43+
undefined,
44+
];
45+
46+
it("should return no breadcrumbs without a URN", () => {
47+
const result = parseResourceBreadcrumbsNoProject(undefined, URL_PREFIX);
48+
expect(result).toEqual([]);
49+
});
50+
51+
it("should return no breadcrumbs with a short URN", () => {
52+
const result = parseResourceBreadcrumbsNoProject("monitor", URL_PREFIX);
53+
expect(result).toEqual([]);
54+
});
55+
56+
it("should return the correct breadcrumbs when URN is provided", () => {
57+
const result = parseResourceBreadcrumbsNoProject(URN, URL_PREFIX);
58+
result.forEach((breadcrumb, idx) => {
59+
expect(breadcrumb.title).toEqual(EXPECTED_TITLES[idx]);
60+
expect(breadcrumb.href).toEqual(EXPECTED_HREFS[idx]);
61+
});
62+
});
63+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { Icons } from "fidesui";
2+
3+
import { NextBreadcrumbProps } from "~/features/common/nav/v2/NextBreadcrumb";
4+
5+
const URN_SEPARATOR = ".";
6+
7+
export const getProjectName = (urn: string) => {
8+
const urnParts = urn.split(URN_SEPARATOR);
9+
return urnParts[1];
10+
};
11+
12+
const RESOURCE_ICONS = [
13+
<Icons.Layers key="layers" />,
14+
<Icons.Table key="table" />,
15+
<Icons.ShowDataCards key="field" style={{ transform: "rotate(-90deg)" }} />,
16+
];
17+
18+
export const parseResourceBreadcrumbsWithProject = (
19+
urn: string | undefined,
20+
urlPrefix: string,
21+
) => {
22+
if (!urn) {
23+
return [];
24+
}
25+
const urnParts = urn.split(URN_SEPARATOR);
26+
if (urnParts.length < 2) {
27+
return [];
28+
}
29+
const projectUrn = urnParts.splice(0, 2).join(URN_SEPARATOR);
30+
const subResourceUrns: NextBreadcrumbProps["items"] = [];
31+
32+
urnParts.reduce((prev, current, idx) => {
33+
const isLast = idx === urnParts.length - 1;
34+
const next = `${prev}${URN_SEPARATOR}${current}`;
35+
subResourceUrns.push({
36+
title: current,
37+
href: !isLast ? `${urlPrefix}/${projectUrn}/${next}` : undefined,
38+
icon: RESOURCE_ICONS[idx],
39+
});
40+
return next;
41+
}, projectUrn);
42+
43+
return [
44+
{
45+
title: getProjectName(projectUrn),
46+
href: `${urlPrefix}/${projectUrn}`,
47+
icon: <Icons.Db2Database />,
48+
},
49+
...subResourceUrns,
50+
];
51+
};
52+
53+
export const parseResourceBreadcrumbsNoProject = (
54+
urn: string | undefined,
55+
urlPrefix: string,
56+
) => {
57+
if (!urn) {
58+
return [];
59+
}
60+
61+
const urnParts = urn.split(URN_SEPARATOR);
62+
if (urnParts.length < 2) {
63+
return [];
64+
}
65+
const monitorId = urnParts.shift();
66+
const subResourceUrns: NextBreadcrumbProps["items"] = [];
67+
68+
urnParts.reduce((prev, current, idx) => {
69+
const isLast = idx === urnParts.length - 1;
70+
const next = `${prev}${URN_SEPARATOR}${current}`;
71+
subResourceUrns.push({
72+
title: current,
73+
href: !isLast ? `${urlPrefix}/${next}` : undefined,
74+
icon: RESOURCE_ICONS[idx],
75+
});
76+
return next;
77+
}, monitorId);
78+
79+
return subResourceUrns;
80+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { NextPage } from "next";
2+
import { useRouter } from "next/router";
3+
4+
import FidesSpinner from "~/features/common/FidesSpinner";
5+
import Layout from "~/features/common/Layout";
6+
import { DATA_CATALOG_ROUTE } from "~/features/common/nav/v2/routes";
7+
import PageHeader from "~/features/common/PageHeader";
8+
import CatalogResourcesTable from "~/features/data-catalog/staged-resources/CatalogResourcesTable";
9+
import { parseResourceBreadcrumbsWithProject } from "~/features/data-catalog/utils/urnParsing";
10+
import { useGetSystemByFidesKeyQuery } from "~/features/system";
11+
12+
const CatalogResourceView: NextPage = () => {
13+
const { query } = useRouter();
14+
const systemId = query.systemId as string;
15+
const projectUrn = query.projectUrn as string;
16+
const resourceUrn = query.resourceUrn as string;
17+
const { data: system, isLoading } = useGetSystemByFidesKeyQuery(systemId);
18+
19+
const router = useRouter();
20+
21+
const resourceBreadcrumbs =
22+
parseResourceBreadcrumbsWithProject(
23+
resourceUrn,
24+
`${DATA_CATALOG_ROUTE}/${systemId}/projects`,
25+
) ?? [];
26+
27+
if (isLoading) {
28+
return <FidesSpinner />;
29+
}
30+
31+
return (
32+
<Layout title="Data catalog">
33+
<PageHeader
34+
heading="Data catalog"
35+
breadcrumbItems={[
36+
{ title: "All systems", href: DATA_CATALOG_ROUTE },
37+
{
38+
title: system?.name ?? system?.fides_key,
39+
href: DATA_CATALOG_ROUTE,
40+
},
41+
...resourceBreadcrumbs,
42+
]}
43+
/>
44+
<CatalogResourcesTable
45+
resourceUrn={resourceUrn}
46+
onRowClick={(row) =>
47+
router.push(
48+
`${DATA_CATALOG_ROUTE}/${system!.fides_key}/projects/${projectUrn}/${row.urn}`,
49+
)
50+
}
51+
/>
52+
</Layout>
53+
);
54+
};
55+
56+
export default CatalogResourceView;

clients/admin-ui/src/pages/data-catalog/[systemId]/projects/[projectId].tsx renamed to clients/admin-ui/src/pages/data-catalog/[systemId]/projects/[projectUrn]/index.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
getGroupedRowModel,
55
useReactTable,
66
} from "@tanstack/react-table";
7+
import { Icons } from "fidesui";
78
import { useRouter } from "next/router";
89
import { useEffect, useMemo } from "react";
910

@@ -18,6 +19,7 @@ import {
1819
} from "~/features/common/table/v2";
1920
import EmptyCatalogTableNotice from "~/features/data-catalog/datasets/EmptyCatalogTableNotice";
2021
import useCatalogDatasetColumns from "~/features/data-catalog/datasets/useCatalogDatasetColumns";
22+
import { getProjectName } from "~/features/data-catalog/utils/urnParsing";
2123
import { useGetMonitorResultsQuery } from "~/features/data-discovery-and-detection/discovery-detection.slice";
2224
import { useGetSystemByFidesKeyQuery } from "~/features/system";
2325
import { StagedResourceAPIResponse } from "~/types/api";
@@ -33,7 +35,7 @@ const EMPTY_RESPONSE = {
3335
const CatalogDatasetView = () => {
3436
const { query, push } = useRouter();
3537
const systemKey = query.systemId as string;
36-
const projectId = query.projectId as string;
38+
const projectUrn = query.projectUrn as string;
3739

3840
const { data: system, isLoading: systemIsLoading } =
3941
useGetSystemByFidesKeyQuery(systemKey);
@@ -57,7 +59,7 @@ const CatalogDatasetView = () => {
5759
isLoading,
5860
data: resources,
5961
} = useGetMonitorResultsQuery({
60-
staged_resource_urn: projectId,
62+
staged_resource_urn: projectUrn,
6163
page: pageIndex,
6264
size: pageSize,
6365
});
@@ -94,9 +96,9 @@ const CatalogDatasetView = () => {
9496
{ title: "All systems", href: DATA_CATALOG_ROUTE },
9597
{
9698
title: system?.name || systemKey,
97-
href: `${DATA_CATALOG_ROUTE}/${systemKey}/projects`,
99+
href: DATA_CATALOG_ROUTE,
98100
},
99-
{ title: projectId },
101+
{ title: getProjectName(projectUrn), icon: <Icons.Db2Database /> },
100102
]}
101103
/>
102104
{!showContent && <TableSkeletonLoader rowHeight={36} numRows={36} />}
@@ -106,7 +108,9 @@ const CatalogDatasetView = () => {
106108
tableInstance={tableInstance}
107109
emptyTableNotice={<EmptyCatalogTableNotice />}
108110
onRowClick={(row) =>
109-
push(`${DATA_CATALOG_ROUTE}/${systemKey}/${row.urn}`)
111+
push(
112+
`${DATA_CATALOG_ROUTE}/${systemKey}/projects/${projectUrn}/${row.urn}/`,
113+
)
110114
}
111115
/>
112116
<PaginationBar

clients/admin-ui/src/pages/data-catalog/[systemId]/[resourceUrn].tsx renamed to clients/admin-ui/src/pages/data-catalog/[systemId]/resources/[resourceUrn].tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Layout from "~/features/common/Layout";
66
import { DATA_CATALOG_ROUTE } from "~/features/common/nav/v2/routes";
77
import PageHeader from "~/features/common/PageHeader";
88
import CatalogResourcesTable from "~/features/data-catalog/staged-resources/CatalogResourcesTable";
9-
import parseUrnToBreadcrumbs from "~/features/data-catalog/staged-resources/parseUrnToBreadcrumbs";
9+
import { parseResourceBreadcrumbsNoProject } from "~/features/data-catalog/utils/urnParsing";
1010
import { useGetSystemByFidesKeyQuery } from "~/features/system";
1111

1212
const CatalogResourceView: NextPage = () => {
@@ -15,9 +15,12 @@ const CatalogResourceView: NextPage = () => {
1515
const resourceUrn = query.resourceUrn as string;
1616
const { data: system, isLoading } = useGetSystemByFidesKeyQuery(systemId);
1717

18-
const resourceBreadcrumbs =
19-
parseUrnToBreadcrumbs(resourceUrn, `${DATA_CATALOG_ROUTE}/${systemId}`) ??
20-
[];
18+
const router = useRouter();
19+
20+
const resourceBreadcrumbs = parseResourceBreadcrumbsNoProject(
21+
resourceUrn,
22+
`${DATA_CATALOG_ROUTE}/${systemId}/resources`,
23+
);
2124

2225
if (isLoading) {
2326
return <FidesSpinner />;
@@ -31,12 +34,19 @@ const CatalogResourceView: NextPage = () => {
3134
{ title: "All systems", href: DATA_CATALOG_ROUTE },
3235
{
3336
title: system?.name ?? system?.fides_key,
34-
href: `${DATA_CATALOG_ROUTE}/${systemId}`,
37+
href: `${DATA_CATALOG_ROUTE}`,
3538
},
3639
...resourceBreadcrumbs,
3740
]}
3841
/>
39-
<CatalogResourcesTable resourceUrn={resourceUrn} system={system!} />
42+
<CatalogResourcesTable
43+
resourceUrn={resourceUrn}
44+
onRowClick={(row) =>
45+
router.push(
46+
`${DATA_CATALOG_ROUTE}/${system!.fides_key}/resources/${row.urn}`,
47+
)
48+
}
49+
/>
4050
</Layout>
4151
);
4252
};

0 commit comments

Comments
 (0)