Skip to content

Commit ee60e7e

Browse files
atrakhConvex, Inc.
authored and
Convex, Inc.
committedApr 4, 2025·
dashboard: improve error display for filters (including range bounds) (#36232)
- Fixes a bug where validation ui was not shown for regular filters - Shows error validation for index filters, and adds a new type of validation for the is between operator - GitOrigin-RevId: 6b58812de243bbc44e9a787bdd11402f6c9be6be
1 parent d8f666c commit ee60e7e

File tree

3 files changed

+82
-10
lines changed

3 files changed

+82
-10
lines changed
 

‎npm-packages/dashboard-common/src/features/data/components/DataFilters/DataFilters.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ export function DataFilters({
224224
setDraftFilters={setDraftFilters}
225225
onChangeOrder={onChangeOrder}
226226
onChangeIndexFilter={onChangeIndexFilter}
227+
invalidFilters={invalidFilters}
227228
onError={(...args) => onError("index", ...args)}
228229
hasInvalidFilters={hasInvalidFilters}
229230
/>
@@ -272,8 +273,10 @@ export function DataFilters({
272273
}}
273274
onError={(...args) => onError("filter", ...args)}
274275
error={
275-
dataFetchErrors?.find((e) => e.filter === idx)?.error ||
276-
invalidFilters[`filter:${idx}`]
276+
clause.enabled !== false
277+
? dataFetchErrors?.find((e) => e.filter === idx)
278+
?.error || invalidFilters[`filter/${idx}`]
279+
: undefined
277280
}
278281
autoFocusValueEditor={
279282
idx === shownFilters.clauses.length - 1
@@ -410,7 +413,9 @@ function FilterItem({
410413
/>
411414
{error && (
412415
<Tooltip tip={error}>
413-
<ExclamationTriangleIcon className="mt-1.5 size-4 text-content-errorSecondary" />
416+
<div className="rounded border bg-background-error p-1">
417+
<ExclamationTriangleIcon className="size-4 text-content-errorSecondary" />
418+
</div>
414419
</Tooltip>
415420
)}
416421
</div>
@@ -658,7 +663,7 @@ function useDataFilters({
658663
...shownFilters,
659664
clauses: shownFilters.clauses.map((filter, idx) => ({
660665
...filter,
661-
enabled: invalidFilters[`filter:${idx}`] ? false : filter.enabled,
666+
enabled: invalidFilters[`filter/${idx}`] ? false : filter.enabled,
662667
})),
663668
order: newOrder,
664669
};

‎npm-packages/dashboard-common/src/features/data/components/DataFilters/IndexFilterEditor.tsx

+68-6
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import { DateTimePicker } from "@common/features/data/components/FilterEditor/Da
1111
import { cn } from "@common/lib/cn";
1212
import { UNDEFINED_PLACEHOLDER } from "system-udfs/convex/_system/frontend/patchDocumentsFields";
1313
import { Tooltip } from "@common/elements/Tooltip";
14+
import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
1415

1516
export type IndexFilterState = FilterByIndex | FilterByIndexRange;
1617

1718
export type IndexFilterEditorProps = {
1819
idx: number;
1920
field: string;
21+
error: string | undefined;
2022
onChange(filter: IndexFilterState, idx: number): void;
2123
onApplyFilters(): void;
2224
onError(idx: number, errors: string[]): void;
@@ -38,9 +40,14 @@ const filterTypeOptions: Option<string>[] = [
3840
{ value: "between", label: "is between" },
3941
];
4042

43+
// Define a constant for the error message
44+
const RANGE_ERROR_MESSAGE =
45+
"The lower bound of this range is currently set to a value that is higher than the upper bound. This filter would never match any documents.";
46+
4147
export function IndexFilterEditor({
4248
idx,
4349
field,
50+
error,
4451
onChange,
4552
onApplyFilters,
4653
onError,
@@ -186,21 +193,45 @@ export function IndexFilterEditor({
186193
// Handle lower date change for _creationTime between
187194
const handleLowerDateChange = useCallback(
188195
(date: Date) => {
196+
const timestamp = date.getTime();
189197
if ("lowerValue" in filter) {
190-
onChange({ ...filter, lowerValue: date.getTime() }, idx);
198+
onChange({ ...filter, lowerValue: timestamp }, idx);
199+
200+
// Check if lowerValue is greater than upperValue
201+
if (
202+
filter.type === "indexRange" &&
203+
typeof filter.upperValue === "number" &&
204+
timestamp > filter.upperValue
205+
) {
206+
onError(idx, [RANGE_ERROR_MESSAGE]);
207+
} else if (error === RANGE_ERROR_MESSAGE) {
208+
onError(idx, []);
209+
}
191210
}
192211
},
193-
[filter, idx, onChange],
212+
[filter, idx, onChange, onError, error],
194213
);
195214

196215
// Handle upper date change for _creationTime between
197216
const handleUpperDateChange = useCallback(
198217
(date: Date) => {
218+
const timestamp = date.getTime();
199219
if ("upperValue" in filter) {
200-
onChange({ ...filter, upperValue: date.getTime() }, idx);
220+
onChange({ ...filter, upperValue: timestamp }, idx);
221+
222+
// Check if upperValue is less than lowerValue
223+
if (
224+
filter.type === "indexRange" &&
225+
typeof filter.lowerValue === "number" &&
226+
timestamp < filter.lowerValue
227+
) {
228+
onError(idx, [RANGE_ERROR_MESSAGE]);
229+
} else if (error === RANGE_ERROR_MESSAGE) {
230+
onError(idx, []);
231+
}
201232
}
202233
},
203-
[filter, idx, onChange],
234+
[filter, idx, onChange, onError, error],
204235
);
205236

206237
// Handle changes to range filter values
@@ -215,9 +246,21 @@ export function IndexFilterEditor({
215246
? Number(value)
216247
: (value as JSONValue);
217248
onChange({ ...filter, lowerValue: jsonValue }, idx);
249+
250+
// Check if lowerValue is greater than upperValue
251+
if (
252+
filter.type === "indexRange" &&
253+
jsonValue !== null &&
254+
filter.upperValue !== null &&
255+
filter.upperValue !== undefined &&
256+
typeof jsonValue === typeof filter.upperValue &&
257+
jsonValue > filter.upperValue
258+
) {
259+
onError(idx, [RANGE_ERROR_MESSAGE]);
260+
}
218261
}
219262
},
220-
[filter, idx, onChange],
263+
[filter, idx, onChange, onError],
221264
);
222265

223266
const handleUpperValueChange = useCallback(
@@ -231,9 +274,21 @@ export function IndexFilterEditor({
231274
? Number(value)
232275
: (value as JSONValue);
233276
onChange({ ...filter, upperValue: jsonValue }, idx);
277+
278+
// Check if upperValue is less than lowerValue
279+
if (
280+
filter.type === "indexRange" &&
281+
jsonValue !== null &&
282+
filter.lowerValue !== null &&
283+
filter.lowerValue !== undefined &&
284+
typeof jsonValue === typeof filter.lowerValue &&
285+
jsonValue < filter.lowerValue
286+
) {
287+
onError(idx, [RANGE_ERROR_MESSAGE]);
288+
}
234289
}
235290
},
236-
[filter, idx, onChange],
291+
[filter, idx, onChange, onError],
237292
);
238293

239294
// Convert to range filter
@@ -612,6 +667,13 @@ export function IndexFilterEditor({
612667

613668
{/* Render the appropriate value editor */}
614669
{renderValueEditor()}
670+
{error && (
671+
<Tooltip tip={error}>
672+
<div className="ml-1 rounded border bg-background-error p-1">
673+
<ExclamationTriangleIcon className="size-4 text-content-errorSecondary" />
674+
</div>
675+
</Tooltip>
676+
)}
615677
</div>
616678
</div>
617679
);

‎npm-packages/dashboard-common/src/features/data/components/DataFilters/IndexFilters.tsx

+5
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,13 @@ type IndexFiltersProps = {
7272
onChangeIndexFilter: (filter: IndexFilterState, idx: number) => void;
7373
onError: (idx: number, errors: string[]) => void;
7474
hasInvalidFilters: boolean;
75+
invalidFilters: Record<string, string>;
7576
};
7677

7778
export function IndexFilters({
7879
shownFilters,
7980
defaultDocument,
81+
invalidFilters,
8082
indexes,
8183
tableName,
8284
activeSchema,
@@ -313,6 +315,9 @@ export function IndexFilters({
313315
key={idx}
314316
idx={idx}
315317
field={fieldName}
318+
error={
319+
clause.enabled ? invalidFilters[`index/${idx}`] : undefined
320+
}
316321
onChange={onChangeIndexFilter}
317322
onApplyFilters={() => {
318323
if (hasInvalidFilters) {

0 commit comments

Comments
 (0)
Please sign in to comment.