Skip to content

Commit

Permalink
Merge pull request #8 from riverqueue/bg-job-detail-display-errors-an…
Browse files Browse the repository at this point in the history
…d-attempted-by-

job detail: render list of attempted by, errors
  • Loading branch information
bgentry authored May 22, 2024
2 parents ceca356 + fd34b4c commit bd6f02a
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 27 deletions.
73 changes: 73 additions & 0 deletions ui/src/components/JobAttemptErrors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Job } from "@services/jobs";
import RelativeTimeFormatter from "./RelativeTimeFormatter";
import { useState } from "react";

type JobAttemptErrorsProps = {
job: Job;
};

const defaultErrorDisplayCount = 5;

export default function JobAttemptErrors({ job }: JobAttemptErrorsProps) {
const [showAllErrors, setShowAllErrors] = useState(false);
const errorsToDisplay = showAllErrors
? job.errors.slice().reverse()
: job.errors.slice(-1 * defaultErrorDisplayCount).reverse();

return (
<div className="bg-white sm:col-span-2">
<div className="px-4 lg:px-0">
<dt className="text-sm font-medium leading-6 text-slate-900 dark:text-slate-100">
Errors
</dt>
<dd className="mt-1 text-sm leading-6 text-slate-700 dark:text-slate-300 sm:mt-2">
{job.errors.length === 0 ? (
<>No errors</>
) : (
<>
<ol role="list" className="divide-y divide-gray-200">
{errorsToDisplay.map((error) => (
<li key={error.attempt} className="p-4 sm:p-6">
<div className="flex items-start">
<p className="leading-5 text-slate-900 dark:text-slate-100">
{error.attempt.toString()}
</p>
<div className="ml-4">
<h5 className="font-mono text-sm font-medium text-slate-900 dark:text-slate-100">
{error.error}
</h5>
{error.trace && (
<pre className="mt-1 max-h-20 overflow-scroll bg-slate-300/10 text-sm text-slate-700 dark:bg-slate-700/10 dark:text-slate-300">
{error.trace}
</pre>
)}
<p className="mt-1 text-sm text-slate-700 dark:text-slate-300">
<RelativeTimeFormatter time={error.at} addSuffix />
</p>
</div>
</div>
</li>
))}
</ol>
{job.errors.length > defaultErrorDisplayCount && (
<>
<div className="mt-2 flex">
<button
type="button"
className="mt-4 text-sm font-semibold text-indigo-600 hover:underline dark:text-slate-100"
onClick={() => setShowAllErrors(!showAllErrors)}
>
{showAllErrors
? "Show fewer"
: `Show all ${job.errors.length} errors`}
</button>
</div>
</>
)}
</>
)}
</dd>
</div>
</div>
);
}
49 changes: 42 additions & 7 deletions ui/src/components/JobDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import { Heroicon, JobState } from "@services/types";
import { capitalize } from "@utils/string";
import clsx from "clsx";
import JobTimeline from "./JobTimeline";
import { FormEvent } from "react";
import { FormEvent, useMemo, useState } from "react";
import { Link } from "@tanstack/react-router";
import TopNavTitleOnly from "./TopNavTitleOnly";
import RelativeTimeFormatter from "./RelativeTimeFormatter";
import JobAttemptErrors from "./JobAttemptErrors";

type JobDetailProps = {
cancel: () => void;
Expand Down Expand Up @@ -94,6 +95,14 @@ function ActionButtons({
}

export default function JobDetail({ cancel, job, retry }: JobDetailProps) {
const [showAllAttempts, setShowAllAttempts] = useState(false);
const attemptsToDisplay = useMemo(() => {
if (showAllAttempts) {
return job.attemptedBy.slice().reverse();
}
return job.attemptedBy.slice(-5).reverse();
}, [job.attemptedBy, showAllAttempts]);

return (
<>
<TopNavTitleOnly title="Job Details" />
Expand All @@ -118,7 +127,7 @@ export default function JobDetail({ cancel, job, retry }: JobDetailProps) {
</div>
</header>

<div className="mx-auto grid gap-8 sm:grid-cols-2 sm:px-6 lg:px-8">
<div className="mx-auto grid gap-8 pb-16 sm:grid-cols-2 sm:px-6 lg:px-8">
{/* Description list */}
<div className="">
<dl className="grid grid-cols-12">
Expand Down Expand Up @@ -218,13 +227,39 @@ export default function JobDetail({ cancel, job, retry }: JobDetailProps) {
Attempted By
</dt>
<dd className="mt-1 text-sm leading-6 text-slate-700 dark:text-slate-300 sm:mt-2">
{job.attemptedBy.map((attemptedBy, i) => (
<p className="font-mono" key={i}>
{attemptedBy}
</p>
))}
<ul role="list">
{attemptsToDisplay.map((attemptedBy, i) => (
<li
className="font-mono"
key={i}
title={job.errors.at(i)?.at.toISOString()}
>
{attemptedBy}
</li>
))}
</ul>
{!showAllAttempts && job.attemptedBy.length > 5 && (
<button
type="button"
className="mt-4 text-sm font-semibold text-indigo-600 hover:underline dark:text-slate-100"
onClick={() => setShowAllAttempts(true)}
>
Show all {job.attemptedBy.length} attempts
</button>
)}
{showAllAttempts && (
<button
type="button"
className="mt-4 text-sm font-semibold text-indigo-600 hover:underline dark:text-slate-100"
onClick={() => setShowAllAttempts(false)}
>
Show fewer attempts
</button>
)}
</dd>
</div>

<JobAttemptErrors job={job} />
</dl>
</div>
</div>
Expand Down
5 changes: 1 addition & 4 deletions ui/src/components/RefreshPauser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type RefreshIntervalSetting = {

const refreshIntervals: RefreshIntervalSetting[] = [
{ name: "Pause", value: 0 },
{ name: "1s", value: 1000 },
{ name: "2s", value: 2000 },
{ name: "5s", value: 5000 },
{ name: "10s", value: 10000 },
Expand Down Expand Up @@ -62,10 +63,6 @@ export function RefreshPauser(
<ListboxButton
className="relative z-10 flex size-7 items-center justify-center rounded-lg text-slate-700 hover:bg-slate-100 hover:text-slate-900 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-slate-100"
aria-label="Theme"
// type="button"
// className="-m-2.5 p-2.5 text-gray-400 hover:text-gray-500"
// onClick={() => setDisabled(!disabled)}
// title={disabled ? "Resume live updates" : "Pause live updates"}
>
<span className="sr-only">
{disabled ? "Resume live updates" : "Pause live updates"}
Expand Down
4 changes: 2 additions & 2 deletions ui/src/services/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { API } from "@utils/api";
// except with keys as snake_case instead of camelCase.
type AttemptErrorFromAPI = {
at: string;
attempt: number;
error: string;
num: number;
trace: string;
};

Expand Down Expand Up @@ -87,9 +87,9 @@ const apiAttemptErrorsToAttemptErrors = (
errors: AttemptErrorFromAPI[]
): AttemptError[] => {
return errors.map((error) => ({
attempt: error.attempt,
at: new Date(error.at),
error: error.error,
num: error.num,
trace: error.trace,
}));
};
Expand Down
28 changes: 14 additions & 14 deletions ui/src/test/factories/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import { add, sub } from "date-fns";
class AttemptErrorFactory extends Factory<AttemptError, object> {}

export const attemptErrorFactory = AttemptErrorFactory.define(({ params }) => {
const num = params.num || 1;
const attempt = params.attempt || 1;
return {
at: params.at || sub(new Date(), { seconds: (21 - num) * 7.3 }),
at: params.at || sub(new Date(), { seconds: (21 - attempt) * 7.3 }),
attempt,
error: "Failed yet again with some Go message",
num,
trace: "...",
};
});
Expand Down Expand Up @@ -69,7 +69,7 @@ class JobFactory extends Factory<Job, object> {
at: add(finalizedAt, {
seconds: faker.number.float({ min: 0.01, max: 2.5 }),
}),
num: 1,
attempt: 1,
}),
],
createdAt,
Expand Down Expand Up @@ -106,20 +106,20 @@ class JobFactory extends Factory<Job, object> {
"worker-3",
],
errors: [
attemptErrorFactory.build({ num: 1 }),
attemptErrorFactory.build({ num: 2 }),
attemptErrorFactory.build({ num: 3 }),
attemptErrorFactory.build({ num: 4 }),
attemptErrorFactory.build({ num: 5 }),
attemptErrorFactory.build({ num: 6 }),
attemptErrorFactory.build({ num: 7 }),
attemptErrorFactory.build({ num: 8 }),
attemptErrorFactory.build({ num: 9 }),
attemptErrorFactory.build({ attempt: 1 }),
attemptErrorFactory.build({ attempt: 2 }),
attemptErrorFactory.build({ attempt: 3 }),
attemptErrorFactory.build({ attempt: 4 }),
attemptErrorFactory.build({ attempt: 5 }),
attemptErrorFactory.build({ attempt: 6 }),
attemptErrorFactory.build({ attempt: 7 }),
attemptErrorFactory.build({ attempt: 8 }),
attemptErrorFactory.build({ attempt: 9 }),
attemptErrorFactory.build({
at: add(attemptedAt, {
seconds: faker.number.float({ min: 0.01, max: 95 }),
}),
num: 10,
attempt: 10,
}),
],
createdAt: sub(attemptedAt, { minutes: 31, seconds: 30 }),
Expand Down

0 comments on commit bd6f02a

Please sign in to comment.