Skip to content

Commit 7574cce

Browse files
authored
bug: Fix infinite loop on schema checks route (#6814)
1 parent ae65069 commit 7574cce

File tree

6 files changed

+108
-69
lines changed

6 files changed

+108
-69
lines changed

.changeset/four-wombats-fix.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'hive': patch
3+
---
4+
5+
Fix random infinite loop on schema checks page

packages/web/app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"@tanstack/react-router": "1.34.9",
6666
"@tanstack/react-table": "8.20.6",
6767
"@tanstack/router-devtools": "1.34.9",
68+
"@tanstack/zod-adapter": "1.120.5",
6869
"@theguild/editor": "1.2.5",
6970
"@trpc/client": "10.45.2",
7071
"@trpc/server": "10.45.2",

packages/web/app/src/components/ui/empty-list.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ export const EmptyList = ({
2222
}): ReactElement => {
2323
return (
2424
<Card
25-
className={cn('flex grow cursor-default flex-col items-center gap-y-2 p-4', className)}
25+
className={cn(
26+
'flex max-h-screen min-h-[400px] grow cursor-default flex-col items-center gap-y-2 p-4',
27+
className,
28+
)}
2629
data-cy="empty-list"
2730
>
2831
<img

packages/web/app/src/pages/target-checks.tsx

Lines changed: 53 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,19 @@ import { Label } from '@/components/ui/label';
99
import { Meta } from '@/components/ui/meta';
1010
import { Subtitle, Title } from '@/components/ui/page';
1111
import { QueryError } from '@/components/ui/query-error';
12+
import { Spinner } from '@/components/ui/spinner';
1213
import { Switch } from '@/components/ui/switch';
1314
import { TimeAgo } from '@/components/ui/time-ago';
1415
import { graphql } from '@/gql';
1516
import { cn } from '@/lib/utils';
1617
import { ExternalLinkIcon } from '@radix-ui/react-icons';
17-
import { Outlet, Link as RouterLink, useParams, useRouter } from '@tanstack/react-router';
18+
import {
19+
Outlet,
20+
Link as RouterLink,
21+
useNavigate,
22+
useParams,
23+
useSearch,
24+
} from '@tanstack/react-router';
1825

1926
const SchemaChecks_NavigationQuery = graphql(`
2027
query SchemaChecks_NavigationQuery(
@@ -59,20 +66,21 @@ const SchemaChecks_NavigationQuery = graphql(`
5966
`);
6067

6168
interface SchemaCheckFilters {
62-
showOnlyFailed?: boolean;
63-
showOnlyChanged?: boolean;
69+
showOnlyFailed: boolean;
70+
showOnlyChanged: boolean;
6471
}
6572

66-
const Navigation = (props: {
67-
after: string | null;
68-
isLastPage: boolean;
69-
onLoadMore: (cursor: string) => void;
70-
filters?: SchemaCheckFilters;
71-
organizationSlug: string;
72-
projectSlug: string;
73-
targetSlug: string;
74-
schemaCheckId?: string;
75-
}) => {
73+
const Navigation = (
74+
props: {
75+
after: string | null;
76+
isLastPage: boolean;
77+
onLoadMore: (cursor: string) => void;
78+
organizationSlug: string;
79+
projectSlug: string;
80+
targetSlug: string;
81+
schemaCheckId?: string;
82+
} & SchemaCheckFilters,
83+
) => {
7684
const [query] = useQuery({
7785
query: SchemaChecks_NavigationQuery,
7886
variables: {
@@ -81,8 +89,8 @@ const Navigation = (props: {
8189
targetSlug: props.targetSlug,
8290
after: props.after,
8391
filters: {
84-
changed: props.filters?.showOnlyChanged ?? false,
85-
failed: props.filters?.showOnlyFailed ?? false,
92+
changed: props.showOnlyChanged,
93+
failed: props.showOnlyFailed,
8694
},
8795
},
8896
});
@@ -109,8 +117,8 @@ const Navigation = (props: {
109117
schemaCheckId: edge.node.id,
110118
}}
111119
search={{
112-
filter_changed: props.filters?.showOnlyChanged,
113-
filter_failed: props.filters?.showOnlyFailed,
120+
filter_changed: props.showOnlyChanged,
121+
filter_failed: props.showOnlyFailed,
114122
}}
115123
>
116124
<h3 className="truncate text-sm font-semibold">
@@ -222,22 +230,18 @@ function ChecksPageContent(props: {
222230
const [paginationVariables, setPaginationVariables] = useState<Array<string | null>>(() => [
223231
null,
224232
]);
225-
226-
const router = useRouter();
233+
const [hasSchemaChecks, setHasSchemaChecks] = useState(false);
234+
const navigate = useNavigate();
227235
const { schemaCheckId } = useParams({
228236
strict: false /* allows to read the $schemaCheckId param of its child route */,
229237
}) as { schemaCheckId?: string };
230-
const search = router.latestLocation.search as {
231-
filter_changed?: string;
232-
filter_failed?: string;
238+
const search = useSearch({
239+
from: '/authenticated/$organizationSlug/$projectSlug/$targetSlug/checks',
240+
}) as {
241+
filter_changed: boolean;
242+
filter_failed: boolean;
233243
};
234-
const showOnlyChanged = search?.filter_changed === 'true';
235-
const showOnlyFailed = search?.filter_failed === 'true';
236-
237-
const [filters, setFilters] = useState<SchemaCheckFilters>({
238-
showOnlyChanged: showOnlyChanged ?? false,
239-
showOnlyFailed: showOnlyFailed ?? false,
240-
});
244+
const { filter_changed: showOnlyChanged, filter_failed: showOnlyFailed } = search;
241245

242246
const [query] = useQuery({
243247
query: ChecksPageQuery,
@@ -246,8 +250,8 @@ function ChecksPageContent(props: {
246250
projectSlug: props.projectSlug,
247251
targetSlug: props.targetSlug,
248252
filters: {
249-
changed: filters.showOnlyChanged ?? false,
250-
failed: filters.showOnlyFailed ?? false,
253+
changed: showOnlyChanged,
254+
failed: showOnlyFailed,
251255
},
252256
},
253257
});
@@ -262,39 +266,28 @@ function ChecksPageContent(props: {
262266
);
263267
}
264268

265-
const hasSchemaChecks = !!query.data?.target?.schemaChecks?.edges?.length;
269+
if (!hasSchemaChecks && !!query.data?.target?.schemaChecks?.edges?.length) {
270+
setHasSchemaChecks(true);
271+
}
266272
const hasFilteredSchemaChecks = !!query.data?.target?.filteredSchemaChecks?.edges?.length;
267273
const hasActiveSchemaCheck = !!schemaCheckId;
268274

269275
const handleShowOnlyFilterChange = () => {
270-
const updatedFilters = !filters.showOnlyChanged;
271-
272-
void router.navigate({
276+
void navigate({
273277
search: {
274278
...search,
275-
filter_changed: updatedFilters,
279+
filter_changed: !showOnlyChanged,
276280
},
277281
});
278-
setFilters(filters => ({
279-
...filters,
280-
showOnlyChanged: !filters.showOnlyChanged,
281-
}));
282282
};
283283

284284
const handleShowOnlyFilterFailed = () => {
285-
const updatedFilters = !filters.showOnlyFailed;
286-
287-
void router.navigate({
285+
void navigate({
288286
search: {
289287
...search,
290-
filter_failed: updatedFilters,
288+
filter_failed: !showOnlyFailed,
291289
},
292290
});
293-
294-
setFilters(filters => ({
295-
...filters,
296-
showOnlyFailed: !filters.showOnlyFailed,
297-
}));
298291
};
299292

300293
return (
@@ -304,7 +297,7 @@ function ChecksPageContent(props: {
304297
<Title>Schema Checks</Title>
305298
<Subtitle>Recently checked schemas.</Subtitle>
306299
</div>
307-
{query.fetching || query.stale ? null : hasSchemaChecks ? (
300+
{hasSchemaChecks ? (
308301
<div className="flex flex-col gap-5">
309302
<div>
310303
<div className="flex h-9 flex-row items-center justify-between">
@@ -315,7 +308,7 @@ function ChecksPageContent(props: {
315308
Show only changed schemas
316309
</Label>
317310
<Switch
318-
checked={filters.showOnlyChanged ?? false}
311+
checked={showOnlyChanged}
319312
onCheckedChange={handleShowOnlyFilterChange}
320313
id="filter-toggle-has-changes"
321314
/>
@@ -328,7 +321,7 @@ function ChecksPageContent(props: {
328321
Show only failed checks
329322
</Label>
330323
<Switch
331-
checked={filters.showOnlyFailed ?? false}
324+
checked={showOnlyFailed}
332325
onCheckedChange={handleShowOnlyFilterFailed}
333326
id="filter-toggle-status-failed"
334327
/>
@@ -346,16 +339,21 @@ function ChecksPageContent(props: {
346339
isLastPage={index + 1 === paginationVariables.length}
347340
onLoadMore={cursor => setPaginationVariables(cursors => [...cursors, cursor])}
348341
key={cursor ?? 'first'}
349-
filters={filters}
342+
showOnlyChanged={showOnlyChanged}
343+
showOnlyFailed={showOnlyFailed}
350344
/>
351345
))}
352346
</div>
347+
) : query.fetching || query.stale ? (
348+
<Spinner />
353349
) : (
354-
<div className="cursor-default text-sm">
350+
<div className="my-4 cursor-default text-center text-sm text-gray-400">
355351
No schema checks found with the current filters
356352
</div>
357353
)}
358354
</div>
355+
) : query.fetching ? (
356+
<Spinner />
359357
) : (
360358
<div>
361359
<div className="cursor-default text-sm">
@@ -405,7 +403,7 @@ export function TargetChecksPage(props: {
405403
projectSlug={props.projectSlug}
406404
targetSlug={props.targetSlug}
407405
page={Page.Checks}
408-
className={cn('flex flex-row gap-x-6')}
406+
className="flex flex-row gap-x-6"
409407
>
410408
<ChecksPageContent {...props} />
411409
</TargetLayout>

packages/web/app/src/router.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import { ErrorComponent } from './components/error';
2525
import { NotFound } from './components/not-found';
2626
import 'react-toastify/dist/ReactToastify.css';
27+
import { zodValidator } from '@tanstack/zod-adapter';
2728
import { authenticated } from './components/authenticated-container';
2829
import { AuthPage } from './pages/auth';
2930
import { AuthCallbackPage } from './pages/auth-callback';
@@ -782,6 +783,12 @@ const targetExplorerUnusedRoute = createRoute({
782783
});
783784

784785
const targetChecksRoute = createRoute({
786+
validateSearch: zodValidator(
787+
z.object({
788+
filter_changed: z.boolean().default(false),
789+
filter_failed: z.boolean().default(false),
790+
}),
791+
),
785792
getParentRoute: () => targetRoute,
786793
path: 'checks',
787794
component: function TargetChecksRoute() {

0 commit comments

Comments
 (0)