Skip to content

Commit b50de50

Browse files
committed
Added machine filtering
1 parent 8b3289d commit b50de50

File tree

9 files changed

+252
-27
lines changed

9 files changed

+252
-27
lines changed

apps/webapp/app/assets/icons/MachineIcon.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export function MachineIcon({ preset, className }: { preset?: string; className?
2727
}
2828
}
2929

30-
function MachineDefaultIcon({ className }: { className?: string }) {
30+
export function MachineDefaultIcon({ className }: { className?: string }) {
3131
return (
3232
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
3333
<path

apps/webapp/app/components/MachineLabelCombo.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { type MachinePresetName } from "@trigger.dev/core/v3";
1+
import { MachinePresetName } from "@trigger.dev/core/v3";
22
import { MachineIcon } from "~/assets/icons/MachineIcon";
33
import { cn } from "~/utils/cn";
44

5+
export const machines = Object.values(MachinePresetName.enum);
6+
57
export function MachineLabelCombo({
68
preset,
79
className,

apps/webapp/app/components/runs/v3/RunFilters.tsx

Lines changed: 141 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,21 @@ import {
1010
} from "@heroicons/react/20/solid";
1111
import { Form, useFetcher } from "@remix-run/react";
1212
import { IconToggleLeft } from "@tabler/icons-react";
13+
import { MachinePresetName } from "@trigger.dev/core/v3";
1314
import type { BulkActionType, TaskRunStatus, TaskTriggerSource } from "@trigger.dev/database";
1415
import { ListFilterIcon } from "lucide-react";
1516
import { matchSorter } from "match-sorter";
1617
import { type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
1718
import { z } from "zod";
1819
import { ListCheckedIcon } from "~/assets/icons/ListCheckedIcon";
20+
import { MachineDefaultIcon } from "~/assets/icons/MachineIcon";
1921
import { StatusIcon } from "~/assets/icons/StatusIcon";
2022
import { TaskIcon } from "~/assets/icons/TaskIcon";
23+
import {
24+
formatMachinePresetName,
25+
MachineLabelCombo,
26+
machines,
27+
} from "~/components/MachineLabelCombo";
2128
import { AppliedFilter } from "~/components/primitives/AppliedFilter";
2229
import { DateTime } from "~/components/primitives/DateTime";
2330
import { FormError } from "~/components/primitives/FormError";
@@ -42,6 +49,7 @@ import {
4249
TooltipProvider,
4350
TooltipTrigger,
4451
} from "~/components/primitives/Tooltip";
52+
import { useDebounceEffect } from "~/hooks/useDebounce";
4553
import { useEnvironment } from "~/hooks/useEnvironment";
4654
import { useOptimisticLocation } from "~/hooks/useOptimisticLocation";
4755
import { useOrganization } from "~/hooks/useOrganizations";
@@ -60,7 +68,6 @@ import {
6068
TaskRunStatusCombo,
6169
} from "./TaskRunStatus";
6270
import { TaskTriggerSourceIcon } from "./TaskTriggerSource";
63-
import { useDebounceEffect } from "~/hooks/useDebounce";
6471

6572
export const RunStatus = z.enum(allTaskRunStatuses);
6673

@@ -80,6 +87,27 @@ const StringOrStringArray = z.preprocess((value) => {
8087
return undefined;
8188
}, z.string().array().optional());
8289

90+
export const MachinePresetOrMachinePresetArray = z.preprocess((value) => {
91+
if (typeof value === "string") {
92+
if (value.length > 0) {
93+
const parsed = MachinePresetName.safeParse(value);
94+
return parsed.success ? [parsed.data] : undefined;
95+
}
96+
97+
return undefined;
98+
}
99+
100+
if (Array.isArray(value)) {
101+
return value
102+
.filter((v) => typeof v === "string" && v.length > 0)
103+
.map((v) => MachinePresetName.safeParse(v))
104+
.filter((result) => result.success)
105+
.map((result) => result.data);
106+
}
107+
108+
return undefined;
109+
}, MachinePresetName.array().optional());
110+
83111
export const TaskRunListSearchFilters = z.object({
84112
cursor: z.string().optional(),
85113
direction: z.enum(["forward", "backward"]).optional(),
@@ -111,6 +139,7 @@ export const TaskRunListSearchFilters = z.object({
111139
runId: StringOrStringArray,
112140
scheduleId: z.string().optional(),
113141
queues: StringOrStringArray,
142+
machines: MachinePresetOrMachinePresetArray,
114143
});
115144

116145
export type TaskRunListSearchFilters = z.infer<typeof TaskRunListSearchFilters>;
@@ -146,6 +175,8 @@ export function filterTitle(filterKey: string) {
146175
return "Schedule ID";
147176
case "queues":
148177
return "Queues";
178+
case "machines":
179+
return "Machine";
149180
default:
150181
return filterKey;
151182
}
@@ -157,7 +188,7 @@ export function filterIcon(filterKey: string): ReactNode | undefined {
157188
case "direction":
158189
return undefined;
159190
case "statuses":
160-
return <StatusIcon className="size-4" />;
191+
return <StatusIcon className="size-4 border-text-bright" />;
161192
case "tasks":
162193
return <TaskIcon className="size-4" />;
163194
case "tags":
@@ -180,6 +211,8 @@ export function filterIcon(filterKey: string): ReactNode | undefined {
180211
return <ClockIcon className="size-4" />;
181212
case "queues":
182213
return <RectangleStackIcon className="size-4" />;
214+
case "machines":
215+
return <MachineDefaultIcon className="size-4" />;
183216
default:
184217
return undefined;
185218
}
@@ -218,6 +251,10 @@ export function getRunFiltersFromSearchParams(
218251
searchParams.getAll("queues").filter((v) => v.length > 0).length > 0
219252
? searchParams.getAll("queues")
220253
: undefined,
254+
machines:
255+
searchParams.getAll("machines").filter((v) => v.length > 0).length > 0
256+
? searchParams.getAll("machines")
257+
: undefined,
221258
};
222259

223260
const parsed = TaskRunListSearchFilters.safeParse(params);
@@ -252,7 +289,8 @@ export function RunsFilters(props: RunFiltersProps) {
252289
searchParams.has("batchId") ||
253290
searchParams.has("runId") ||
254291
searchParams.has("scheduleId") ||
255-
searchParams.has("queues");
292+
searchParams.has("queues") ||
293+
searchParams.has("machines");
256294

257295
return (
258296
<div className="flex flex-row flex-wrap items-center gap-1">
@@ -276,11 +314,12 @@ const filterTypes = [
276314
{
277315
name: "statuses",
278316
title: "Status",
279-
icon: <StatusIcon className="size-4" />,
317+
icon: <StatusIcon className="size-4 border-text-bright" />,
280318
},
281319
{ name: "tasks", title: "Tasks", icon: <TaskIcon className="size-4" /> },
282320
{ name: "tags", title: "Tags", icon: <TagIcon className="size-4" /> },
283321
{ name: "queues", title: "Queues", icon: <RectangleStackIcon className="size-4" /> },
322+
{ name: "machines", title: "Machines", icon: <MachineDefaultIcon className="size-4" /> },
284323
{ name: "run", title: "Run ID", icon: <FingerPrintIcon className="size-4" /> },
285324
{ name: "batch", title: "Batch ID", icon: <Squares2X2Icon className="size-4" /> },
286325
{ name: "schedule", title: "Schedule ID", icon: <ClockIcon className="size-4" /> },
@@ -332,6 +371,7 @@ function AppliedFilters({ possibleTasks, bulkActions }: RunFiltersProps) {
332371
<AppliedTaskFilter possibleTasks={possibleTasks} />
333372
<AppliedTagsFilter />
334373
<AppliedQueuesFilter />
374+
<AppliedMachinesFilter />
335375
<AppliedRunIdFilter />
336376
<AppliedBatchIdFilter />
337377
<AppliedScheduleIdFilter />
@@ -362,6 +402,8 @@ function Menu(props: MenuProps) {
362402
return <TagsDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
363403
case "queues":
364404
return <QueuesDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
405+
case "machines":
406+
return <MachinesDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
365407
case "run":
366408
return <RunIdDropdown onClose={() => props.setFilterType(undefined)} {...props} />;
367409
case "batch":
@@ -874,10 +916,6 @@ function QueuesDropdown({
874916

875917
const filtered = useMemo(() => {
876918
let items: { name: string; type: "custom" | "task"; value: string }[] = [];
877-
if (searchValue === "") {
878-
// items = selected ?? [];
879-
items = [];
880-
}
881919

882920
for (const queueName of selected ?? []) {
883921
const queueItem = fetcher.data?.queues.find((q) => q.name === queueName);
@@ -997,6 +1035,101 @@ function AppliedQueuesFilter() {
9971035
);
9981036
}
9991037

1038+
function MachinesDropdown({
1039+
trigger,
1040+
clearSearchValue,
1041+
searchValue,
1042+
onClose,
1043+
}: {
1044+
trigger: ReactNode;
1045+
clearSearchValue: () => void;
1046+
searchValue: string;
1047+
onClose?: () => void;
1048+
}) {
1049+
const { values, replace } = useSearchParams();
1050+
1051+
const handleChange = (values: string[]) => {
1052+
clearSearchValue();
1053+
replace({ machines: values, cursor: undefined, direction: undefined });
1054+
};
1055+
1056+
const filtered = useMemo(() => {
1057+
if (searchValue === "") {
1058+
return machines;
1059+
}
1060+
return matchSorter(machines, searchValue);
1061+
}, [searchValue]);
1062+
1063+
return (
1064+
<SelectProvider value={values("machines")} setValue={handleChange} virtualFocus={true}>
1065+
{trigger}
1066+
<SelectPopover
1067+
className="min-w-0 max-w-[min(240px,var(--popover-available-width))]"
1068+
hideOnEscape={() => {
1069+
if (onClose) {
1070+
onClose();
1071+
return false;
1072+
}
1073+
1074+
return true;
1075+
}}
1076+
>
1077+
<ComboBox placeholder={"Filter by machine..."} value={searchValue} />
1078+
<SelectList>
1079+
{filtered.map((item, index) => (
1080+
<SelectItem
1081+
key={item}
1082+
value={item}
1083+
shortcut={shortcutFromIndex(index, { shortcutsEnabled: true })}
1084+
>
1085+
<MachineLabelCombo preset={item} />
1086+
</SelectItem>
1087+
))}
1088+
</SelectList>
1089+
</SelectPopover>
1090+
</SelectProvider>
1091+
);
1092+
}
1093+
1094+
function AppliedMachinesFilter() {
1095+
const { values, del } = useSearchParams();
1096+
const machines = values("machines");
1097+
1098+
if (machines.length === 0 || machines.every((v) => v === "")) {
1099+
return null;
1100+
}
1101+
1102+
return (
1103+
<FilterMenuProvider>
1104+
{(search, setSearch) => (
1105+
<MachinesDropdown
1106+
trigger={
1107+
<Ariakit.Select render={<div className="group cursor-pointer focus-custom" />}>
1108+
<AppliedFilter
1109+
label="Machines"
1110+
icon={filterIcon("machines")}
1111+
value={appliedSummary(
1112+
machines.map((v) => {
1113+
const parsed = MachinePresetName.safeParse(v);
1114+
if (!parsed.success) {
1115+
return v;
1116+
}
1117+
return formatMachinePresetName(parsed.data);
1118+
})
1119+
)}
1120+
onRemove={() => del(["machines", "cursor", "direction"])}
1121+
variant="secondary/small"
1122+
/>
1123+
</Ariakit.Select>
1124+
}
1125+
searchValue={search}
1126+
clearSearchValue={() => setSearch("")}
1127+
/>
1128+
)}
1129+
</FilterMenuProvider>
1130+
);
1131+
}
1132+
10001133
function RootOnlyToggle({ defaultValue }: { defaultValue: boolean }) {
10011134
const { value, values, replace } = useSearchParams();
10021135
const searchValue = value("rootOnly");

apps/webapp/app/components/runs/v3/TaskRunsTable.tsx

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,11 @@ import {
88
} from "@heroicons/react/20/solid";
99
import { BeakerIcon, BookOpenIcon, CheckIcon } from "@heroicons/react/24/solid";
1010
import { useLocation } from "@remix-run/react";
11-
import {
12-
formatDuration,
13-
formatDurationMilliseconds,
14-
MachinePresetName,
15-
} from "@trigger.dev/core/v3";
11+
import { formatDuration, formatDurationMilliseconds } from "@trigger.dev/core/v3";
1612
import { useCallback, useRef } from "react";
13+
import { TaskIconSmall } from "~/assets/icons/TaskIcon";
14+
import { MachineLabelCombo } from "~/components/MachineLabelCombo";
15+
import { MachineTooltipInfo } from "~/components/MachineTooltipInfo";
1716
import { Badge } from "~/components/primitives/Badge";
1817
import { Button, LinkButton } from "~/components/primitives/Buttons";
1918
import { Checkbox } from "~/components/primitives/Checkbox";
@@ -56,10 +55,6 @@ import {
5655
filterableTaskRunStatuses,
5756
TaskRunStatusCombo,
5857
} from "./TaskRunStatus";
59-
import { MachineIcon } from "~/assets/icons/MachineIcon";
60-
import { MachineLabelCombo } from "~/components/MachineLabelCombo";
61-
import { MachineTooltipInfo } from "~/components/MachineTooltipInfo";
62-
import { TaskIconSmall } from "~/assets/icons/TaskIcon";
6358

6459
type RunsTableProps = {
6560
total: number;
@@ -211,9 +206,9 @@ export function TaskRunsTable({
211206
<TableHeaderCell className="pl-4" tooltip={<MachineTooltipInfo />}>
212207
Machine
213208
</TableHeaderCell>
209+
<TableHeaderCell>Queue</TableHeaderCell>
214210
<TableHeaderCell>Test</TableHeaderCell>
215211
<TableHeaderCell>Created at</TableHeaderCell>
216-
<TableHeaderCell>Queue</TableHeaderCell>
217212
<TableHeaderCell
218213
tooltip={
219214
<div className="max-w-xs p-1">
@@ -389,12 +384,6 @@ export function TaskRunsTable({
389384
<TableCell to={path}>
390385
<MachineLabelCombo preset={run.machinePreset} />
391386
</TableCell>
392-
<TableCell to={path}>
393-
{run.isTest ? <CheckIcon className="size-4 text-charcoal-400" /> : "–"}
394-
</TableCell>
395-
<TableCell to={path}>
396-
{run.createdAt ? <DateTime date={run.createdAt} /> : "–"}
397-
</TableCell>
398387
<TableCell to={path}>
399388
<span className="flex items-center gap-1">
400389
{run.queue.type === "task" ? (
@@ -411,6 +400,12 @@ export function TaskRunsTable({
411400
<span>{run.queue.name}</span>
412401
</span>
413402
</TableCell>
403+
<TableCell to={path}>
404+
{run.isTest ? <CheckIcon className="size-4 text-charcoal-400" /> : "–"}
405+
</TableCell>
406+
<TableCell to={path}>
407+
{run.createdAt ? <DateTime date={run.createdAt} /> : "–"}
408+
</TableCell>
414409
<TableCell to={path}>
415410
{run.delayUntil ? <DateTime date={run.delayUntil} /> : "–"}
416411
</TableCell>

apps/webapp/app/presenters/RunFilters.server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export async function getRunFiltersFromRequest(request: Request): Promise<Filter
3333
batchId,
3434
scheduleId,
3535
queues,
36+
machines,
3637
} = TaskRunListSearchFilters.parse(s);
3738

3839
return {
@@ -51,5 +52,6 @@ export async function getRunFiltersFromRequest(request: Request): Promise<Filter
5152
direction: direction,
5253
cursor: cursor,
5354
queues,
55+
machines,
5456
};
5557
}

0 commit comments

Comments
 (0)