|
9 | 9 | XMarkIcon, |
10 | 10 | } from "@heroicons/react/20/solid"; |
11 | 11 | import { Form, useFetcher } from "@remix-run/react"; |
12 | | -import { IconRotateClockwise2, IconToggleLeft } from "@tabler/icons-react"; |
| 12 | +import { IconBugFilled, IconRotateClockwise2, IconToggleLeft } from "@tabler/icons-react"; |
13 | 13 | import { MachinePresetName } from "@trigger.dev/core/v3"; |
14 | 14 | import type { BulkActionType, TaskRunStatus, TaskTriggerSource } from "@trigger.dev/database"; |
15 | 15 | import { ListFilterIcon } from "lucide-react"; |
@@ -181,6 +181,7 @@ export const TaskRunListSearchFilters = z.object({ |
181 | 181 | machines: MachinePresetOrMachinePresetArray.describe( |
182 | 182 | `Machine presets to filter by (${machines.join(", ")})` |
183 | 183 | ), |
| 184 | + errorId: z.string().optional().describe("Error ID to filter runs by (e.g. error_abc123)"), |
184 | 185 | }); |
185 | 186 |
|
186 | 187 | export type TaskRunListSearchFilters = z.infer<typeof TaskRunListSearchFilters>; |
@@ -220,6 +221,8 @@ export function filterTitle(filterKey: string) { |
220 | 221 | return "Machine"; |
221 | 222 | case "versions": |
222 | 223 | return "Version"; |
| 224 | + case "errorId": |
| 225 | + return "Error ID"; |
223 | 226 | default: |
224 | 227 | return filterKey; |
225 | 228 | } |
@@ -258,6 +261,8 @@ export function filterIcon(filterKey: string): ReactNode | undefined { |
258 | 261 | return <MachineDefaultIcon className="size-4" />; |
259 | 262 | case "versions": |
260 | 263 | return <IconRotateClockwise2 className="size-4" />; |
| 264 | + case "errorId": |
| 265 | + return <IconBugFilled className="size-4" />; |
261 | 266 | default: |
262 | 267 | return undefined; |
263 | 268 | } |
@@ -304,6 +309,7 @@ export function getRunFiltersFromSearchParams( |
304 | 309 | searchParams.getAll("versions").filter((v) => v.length > 0).length > 0 |
305 | 310 | ? searchParams.getAll("versions") |
306 | 311 | : undefined, |
| 312 | + errorId: searchParams.get("errorId") ?? undefined, |
307 | 313 | }; |
308 | 314 |
|
309 | 315 | const parsed = TaskRunListSearchFilters.safeParse(params); |
@@ -344,7 +350,8 @@ export function RunsFilters(props: RunFiltersProps) { |
344 | 350 | searchParams.has("scheduleId") || |
345 | 351 | searchParams.has("queues") || |
346 | 352 | searchParams.has("machines") || |
347 | | - searchParams.has("versions"); |
| 353 | + searchParams.has("versions") || |
| 354 | + searchParams.has("errorId"); |
348 | 355 |
|
349 | 356 | return ( |
350 | 357 | <div className="flex flex-row flex-wrap items-center gap-1"> |
@@ -380,6 +387,7 @@ const filterTypes = [ |
380 | 387 | { name: "batch", title: "Batch ID", icon: <Squares2X2Icon className="size-4" /> }, |
381 | 388 | { name: "schedule", title: "Schedule ID", icon: <ClockIcon className="size-4" /> }, |
382 | 389 | { name: "bulk", title: "Bulk action", icon: <ListCheckedIcon className="size-4" /> }, |
| 390 | + { name: "error", title: "Error ID", icon: <IconBugFilled className="size-4" /> }, |
383 | 391 | ] as const; |
384 | 392 |
|
385 | 393 | type FilterType = (typeof filterTypes)[number]["name"]; |
@@ -434,6 +442,7 @@ function AppliedFilters({ possibleTasks, bulkActions }: RunFiltersProps) { |
434 | 442 | <AppliedBatchIdFilter /> |
435 | 443 | <AppliedScheduleIdFilter /> |
436 | 444 | <AppliedBulkActionsFilter bulkActions={bulkActions} /> |
| 445 | + <AppliedErrorIdFilter /> |
437 | 446 | </> |
438 | 447 | ); |
439 | 448 | } |
@@ -470,6 +479,8 @@ function Menu(props: MenuProps) { |
470 | 479 | return <ScheduleIdDropdown onClose={() => props.setFilterType(undefined)} {...props} />; |
471 | 480 | case "versions": |
472 | 481 | return <VersionsDropdown onClose={() => props.setFilterType(undefined)} {...props} />; |
| 482 | + case "error": |
| 483 | + return <ErrorIdDropdown onClose={() => props.setFilterType(undefined)} {...props} />; |
473 | 484 | } |
474 | 485 | } |
475 | 486 |
|
@@ -655,7 +666,7 @@ function TasksDropdown({ |
655 | 666 | <TaskTriggerSourceIcon source={item.triggerSource} className="size-4 flex-none" /> |
656 | 667 | } |
657 | 668 | > |
658 | | - <MiddleTruncate text={item.slug}/> |
| 669 | + <MiddleTruncate text={item.slug} /> |
659 | 670 | </SelectItem> |
660 | 671 | ))} |
661 | 672 | </SelectList> |
@@ -1740,3 +1751,121 @@ function AppliedScheduleIdFilter() { |
1740 | 1751 | </FilterMenuProvider> |
1741 | 1752 | ); |
1742 | 1753 | } |
| 1754 | + |
| 1755 | +function ErrorIdDropdown({ |
| 1756 | + trigger, |
| 1757 | + clearSearchValue, |
| 1758 | + searchValue, |
| 1759 | + onClose, |
| 1760 | +}: { |
| 1761 | + trigger: ReactNode; |
| 1762 | + clearSearchValue: () => void; |
| 1763 | + searchValue: string; |
| 1764 | + onClose?: () => void; |
| 1765 | +}) { |
| 1766 | + const [open, setOpen] = useState<boolean | undefined>(); |
| 1767 | + const { value, replace } = useSearchParams(); |
| 1768 | + const errorIdValue = value("errorId"); |
| 1769 | + |
| 1770 | + const [errorId, setErrorId] = useState(errorIdValue); |
| 1771 | + |
| 1772 | + const apply = useCallback(() => { |
| 1773 | + clearSearchValue(); |
| 1774 | + replace({ |
| 1775 | + cursor: undefined, |
| 1776 | + direction: undefined, |
| 1777 | + errorId: errorId === "" ? undefined : errorId?.toString(), |
| 1778 | + }); |
| 1779 | + |
| 1780 | + setOpen(false); |
| 1781 | + }, [errorId, replace]); |
| 1782 | + |
| 1783 | + let error: string | undefined = undefined; |
| 1784 | + if (errorId) { |
| 1785 | + if (!errorId.startsWith("error_")) { |
| 1786 | + error = "Error IDs start with 'error_'"; |
| 1787 | + } |
| 1788 | + } |
| 1789 | + |
| 1790 | + return ( |
| 1791 | + <SelectProvider virtualFocus={true} open={open} setOpen={setOpen}> |
| 1792 | + {trigger} |
| 1793 | + <SelectPopover |
| 1794 | + hideOnEnter={false} |
| 1795 | + hideOnEscape={() => { |
| 1796 | + if (onClose) { |
| 1797 | + onClose(); |
| 1798 | + return false; |
| 1799 | + } |
| 1800 | + |
| 1801 | + return true; |
| 1802 | + }} |
| 1803 | + className="max-w-[min(32ch,var(--popover-available-width))]" |
| 1804 | + > |
| 1805 | + <div className="flex flex-col gap-4 p-3"> |
| 1806 | + <div className="flex flex-col gap-1"> |
| 1807 | + <Label>Error ID</Label> |
| 1808 | + <Input |
| 1809 | + placeholder="error_" |
| 1810 | + value={errorId ?? ""} |
| 1811 | + onChange={(e) => setErrorId(e.target.value)} |
| 1812 | + variant="small" |
| 1813 | + className="w-[29ch] font-mono" |
| 1814 | + spellCheck={false} |
| 1815 | + /> |
| 1816 | + {error ? <FormError>{error}</FormError> : null} |
| 1817 | + </div> |
| 1818 | + <div className="flex justify-between gap-1 border-t border-grid-dimmed pt-3"> |
| 1819 | + <Button variant="tertiary/small" onClick={() => setOpen(false)}> |
| 1820 | + Cancel |
| 1821 | + </Button> |
| 1822 | + <Button |
| 1823 | + disabled={error !== undefined || !errorId} |
| 1824 | + variant="secondary/small" |
| 1825 | + shortcut={{ |
| 1826 | + modifiers: ["mod"], |
| 1827 | + key: "Enter", |
| 1828 | + enabledOnInputElements: true, |
| 1829 | + }} |
| 1830 | + onClick={() => apply()} |
| 1831 | + > |
| 1832 | + Apply |
| 1833 | + </Button> |
| 1834 | + </div> |
| 1835 | + </div> |
| 1836 | + </SelectPopover> |
| 1837 | + </SelectProvider> |
| 1838 | + ); |
| 1839 | +} |
| 1840 | + |
| 1841 | +function AppliedErrorIdFilter() { |
| 1842 | + const { value, del } = useSearchParams(); |
| 1843 | + |
| 1844 | + if (value("errorId") === undefined) { |
| 1845 | + return null; |
| 1846 | + } |
| 1847 | + |
| 1848 | + const errorId = value("errorId"); |
| 1849 | + |
| 1850 | + return ( |
| 1851 | + <FilterMenuProvider> |
| 1852 | + {(search, setSearch) => ( |
| 1853 | + <ErrorIdDropdown |
| 1854 | + trigger={ |
| 1855 | + <Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}> |
| 1856 | + <AppliedFilter |
| 1857 | + label="Error ID" |
| 1858 | + icon={filterIcon("errorId")} |
| 1859 | + value={errorId} |
| 1860 | + onRemove={() => del(["errorId", "cursor", "direction"])} |
| 1861 | + variant="secondary/small" |
| 1862 | + /> |
| 1863 | + </Ariakit.Select> |
| 1864 | + } |
| 1865 | + searchValue={search} |
| 1866 | + clearSearchValue={() => setSearch("")} |
| 1867 | + /> |
| 1868 | + )} |
| 1869 | + </FilterMenuProvider> |
| 1870 | + ); |
| 1871 | +} |
0 commit comments