From 7f72589fdab0f69785112c2f82c0c19130a0e131 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 13 Jun 2025 15:31:58 +0100 Subject: [PATCH 1/5] Runs filter by org id and add created at to ordering --- .../v3/NextRunListPresenter.server.ts | 2 ++ .../route.tsx | 2 +- .../app/services/runsRepository.server.ts | 24 +++++++++++-------- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/apps/webapp/app/presenters/v3/NextRunListPresenter.server.ts b/apps/webapp/app/presenters/v3/NextRunListPresenter.server.ts index bee9159016..ec5703b60b 100644 --- a/apps/webapp/app/presenters/v3/NextRunListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/NextRunListPresenter.server.ts @@ -45,6 +45,7 @@ export class NextRunListPresenter { ) {} public async call( + organizationId: string, environmentId: string, { userId, @@ -190,6 +191,7 @@ export class NextRunListPresenter { }); const { runs, pagination } = await runsRepository.listRuns({ + organizationId, environmentId, projectId, tasks, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.next.runs._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.next.runs._index/route.tsx index d68d8287d1..e73b1c883e 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.next.runs._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.next.runs._index/route.tsx @@ -128,7 +128,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { } const presenter = new NextRunListPresenter($replica, clickhouseClient); - const list = presenter.call(environment.id, { + const list = presenter.call(project.organizationId, environment.id, { userId, projectId: project.id, tasks, diff --git a/apps/webapp/app/services/runsRepository.server.ts b/apps/webapp/app/services/runsRepository.server.ts index 8d9445ce7f..37e32d2269 100644 --- a/apps/webapp/app/services/runsRepository.server.ts +++ b/apps/webapp/app/services/runsRepository.server.ts @@ -1,8 +1,8 @@ -import { ClickHouse } from "@internal/clickhouse"; -import { Tracer } from "@internal/tracing"; -import { Logger, LogLevel } from "@trigger.dev/core/logger"; -import { TaskRunStatus } from "@trigger.dev/database"; -import { PrismaClient } from "~/db.server"; +import { type ClickHouse } from "@internal/clickhouse"; +import { type Tracer } from "@internal/tracing"; +import { type Logger, type LogLevel } from "@trigger.dev/core/logger"; +import { type TaskRunStatus } from "@trigger.dev/database"; +import { type PrismaClient } from "~/db.server"; export type RunsRepositoryOptions = { clickhouse: ClickHouse; @@ -13,6 +13,7 @@ export type RunsRepositoryOptions = { }; export type ListRunsOptions = { + organizationId: string; projectId: string; environmentId: string; //filters @@ -43,11 +44,14 @@ export class RunsRepository { async listRuns(options: ListRunsOptions) { const queryBuilder = this.options.clickhouse.taskRuns.queryBuilder(); queryBuilder - .where("environment_id = {environmentId: String}", { - environmentId: options.environmentId, + .where("organization_id = {organizationId: String}", { + organizationId: options.organizationId, }) .where("project_id = {projectId: String}", { projectId: options.projectId, + }) + .where("environment_id = {environmentId: String}", { + environmentId: options.environmentId, }); if (options.tasks && options.tasks.length > 0) { @@ -115,17 +119,17 @@ export class RunsRepository { if (options.page.direction === "forward") { queryBuilder .where("run_id < {runId: String}", { runId: options.page.cursor }) - .orderBy("run_id DESC") + .orderBy("created_at DESC, run_id DESC") .limit(options.page.size + 1); } else { queryBuilder .where("run_id > {runId: String}", { runId: options.page.cursor }) - .orderBy("run_id DESC") + .orderBy("created_at DESC, run_id DESC") .limit(options.page.size + 1); } } else { // Initial page - no cursor provided - queryBuilder.orderBy("run_id DESC").limit(options.page.size + 1); + queryBuilder.orderBy("created_at DESC, run_id DESC").limit(options.page.size + 1); } const [queryError, result] = await queryBuilder.execute(); From 4b26354812884c897caf4411459b364fd36c7a8a Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 13 Jun 2025 16:50:32 +0100 Subject: [PATCH 2/5] CopyableText can accept an alternative value for copying --- .../app/components/primitives/CopyableText.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/webapp/app/components/primitives/CopyableText.tsx b/apps/webapp/app/components/primitives/CopyableText.tsx index 6a37c6f2bf..99664b3dc3 100644 --- a/apps/webapp/app/components/primitives/CopyableText.tsx +++ b/apps/webapp/app/components/primitives/CopyableText.tsx @@ -4,9 +4,17 @@ import { SimpleTooltip } from "~/components/primitives/Tooltip"; import { useCopy } from "~/hooks/useCopy"; import { cn } from "~/utils/cn"; -export function CopyableText({ value, className }: { value: string; className?: string }) { +export function CopyableText({ + value, + copyValue, + className, +}: { + value: string; + copyValue?: string; + className?: string; +}) { const [isHovered, setIsHovered] = useState(false); - const { copy, copied } = useCopy(value); + const { copy, copied } = useCopy(copyValue ?? value); return ( Date: Fri, 13 Jun 2025 16:50:44 +0100 Subject: [PATCH 3/5] The runs table now shows the ID instead of run number --- .../app/components/runs/v3/TaskRunsTable.tsx | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/webapp/app/components/runs/v3/TaskRunsTable.tsx b/apps/webapp/app/components/runs/v3/TaskRunsTable.tsx index d2db67d2a1..c85963edcb 100644 --- a/apps/webapp/app/components/runs/v3/TaskRunsTable.tsx +++ b/apps/webapp/app/components/runs/v3/TaskRunsTable.tsx @@ -52,6 +52,8 @@ import { TaskRunStatusCombo, } from "./TaskRunStatus"; import { useEnvironment } from "~/hooks/useEnvironment"; +import { CopyableText } from "~/components/primitives/CopyableText"; +import { ClipboardField } from "~/components/primitives/ClipboardField"; type RunsTableProps = { total: number; @@ -134,7 +136,7 @@ export function TaskRunsTable({ )} )} - Run # + ID Task Version )} - - {formatNumber(run.number)} + + + + + } + asChild + disableHoverableContent + /> From 5039c0e966768e963e3962b4a2b99e9140191654 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 13 Jun 2025 17:22:15 +0100 Subject: [PATCH 4/5] Paginating back/forwards fix --- .../app/services/runsRepository.server.ts | 53 +++++++++---------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/apps/webapp/app/services/runsRepository.server.ts b/apps/webapp/app/services/runsRepository.server.ts index 37e32d2269..6d2befbb2c 100644 --- a/apps/webapp/app/services/runsRepository.server.ts +++ b/apps/webapp/app/services/runsRepository.server.ts @@ -124,7 +124,7 @@ export class RunsRepository { } else { queryBuilder .where("run_id > {runId: String}", { runId: options.page.cursor }) - .orderBy("created_at DESC, run_id DESC") + .orderBy("created_at ASC, run_id ASC") .limit(options.page.size + 1); } } else { @@ -147,38 +147,33 @@ export class RunsRepository { let previousCursor: string | null = null; //get cursors for next and previous pages - if (options.page.cursor) { - switch (options.page.direction) { - case "forward": - previousCursor = runIds.at(0) ?? null; - if (hasMore) { - // The next cursor should be the last run ID from this page - nextCursor = runIds[options.page.size - 1]; - } - break; - case "backward": - // No need to reverse since we're using DESC ordering consistently - if (hasMore) { - previousCursor = runIds[options.page.size - 1]; - } - nextCursor = runIds.at(0) ?? null; - break; - default: - // This shouldn't happen if cursor is provided, but handle it - if (hasMore) { - nextCursor = runIds[options.page.size - 1]; - } - break; + const direction = options.page.direction ?? "forward"; + switch (direction) { + case "forward": { + previousCursor = options.page.cursor ? runIds.at(0) ?? null : null; + if (hasMore) { + // The next cursor should be the last run ID from this page + nextCursor = runIds[options.page.size - 1]; + } + break; } - } else { - // Initial page - no cursor - if (hasMore) { - // The next cursor should be the last run ID from this page - nextCursor = runIds[options.page.size - 1]; + case "backward": { + const reversedRunIds = [...runIds].reverse(); + if (hasMore) { + previousCursor = reversedRunIds.at(1) ?? null; + nextCursor = reversedRunIds.at(options.page.size) ?? null; + } else { + nextCursor = reversedRunIds.at(options.page.size - 1) ?? null; + } + + break; } } - const runIdsToReturn = hasMore ? runIds.slice(0, -1) : runIds; + const runIdsToReturn = + options.page.direction === "backward" && hasMore + ? runIds.slice(1, options.page.size + 1) + : runIds.slice(0, options.page.size); const runs = await this.options.prisma.taskRun.findMany({ where: { From 382a71c23c79d045bd103bf93e87b94f6f0566de Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Fri, 13 Jun 2025 18:40:12 +0100 Subject: [PATCH 5/5] The task stats need org id and project id too --- .../presenters/v3/TaskListPresenter.server.ts | 22 ++++++++++++----- .../route.tsx | 2 ++ .../environmentMetricsRepository.server.ts | 24 +++++++++++++++++++ apps/webapp/test/runsRepository.test.ts | 16 +++++++++++++ internal-packages/clickhouse/src/taskRuns.ts | 18 +++++++++++--- 5 files changed, 73 insertions(+), 9 deletions(-) diff --git a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts index 4c8acddb99..59313a41c1 100644 --- a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts @@ -1,16 +1,16 @@ import { - PrismaClientOrTransaction, - RuntimeEnvironmentType, + type PrismaClientOrTransaction, + type RuntimeEnvironmentType, type TaskTriggerSource, } from "@trigger.dev/database"; import { $replica } from "~/db.server"; import { clickhouseClient } from "~/services/clickhouseInstance.server"; import { - AverageDurations, + type AverageDurations, ClickHouseEnvironmentMetricsRepository, - CurrentRunningStats, - DailyTaskActivity, - EnvironmentMetricsRepository, + type CurrentRunningStats, + type DailyTaskActivity, + type EnvironmentMetricsRepository, PostgrestEnvironmentMetricsRepository, } from "~/services/environmentMetricsRepository.server"; import { singleton } from "~/utils/singleton"; @@ -32,9 +32,13 @@ export class TaskListPresenter { ) {} public async call({ + organizationId, + projectId, environmentId, environmentType, }: { + organizationId: string; + projectId: string; environmentId: string; environmentType: RuntimeEnvironmentType; }) { @@ -76,18 +80,24 @@ export class TaskListPresenter { // IMPORTANT: Don't await these, we want to return the promises // so we can defer the loading of the data const activity = this.environmentMetricsRepository.getDailyTaskActivity({ + organizationId, + projectId, environmentId, days: 6, // This actually means 7 days, because we want to show the current day too tasks: slugs, }); const runningStats = this.environmentMetricsRepository.getCurrentRunningStats({ + organizationId, + projectId, environmentId, days: 6, tasks: slugs, }); const durations = this.environmentMetricsRepository.getAverageDurations({ + organizationId, + projectId, environmentId, days: 6, tasks: slugs, diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx index d2fbf5794d..6152f8579c 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx @@ -125,6 +125,8 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { try { const { tasks, activity, runningStats, durations } = await taskListPresenter.call({ + organizationId: project.organizationId, + projectId: project.id, environmentId: environment.id, environmentType: environment.type, }); diff --git a/apps/webapp/app/services/environmentMetricsRepository.server.ts b/apps/webapp/app/services/environmentMetricsRepository.server.ts index 6b0251b753..2849afbff6 100644 --- a/apps/webapp/app/services/environmentMetricsRepository.server.ts +++ b/apps/webapp/app/services/environmentMetricsRepository.server.ts @@ -10,18 +10,24 @@ export type AverageDurations = Record; export interface EnvironmentMetricsRepository { getDailyTaskActivity(options: { + organizationId: string; + projectId: string; environmentId: string; days: number; tasks: string[]; }): Promise; getCurrentRunningStats(options: { + organizationId: string; + projectId: string; environmentId: string; days: number; tasks: string[]; }): Promise; getAverageDurations(options: { + organizationId: string; + projectId: string; environmentId: string; days: number; tasks: string[]; @@ -177,10 +183,14 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric constructor(private readonly options: ClickHouseEnvironmentMetricsRepositoryOptions) {} public async getDailyTaskActivity({ + organizationId, + projectId, environmentId, days, tasks, }: { + organizationId: string; + projectId: string; environmentId: string; days: number; tasks: string[]; @@ -190,6 +200,8 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric } const [queryError, activity] = await this.options.clickhouse.taskRuns.getTaskActivity({ + organizationId, + projectId, environmentId, days, }); @@ -210,10 +222,14 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric } public async getCurrentRunningStats({ + organizationId, + projectId, environmentId, days, tasks, }: { + organizationId: string; + projectId: string; environmentId: string; days: number; tasks: string[]; @@ -223,6 +239,8 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric } const [queryError, stats] = await this.options.clickhouse.taskRuns.getCurrentRunningStats({ + organizationId, + projectId, environmentId, days, }); @@ -242,10 +260,14 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric } public async getAverageDurations({ + organizationId, + projectId, environmentId, days, tasks, }: { + organizationId: string; + projectId: string; environmentId: string; days: number; tasks: string[]; @@ -255,6 +277,8 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric } const [queryError, durations] = await this.options.clickhouse.taskRuns.getAverageDurations({ + organizationId, + projectId, environmentId, days, }); diff --git a/apps/webapp/test/runsRepository.test.ts b/apps/webapp/test/runsRepository.test.ts index 91c1f5de5e..7ee3b05b4f 100644 --- a/apps/webapp/test/runsRepository.test.ts +++ b/apps/webapp/test/runsRepository.test.ts @@ -72,6 +72,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, }); expect(runs).toHaveLength(1); @@ -180,6 +181,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, tasks: ["task-1", "task-2"], }); @@ -290,6 +292,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, versions: ["1.0.0", "3.0.0"], }); @@ -400,6 +403,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, statuses: ["PENDING", "COMPLETED_SUCCESSFULLY"], }); @@ -510,6 +514,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, tags: ["urgent"], }); @@ -619,6 +624,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, scheduleId: "schedule_1", }); @@ -712,6 +718,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, isTest: true, }); @@ -723,6 +730,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, isTest: false, }); @@ -816,6 +824,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, rootOnly: true, }); @@ -945,6 +954,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, batchId: batchRun1.id, }); @@ -1052,6 +1062,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, runFriendlyIds: ["run_abc", "run_xyz"], }); @@ -1159,6 +1170,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, runIds: [run1.id, run3.id], }); @@ -1273,6 +1285,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, from: yesterday.getTime(), to: now.getTime(), }); @@ -1393,6 +1406,7 @@ describe("RunsRepository", () => { page: { size: 10 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, tasks: ["task-1"], versions: ["1.0.0"], statuses: ["COMPLETED_SUCCESSFULLY"], @@ -1476,6 +1490,7 @@ describe("RunsRepository", () => { page: { size: 2 }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, }); expect(firstPage.runs).toHaveLength(2); @@ -1491,6 +1506,7 @@ describe("RunsRepository", () => { }, projectId: project.id, environmentId: runtimeEnvironment.id, + organizationId: organization.id, }); expect(secondPage.runs).toHaveLength(2); diff --git a/internal-packages/clickhouse/src/taskRuns.ts b/internal-packages/clickhouse/src/taskRuns.ts index 396f990efa..b57ddbfc8b 100644 --- a/internal-packages/clickhouse/src/taskRuns.ts +++ b/internal-packages/clickhouse/src/taskRuns.ts @@ -113,6 +113,8 @@ export const TaskActivityQueryResult = z.object({ export type TaskActivityQueryResult = z.infer; export const TaskActivityQueryParams = z.object({ + organizationId: z.string(), + projectId: z.string(), environmentId: z.string(), days: z.number().int(), }); @@ -128,7 +130,9 @@ export function getTaskActivityQueryBuilder(ch: ClickhouseReader, settings?: Cli count() as count FROM trigger_dev.task_runs_v2 FINAL WHERE - environment_id = {environmentId: String} + organization_id = {organizationId: String} + AND project_id = {projectId: String} + AND environment_id = {environmentId: String} AND created_at >= today() - {days: Int64} AND _is_deleted = 0 GROUP BY @@ -155,6 +159,8 @@ export const CurrentRunningStatsQueryResult = z.object({ export type CurrentRunningStatsQueryResult = z.infer; export const CurrentRunningStatsQueryParams = z.object({ + organizationId: z.string(), + projectId: z.string(), environmentId: z.string(), days: z.number().int(), }); @@ -169,7 +175,9 @@ export function getCurrentRunningStats(ch: ClickhouseReader, settings?: ClickHou count() as count FROM trigger_dev.task_runs_v2 FINAL WHERE - environment_id = {environmentId: String} + organization_id = {organizationId: String} + AND project_id = {projectId: String} + AND environment_id = {environmentId: String} AND status IN ('PENDING', 'WAITING_FOR_DEPLOY', 'WAITING_TO_RESUME', 'QUEUED', 'EXECUTING') AND _is_deleted = 0 AND created_at >= now() - INTERVAL {days: Int64} DAY @@ -193,6 +201,8 @@ export const AverageDurationsQueryResult = z.object({ export type AverageDurationsQueryResult = z.infer; export const AverageDurationsQueryParams = z.object({ + organizationId: z.string(), + projectId: z.string(), environmentId: z.string(), days: z.number().int(), }); @@ -206,7 +216,9 @@ export function getAverageDurations(ch: ClickhouseReader, settings?: ClickHouseS avg(toUnixTimestamp(completed_at) - toUnixTimestamp(started_at)) as duration FROM trigger_dev.task_runs_v2 FINAL WHERE - environment_id = {environmentId: String} + organization_id = {organizationId: String} + AND project_id = {projectId: String} + AND environment_id = {environmentId: String} AND created_at >= today() - {days: Int64} AND status IN ('COMPLETED_SUCCESSFULLY', 'COMPLETED_WITH_ERRORS') AND started_at IS NOT NULL