Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

job detail: render list of attempted by, errors #8

Merged
merged 1 commit into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading