diff --git a/apps/webapp/app/components/primitives/CopyableText.tsx b/apps/webapp/app/components/primitives/CopyableText.tsx index 6a37c6f2b..99664b3dc 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 ( )} - Run # + ID Task Version )} - - {formatNumber(run.number)} + + + + + } + asChild + disableHoverableContent + /> diff --git a/apps/webapp/app/presenters/v3/NextRunListPresenter.server.ts b/apps/webapp/app/presenters/v3/NextRunListPresenter.server.ts index bee915901..ec5703b60 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/presenters/v3/TaskListPresenter.server.ts b/apps/webapp/app/presenters/v3/TaskListPresenter.server.ts index 4c8acddb9..59313a41c 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 d2fbf5794..6152f8579 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/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 d68d8287d..e73b1c883 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/environmentMetricsRepository.server.ts b/apps/webapp/app/services/environmentMetricsRepository.server.ts index 6b0251b75..2849afbff 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/app/services/runsRepository.server.ts b/apps/webapp/app/services/runsRepository.server.ts index 8d9445ce7..6d2befbb2 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 ASC, run_id ASC") .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(); @@ -143,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: { diff --git a/apps/webapp/test/runsRepository.test.ts b/apps/webapp/test/runsRepository.test.ts index 91c1f5de5..7ee3b05b4 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/docs/troubleshooting.mdx b/docs/troubleshooting.mdx index b6b321178..ca0acb072 100644 --- a/docs/troubleshooting.mdx +++ b/docs/troubleshooting.mdx @@ -59,6 +59,16 @@ Here are some common problems and their solutions: There should be a link below the error message to the full build logs on your machine. Take a look at these to see what went wrong. Join [our Discord](https://trigger.dev/discord) and you share it privately with us if you can't figure out what's going wrong. Do NOT share these publicly as the verbose logs might reveal private information about your project. +### `Error: failed to solve: failed to resolve source metadata for docker.io/docker/dockerfile:1` + +If you see this error after uninstalling Docker Desktop: + +``` +Error: failed to solve: failed to resolve source metadata for docker.io/docker/dockerfile:1: error getting credentials - err: exec: "docker-credential-desktop": executable file not found in $PATH +``` + +This happens because Docker Desktop left behind a config file that's still trying to use its credential store. To fix this, remove or update the `~/.docker/config.json` file. You don't need Docker Desktop installed to use Trigger.dev. + ### `Deployment encountered an error` Usually there will be some useful guidance below this message. If you can't figure out what's going wrong then join [our Discord](https://trigger.dev/discord) and create a Help forum post with a link to your deployment. diff --git a/internal-packages/clickhouse/src/taskRuns.ts b/internal-packages/clickhouse/src/taskRuns.ts index 396f990ef..b57ddbfc8 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