Skip to content

Commit eb9704a

Browse files
atrakhConvex, Inc.
authored andcommitted
dashboard: update paginatedTableDocuments to support disabling filters (#35061)
Adds backwards-compatible support for an `enabled` field on filters for the data page. Fields that set this field to false will not be including in filtering. Also adds a test configuration for `system-udfs` because we moved tests there at some point, but did not make sure they were still running. GitOrigin-RevId: 85a53d643e2c31d57a2686bd721a7730e4f204ea
1 parent 6e212a4 commit eb9704a

File tree

9 files changed

+157
-15
lines changed

9 files changed

+157
-15
lines changed

npm-packages/common/config/rush/pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Mock for convex/server
2+
module.exports = {
3+
GenericDocument: {},
4+
Index: {},
5+
SearchIndex: {},
6+
VectorIndex: {},
7+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Mock for convex/values
2+
module.exports = {
3+
JSONValue: {},
4+
ValidatorJSON: {},
5+
Value: {},
6+
jsonToConvex: (value) => {
7+
// Simple validation: reject objects with keys starting with $
8+
if (typeof value === "object" && value !== null) {
9+
for (const key in value) {
10+
if (key.startsWith("$")) {
11+
throw new Error(`Invalid value: ${JSON.stringify(value)}.`);
12+
}
13+
}
14+
}
15+
return value;
16+
},
17+
validateValue: (value) => {
18+
// Simple validation: reject objects with keys starting with $
19+
if (typeof value === "object" && value !== null) {
20+
for (const key in value) {
21+
if (key.startsWith("$")) {
22+
throw new Error(`Invalid value: ${JSON.stringify(value)}.`);
23+
}
24+
}
25+
}
26+
return value;
27+
},
28+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Mock for id-encoding
2+
module.exports = {
3+
isId: (value) => typeof value === "string" && value.length === 32,
4+
};

npm-packages/system-udfs/convex/_system/frontend/lib/filters.test.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,26 @@ describe("filters", () => {
5151
]);
5252
});
5353

54+
it("should partition filters with enabled field", () => {
55+
expect(
56+
partitionFiltersByOperator([
57+
{ field: "foo", op: "type", value: "string", enabled: true },
58+
{ field: "foo", op: "eq", value: "bar", enabled: false },
59+
{ field: "foo", op: "notype", value: "string", enabled: true },
60+
{ field: "foo", op: "neq", value: "bar", enabled: false },
61+
]),
62+
).toStrictEqual([
63+
[
64+
{ field: "foo", op: "eq", value: "bar", enabled: false },
65+
{ field: "foo", op: "neq", value: "bar", enabled: false },
66+
],
67+
[
68+
{ field: "foo", op: "type", value: "string", enabled: true },
69+
{ field: "foo", op: "notype", value: "string", enabled: true },
70+
],
71+
]);
72+
});
73+
5474
it("should return empty lists", () => {
5575
expect(partitionFiltersByOperator([])).toStrictEqual([[], []]);
5676
});
@@ -74,7 +94,10 @@ describe("filters", () => {
7494
test.each<{
7595
name: string;
7696
page: GenericDocument[];
77-
filters: Omit<ValidFilterByType, "id">[];
97+
filters: (
98+
| Omit<ValidFilterByType, "id">
99+
| (Omit<ValidFilterByType, "id" | "enabled"> & { enabled?: boolean })
100+
)[];
78101
expected: GenericDocument[];
79102
}>([
80103
{
@@ -152,8 +175,49 @@ describe("filters", () => {
152175
],
153176
expected: [samplePage[1]],
154177
},
178+
{
179+
name: "respects enabled=false",
180+
page: samplePage,
181+
filters: [
182+
{
183+
field: "variableType",
184+
op: "type",
185+
value: "string",
186+
enabled: false,
187+
},
188+
],
189+
expected: samplePage,
190+
},
191+
{
192+
name: "respects enabled=true",
193+
page: samplePage,
194+
filters: [
195+
{ field: "variableType", op: "type", value: "string", enabled: true },
196+
],
197+
expected: [samplePage[0], samplePage[1], samplePage[6]],
198+
},
199+
{
200+
name: "mixed enabled filters",
201+
page: samplePage,
202+
filters: [
203+
{ field: "variableType", op: "type", value: "string", enabled: true },
204+
{ field: "name", op: "type", value: "string", enabled: false },
205+
],
206+
expected: [samplePage[0], samplePage[1], samplePage[6]],
207+
},
208+
{
209+
name: "respects missing enabled field (backward compatibility)",
210+
page: samplePage,
211+
filters: [
212+
{ field: "variableType", op: "type", value: "string" }, // enabled field is missing
213+
],
214+
expected: [samplePage[0], samplePage[1], samplePage[6]],
215+
},
155216
])(`filterPage $name`, ({ page, filters, expected }) => {
156-
const filteredPage = applyTypeFilters(page, filters);
217+
const filteredPage = applyTypeFilters(
218+
page,
219+
filters as ValidFilterByType[],
220+
);
157221
expect(filteredPage).toEqual(expected);
158222
});
159223
});

npm-packages/system-udfs/convex/_system/frontend/lib/filters.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface FilterExpression {
1818
export type FilterCommon = {
1919
id?: string;
2020
field?: string;
21+
enabled?: boolean;
2122
};
2223

2324
export type FilterByBuiltin = {
@@ -26,17 +27,21 @@ export type FilterByBuiltin = {
2627
};
2728

2829
export type ValidFilterByBuiltin = {
29-
[P in keyof FilterCommon]-?: FilterCommon[P];
30-
} & { [P in keyof FilterByBuiltin]-?: FilterByBuiltin[P] };
30+
[P in keyof Omit<FilterCommon, "enabled">]-?: FilterCommon[P];
31+
} & { [P in keyof FilterByBuiltin]-?: FilterByBuiltin[P] } & {
32+
enabled?: boolean;
33+
};
3134

3235
export type FilterByOr = {
3336
op: "anyOf" | "noneOf";
3437
value?: JSONValue[];
3538
};
3639

3740
export type ValidFilterByOr = {
38-
[P in keyof FilterCommon]-?: FilterCommon[P];
39-
} & { [P in keyof FilterByOr]-?: FilterByOr[P] };
41+
[P in keyof Omit<FilterCommon, "enabled">]-?: FilterCommon[P];
42+
} & { [P in keyof FilterByOr]-?: FilterByOr[P] } & {
43+
enabled?: boolean;
44+
};
4045

4146
export type ValidFilterByBuiltInOrOr = ValidFilterByBuiltin | ValidFilterByOr;
4247

@@ -61,8 +66,10 @@ export type FilterByType = {
6166
};
6267

6368
export type ValidFilterByType = {
64-
[P in keyof FilterCommon]-?: FilterCommon[P];
65-
} & { [P in keyof FilterByBuiltin]-?: FilterByType[P] };
69+
[P in keyof Omit<FilterCommon, "enabled">]-?: FilterCommon[P];
70+
} & { [P in keyof FilterByBuiltin]-?: FilterByType[P] } & {
71+
enabled?: boolean;
72+
};
6673

6774
const TypeFilterOpKeys = ["type", "notype"] as const;
6875

@@ -95,18 +102,23 @@ const FilterSchema = z.array(
95102
field: z.string().optional(),
96103
value: z.any().optional(),
97104
id: z.string().optional(),
105+
enabled: z.boolean().optional(),
98106
}),
99107
z.object({
100108
op: z.union([z.literal("type"), z.literal("notype")]),
101109
field: z.string().optional(),
102110
// @ts-expect-error I don't know how to fix this type error,
103111
// but i'll test to make sure this works.
104112
value: z.union(TypeFilterSchema).optional(),
113+
id: z.string().optional(),
114+
enabled: z.boolean().optional(),
105115
}),
106116
z.object({
107117
op: z.union([z.literal("anyOf"), z.literal("noneOf")]),
108118
field: z.string().optional(),
109119
value: z.array(z.any()).optional(),
120+
id: z.string().optional(),
121+
enabled: z.boolean().optional(),
110122
}),
111123
]),
112124
);
@@ -172,9 +184,10 @@ export function applyTypeFilters(
172184
page: GenericDocument[],
173185
filters: FilterByType[],
174186
) {
175-
const validatedFilters = filters.filter<ValidFilterByType>(
176-
(f): f is ValidFilterByType => isValidFilter(f),
177-
);
187+
const validatedFilters = filters
188+
.filter<ValidFilterByType>((f): f is ValidFilterByType => isValidFilter(f))
189+
// Only apply filters that are enabled (or where enabled is undefined for backward compatibility)
190+
.filter((f) => f.enabled !== false);
178191
return page.filter((doc) => {
179192
for (const filter of validatedFilters) {
180193
const value = doc[filter.field];

npm-packages/system-udfs/convex/_system/frontend/paginatedTableDocuments.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,13 @@ export default queryGeneric({
7272
});
7373
}
7474
}
75-
76-
const [builtinFilters, typeFilters] = partitionFiltersByOperator(
77-
parsedFilters?.clauses,
75+
const enabledFilters = parsedFilters?.clauses?.filter(
76+
(f) => f.enabled !== false,
7877
);
7978

79+
const [builtinFilters, typeFilters] =
80+
partitionFiltersByOperator(enabledFilters);
81+
8082
const queryInitializer = db.query(table);
8183
let query: OrderedQuery<any> | undefined = undefined;
8284

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/** @type {import('jest').Config} */
2+
module.exports = {
3+
preset: "ts-jest",
4+
testEnvironment: "node",
5+
testMatch: ["**/*.test.ts"],
6+
transform: {
7+
"^.+\\.tsx?$": [
8+
"ts-jest",
9+
{
10+
tsconfig: "tsconfig.json",
11+
},
12+
],
13+
},
14+
moduleNameMapper: {
15+
"convex/(.*)": "<rootDir>/__mocks__/convex/$1",
16+
"^id-encoding$": "<rootDir>/__mocks__/id-encoding",
17+
},
18+
moduleDirectories: ["node_modules", "../common/temp/node_modules/.pnpm"],
19+
};

npm-packages/system-udfs/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"build": "rm -rf dist && tsc",
88
"clean": "rm -rf dist",
99
"lint": "tsc && cd convex && eslint .",
10-
"prepare": "npm run build"
10+
"prepare": "npm run build",
11+
"test": "jest"
1112
},
1213
"dependencies": {
1314
"object-inspect": "^1.12.0",
@@ -31,6 +32,7 @@
3132
"eslint-plugin-react-hooks": "^5.1.0-beta-26f2496093-20240514",
3233
"eslint-plugin-react": "^7.37.2",
3334
"jest": "^29.6.0",
35+
"ts-jest": "^29.1.1",
3436
"@typescript-eslint/eslint-plugin": "^6.7.4",
3537
"@typescript-eslint/parser": "^6.7.4",
3638
"eslint-config-prettier": "^9.1.0",

0 commit comments

Comments
 (0)