Set endpoint URLs to null, instead of deleting them (#878)
🚀 Publish Trigger.dev Docker / units (push) Failing after 4s
🚀 Publish Trigger.dev Docker / typecheck (push) Failing after 5s
🚀 Publish Trigger.dev Docker / e2e (push) Failing after 4s
🚀 Publish Trigger.dev Docker / publish (push) Has been skipped

* Added Endpoint deletedAt column

* Only show endpoints where they’re not deleted

* Don’t delete Endpoints, set the deletedAt and change their slug name

* Only perform indexing if the endpoint isn’t deleted

* Have a nullable URL for endpoints

* Deal with null URLs throughout the app

* Re-running and retrying behaves properly when there’s no endpoint URL

* Remove console.log

* Better error message when doing a run
This commit is contained in:
Matt Aitken
2024-01-30 10:45:12 +00:00
committed by GitHub
parent 2e354d342c
commit 4b3b418abb
18 changed files with 103 additions and 10 deletions
@@ -117,6 +117,7 @@ export function RunOverview({ run, trigger, showRerun, paths, currentUser }: Run
{showRerun && run.isFinished && (
<RerunPopover
runId={run.id}
runPath={paths.run}
runsPath={paths.runsPath}
environmentType={run.environment.type}
status={run.basicStatus}
@@ -317,18 +318,20 @@ function BlankTasks({ status }: { status: RunBasicStatus }) {
function RerunPopover({
runId,
runPath,
runsPath,
environmentType,
status,
}: {
runId: string;
runPath: string;
runsPath: string;
environmentType: RuntimeEnvironmentType;
status: RunBasicStatus;
}) {
const lastSubmission = useActionData();
const [form, { successRedirect }] = useForm({
const [form, { successRedirect, failureRedirect }] = useForm({
id: "rerun",
// TODO: type this
lastSubmission: lastSubmission as any,
@@ -347,6 +350,7 @@ function RerunPopover({
<PopoverContent className="flex min-w-[20rem] max-w-[20rem] flex-col gap-2 p-0" align="end">
<Form method="post" action={`/resources/runs/${runId}/rerun`} {...form.props}>
<input {...conform.input(successRedirect, { type: "hidden" })} defaultValue={runsPath} />
<input {...conform.input(failureRedirect, { type: "hidden" })} defaultValue={runPath} />
{environmentType === "PRODUCTION" && (
<div className="px-4 pt-4">
<Callout variant="warning">
@@ -38,7 +38,7 @@ export type ClientEndpoint =
state: "configured";
id: string;
slug: string;
url: string;
url: string | null;
indexWebhookPath: string;
latestIndex?: {
status: EndpointIndexStatus;
@@ -102,6 +102,11 @@ export class EnvironmentsPresenter {
},
},
},
where: {
url: {
not: null,
},
},
},
},
where: {
@@ -111,7 +111,7 @@ export function ConfigureEndpointSheet({ slug, endpoint, onClose }: ConfigureEnd
<Input
className="rounded-r-none"
{...conform.input(url, { type: "url" })}
defaultValue={"url" in endpoint ? endpoint.url : ""}
defaultValue={"url" in endpoint ? endpoint.url ?? "" : ""}
placeholder="URL for your Trigger API route"
/>
<Button
@@ -1,12 +1,18 @@
import { parse } from "@conform-to/zod";
import { ActionFunction, json } from "@remix-run/node";
import { z } from "zod";
import { redirectBackWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import {
redirectBackWithErrorMessage,
redirectWithErrorMessage,
redirectWithSuccessMessage,
} from "~/models/message.server";
import { ContinueRunService } from "~/services/runs/continueRun.server";
import { ReRunService } from "~/services/runs/reRun.server";
import { rootPath, runPath } from "~/utils/pathBuilder";
export const schema = z.object({
successRedirect: z.string(),
failureRedirect: z.string(),
});
const ParamSchema = z.object({
@@ -20,7 +26,11 @@ export const action: ActionFunction = async ({ request, params }) => {
const submission = parse(formData, { schema });
if (!submission.value) {
return json(submission);
return redirectWithErrorMessage(
rootPath(),
request,
submission.error ? JSON.stringify(submission.error) : "Invalid form"
);
}
try {
@@ -29,7 +39,11 @@ export const action: ActionFunction = async ({ request, params }) => {
const run = await rerunService.call({ runId });
if (!run) {
return redirectBackWithErrorMessage(request, "Unable to retry run");
return redirectWithErrorMessage(
submission.value.failureRedirect,
request,
"Unable to retry run"
);
}
return redirectWithSuccessMessage(
@@ -48,6 +62,10 @@ export const action: ActionFunction = async ({ request, params }) => {
);
}
} catch (error: any) {
return json({ errors: { body: error.message } }, { status: 400 });
return redirectWithErrorMessage(
submission.value.failureRedirect,
request,
error instanceof Error ? error.message : JSON.stringify(error)
);
}
};
@@ -9,7 +9,10 @@ export class DeleteEndpointIndexService {
}
public async call(id: string, userId: string): Promise<void> {
await this.#prismaClient.endpoint.delete({
await this.#prismaClient.endpoint.update({
data: {
url: null,
},
where: {
id,
organization: {
@@ -54,6 +54,13 @@ export class PerformEndpointIndexService {
logger.debug("Performing endpoint index", endpointIndex);
if (!endpointIndex.endpoint.url) {
logger.debug("Endpoint URL is not set", endpointIndex);
return updateEndpointIndexWithError(this.#prismaClient, id, {
message: "Endpoint URL is not set",
});
}
// Make a request to the endpoint to fetch a list of jobs
const client = new EndpointApi(
endpointIndex.endpoint.environment.apiKey,
@@ -29,6 +29,13 @@ export class ProbeEndpointService {
id,
});
if (!endpoint.url) {
logger.debug(`Endpoint has no url`, {
id,
});
return;
}
const client = new EndpointApi(endpoint.environment.apiKey, endpoint.url);
const { response, durationInMs } = await client.probe(MAX_RUN_CHUNK_EXECUTION_LIMIT);
@@ -16,6 +16,9 @@ export class RecurringEndpointIndexService {
const endpoints = await this.#prismaClient.endpoint.findMany({
where: {
url: {
not: null,
},
environment: {
type: {
in: [RuntimeEnvironmentType.PRODUCTION, RuntimeEnvironmentType.STAGING],
@@ -96,6 +96,14 @@ export class HandleHttpEndpointService {
);
}
if (!httpEndpointEnvironment.endpoint.url) {
logger.debug("Endpoint has no url", {
httpEndpointId: httpEndpoint.id,
environmentId: environment.id,
});
return json({ error: true, message: "Endpoint has no url" }, { status: 404 });
}
const immediateResponseFilter = RequestFilterSchema.nullable().safeParse(
httpEndpointEnvironment.immediateResponseFilter
);
@@ -37,6 +37,11 @@ export class CreateRunService {
},
});
if (!endpoint.url) {
logger.debug("Endpoint has no url", endpoint);
return;
}
const eventRecord = await this.#prismaClient.eventRecord.findUniqueOrThrow({
where: {
id: eventId,
@@ -97,6 +97,10 @@ export class DeliverRunSubscriptionService {
return true;
}
if (subscription.run.endpoint.url === null) {
return true;
}
const client = new EndpointApi(
subscription.run.environment.apiKey,
subscription.run.endpoint.url
@@ -135,6 +135,12 @@ export class PerformRunExecutionV3Service {
return;
}
if (!run.endpoint.url) {
return await this.#failRunExecution(this.#prismaClient, run, {
message: `Endpoint has no URL set`,
});
}
const client = new EndpointApi(run.environment.apiKey, run.endpoint.url);
const event = eventRecordToApiJson(run.event);
@@ -43,6 +43,10 @@ export class DeliverHttpSourceRequestService {
return;
}
if (!httpSourceRequest.endpoint.url) {
return;
}
const secretStore = getSecretStore(httpSourceRequest.source.secretReference.provider);
const secret = await secretStore.getSecret(
@@ -49,6 +49,10 @@ export class DeliverWebhookRequestService {
return;
}
if (!requestDelivery.endpoint.url) {
return;
}
const { secretReference } = requestDelivery.webhook.httpEndpoint;
const secretStore = getSecretStore(secretReference.provider);
@@ -35,6 +35,10 @@ export class InitializeTriggerService {
},
});
if (!endpoint.url) {
throw new Error("This environment's endpoint doesn't have a URL set");
}
const dynamicTrigger = await this.#prismaClient.dynamicTrigger.findUniqueOrThrow({
where: {
endpointId_slug_type: {
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Endpoint" ADD COLUMN "deletedAt" TIMESTAMP(3);
@@ -0,0 +1,9 @@
/*
Warnings:
- You are about to drop the column `deletedAt` on the `Endpoint` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "Endpoint" DROP COLUMN "deletedAt",
ALTER COLUMN "url" DROP NOT NULL;
+2 -2
View File
@@ -413,9 +413,9 @@ model Project {
}
model Endpoint {
id String @id @default(cuid())
id String @id @default(cuid())
slug String
url String
url String?
environment RuntimeEnvironment @relation(fields: [environmentId], references: [id], onDelete: Cascade, onUpdate: Cascade)
environmentId String