Files
triggerdotdev--trigger.dev/apps/webapp/app/v3/services/errorGroupActions.server.ts
T
2026-07-13 13:32:57 +08:00

148 lines
4.4 KiB
TypeScript

import { type PrismaClientOrTransaction, prisma } from "~/db.server";
type ErrorGroupIdentifier = {
organizationId: string;
projectId: string;
environmentId: string;
taskIdentifier: string;
errorFingerprint: string;
};
export class ErrorGroupActions {
constructor(private readonly _prisma: PrismaClientOrTransaction = prisma) {}
async resolveError(
identifier: ErrorGroupIdentifier,
params: {
// Nullable: a resolve via an env API key has no acting user, so
// `resolvedBy` stays null. The dashboard always passes a userId; the
// API passes the `act.sub` user from a PAT/UAT-exchanged JWT, else null.
userId?: string | null;
resolvedInVersion?: string;
}
) {
const where = {
environmentId_taskIdentifier_errorFingerprint: {
environmentId: identifier.environmentId,
taskIdentifier: identifier.taskIdentifier,
errorFingerprint: identifier.errorFingerprint,
},
};
const now = new Date();
return this._prisma.errorGroupState.upsert({
where,
update: {
status: "RESOLVED",
resolvedAt: now,
resolvedInVersion: params.resolvedInVersion ?? null,
resolvedBy: params.userId ?? null,
ignoredUntil: null,
ignoredUntilOccurrenceRate: null,
ignoredUntilTotalOccurrences: null,
ignoredAtOccurrenceCount: null,
ignoredAt: null,
ignoredReason: null,
ignoredByUserId: null,
},
create: {
organizationId: identifier.organizationId,
projectId: identifier.projectId,
environmentId: identifier.environmentId,
taskIdentifier: identifier.taskIdentifier,
errorFingerprint: identifier.errorFingerprint,
status: "RESOLVED",
resolvedAt: now,
resolvedInVersion: params.resolvedInVersion ?? null,
resolvedBy: params.userId ?? null,
},
});
}
async ignoreError(
identifier: ErrorGroupIdentifier,
params: {
userId?: string | null;
duration?: number;
occurrenceRateThreshold?: number;
totalOccurrencesThreshold?: number;
occurrenceCountAtIgnoreTime?: number;
reason?: string;
}
) {
const where = {
environmentId_taskIdentifier_errorFingerprint: {
environmentId: identifier.environmentId,
taskIdentifier: identifier.taskIdentifier,
errorFingerprint: identifier.errorFingerprint,
},
};
const now = new Date();
const ignoredUntil = params.duration ? new Date(now.getTime() + params.duration) : null;
const data = {
status: "IGNORED" as const,
ignoredAt: now,
ignoredUntil,
ignoredUntilOccurrenceRate: params.occurrenceRateThreshold ?? null,
ignoredUntilTotalOccurrences: params.totalOccurrencesThreshold ?? null,
ignoredAtOccurrenceCount: params.occurrenceCountAtIgnoreTime ?? null,
ignoredReason: params.reason ?? null,
ignoredByUserId: params.userId ?? null,
resolvedAt: null,
resolvedInVersion: null,
resolvedBy: null,
};
return this._prisma.errorGroupState.upsert({
where,
update: data,
create: {
organizationId: identifier.organizationId,
projectId: identifier.projectId,
environmentId: identifier.environmentId,
taskIdentifier: identifier.taskIdentifier,
errorFingerprint: identifier.errorFingerprint,
...data,
},
});
}
async unresolveError(identifier: ErrorGroupIdentifier) {
const where = {
environmentId_taskIdentifier_errorFingerprint: {
environmentId: identifier.environmentId,
taskIdentifier: identifier.taskIdentifier,
errorFingerprint: identifier.errorFingerprint,
},
};
return this._prisma.errorGroupState.upsert({
where,
update: {
status: "UNRESOLVED",
resolvedAt: null,
resolvedInVersion: null,
resolvedBy: null,
ignoredUntil: null,
ignoredUntilOccurrenceRate: null,
ignoredUntilTotalOccurrences: null,
ignoredAtOccurrenceCount: null,
ignoredAt: null,
ignoredReason: null,
ignoredByUserId: null,
},
create: {
organizationId: identifier.organizationId,
projectId: identifier.projectId,
environmentId: identifier.environmentId,
taskIdentifier: identifier.taskIdentifier,
errorFingerprint: identifier.errorFingerprint,
status: "UNRESOLVED",
},
});
}
}