From 13f3ae80f8f7693a16f90b7a11208f6b18f52ef6 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 15 Jul 2025 14:02:46 +0100 Subject: [PATCH 1/2] Fix for filtering by run id Simplified to have a single filter for runId which accepts an array. This unifies the filtering by multiple runs (used by the waitpoint page) and the individual run filtering. It fixes a bug with bulk actions --- .../app/components/runs/v3/RunFilters.tsx | 7 +++++-- apps/webapp/app/presenters/RunFilters.server.ts | 2 +- .../presenters/v3/BulkActionPresenter.server.ts | 6 +++++- .../v3/NextRunListPresenter.server.ts | 17 ++++++++++------- .../presenters/v3/WaitpointPresenter.server.ts | 2 +- .../app/services/runsRepository.server.ts | 17 +++++++++++------ .../app/v3/services/bulk/BulkActionV2.server.ts | 15 ++++++++++----- apps/webapp/test/runsRepository.test.ts | 4 ++-- 8 files changed, 45 insertions(+), 25 deletions(-) diff --git a/apps/webapp/app/components/runs/v3/RunFilters.tsx b/apps/webapp/app/components/runs/v3/RunFilters.tsx index 498a89859a0..9db584cbeb4 100644 --- a/apps/webapp/app/components/runs/v3/RunFilters.tsx +++ b/apps/webapp/app/components/runs/v3/RunFilters.tsx @@ -104,7 +104,7 @@ export const TaskRunListSearchFilters = z.object({ to: z.coerce.number().optional(), rootOnly: z.coerce.boolean().optional(), batchId: z.string().optional(), - runId: z.string().optional(), + runId: StringOrStringArray, scheduleId: z.string().optional(), }); @@ -199,7 +199,10 @@ export function getRunFiltersFromSearchParams( from: searchParams.get("from") ?? undefined, to: searchParams.get("to") ?? undefined, rootOnly: searchParams.has("rootOnly") ? searchParams.get("rootOnly") === "true" : undefined, - runId: searchParams.get("runId") ?? undefined, + runId: + searchParams.getAll("runId").filter((v) => v.length > 0).length > 0 + ? searchParams.getAll("runId") + : undefined, batchId: searchParams.get("batchId") ?? undefined, scheduleId: searchParams.get("scheduleId") ?? undefined, }; diff --git a/apps/webapp/app/presenters/RunFilters.server.ts b/apps/webapp/app/presenters/RunFilters.server.ts index 91cf02e9432..37a5a4755bc 100644 --- a/apps/webapp/app/presenters/RunFilters.server.ts +++ b/apps/webapp/app/presenters/RunFilters.server.ts @@ -41,7 +41,7 @@ export async function getRunFiltersFromRequest(request: Request) { from, to, batchId, - runIds: runId ? [runId] : undefined, + runId, scheduleId, rootOnly: rootOnlyValue, direction: direction, diff --git a/apps/webapp/app/presenters/v3/BulkActionPresenter.server.ts b/apps/webapp/app/presenters/v3/BulkActionPresenter.server.ts index cf62cd26532..0434045ea49 100644 --- a/apps/webapp/app/presenters/v3/BulkActionPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/BulkActionPresenter.server.ts @@ -53,7 +53,11 @@ export class BulkActionPresenter extends BasePresenter { ); let mode: BulkActionMode = "filter"; - if (filtersParsed.success && Object.keys(filtersParsed.data).length === 0) { + if ( + filtersParsed.success && + Object.keys(filtersParsed.data).length === 1 && + filtersParsed.data.runId?.length + ) { mode = "selected"; } diff --git a/apps/webapp/app/presenters/v3/NextRunListPresenter.server.ts b/apps/webapp/app/presenters/v3/NextRunListPresenter.server.ts index b799c99a198..bc061b36d78 100644 --- a/apps/webapp/app/presenters/v3/NextRunListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/NextRunListPresenter.server.ts @@ -1,5 +1,9 @@ -import { ClickHouse } from "@internal/clickhouse"; -import { PrismaClient, PrismaClientOrTransaction, type TaskRunStatus } from "@trigger.dev/database"; +import { type ClickHouse } from "@internal/clickhouse"; +import { + type PrismaClient, + type PrismaClientOrTransaction, + type TaskRunStatus, +} from "@trigger.dev/database"; import { type Direction } from "~/components/ListPagination"; import { timeFilters } from "~/components/runs/v3/SharedFilters"; import { findDisplayableEnvironment } from "~/models/runtimeEnvironment.server"; @@ -7,7 +11,6 @@ import { getAllTaskIdentifiers } from "~/models/task.server"; import { RunsRepository } from "~/services/runsRepository.server"; import { ServiceValidationError } from "~/v3/services/baseService.server"; import { isCancellableRunStatus, isFinalRunStatus, isPendingRunStatus } from "~/v3/taskStatus"; -import parseDuration from "parse-duration"; export type RunListOptions = { userId?: string; @@ -25,7 +28,7 @@ export type RunListOptions = { isTest?: boolean; rootOnly?: boolean; batchId?: string; - runIds?: string[]; + runId?: string[]; //pagination direction?: Direction; cursor?: string; @@ -60,7 +63,7 @@ export class NextRunListPresenter { isTest, rootOnly, batchId, - runIds, + runId, from, to, direction = "forward", @@ -85,7 +88,7 @@ export class NextRunListPresenter { (scheduleId !== undefined && scheduleId !== "") || (tags !== undefined && tags.length > 0) || batchId !== undefined || - (runIds !== undefined && runIds.length > 0) || + (runId !== undefined && runId.length > 0) || typeof isTest === "boolean" || rootOnly === true || !time.isDefault; @@ -167,7 +170,7 @@ export class NextRunListPresenter { isTest, rootOnly, batchId, - runIds, + runId, bulkId, page: { size: pageSize, diff --git a/apps/webapp/app/presenters/v3/WaitpointPresenter.server.ts b/apps/webapp/app/presenters/v3/WaitpointPresenter.server.ts index 50890a44114..9abcdf32215 100644 --- a/apps/webapp/app/presenters/v3/WaitpointPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/WaitpointPresenter.server.ts @@ -85,7 +85,7 @@ export class WaitpointPresenter extends BasePresenter { environmentId, { projectId: projectId, - runIds: connectedRunIds, + runId: connectedRunIds, pageSize: 5, period: "31d", } diff --git a/apps/webapp/app/services/runsRepository.server.ts b/apps/webapp/app/services/runsRepository.server.ts index 5b434ea67cf..156efa45e35 100644 --- a/apps/webapp/app/services/runsRepository.server.ts +++ b/apps/webapp/app/services/runsRepository.server.ts @@ -34,11 +34,15 @@ const RunListInputOptionsSchema = z.object({ isTest: z.boolean().optional(), rootOnly: z.boolean().optional(), batchId: z.string().optional(), - runIds: z.array(z.string()).optional(), + runId: z.array(z.string()).optional(), bulkId: z.string().optional(), }); export type RunListInputOptions = z.infer; +export type RunListInputFilters = Omit< + RunListInputOptions, + "organizationId" | "projectId" | "environmentId" +>; type FilterRunsOptions = Omit & { period: number | undefined; @@ -256,13 +260,13 @@ export class RunsRepository { convertedOptions.bulkId = BulkActionId.toId(options.bulkId); } - if (options.runIds) { + if (options.runId) { //convert to friendlyId - convertedOptions.runIds = options.runIds.map((runId) => RunId.toFriendlyId(runId)); + convertedOptions.runId = options.runId.map((r) => RunId.toFriendlyId(r)); } // Show all runs if we are filtering by batchId or runId - if (options.batchId || options.runIds?.length || options.scheduleId || options.tasks?.length) { + if (options.batchId || options.runId?.length || options.scheduleId || options.tasks?.length) { convertedOptions.rootOnly = false; } @@ -342,9 +346,10 @@ function applyRunFiltersToQueryBuilder( }); } - if (options.runIds && options.runIds.length > 0) { + if (options.runId && options.runId.length > 0) { + // it's important that in the query it's "runIds", otherwise it clashes with the cursor which is called "runId" queryBuilder.where("friendly_id IN {runIds: Array(String)}", { - runIds: options.runIds.map((runId) => RunId.toFriendlyId(runId)), + runIds: options.runId.map((runId) => RunId.toFriendlyId(runId)), }); } } diff --git a/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts b/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts index 8ffd9cebf86..8b0f23eb881 100644 --- a/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts +++ b/apps/webapp/app/v3/services/bulk/BulkActionV2.server.ts @@ -8,7 +8,11 @@ import { import { getRunFiltersFromRequest } from "~/presenters/RunFilters.server"; import { type CreateBulkActionPayload } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.bulkaction"; import { clickhouseClient } from "~/services/clickhouseInstance.server"; -import { parseRunListInputOptions, RunsRepository } from "~/services/runsRepository.server"; +import { + parseRunListInputOptions, + type RunListInputFilters, + RunsRepository, +} from "~/services/runsRepository.server"; import { BaseService } from "../baseService.server"; import { commonWorker } from "~/v3/commonWorker.server"; import { env } from "~/env.server"; @@ -378,12 +382,13 @@ export class BulkActionService extends BaseService { } } -async function getFilters(payload: CreateBulkActionPayload, request: Request) { +async function getFilters( + payload: CreateBulkActionPayload, + request: Request +): Promise { if (payload.mode === "selected") { return { - runIds: payload.selectedRunIds, - cursor: undefined, - direction: undefined, + runId: payload.selectedRunIds, }; } diff --git a/apps/webapp/test/runsRepository.test.ts b/apps/webapp/test/runsRepository.test.ts index 4dbdef8c28c..56e6bf92d6e 100644 --- a/apps/webapp/test/runsRepository.test.ts +++ b/apps/webapp/test/runsRepository.test.ts @@ -1063,7 +1063,7 @@ describe("RunsRepository", () => { projectId: project.id, environmentId: runtimeEnvironment.id, organizationId: organization.id, - runIds: ["run_abc", "run_xyz"], + runId: ["run_abc", "run_xyz"], }); expect(runs).toHaveLength(2); @@ -1171,7 +1171,7 @@ describe("RunsRepository", () => { projectId: project.id, environmentId: runtimeEnvironment.id, organizationId: organization.id, - runIds: [run1.friendlyId, run3.friendlyId], + runId: [run1.friendlyId, run3.friendlyId], }); expect(runs).toHaveLength(2); From 48e24838921126758163e3f0a186386007653f83 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Tue, 15 Jul 2025 14:30:08 +0100 Subject: [PATCH 2/2] Truncate long bulk action titles --- .../route.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx index a81f39dbfcc..8aa34624d08 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx @@ -157,8 +157,8 @@ export default function Page() { return (
-
- +
+ {bulkAction.name || bulkAction.friendlyId}