Skip to content

Commit 8f5deee

Browse files
authored
Merge pull request #975 from DataRecce/feature/refactor-routing
Feature/refactor routing
2 parents 77fc5e0 + 512d85f commit 8f5deee

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+4315
-736
lines changed

.github/workflows/tests-js.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ jobs:
4949
working-directory: ./js
5050
run: pnpm lint
5151

52+
- name: Run Jest Tests
53+
working-directory: ./js
54+
run: pnpm test
55+
5256
- name: Build the static files
5357
working-directory: ./js
5458
run: pnpm build

js/.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ if [ -n "$staged_js_files" ]; then
88
pushd js
99
pnpm lint:staged
1010
pnpm type:check
11+
pnpm test
1112
popd
1213
else
1314
echo "No TypeScript/JavaScript files changed, skipping lint and type check."

js/app/(mainComponents)/NavBar.tsx

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"use client";
2+
3+
import { Box, Flex, Link, Tabs } from "@chakra-ui/react";
4+
import { useQuery } from "@tanstack/react-query";
5+
import NextLink from "next/link";
6+
import { usePathname } from "next/navigation";
7+
import React, { Activity, ReactNode, useEffect, useMemo } from "react";
8+
import { EnvInfo } from "@/components/app/EnvInfo";
9+
import { Filename } from "@/components/app/Filename";
10+
import { StateExporter } from "@/components/app/StateExporter";
11+
import { TopLevelShare } from "@/components/app/StateSharing";
12+
import { StateSynchronizer } from "@/components/app/StateSynchronizer";
13+
import { cacheKeys } from "@/lib/api/cacheKeys";
14+
import { Check, listChecks } from "@/lib/api/checks";
15+
import { trackNavigation } from "@/lib/api/track";
16+
import { useLineageGraphContext } from "@/lib/hooks/LineageGraphContext";
17+
import { useRecceInstanceContext } from "@/lib/hooks/RecceInstanceContext";
18+
import { useRecceServerFlag } from "@/lib/hooks/useRecceServerFlag";
19+
20+
/**
21+
* Route configuration for tabs
22+
*/
23+
const ROUTE_CONFIG = [
24+
{ path: "/lineage", name: "Lineage" },
25+
{ path: "/query", name: "Query" },
26+
{ path: "/checks", name: "Checklist" },
27+
] as const;
28+
29+
interface TabBadgeProps<T> {
30+
queryKey: string[];
31+
fetchCallback: () => Promise<T>;
32+
selectCallback?: (data: T) => number;
33+
}
34+
35+
function TabBadge<T>({
36+
queryKey,
37+
fetchCallback,
38+
selectCallback,
39+
}: TabBadgeProps<T>): ReactNode {
40+
const {
41+
data: count,
42+
isLoading,
43+
error,
44+
} = useQuery({
45+
queryKey: queryKey,
46+
queryFn: fetchCallback,
47+
select: selectCallback,
48+
});
49+
50+
if (isLoading || error || count === 0) {
51+
return <></>;
52+
}
53+
54+
return (
55+
<Box
56+
ml="2px"
57+
maxH={"20px"}
58+
height="80%"
59+
aspectRatio={1}
60+
borderRadius="full"
61+
bg="tomato"
62+
alignContent={"center"}
63+
color="white"
64+
fontSize="xs"
65+
>
66+
{count}
67+
</Box>
68+
);
69+
}
70+
71+
// NavBar component with Next.js Link navigation
72+
export default function NavBar() {
73+
const pathname = usePathname();
74+
const { isDemoSite, isLoading, cloudMode } = useLineageGraphContext();
75+
const { featureToggles } = useRecceInstanceContext();
76+
const { data: flag, isLoading: isFlagLoading } = useRecceServerFlag();
77+
const ChecklistBadge = (
78+
<TabBadge<Check[]>
79+
queryKey={cacheKeys.checks()}
80+
fetchCallback={listChecks}
81+
selectCallback={(checks: Check[]) => {
82+
return checks.filter((check) => !check.is_checked).length;
83+
}}
84+
/>
85+
);
86+
// Track navigation changes
87+
useEffect(() => {
88+
trackNavigation({ from: location.pathname, to: pathname });
89+
}, [pathname]);
90+
91+
// Get current tab value from pathname
92+
const currentTab = useMemo(() => {
93+
if (pathname.startsWith("/checks")) return "/checks";
94+
if (pathname.startsWith("/query")) return "/query";
95+
if (pathname.startsWith("/runs")) return "/runs";
96+
return "/lineage";
97+
}, [pathname]);
98+
99+
return (
100+
<Tabs.Root
101+
colorPalette="iochmara"
102+
value={currentTab}
103+
size="sm"
104+
variant="line"
105+
borderBottom="1px solid lightgray"
106+
px="12px"
107+
>
108+
<Tabs.List
109+
display="grid"
110+
gridTemplateColumns="1fr auto 1fr"
111+
width="100%"
112+
borderBottom="none"
113+
>
114+
{/* Left section: Tabs */}
115+
<Box display="flex" alignItems="center" gap="4px">
116+
{ROUTE_CONFIG.map(({ path, name }) => {
117+
const disable = name === "Query" && flag?.single_env_onboarding;
118+
119+
return (
120+
<Tabs.Trigger
121+
key={path}
122+
value={path}
123+
disabled={isLoading || isFlagLoading || disable}
124+
hidden={disable}
125+
>
126+
<Link asChild>
127+
<NextLink href={path}>{name}</NextLink>
128+
</Link>
129+
<Activity mode={name === "Checklist" ? "visible" : "hidden"}>
130+
{ChecklistBadge}
131+
</Activity>
132+
</Tabs.Trigger>
133+
);
134+
})}
135+
</Box>
136+
137+
{/* Center section: Filename and TopLevelShare */}
138+
<Flex alignItems="center" gap="12px" justifyContent="center">
139+
{!isLoading && !isDemoSite && <Filename />}
140+
{!isLoading &&
141+
!isDemoSite &&
142+
!flag?.single_env_onboarding &&
143+
!featureToggles.disableShare && <TopLevelShare />}
144+
</Flex>
145+
146+
{/* Right section: EnvInfo, StateSynchronizer, StateExporter */}
147+
{!isLoading && (
148+
<Flex justifyContent="right" alignItems="center" mr="8px">
149+
<EnvInfo />
150+
{cloudMode && <StateSynchronizer />}
151+
<StateExporter />
152+
</Flex>
153+
)}
154+
</Tabs.List>
155+
</Tabs.Root>
156+
);
157+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Badge, Code, Link, Text } from "@chakra-ui/react";
2+
import React, { useEffect, useMemo } from "react";
3+
import { toaster } from "@/components/ui/toaster";
4+
import { useVersionNumber } from "@/lib/api/version";
5+
6+
export default function RecceVersionBadge() {
7+
const { version, latestVersion } = useVersionNumber();
8+
const versionFormatRegex = useMemo(
9+
() => new RegExp("^\\d+\\.\\d+\\.\\d+$"),
10+
[],
11+
);
12+
13+
useEffect(() => {
14+
if (versionFormatRegex.test(version) && version !== latestVersion) {
15+
const storageKey = "recce-update-toast-shown";
16+
const hasShownForThisVersion = sessionStorage.getItem(storageKey);
17+
if (hasShownForThisVersion) {
18+
return;
19+
}
20+
// Defer toast creation to next tick to avoid React's flushSync error
21+
// This prevents "flushSync called from inside lifecycle method" when
22+
// the toast library tries to immediately update DOM during render cycle
23+
setTimeout(() => {
24+
toaster.create({
25+
id: "recce-update-available", // Fixed ID prevents duplicates
26+
title: "Update available",
27+
description: (
28+
<span>
29+
A new version of Recce (v{latestVersion}) is available.
30+
<br />
31+
Please run <Code>pip install --upgrade recce</Code> to update
32+
Recce.
33+
<br />
34+
<Link
35+
color="brand.700"
36+
fontWeight={"bold"}
37+
href={`https://github.com/DataRecce/recce/releases/tag/v${latestVersion}`}
38+
_hover={{ textDecoration: "underline" }}
39+
target="_blank"
40+
>
41+
Click here to view the detail of latest release
42+
</Link>
43+
</span>
44+
),
45+
duration: 60 * 1000,
46+
// TODO Fix this at a later update
47+
// containerStyle: {
48+
// background: "rgba(20, 20, 20, 0.6)", // Semi-transparent black
49+
// color: "white", // Ensure text is visible
50+
// backdropFilter: "blur(10px)", // Frosted glass effect
51+
// borderRadius: "8px",
52+
// },
53+
closable: true,
54+
});
55+
sessionStorage.setItem(storageKey, "true");
56+
}, 0);
57+
}
58+
}, [version, latestVersion, versionFormatRegex]);
59+
60+
if (!versionFormatRegex.test(version)) {
61+
// If the version is not in the format of x.y.z, don't apply
62+
return (
63+
<Badge
64+
fontSize="sm"
65+
color="white/80"
66+
variant="outline"
67+
textTransform="uppercase"
68+
>
69+
{version}
70+
</Badge>
71+
);
72+
}
73+
74+
// Link to the release page on GitHub if the version is in the format of x.y.z
75+
return (
76+
<Badge
77+
fontSize="sm"
78+
color="white/80"
79+
variant="outline"
80+
textTransform="uppercase"
81+
>
82+
<Link
83+
href={`https://github.com/DataRecce/recce/releases/tag/v${version}`}
84+
_hover={{ textDecoration: "none" }}
85+
>
86+
<Text color="white/80">{version}</Text>
87+
</Link>
88+
</Badge>
89+
);
90+
}

0 commit comments

Comments
 (0)