Skip to content

Commit 50e3d9e

Browse files
authored
Indexing errors don't get displayed anywhere (#605)
* Added EndpointIndex status. Default is PENDING, existing rows are SUCCESS * Made it easier to create a migration SQL file * Created a job-catalog file for misconfigured Jobs that should error when running the CLI * EndpointIndex data and state are now optional * The Environments page now shows the status of the last refresh * Improved the UI about endpoints * Added EndpointIndex error column * WIP making the endpoint indexing more robust * Indexing errors are now surfaced * Improved the error show it shows the job id * Use “performEndpointIndexing” when you create your first endpoint from the UI * Use “performEndpointIndexing” for the recurring endpoint checker * Staging is now auto-indexed every 10 mins too * Use “performEndpointIndexing” for the webhook * Moved the throttling to a util * Removed instructional comments * Created a reusable retry system with exponential backoff * Use p-retry for retrying with backoff * The CLI gets indexing results and displays errors * Improved the indexing error messages and display in the console * Use a pre so the Indexing error is correctly split over multiple lines * Use a db transaction for webhook that triggers endpoint indexing * Tidied up imports * Support older versions of the server * Improved the comment on the misconfigured job * Changeset: When indexing user's jobs errors are now stored and displayed
1 parent 203a431 commit 50e3d9e

File tree

37 files changed

+1090
-470
lines changed

37 files changed

+1090
-470
lines changed

.changeset/seven-turkeys-worry.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@trigger.dev/core": patch
3+
"@trigger.dev/cli": patch
4+
---
5+
6+
When indexing user's jobs errors are now stored and displayed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { CheckCircleIcon, ClockIcon, XCircleIcon } from "@heroicons/react/20/solid";
2+
import { EndpointIndexStatus } from "@trigger.dev/database";
3+
import { cn } from "~/utils/cn";
4+
import { Spinner } from "../primitives/Spinner";
5+
6+
export function EndpointIndexStatusIcon({ status }: { status: EndpointIndexStatus }) {
7+
switch (status) {
8+
case "PENDING":
9+
return <ClockIcon className={cn("h-4 w-4", endpointIndexStatusClassNameColor(status))} />;
10+
case "STARTED":
11+
return <Spinner className={cn("h-4 w-4", endpointIndexStatusClassNameColor(status))} />;
12+
case "SUCCESS":
13+
return (
14+
<CheckCircleIcon className={cn("h-4 w-4", endpointIndexStatusClassNameColor(status))} />
15+
);
16+
case "FAILURE":
17+
return <XCircleIcon className={cn("h-4 w-4", endpointIndexStatusClassNameColor(status))} />;
18+
}
19+
}
20+
21+
export function EndpointIndexStatusLabel({ status }: { status: EndpointIndexStatus }) {
22+
switch (status) {
23+
case "PENDING":
24+
return (
25+
<span className={endpointIndexStatusClassNameColor(status)}>
26+
{endpointIndexStatusTitle(status)}
27+
</span>
28+
);
29+
case "STARTED":
30+
return (
31+
<span className={endpointIndexStatusClassNameColor(status)}>
32+
{endpointIndexStatusTitle(status)}
33+
</span>
34+
);
35+
case "SUCCESS":
36+
return (
37+
<span className={endpointIndexStatusClassNameColor(status)}>
38+
{endpointIndexStatusTitle(status)}
39+
</span>
40+
);
41+
case "FAILURE":
42+
return (
43+
<span className={endpointIndexStatusClassNameColor(status)}>
44+
{endpointIndexStatusTitle(status)}
45+
</span>
46+
);
47+
}
48+
}
49+
50+
export function endpointIndexStatusTitle(status: EndpointIndexStatus): string {
51+
switch (status) {
52+
case "PENDING":
53+
return "Pending";
54+
case "STARTED":
55+
return "Started";
56+
case "SUCCESS":
57+
return "Success";
58+
case "FAILURE":
59+
return "Failure";
60+
}
61+
}
62+
63+
export function endpointIndexStatusClassNameColor(status: EndpointIndexStatus): string {
64+
switch (status) {
65+
case "PENDING":
66+
return "text-dimmed";
67+
case "STARTED":
68+
return "text-blue-500";
69+
case "SUCCESS":
70+
return "text-green-500";
71+
case "FAILURE":
72+
return "text-rose-500";
73+
}
74+
}

apps/webapp/app/components/primitives/Callout.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { Link } from "@remix-run/react";
1414
import { cn } from "~/utils/cn";
1515
import { Paragraph } from "./Paragraph";
16+
import { Spinner } from "./Spinner";
1617

1718
export const variantClasses = {
1819
info: {
@@ -51,8 +52,16 @@ export const variantClasses = {
5152
textColor: "text-blue-200",
5253
linkClassName: "transition hover:bg-blue-400/40",
5354
},
55+
pending: {
56+
className: "border-blue-400/20 bg-blue-800/30",
57+
icon: <Spinner className="h-5 w-5 shrink-0 " />,
58+
textColor: "text-blue-300",
59+
linkClassName: "transition hover:bg-blue-400/40",
60+
},
5461
} as const;
5562

63+
export type CalloutVariant = keyof typeof variantClasses;
64+
5665
export function Callout({
5766
children,
5867
className,
@@ -63,7 +72,7 @@ export function Callout({
6372
children?: React.ReactNode;
6473
className?: string;
6574
icon?: React.ReactNode;
66-
variant: keyof typeof variantClasses;
75+
variant: CalloutVariant;
6776
to?: string;
6877
}) {
6978
const variantDefinition = variantClasses[variant];

apps/webapp/app/components/primitives/FormError.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,25 @@ import type { z } from "zod";
22
import { Paragraph } from "./Paragraph";
33
import { NamedIcon } from "./NamedIcon";
44
import { motion } from "framer-motion";
5+
import { cn } from "~/utils/cn";
56

6-
export function FormError({ children, id }: { children: React.ReactNode; id?: string }) {
7+
export function FormError({
8+
children,
9+
id,
10+
className,
11+
}: {
12+
children: React.ReactNode;
13+
id?: string;
14+
className?: string;
15+
}) {
716
return (
817
<>
918
{children && (
1019
<motion.div
1120
initial={{ opacity: 0 }}
1221
animate={{ opacity: 1 }}
1322
transition={{ duration: 0.3 }}
14-
className="flex items-start gap-0.5"
23+
className={cn("flex items-start gap-0.5", className)}
1524
>
1625
<NamedIcon name="error" className="h-4 w-4 shrink-0 justify-start" />
1726
<Paragraph id={id} variant="extra-small" className="text-rose-500">

apps/webapp/app/models/indexEndpoint.server.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

apps/webapp/app/presenters/EnvironmentsPresenter.server.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { PrismaClient, prisma } from "~/db.server";
2-
import { IndexEndpointStats, parseEndpointIndexStats } from "~/models/indexEndpoint.server";
32
import { Project } from "~/models/project.server";
43
import { User } from "~/models/user.server";
54
import type {
65
Endpoint,
76
EndpointIndex,
7+
EndpointIndexStatus,
88
RuntimeEnvironment,
99
RuntimeEnvironmentType,
1010
} from "@trigger.dev/database";
11+
import {
12+
EndpointIndexError,
13+
EndpointIndexErrorSchema,
14+
IndexEndpointStats,
15+
parseEndpointIndexStats,
16+
} from "@trigger.dev/core";
1117

1218
export type Client = {
1319
slug: string;
@@ -34,9 +40,11 @@ export type ClientEndpoint =
3440
url: string;
3541
indexWebhookPath: string;
3642
latestIndex?: {
43+
status: EndpointIndexStatus;
3744
source: string;
3845
updatedAt: Date;
39-
stats: IndexEndpointStats;
46+
stats?: IndexEndpointStats;
47+
error?: EndpointIndexError;
4048
};
4149
environment: {
4250
id: string;
@@ -81,9 +89,11 @@ export class EnvironmentsPresenter {
8189
indexingHookIdentifier: true,
8290
indexings: {
8391
select: {
92+
status: true,
8493
source: true,
8594
updatedAt: true,
8695
stats: true,
96+
error: true,
8797
},
8898
take: 1,
8999
orderBy: {
@@ -214,7 +224,7 @@ const environmentSortOrder: RuntimeEnvironmentType[] = [
214224

215225
function endpointClient(
216226
endpoint: Pick<Endpoint, "id" | "slug" | "url" | "indexingHookIdentifier"> & {
217-
indexings: Pick<EndpointIndex, "source" | "updatedAt" | "stats">[];
227+
indexings: Pick<EndpointIndex, "status" | "source" | "updatedAt" | "stats" | "error">[];
218228
},
219229
environment: Pick<RuntimeEnvironment, "id" | "apiKey" | "type">,
220230
baseUrl: string
@@ -227,9 +237,13 @@ function endpointClient(
227237
indexWebhookPath: `${baseUrl}/api/v1/endpoints/${environment.id}/${endpoint.slug}/index/${endpoint.indexingHookIdentifier}`,
228238
latestIndex: endpoint.indexings[0]
229239
? {
240+
status: endpoint.indexings[0].status,
230241
source: endpoint.indexings[0].source,
231242
updatedAt: endpoint.indexings[0].updatedAt,
232243
stats: parseEndpointIndexStats(endpoint.indexings[0].stats),
244+
error: endpoint.indexings[0].error
245+
? EndpointIndexErrorSchema.parse(endpoint.indexings[0].error)
246+
: undefined,
233247
}
234248
: undefined,
235249
environment: environment,

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/ConfigureEndpointSheet.tsx

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useEventSource } from "remix-utils";
66
import { InlineCode } from "~/components/code/InlineCode";
77
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
88
import { Button } from "~/components/primitives/Buttons";
9-
import { Callout } from "~/components/primitives/Callout";
9+
import { Callout, CalloutVariant } from "~/components/primitives/Callout";
1010
import { ClipboardField } from "~/components/primitives/ClipboardField";
1111
import { DateTime } from "~/components/primitives/DateTime";
1212
import { FormError } from "~/components/primitives/FormError";
@@ -18,8 +18,14 @@ import { Paragraph } from "~/components/primitives/Paragraph";
1818
import { Sheet, SheetBody, SheetContent, SheetHeader } from "~/components/primitives/Sheet";
1919
import { ClientEndpoint } from "~/presenters/EnvironmentsPresenter.server";
2020
import { endpointStreamingPath } from "~/utils/pathBuilder";
21-
import { RuntimeEnvironmentType } from "../../../../../packages/database/src";
21+
import { EndpointIndexStatus, RuntimeEnvironmentType } from "../../../../../packages/database/src";
2222
import { bodySchema } from "../resources.environments.$environmentParam.endpoint";
23+
import {
24+
EndpointIndexStatusIcon,
25+
EndpointIndexStatusLabel,
26+
endpointIndexStatusTitle,
27+
} from "~/components/environments/EndpointIndexStatus";
28+
import { CodeBlock } from "~/components/code/CodeBlock";
2329

2430
type ConfigureEndpointSheetProps = {
2531
slug: string;
@@ -119,15 +125,29 @@ export function ConfigureEndpointSheet({ slug, endpoint, onClose }: ConfigureEnd
119125
method="post"
120126
action={`/resources/environments/${endpoint.environment.id}/endpoint/${endpoint.id}`}
121127
>
122-
<Callout variant="success" className="justiy-between items-center">
123-
<Paragraph variant="small" className="grow text-green-200">
124-
Endpoint configured. Last refreshed:{" "}
125-
{endpoint.latestIndex ? (
126-
<DateTime date={endpoint.latestIndex.updatedAt} />
127-
) : (
128-
"–"
129-
)}
130-
</Paragraph>
128+
<Callout
129+
variant="info"
130+
icon={
131+
<EndpointIndexStatusIcon status={endpoint.latestIndex?.status ?? "PENDING"} />
132+
}
133+
className="justiy-between items-center"
134+
>
135+
<div className="flex grow items-center gap-2">
136+
<EndpointIndexStatusLabel
137+
status={endpoint.latestIndex?.status ?? "PENDING"}
138+
/>
139+
<Paragraph variant="small" className="grow">
140+
Last refreshed:{" "}
141+
{endpoint.latestIndex ? (
142+
<>
143+
<DateTime date={endpoint.latestIndex.updatedAt} />
144+
</>
145+
) : (
146+
"–"
147+
)}
148+
</Paragraph>
149+
</div>
150+
131151
<Button
132152
variant="primary/small"
133153
type="submit"
@@ -138,6 +158,11 @@ export function ConfigureEndpointSheet({ slug, endpoint, onClose }: ConfigureEnd
138158
{refreshingEndpoint ? "Refreshing" : "Refresh now"}
139159
</Button>
140160
</Callout>
161+
{endpoint.latestIndex?.error && (
162+
<FormError className="p-2">
163+
<pre>{endpoint.latestIndex.error.message}</pre>
164+
</FormError>
165+
)}
141166
</refreshEndpointFetcher.Form>
142167
</div>
143168
<div className="max-w-full overflow-hidden">
@@ -155,3 +180,16 @@ export function ConfigureEndpointSheet({ slug, endpoint, onClose }: ConfigureEnd
155180
</Sheet>
156181
);
157182
}
183+
184+
function calloutVariantFromStatus(status: EndpointIndexStatus): CalloutVariant {
185+
switch (status) {
186+
case "PENDING":
187+
return "pending";
188+
case "STARTED":
189+
return "pending";
190+
case "SUCCESS":
191+
return "success";
192+
case "FAILURE":
193+
return "error";
194+
}
195+
}

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.environments/route.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ import { LoaderArgs } from "@remix-run/server-runtime";
33
import { useEffect, useMemo, useState } from "react";
44
import { typedjson, useTypedLoaderData } from "remix-typedjson";
55
import { useEventSource } from "remix-utils";
6+
import {
7+
EndpointIndexStatusIcon,
8+
EndpointIndexStatusLabel,
9+
} from "~/components/environments/EndpointIndexStatus";
610
import { EnvironmentLabel, environmentTitle } from "~/components/environments/EnvironmentLabel";
711
import { HowToUseApiKeysAndEndpoints } from "~/components/helpContent/HelpContentText";
812
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
913
import { BreadcrumbLink } from "~/components/navigation/NavBar";
10-
import { Button, ButtonContent } from "~/components/primitives/Buttons";
14+
import { Badge } from "~/components/primitives/Badge";
15+
import { ButtonContent } from "~/components/primitives/Buttons";
1116
import { ClipboardField } from "~/components/primitives/ClipboardField";
1217
import { DateTime } from "~/components/primitives/DateTime";
1318
import { Header2, Header3 } from "~/components/primitives/Headers";
@@ -38,7 +43,6 @@ import { ProjectParamSchema, projectEnvironmentsStreamingPath } from "~/utils/pa
3843
import { requestUrl } from "~/utils/requestUrl.server";
3944
import { RuntimeEnvironmentType } from "../../../../../packages/database/src";
4045
import { ConfigureEndpointSheet } from "./ConfigureEndpointSheet";
41-
import { Badge } from "~/components/primitives/Badge";
4246
import { FirstEndpointSheet } from "./FirstEndpointSheet";
4347

4448
export const loader = async ({ request, params }: LoaderArgs) => {
@@ -180,6 +184,7 @@ export default function Page() {
180184
<TableHeaderCell>Environment</TableHeaderCell>
181185
<TableHeaderCell>Url</TableHeaderCell>
182186
<TableHeaderCell>Last refreshed</TableHeaderCell>
187+
<TableHeaderCell>Last refresh Status</TableHeaderCell>
183188
<TableHeaderCell>Jobs</TableHeaderCell>
184189
<TableHeaderCell hiddenLabel>Go to page</TableHeaderCell>
185190
</TableRow>
@@ -268,7 +273,7 @@ function EndpointRow({
268273
<EnvironmentLabel environment={{ type }} />
269274
</div>
270275
</TableCell>
271-
<TableCell onClick={onClick} colSpan={4} alignment="right">
276+
<TableCell onClick={onClick} colSpan={5} alignment="right">
272277
<div className="flex items-center justify-end gap-4">
273278
<span className="text-amber-500">
274279
The {environmentTitle({ type })} environment is not configured
@@ -290,7 +295,17 @@ function EndpointRow({
290295
<TableCell onClick={onClick}>
291296
{endpoint.latestIndex ? <DateTime date={endpoint.latestIndex.updatedAt} /> : "–"}
292297
</TableCell>
293-
<TableCell onClick={onClick}>{endpoint.latestIndex?.stats.jobs ?? "–"}</TableCell>
298+
<TableCell onClick={onClick}>
299+
{endpoint.latestIndex ? (
300+
<div className="flex items-center gap-1">
301+
<EndpointIndexStatusIcon status={endpoint.latestIndex.status} />
302+
<EndpointIndexStatusLabel status={endpoint.latestIndex.status} />
303+
</div>
304+
) : (
305+
"–"
306+
)}
307+
</TableCell>
308+
<TableCell onClick={onClick}>{endpoint.latestIndex?.stats?.jobs ?? "–"}</TableCell>
294309
<TableCellChevron onClick={onClick} />
295310
</TableRow>
296311
);

0 commit comments

Comments
 (0)