8fc8f57b39
* WIP * Allow marqsv2 and v2 graphile to run in parallel * Fix missing GraphileLogger import * Fixed heartbeat after rebase * Replace postgres based run counters with redis ones with a backfill * Add back in the graphile logger * Remove duplicate visibility timeout calls * Clamp simple weighted strategy to max of 5
246 lines
7.4 KiB
TypeScript
246 lines
7.4 KiB
TypeScript
import {
|
|
type ConnectionType,
|
|
type Integration,
|
|
type IntegrationConnection,
|
|
} from "@trigger.dev/database";
|
|
import type { PrismaClient, PrismaClientOrTransaction } from "~/db.server";
|
|
import { prisma } from "~/db.server";
|
|
import { autoIncrementCounter } from "../autoIncrementCounter.server";
|
|
import { logger } from "../logger.server";
|
|
import { workerQueue } from "../worker.server";
|
|
import { ResumeRunService } from "./resumeRun.server";
|
|
|
|
type FoundRun = NonNullable<Awaited<ReturnType<typeof findRun>>>;
|
|
type RunConnectionsByKey = Awaited<ReturnType<typeof createRunConnections>>;
|
|
|
|
export class StartRunService {
|
|
#prismaClient: PrismaClient;
|
|
|
|
constructor(prismaClient: PrismaClient = prisma) {
|
|
this.#prismaClient = prismaClient;
|
|
}
|
|
|
|
public async call(id: string) {
|
|
const run = await findRun(this.#prismaClient, id);
|
|
|
|
if (!run || !this.#runIsStartable(run)) {
|
|
return;
|
|
}
|
|
|
|
const runConnectionsByKey = await createRunConnections(this.#prismaClient, run);
|
|
|
|
if (hasMissingConnections(runConnectionsByKey)) {
|
|
await this.#handleMissingConnections(id, runConnectionsByKey);
|
|
} else {
|
|
await this.#startRun(id, run, runConnectionsByKey);
|
|
}
|
|
}
|
|
|
|
#runIsStartable(run: FoundRun) {
|
|
if (!run.organization.runsEnabled) {
|
|
logger.debug("StartRunService: Runs are disabled for this organization", {
|
|
organizationId: run.organization.id,
|
|
});
|
|
return false;
|
|
}
|
|
|
|
const startableStatuses = ["PENDING", "WAITING_ON_CONNECTIONS"] as const;
|
|
return startableStatuses.includes(run.status);
|
|
}
|
|
|
|
async #startRun(id: string, run: FoundRun, runConnectionsByKey: RunConnectionsByKey) {
|
|
const createRunConnections = Object.entries(runConnectionsByKey)
|
|
.map(([key, runConnection]) =>
|
|
runConnection.result === "resolvedHosted"
|
|
? ({
|
|
key,
|
|
connectionId: runConnection.connection.id,
|
|
integrationId: runConnection.integration.id,
|
|
authSource: "HOSTED",
|
|
} as const)
|
|
: runConnection.result === "resolvedLocal" || runConnection.result === "resolvedResolver"
|
|
? ({
|
|
key,
|
|
integrationId: runConnection.integration.id,
|
|
authSource: runConnection.result === "resolvedLocal" ? "LOCAL" : "RESOLVER",
|
|
} as const)
|
|
: undefined
|
|
)
|
|
.filter(Boolean);
|
|
|
|
await autoIncrementCounter.incrementInTransaction(
|
|
`v2-run:${run.jobId}`,
|
|
async (num, tx) => {
|
|
const updatedRun = await tx.jobRun.update({
|
|
where: { id },
|
|
data: {
|
|
number: num,
|
|
status: "QUEUED",
|
|
queuedAt: new Date(),
|
|
runConnections: {
|
|
create: createRunConnections,
|
|
},
|
|
},
|
|
});
|
|
|
|
await ResumeRunService.enqueue(updatedRun, tx);
|
|
},
|
|
async (_, tx) => {
|
|
const counter = await tx.jobCounter.findUnique({
|
|
where: { jobId: run.jobId },
|
|
select: { lastNumber: true },
|
|
});
|
|
|
|
return counter?.lastNumber;
|
|
},
|
|
this.#prismaClient,
|
|
{ timeout: 10_000 }
|
|
);
|
|
}
|
|
|
|
async #handleMissingConnections(id: string, runConnectionsByKey: RunConnectionsByKey) {
|
|
const missingConnections = Object.values(runConnectionsByKey)
|
|
.map((runConnection) => (runConnection.result === "missing" ? runConnection : undefined))
|
|
.filter(Boolean);
|
|
|
|
const updatedRun = await this.#prismaClient.jobRun.update({
|
|
where: { id },
|
|
data: {
|
|
status: "WAITING_ON_CONNECTIONS",
|
|
missingConnections: {
|
|
connectOrCreate: missingConnections.map((connection) => ({
|
|
where: {
|
|
integrationId_connectionType_accountIdentifier: {
|
|
integrationId: connection.integration.id,
|
|
connectionType: connection.connectionType,
|
|
accountIdentifier: connection.externalAccountId ?? "DEVELOPER",
|
|
},
|
|
},
|
|
create: {
|
|
integrationId: connection.integration.id,
|
|
connectionType: connection.connectionType,
|
|
accountIdentifier: connection.externalAccountId ?? "DEVELOPER",
|
|
externalAccountId: connection.externalAccountId,
|
|
resolved: false,
|
|
},
|
|
})),
|
|
},
|
|
},
|
|
include: {
|
|
missingConnections: {
|
|
include: {
|
|
_count: {
|
|
select: { runs: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
for (const missingConnection of updatedRun.missingConnections) {
|
|
if (missingConnection._count.runs === 1) {
|
|
workerQueue.enqueue("missingConnectionCreated", {
|
|
id: missingConnection.id,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async function findRun(tx: PrismaClientOrTransaction, id: string) {
|
|
return await tx.jobRun.findUnique({
|
|
where: { id },
|
|
include: {
|
|
queue: true,
|
|
environment: true,
|
|
organization: true,
|
|
version: {
|
|
include: {
|
|
integrations: {
|
|
include: {
|
|
integration: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async function createRunConnections(tx: PrismaClientOrTransaction, run: FoundRun) {
|
|
return await run.version.integrations.reduce(
|
|
async (
|
|
accP: Promise<
|
|
Record<
|
|
string,
|
|
| {
|
|
result: "resolvedHosted";
|
|
connection: IntegrationConnection;
|
|
integration: Integration;
|
|
}
|
|
| { result: "resolvedLocal"; integration: Integration }
|
|
| { result: "resolvedResolver"; integration: Integration }
|
|
| {
|
|
result: "missing";
|
|
connectionType: ConnectionType;
|
|
integration: Integration;
|
|
externalAccountId?: string;
|
|
}
|
|
>
|
|
>,
|
|
jobIntegration
|
|
) => {
|
|
const acc = await accP;
|
|
|
|
if (jobIntegration.integration.authSource === "LOCAL") {
|
|
acc[jobIntegration.key] = {
|
|
result: "resolvedLocal",
|
|
integration: jobIntegration.integration,
|
|
};
|
|
} else if (jobIntegration.integration.authSource === "RESOLVER") {
|
|
acc[jobIntegration.key] = {
|
|
result: "resolvedResolver",
|
|
integration: jobIntegration.integration,
|
|
};
|
|
} else {
|
|
const connection = run.externalAccountId
|
|
? await tx.integrationConnection.findFirst({
|
|
where: {
|
|
integrationId: jobIntegration.integration.id,
|
|
connectionType: "EXTERNAL",
|
|
externalAccountId: run.externalAccountId,
|
|
},
|
|
})
|
|
: await tx.integrationConnection.findFirst({
|
|
where: {
|
|
integrationId: jobIntegration.integration.id,
|
|
connectionType: "DEVELOPER",
|
|
},
|
|
});
|
|
|
|
if (connection) {
|
|
acc[jobIntegration.key] = {
|
|
result: "resolvedHosted",
|
|
connection,
|
|
integration: jobIntegration.integration,
|
|
};
|
|
} else {
|
|
acc[jobIntegration.key] = {
|
|
result: "missing",
|
|
connectionType: run.externalAccountId ? "EXTERNAL" : "DEVELOPER",
|
|
externalAccountId: run.externalAccountId ?? undefined,
|
|
integration: jobIntegration.integration,
|
|
};
|
|
}
|
|
}
|
|
|
|
return acc;
|
|
},
|
|
Promise.resolve({})
|
|
);
|
|
}
|
|
|
|
function hasMissingConnections(runConnectionsByKey: RunConnectionsByKey) {
|
|
return Object.values(runConnectionsByKey).some((connection) => connection.result === "missing");
|
|
}
|