From b2d5bf8c44a93198de156a8828c9be64d2df5473 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 26 Jul 2026 18:18:30 +0000 Subject: [PATCH] fix(webapp): remove unawaited task list metrics promises MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `TaskListPresenter.call` returned three deferred promises — `activity`, `runningStats` and `durations` — but its only caller, `UnifiedTaskListPresenter`, reads just `tasks` and `runningStats`. The `activity` and `durations` promises were never awaited, read or given a rejection handler, so a failing query became an unhandled promise rejection, and the two ClickHouse round-trips behind them ran on every task list page load for results nothing rendered. `UnifiedTaskListPresenter` builds its own hourly activity query for the chart the page actually shows, so the daily activity and average duration data had no consumer left. Remove both fields and the calls that feed them, plus the `getDailyTaskActivity` and `getAverageDurations` methods on `ClickHouseEnvironmentMetricsRepository` (and the dead `TaskActivity` / `DailyTaskActivity` / `AverageDurations` types), which had no other callers. `runningStats` is kept and is unaffected: it is consumed by `UnifiedTaskListPresenter`, which is why its rejections were already handled. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PtQ34usZSzrSALPVBmoLYA --- ...task-list-remove-unused-metrics-queries.md | 6 + .../presenters/v3/TaskListPresenter.server.ts | 30 +--- .../environmentMetricsRepository.server.ts | 133 ------------------ 3 files changed, 11 insertions(+), 158 deletions(-) create mode 100644 .server-changes/task-list-remove-unused-metrics-queries.md diff --git a/.server-changes/task-list-remove-unused-metrics-queries.md b/.server-changes/task-list-remove-unused-metrics-queries.md new file mode 100644 index 00000000000..23164a205ca --- /dev/null +++ b/.server-changes/task-list-remove-unused-metrics-queries.md @@ -0,0 +1,6 @@ +--- +area: webapp +type: fix +--- + +The tasks page no longer runs two queries whose results were never displayed, cutting wasted work on every page load and removing a source of hidden server errors diff --git a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts index f62cfef5c76..1541329884f 100644 --- a/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts @@ -6,10 +6,8 @@ import { import { $replica } from "~/db.server"; import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server"; import { - type AverageDurations, ClickHouseEnvironmentMetricsRepository, type CurrentRunningStats, - type DailyTaskActivity, } from "~/services/environmentMetricsRepository.server"; import { singleton } from "~/utils/singleton"; import { findCurrentWorkerFromEnvironment } from "~/v3/models/workerDeployment.server"; @@ -21,8 +19,6 @@ export type TaskListItem = { triggerSource: TaskTriggerSource; }; -export type TaskActivity = DailyTaskActivity[string]; - export class TaskListPresenter { constructor(private readonly _replica: PrismaClientOrTransaction) {} @@ -55,9 +51,7 @@ export class TaskListPresenter { if (!currentWorker) { return { tasks: [], - activity: Promise.resolve({} as DailyTaskActivity), runningStats: Promise.resolve({} as CurrentRunningStats), - durations: Promise.resolve({} as AverageDurations), }; } @@ -89,16 +83,10 @@ export class TaskListPresenter { clickhouse, }); - // IMPORTANT: Don't await these, we want to return the promises - // so we can defer the loading of the data - const activity = environmentMetricsRepository.getDailyTaskActivity({ - organizationId, - projectId, - environmentId, - days: 6, // This actually means 7 days, because we want to show the current day too - tasks: slugs, - }); - + // IMPORTANT: Don't await this, we want to return the promise + // so we can defer the loading of the data. The caller is responsible for + // consuming it — an unconsumed promise here would become an unhandled + // rejection if the underlying query fails. const runningStats = environmentMetricsRepository.getCurrentRunningStats({ organizationId, projectId, @@ -107,15 +95,7 @@ export class TaskListPresenter { tasks: slugs, }); - const durations = environmentMetricsRepository.getAverageDurations({ - organizationId, - projectId, - environmentId, - days: 6, - tasks: slugs, - }); - - return { tasks, activity, runningStats, durations }; + return { tasks, runningStats }; } } diff --git a/apps/webapp/app/services/environmentMetricsRepository.server.ts b/apps/webapp/app/services/environmentMetricsRepository.server.ts index 61b53a4d6ca..5ecdf13f20f 100644 --- a/apps/webapp/app/services/environmentMetricsRepository.server.ts +++ b/apps/webapp/app/services/environmentMetricsRepository.server.ts @@ -2,19 +2,9 @@ import { type ClickHouse } from "@internal/clickhouse"; import type { TaskRunStatus } from "@trigger.dev/database"; import { QUEUED_STATUSES } from "~/components/runs/v3/TaskRunStatus"; -export type DailyTaskActivity = Record)[]>; export type CurrentRunningStats = Record; -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; @@ -22,14 +12,6 @@ export interface EnvironmentMetricsRepository { days: number; tasks: string[]; }): Promise; - - getAverageDurations(options: { - organizationId: string; - projectId: string; - environmentId: string; - days: number; - tasks: string[]; - }): Promise; } export type ClickHouseEnvironmentMetricsRepositoryOptions = { @@ -39,45 +21,6 @@ export type ClickHouseEnvironmentMetricsRepositoryOptions = { export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetricsRepository { constructor(private readonly options: ClickHouseEnvironmentMetricsRepositoryOptions) {} - public async getDailyTaskActivity({ - organizationId, - projectId, - environmentId, - days, - tasks, - }: { - organizationId: string; - projectId: string; - environmentId: string; - days: number; - tasks: string[]; - }): Promise { - if (tasks.length === 0) { - return {}; - } - - const [queryError, activity] = await this.options.clickhouse.taskRuns.getTaskActivity({ - organizationId, - projectId, - environmentId, - days, - }); - - if (queryError) { - throw queryError; - } - - return fillInDailyTaskActivity( - activity.map((a) => ({ - taskIdentifier: a.task_identifier, - status: a.status as TaskRunStatus, - day: new Date(a.day), - count: BigInt(a.count), - })), - days - ); - } - public async getCurrentRunningStats({ organizationId, projectId, @@ -115,82 +58,6 @@ export class ClickHouseEnvironmentMetricsRepository implements EnvironmentMetric tasks ); } - - public async getAverageDurations({ - organizationId, - projectId, - environmentId, - days, - tasks, - }: { - organizationId: string; - projectId: string; - environmentId: string; - days: number; - tasks: string[]; - }): Promise { - if (tasks.length === 0) { - return {}; - } - - const [queryError, durations] = await this.options.clickhouse.taskRuns.getAverageDurations({ - organizationId, - projectId, - environmentId, - days, - }); - - if (queryError) { - throw queryError; - } - - return Object.fromEntries(durations.map((d) => [d.task_identifier, Number(d.duration)])); - } -} - -type TaskActivityResults = Array<{ - taskIdentifier: string; - status: TaskRunStatus; - day: Date; - count: bigint; -}>; - -function fillInDailyTaskActivity(activity: TaskActivityResults, days: number): DailyTaskActivity { - //today with no time - const today = new Date(); - today.setUTCHours(0, 0, 0, 0); - - return activity.reduce((acc, a) => { - let existingTask = acc[a.taskIdentifier]; - - if (!existingTask) { - existingTask = []; - //populate the array with the past 7 days - for (let i = days; i >= 0; i--) { - const day = new Date(today); - day.setUTCDate(today.getDate() - i); - day.setUTCHours(0, 0, 0, 0); - - existingTask.push({ - day: day.toISOString(), - ["COMPLETED_SUCCESSFULLY"]: 0, - } as { day: string } & Record); - } - - acc[a.taskIdentifier] = existingTask; - } - - const dayString = a.day.toISOString(); - const day = existingTask.find((d) => d.day === dayString); - - if (!day) { - return acc; - } - - day[a.status] = Number(a.count); - - return acc; - }, {} as DailyTaskActivity); } type CurrentRunningStatsResults = Array<{