chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,366 @@
|
||||
import { type ClickHouse } from "@internal/clickhouse";
|
||||
import type { MachinePresetName } from "@trigger.dev/core/v3";
|
||||
import { RunAnnotations } from "@trigger.dev/core/v3/schemas";
|
||||
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";
|
||||
import { getTaskIdentifiers } from "~/models/task.server";
|
||||
import { RunsRepository } from "~/services/runsRepository/runsRepository.server";
|
||||
import { env } from "~/env.server";
|
||||
import {
|
||||
createCache,
|
||||
createLRUMemoryStore,
|
||||
DefaultStatefulContext,
|
||||
Namespace,
|
||||
} from "@internal/cache";
|
||||
import { RedisCacheStore } from "~/services/unkey/redisCacheStore.server";
|
||||
import { singleton } from "~/utils/singleton";
|
||||
import { regionForDisplay } from "~/runEngine/concerns/workerQueueSplit.server";
|
||||
import { machinePresetFromRun } from "~/v3/machinePresets.server";
|
||||
import { ServiceValidationError } from "~/v3/services/baseService.server";
|
||||
import { isCancellableRunStatus, isFinalRunStatus, isPendingRunStatus } from "~/v3/taskStatus";
|
||||
|
||||
// Positive-only cache: only envs known to have runs are stored (empty envs are re-checked),
|
||||
// so "has runs" is monotonic and the TTL can be very long. Tiered memory + Redis.
|
||||
const runsExistCache = singleton("runsExistCache", () => {
|
||||
const ctx = new DefaultStatefulContext();
|
||||
const memory = createLRUMemoryStore(5000, "runs-has-runs-cache");
|
||||
const redis = new RedisCacheStore({
|
||||
name: "runs-has-runs",
|
||||
connection: {
|
||||
keyPrefix: "tr:cache:runs-has-runs",
|
||||
port: env.CACHE_REDIS_PORT,
|
||||
host: env.CACHE_REDIS_HOST,
|
||||
username: env.CACHE_REDIS_USERNAME,
|
||||
password: env.CACHE_REDIS_PASSWORD,
|
||||
tlsDisabled: env.CACHE_REDIS_TLS_DISABLED === "true",
|
||||
clusterMode: env.CACHE_REDIS_CLUSTER_MODE_ENABLED === "1",
|
||||
},
|
||||
});
|
||||
|
||||
return createCache({
|
||||
hasRuns: new Namespace<boolean>(ctx, {
|
||||
stores: [memory, redis],
|
||||
fresh: env.RUN_LIST_HAS_RUNS_CACHE_FRESH_MS,
|
||||
stale: env.RUN_LIST_HAS_RUNS_CACHE_STALE_MS,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
export type RunListOptions = {
|
||||
userId?: string;
|
||||
projectId: string;
|
||||
//filters
|
||||
tasks?: string[];
|
||||
versions?: string[];
|
||||
statuses?: TaskRunStatus[];
|
||||
tags?: string[];
|
||||
scheduleId?: string;
|
||||
period?: string;
|
||||
bulkId?: string;
|
||||
from?: number;
|
||||
to?: number;
|
||||
isTest?: boolean;
|
||||
rootOnly?: boolean;
|
||||
batchId?: string;
|
||||
runId?: string[];
|
||||
queues?: string[];
|
||||
regions?: string[];
|
||||
machines?: MachinePresetName[];
|
||||
errorId?: string;
|
||||
sources?: string[];
|
||||
//pagination
|
||||
direction?: Direction;
|
||||
cursor?: string;
|
||||
pageSize?: number;
|
||||
// Run the empty-state "has any run ever" probe. Only the runs list consumes it.
|
||||
includeHasAnyRuns?: boolean;
|
||||
};
|
||||
|
||||
const DEFAULT_PAGE_SIZE = 25;
|
||||
|
||||
export type NextRunList = Awaited<ReturnType<NextRunListPresenter["call"]>>;
|
||||
export type NextRunListItem = NextRunList["runs"][0];
|
||||
export type NextRunListAppliedFilters = NextRunList["filters"];
|
||||
|
||||
export class NextRunListPresenter {
|
||||
constructor(
|
||||
private readonly replica: PrismaClientOrTransaction,
|
||||
private readonly clickhouse: ClickHouse,
|
||||
private readonly readThroughDeps?: {
|
||||
// The new run-ops client + the legacy run-ops read replica (never the legacy writer).
|
||||
// Omitted => single-DB / self-host: both default to `replica` (passthrough).
|
||||
newClient?: PrismaClientOrTransaction;
|
||||
legacyReplica?: PrismaClientOrTransaction;
|
||||
// Resolved boot constant from isSplitEnabled(). When false/absent:
|
||||
// list hydrate runs passthrough and the empty-state probe is one plain findFirst.
|
||||
splitEnabled?: boolean;
|
||||
}
|
||||
) {}
|
||||
|
||||
// Empty-state existence probe, served from ClickHouse (same connection as the runs
|
||||
// list) so it no longer scans TaskRun in Postgres. SWR-cached to spare ClickHouse;
|
||||
// RUN_LIST_HAS_RUNS_LOOKBACK_DAYS bounds the prove-absence partition scan.
|
||||
async #anyRunExistsInEnv(
|
||||
runsRepository: RunsRepository,
|
||||
organizationId: string,
|
||||
projectId: string,
|
||||
environmentId: string
|
||||
): Promise<boolean> {
|
||||
const lookbackDays = env.RUN_LIST_HAS_RUNS_LOOKBACK_DAYS;
|
||||
const createdAtLowerBoundMs =
|
||||
lookbackDays > 0 ? Date.now() - lookbackDays * 24 * 60 * 60 * 1000 : undefined;
|
||||
|
||||
const result = await runsExistCache.hasRuns.swr(environmentId, async () => {
|
||||
const exists = await runsRepository.runExistsInEnvironment({
|
||||
organizationId,
|
||||
projectId,
|
||||
environmentId,
|
||||
createdAtLowerBoundMs,
|
||||
});
|
||||
// undefined (not false) so swr does NOT cache the empty result — re-check until a run exists.
|
||||
return exists ? true : undefined;
|
||||
});
|
||||
|
||||
return result.val ?? false;
|
||||
}
|
||||
|
||||
public async call(
|
||||
organizationId: string,
|
||||
environmentId: string,
|
||||
{
|
||||
userId,
|
||||
projectId,
|
||||
tasks,
|
||||
versions,
|
||||
statuses,
|
||||
tags,
|
||||
scheduleId,
|
||||
period,
|
||||
bulkId,
|
||||
isTest,
|
||||
rootOnly,
|
||||
batchId,
|
||||
runId,
|
||||
queues,
|
||||
regions,
|
||||
machines,
|
||||
errorId,
|
||||
sources,
|
||||
from,
|
||||
to,
|
||||
direction = "forward",
|
||||
cursor,
|
||||
pageSize = DEFAULT_PAGE_SIZE,
|
||||
includeHasAnyRuns = false,
|
||||
}: RunListOptions
|
||||
) {
|
||||
//get the time values from the raw values (including a default period)
|
||||
const time = timeFilters({
|
||||
period,
|
||||
from,
|
||||
to,
|
||||
});
|
||||
|
||||
const hasStatusFilters = statuses && statuses.length > 0;
|
||||
|
||||
const hasFilters =
|
||||
(sources !== undefined && sources.length > 0) ||
|
||||
(tasks !== undefined && tasks.length > 0) ||
|
||||
(versions !== undefined && versions.length > 0) ||
|
||||
hasStatusFilters ||
|
||||
(bulkId !== undefined && bulkId !== "") ||
|
||||
(scheduleId !== undefined && scheduleId !== "") ||
|
||||
(tags !== undefined && tags.length > 0) ||
|
||||
batchId !== undefined ||
|
||||
(runId !== undefined && runId.length > 0) ||
|
||||
(queues !== undefined && queues.length > 0) ||
|
||||
(regions !== undefined && regions.length > 0) ||
|
||||
(machines !== undefined && machines.length > 0) ||
|
||||
(errorId !== undefined && errorId !== "") ||
|
||||
typeof isTest === "boolean" ||
|
||||
rootOnly === true ||
|
||||
!time.isDefault;
|
||||
|
||||
const possibleTasksAsync = getTaskIdentifiers(environmentId);
|
||||
|
||||
const bulkActionsAsync = this.replica.bulkActionGroup.findMany({
|
||||
select: {
|
||||
friendlyId: true,
|
||||
type: true,
|
||||
createdAt: true,
|
||||
name: true,
|
||||
},
|
||||
where: {
|
||||
projectId: projectId,
|
||||
environmentId,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
take: 20,
|
||||
});
|
||||
|
||||
const [possibleTasks, bulkActions, displayableEnvironment] = await Promise.all([
|
||||
possibleTasksAsync,
|
||||
bulkActionsAsync,
|
||||
findDisplayableEnvironment(environmentId, userId),
|
||||
]);
|
||||
|
||||
// If the bulk action isn't in the most recent ones, add it separately
|
||||
if (bulkId && !bulkActions.some((bulkAction) => bulkAction.friendlyId === bulkId)) {
|
||||
const selectedBulkAction = await this.replica.bulkActionGroup.findFirst({
|
||||
select: {
|
||||
friendlyId: true,
|
||||
type: true,
|
||||
createdAt: true,
|
||||
name: true,
|
||||
},
|
||||
where: {
|
||||
friendlyId: bulkId,
|
||||
projectId,
|
||||
environmentId,
|
||||
},
|
||||
});
|
||||
|
||||
if (selectedBulkAction) {
|
||||
bulkActions.push(selectedBulkAction);
|
||||
}
|
||||
}
|
||||
|
||||
if (!displayableEnvironment) {
|
||||
throw new ServiceValidationError("No environment found");
|
||||
}
|
||||
|
||||
const runsRepository = new RunsRepository({
|
||||
clickhouse: this.clickhouse,
|
||||
prisma: this.replica as PrismaClient,
|
||||
readThrough: this.readThroughDeps
|
||||
? {
|
||||
newClient: this.readThroughDeps.newClient ?? this.replica,
|
||||
legacyReplica: this.readThroughDeps.legacyReplica ?? this.replica,
|
||||
splitEnabled: this.readThroughDeps.splitEnabled ?? false,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
|
||||
function clampToNow(date: Date): Date {
|
||||
const now = new Date();
|
||||
return date > now ? now : date;
|
||||
}
|
||||
|
||||
const { runs, pagination } = await runsRepository.listRuns({
|
||||
organizationId,
|
||||
environmentId,
|
||||
projectId,
|
||||
tasks,
|
||||
versions,
|
||||
statuses,
|
||||
tags,
|
||||
scheduleId,
|
||||
period,
|
||||
from: time.from ? time.from.getTime() : undefined,
|
||||
to: time.to ? clampToNow(time.to).getTime() : undefined,
|
||||
isTest,
|
||||
rootOnly,
|
||||
batchId,
|
||||
runId,
|
||||
bulkId,
|
||||
queues,
|
||||
regions,
|
||||
machines,
|
||||
errorId,
|
||||
taskKinds: sources,
|
||||
page: {
|
||||
size: pageSize,
|
||||
cursor,
|
||||
direction,
|
||||
},
|
||||
});
|
||||
|
||||
let hasAnyRuns = runs.length > 0;
|
||||
|
||||
if (!hasAnyRuns && includeHasAnyRuns) {
|
||||
hasAnyRuns = await this.#anyRunExistsInEnv(
|
||||
runsRepository,
|
||||
organizationId,
|
||||
projectId,
|
||||
environmentId
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
runs: runs.map((run) => {
|
||||
const hasFinished = isFinalRunStatus(run.status);
|
||||
|
||||
const startedAt = run.startedAt ?? run.lockedAt;
|
||||
|
||||
return {
|
||||
id: run.id,
|
||||
number: 1,
|
||||
friendlyId: run.friendlyId,
|
||||
createdAt: run.createdAt.toISOString(),
|
||||
updatedAt: run.updatedAt.toISOString(),
|
||||
startedAt: startedAt ? startedAt.toISOString() : undefined,
|
||||
delayUntil: run.delayUntil ? run.delayUntil.toISOString() : undefined,
|
||||
hasFinished,
|
||||
finishedAt: hasFinished
|
||||
? (run.completedAt?.toISOString() ?? run.updatedAt.toISOString())
|
||||
: undefined,
|
||||
isTest: run.isTest,
|
||||
status: run.status,
|
||||
version: run.taskVersion,
|
||||
taskIdentifier: run.taskIdentifier,
|
||||
spanId: run.spanId,
|
||||
isReplayable: true,
|
||||
isCancellable: isCancellableRunStatus(run.status),
|
||||
isPending: isPendingRunStatus(run.status),
|
||||
environment: displayableEnvironment,
|
||||
idempotencyKey: run.idempotencyKey ? run.idempotencyKey : undefined,
|
||||
ttl: run.ttl ? run.ttl : undefined,
|
||||
expiredAt: run.expiredAt ? run.expiredAt.toISOString() : undefined,
|
||||
costInCents: run.costInCents,
|
||||
baseCostInCents: run.baseCostInCents,
|
||||
usageDurationMs: Number(run.usageDurationMs),
|
||||
tags: run.runTags ? run.runTags.sort((a, b) => a.localeCompare(b)) : [],
|
||||
depth: run.depth,
|
||||
rootTaskRunId: run.rootTaskRunId,
|
||||
metadata: run.metadata,
|
||||
metadataType: run.metadataType,
|
||||
machinePreset: run.machinePreset ? machinePresetFromRun(run)?.name : undefined,
|
||||
queue: {
|
||||
name: run.queue.replace("task/", ""),
|
||||
type: run.queue.startsWith("task/") ? "task" : "custom",
|
||||
},
|
||||
region: regionForDisplay(run.region, run.workerQueue),
|
||||
taskKind: RunAnnotations.safeParse(run.annotations).data?.taskKind ?? "STANDARD",
|
||||
};
|
||||
}),
|
||||
pagination: {
|
||||
next: pagination.nextCursor ?? undefined,
|
||||
previous: pagination.previousCursor ?? undefined,
|
||||
},
|
||||
possibleTasks,
|
||||
bulkActions: bulkActions.map((bulkAction) => ({
|
||||
id: bulkAction.friendlyId,
|
||||
type: bulkAction.type,
|
||||
createdAt: bulkAction.createdAt,
|
||||
name: bulkAction.name || bulkAction.friendlyId,
|
||||
})),
|
||||
filters: {
|
||||
tasks: tasks || [],
|
||||
versions: versions || [],
|
||||
statuses: statuses || [],
|
||||
from: time.from,
|
||||
to: time.to,
|
||||
},
|
||||
hasFilters,
|
||||
hasAnyRuns,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user