mirror of
https://github.com/graphql-hive/console
synced 2026-04-21 14:37:17 +00:00
add catch() to zod search param fields, add test (#7817)
This commit is contained in:
parent
14b2df2dc2
commit
5b30b08c1e
4 changed files with 55 additions and 22 deletions
|
|
@ -0,0 +1,18 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
const InsightsClientFilter = z.object({
|
||||
name: z.string(),
|
||||
versions: z.array(z.string()).nullable().default(null),
|
||||
});
|
||||
|
||||
export const InsightsFilterSearch = z.object({
|
||||
operations: z.array(z.string()).optional().catch(undefined),
|
||||
clients: z.array(InsightsClientFilter).optional().catch(undefined),
|
||||
excludeOperations: z.boolean().optional().catch(undefined),
|
||||
excludeClients: z.boolean().optional().catch(undefined),
|
||||
from: z.string().optional().catch(undefined),
|
||||
to: z.string().optional().catch(undefined),
|
||||
viewId: z.string().optional().catch(undefined),
|
||||
});
|
||||
|
||||
export type InsightsFilterState = z.infer<typeof InsightsFilterSearch>;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { parse as jsUrlParse, stringify as jsUrlStringify } from 'jsurl2';
|
||||
import { z } from 'zod';
|
||||
import { InsightsFilterSearch } from '@/components/target/insights/search-schemas';
|
||||
|
||||
/**
|
||||
* Simulates TanStack Router's stringifySearchWith per-value behavior.
|
||||
|
|
@ -125,4 +126,34 @@ describe('search params serialization (jsurl2)', () => {
|
|||
versions: ['v1'],
|
||||
});
|
||||
});
|
||||
|
||||
it('gracefully handles malformed search params via .catch() (insights)', () => {
|
||||
// "clients" receives a string instead of array — should not throw
|
||||
const result = InsightsFilterSearch.parse({ clients: 'some-string' });
|
||||
expect(result.clients).toBeUndefined();
|
||||
|
||||
// "operations" receives a number instead of array — should not throw
|
||||
const result2 = InsightsFilterSearch.parse({ operations: 123 });
|
||||
expect(result2.operations).toBeUndefined();
|
||||
|
||||
// Valid values still work
|
||||
const result3 = InsightsFilterSearch.parse({
|
||||
clients: [{ name: 'WebApp' }],
|
||||
operations: ['op1'],
|
||||
});
|
||||
expect(result3.clients).toEqual([{ name: 'WebApp', versions: null }]);
|
||||
expect(result3.operations).toEqual(['op1']);
|
||||
});
|
||||
|
||||
it('gracefully handles malformed search params via .catch() (checks)', () => {
|
||||
// "filter_failed" receives an array instead of boolean — should not throw
|
||||
const schema = z.object({
|
||||
filter_changed: z.boolean().default(false).catch(false),
|
||||
filter_failed: z.boolean().default(false).catch(false),
|
||||
});
|
||||
|
||||
const result = schema.parse({ filter_failed: ['unexpected', 'array'] });
|
||||
expect(result.filter_failed).toBe(false);
|
||||
expect(result.filter_changed).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { ReactElement, useCallback, useEffect, useMemo } from 'react';
|
||||
import { ChevronDown, RefreshCw } from 'lucide-react';
|
||||
import { useMutation, useQuery } from 'urql';
|
||||
import { z } from 'zod';
|
||||
import { FilterDropdown } from '@/components/base/filter-dropdown/filter-dropdown';
|
||||
import type { FilterItem, FilterSelection } from '@/components/base/filter-dropdown/types';
|
||||
import type { SavedFilterView } from '@/components/base/insights-filters';
|
||||
|
|
@ -15,6 +14,7 @@ import {
|
|||
selectionsToClients,
|
||||
selectionsToOperations,
|
||||
} from '@/components/target/insights/search-params';
|
||||
import { InsightsFilterState } from '@/components/target/insights/search-schemas';
|
||||
import { OperationsStats } from '@/components/target/insights/stats';
|
||||
import { DateRangePicker, presetLast7Days } from '@/components/ui/date-range-picker';
|
||||
import { EmptyList } from '@/components/ui/empty-list';
|
||||
|
|
@ -26,23 +26,6 @@ import { OperationStatsFilterInput, SavedFilterVisibilityType } from '@/gql/grap
|
|||
import { useDateRangeController } from '@/lib/hooks/use-date-range-controller';
|
||||
import { useNavigate, useSearch } from '@tanstack/react-router';
|
||||
|
||||
const InsightsClientFilter = z.object({
|
||||
name: z.string(),
|
||||
versions: z.array(z.string()).nullable().default(null),
|
||||
});
|
||||
|
||||
export const InsightsFilterSearch = z.object({
|
||||
operations: z.array(z.string()).optional(),
|
||||
clients: z.array(InsightsClientFilter).optional(),
|
||||
excludeOperations: z.boolean().optional(),
|
||||
excludeClients: z.boolean().optional(),
|
||||
from: z.string().optional(),
|
||||
to: z.string().optional(),
|
||||
viewId: z.string().optional(),
|
||||
});
|
||||
|
||||
type InsightsFilterState = z.infer<typeof InsightsFilterSearch>;
|
||||
|
||||
function buildGraphQLFilter(state: InsightsFilterState): OperationStatsFilterInput {
|
||||
return {
|
||||
operationIds: state.operations?.length ? state.operations : undefined,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import 'react-toastify/dist/ReactToastify.css';
|
|||
import { useLocalStorage } from '@/lib/hooks';
|
||||
import { zodValidator } from '@tanstack/zod-adapter';
|
||||
import { authenticated } from './components/authenticated-container';
|
||||
import { InsightsFilterSearch } from './components/target/insights/search-schemas';
|
||||
import { SchemaProposalStage } from './gql/graphql';
|
||||
import { AuthPage } from './pages/auth';
|
||||
import { AuthCallbackPage } from './pages/auth-callback';
|
||||
|
|
@ -74,7 +75,7 @@ import { TargetExplorerTypePage } from './pages/target-explorer-type';
|
|||
import { TargetExplorerUnusedPage } from './pages/target-explorer-unused';
|
||||
import { TargetHistoryPage } from './pages/target-history';
|
||||
import { TargetHistoryVersionPage } from './pages/target-history-version';
|
||||
import { InsightsFilterSearch, TargetInsightsPage } from './pages/target-insights';
|
||||
import { TargetInsightsPage } from './pages/target-insights';
|
||||
import { TargetInsightsClientPage } from './pages/target-insights-client';
|
||||
import { TargetInsightsCoordinatePage } from './pages/target-insights-coordinate';
|
||||
import { TargetInsightsManageFiltersPage } from './pages/target-insights-manage-filters';
|
||||
|
|
@ -965,8 +966,8 @@ const targetExplorerUnusedRoute = createRoute({
|
|||
const targetChecksRoute = createRoute({
|
||||
validateSearch: zodValidator(
|
||||
z.object({
|
||||
filter_changed: z.boolean().default(false),
|
||||
filter_failed: z.boolean().default(false),
|
||||
filter_changed: z.boolean().default(false).catch(false),
|
||||
filter_failed: z.boolean().default(false).catch(false),
|
||||
}),
|
||||
),
|
||||
getParentRoute: () => targetRoute,
|
||||
|
|
@ -1009,7 +1010,7 @@ const targetProposalsRoute = createRoute({
|
|||
.array()
|
||||
.optional()
|
||||
.catch(() => void 0),
|
||||
user: z.string().array().optional(),
|
||||
user: z.string().array().optional().catch(undefined),
|
||||
}),
|
||||
component: function TargetProposalsRoute() {
|
||||
const { organizationSlug, projectSlug, targetSlug } = targetProposalsRoute.useParams();
|
||||
|
|
|
|||
Loading…
Reference in a new issue