208 lines
5.9 KiB
TypeScript
208 lines
5.9 KiB
TypeScript
import { type ClickHouse } from "@internal/clickhouse";
|
|
import { type Tracer } from "@internal/tracing";
|
|
import { type Logger, type LogLevel } from "@trigger.dev/core/logger";
|
|
import { type Prisma } from "@trigger.dev/database";
|
|
import parseDuration from "parse-duration";
|
|
import { z } from "zod";
|
|
import { type PrismaClientOrTransaction } from "~/db.server";
|
|
import { startActiveSpan } from "~/v3/tracer.server";
|
|
import { ClickHouseSessionsRepository } from "./clickhouseSessionsRepository.server";
|
|
|
|
export type SessionsRepositoryOptions = {
|
|
clickhouse: ClickHouse;
|
|
prisma: PrismaClientOrTransaction;
|
|
logger?: Logger;
|
|
logLevel?: LogLevel;
|
|
tracer?: Tracer;
|
|
};
|
|
|
|
/**
|
|
* Derived status values — `Session` rows don't have a stored status column.
|
|
* `ACTIVE` is the base state; `CLOSED` means `closedAt` is set; `EXPIRED`
|
|
* means `expiresAt` has passed.
|
|
*/
|
|
export const SessionStatus = z.enum(["ACTIVE", "CLOSED", "EXPIRED"]);
|
|
export type SessionStatus = z.infer<typeof SessionStatus>;
|
|
|
|
/**
|
|
* Legacy marker tag for sessions created from the Test/playground before the
|
|
* `Session.isTest` boolean existed. New sessions set `isTest` instead; this tag
|
|
* is hidden from the Tags display so it doesn't surface on pre-isTest rows.
|
|
*/
|
|
export const LEGACY_PLAYGROUND_TAG = "playground";
|
|
|
|
const SessionListInputOptionsSchema = z.object({
|
|
organizationId: z.string(),
|
|
projectId: z.string(),
|
|
environmentId: z.string(),
|
|
// filters
|
|
types: z.array(z.string()).optional(),
|
|
tags: z.array(z.string()).optional(),
|
|
taskIdentifiers: z.array(z.string()).optional(),
|
|
externalId: z.string().optional(),
|
|
statuses: z.array(SessionStatus).optional(),
|
|
period: z.string().optional(),
|
|
from: z.number().optional(),
|
|
to: z.number().optional(),
|
|
});
|
|
|
|
export type SessionListInputOptions = z.infer<typeof SessionListInputOptionsSchema>;
|
|
export type SessionListInputFilters = Omit<
|
|
SessionListInputOptions,
|
|
"organizationId" | "projectId" | "environmentId"
|
|
>;
|
|
|
|
export type FilterSessionsOptions = Omit<SessionListInputOptions, "period"> & {
|
|
/** period converted to milliseconds duration */
|
|
period: number | undefined;
|
|
};
|
|
|
|
type Pagination = {
|
|
page: {
|
|
size: number;
|
|
cursor?: string;
|
|
direction?: "forward" | "backward";
|
|
};
|
|
};
|
|
|
|
export type ListSessionsOptions = SessionListInputOptions & Pagination;
|
|
|
|
type OffsetPagination = {
|
|
offset: number;
|
|
limit: number;
|
|
};
|
|
|
|
export type SessionTagListOptions = {
|
|
organizationId: string;
|
|
projectId: string;
|
|
environmentId: string;
|
|
period?: string;
|
|
from?: number;
|
|
to?: number;
|
|
/** Case-insensitive substring match on the tag name */
|
|
query?: string;
|
|
} & OffsetPagination;
|
|
|
|
export type SessionTagList = {
|
|
tags: string[];
|
|
};
|
|
|
|
export type ListedSession = Prisma.SessionGetPayload<{
|
|
select: {
|
|
id: true;
|
|
friendlyId: true;
|
|
externalId: true;
|
|
type: true;
|
|
taskIdentifier: true;
|
|
isTest: true;
|
|
tags: true;
|
|
metadata: true;
|
|
closedAt: true;
|
|
closedReason: true;
|
|
expiresAt: true;
|
|
createdAt: true;
|
|
updatedAt: true;
|
|
runtimeEnvironmentId: true;
|
|
currentRunId: true;
|
|
};
|
|
}>;
|
|
|
|
export type ISessionsRepository = {
|
|
name: string;
|
|
listSessionIds(options: ListSessionsOptions): Promise<string[]>;
|
|
listSessions(options: ListSessionsOptions): Promise<{
|
|
sessions: ListedSession[];
|
|
pagination: {
|
|
nextCursor: string | null;
|
|
previousCursor: string | null;
|
|
};
|
|
}>;
|
|
countSessions(options: SessionListInputOptions): Promise<number>;
|
|
listTags(options: SessionTagListOptions): Promise<SessionTagList>;
|
|
};
|
|
|
|
export class SessionsRepository implements ISessionsRepository {
|
|
private readonly clickHouseSessionsRepository: ClickHouseSessionsRepository;
|
|
|
|
constructor(private readonly options: SessionsRepositoryOptions) {
|
|
this.clickHouseSessionsRepository = new ClickHouseSessionsRepository(options);
|
|
}
|
|
|
|
get name() {
|
|
return "sessionsRepository";
|
|
}
|
|
|
|
async listSessionIds(options: ListSessionsOptions): Promise<string[]> {
|
|
return startActiveSpan(
|
|
"sessionsRepository.listSessionIds",
|
|
async () => this.clickHouseSessionsRepository.listSessionIds(options),
|
|
{
|
|
attributes: {
|
|
"repository.name": "clickhouse",
|
|
organizationId: options.organizationId,
|
|
projectId: options.projectId,
|
|
environmentId: options.environmentId,
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
async listSessions(options: ListSessionsOptions) {
|
|
return startActiveSpan(
|
|
"sessionsRepository.listSessions",
|
|
async () => this.clickHouseSessionsRepository.listSessions(options),
|
|
{
|
|
attributes: {
|
|
"repository.name": "clickhouse",
|
|
organizationId: options.organizationId,
|
|
projectId: options.projectId,
|
|
environmentId: options.environmentId,
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
async countSessions(options: SessionListInputOptions) {
|
|
return startActiveSpan(
|
|
"sessionsRepository.countSessions",
|
|
async () => this.clickHouseSessionsRepository.countSessions(options),
|
|
{
|
|
attributes: {
|
|
"repository.name": "clickhouse",
|
|
organizationId: options.organizationId,
|
|
projectId: options.projectId,
|
|
environmentId: options.environmentId,
|
|
},
|
|
}
|
|
);
|
|
}
|
|
|
|
async listTags(options: SessionTagListOptions) {
|
|
return startActiveSpan(
|
|
"sessionsRepository.listTags",
|
|
async () => this.clickHouseSessionsRepository.listTags(options),
|
|
{
|
|
attributes: {
|
|
"repository.name": "clickhouse",
|
|
organizationId: options.organizationId,
|
|
projectId: options.projectId,
|
|
environmentId: options.environmentId,
|
|
},
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
export function parseSessionListInputOptions(data: unknown): SessionListInputOptions {
|
|
return SessionListInputOptionsSchema.parse(data);
|
|
}
|
|
|
|
export function convertSessionListInputOptionsToFilterOptions(
|
|
options: SessionListInputOptions
|
|
): FilterSessionsOptions {
|
|
return {
|
|
...options,
|
|
period: options.period ? (parseDuration(options.period) ?? undefined) : undefined,
|
|
};
|
|
}
|