Skip to content

Commit 708ebbd

Browse files
samejrericallam
andauthored
Features: Disabling and deleting jobs (#381)
* Don’t show the Ready To Run Job prompt if you have an Integration that needs attention * Don’t highlight the row red or green * Improvements to the contrast of the app sections and dividers * Removed the Jobs page title as it’s duped info * using the new border variable * Sticky last table cell * Improved the sticky last table cell * Make any last cell in a table sticky by adding isSticky to it * Added a dropdown menu to the menu table cell * table rows can be marked as disabled by adding disabled * Using the jobTestPath function for the test path * tidy up imports * Removed un-used props * Removed the green badge variant * Added a new status badge to the Runs table * Clicking the gradient clicks the row * Delete Job triggers a modal popup * Added a large danger button type * Added some modal styling and started adding data * Added more styling and data to the delete job modal * A table can now be given a full width prop * Large danger button added to Storybook * Danger button disabled state looks disabled now * New active badge component to display in the table and logic for showing the env data * Style updates to the dialog component * active and job status badges can now have a small size * Added a new named icon * The Job page shows the Job status in the PageInfoRow * Small badge style update * Runs table has a sticky right cell * Created a JobStatusTable component * Added some placeholder help panel content for disabling a Job * WIP creating a Settings page * Added a delete button that triggers the delete modal – just need data hooking up * Implemented deleting jobs from the dashboard --------- Co-authored-by: Eric Allam <[email protected]>
1 parent 3ce5397 commit 708ebbd

File tree

50 files changed

+887
-126
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+887
-126
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { cn } from "~/utils/cn";
2+
3+
const variant = {
4+
small:
5+
"py-[0.25rem] px-1.5 text-xxs font-normal inline-flex items-center justify-center whitespace-nowrap rounded-[0.125rem]",
6+
normal:
7+
"py-1 px-1.5 text-xs font-normal inline-flex items-center justify-center whitespace-nowrap rounded-sm",
8+
};
9+
10+
type ActiveBadgeProps = {
11+
active: boolean;
12+
className?: string;
13+
badgeSize?: keyof typeof variant;
14+
};
15+
16+
export function ActiveBadge({ active, className, badgeSize = "normal" }: ActiveBadgeProps) {
17+
switch (active) {
18+
case true:
19+
return (
20+
<span className={cn(variant[badgeSize], "bg-slate-800 text-green-500", className)}>
21+
Active
22+
</span>
23+
);
24+
case false:
25+
return (
26+
<span className={cn(variant[badgeSize], "bg-slate-800 text-dimmed", className)}>
27+
Disabled
28+
</span>
29+
);
30+
}
31+
}
32+
33+
export function MissingIntegrationBadge({
34+
className,
35+
badgeSize = "normal",
36+
}: {
37+
className?: string;
38+
badgeSize?: keyof typeof variant;
39+
}) {
40+
return (
41+
<span className={cn(variant[badgeSize], "bg-rose-600 text-white", className)}>
42+
Missing Integration
43+
</span>
44+
);
45+
}
46+
47+
export function NewBadge({
48+
className,
49+
badgeSize = "normal",
50+
}: {
51+
className?: string;
52+
badgeSize?: keyof typeof variant;
53+
}) {
54+
return (
55+
<span className={cn(variant[badgeSize], "bg-green-600 text-background", className)}>New!</span>
56+
);
57+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { RuntimeEnvironmentType } from "@trigger.dev/database";
2+
import {
3+
Table,
4+
TableBody,
5+
TableCell,
6+
TableHeader,
7+
TableHeaderCell,
8+
TableRow,
9+
} from "~/components/primitives/Table";
10+
import { EnvironmentLabel } from "./environments/EnvironmentLabel";
11+
import { DateTime } from "./primitives/DateTime";
12+
import { ActiveBadge } from "./ActiveBadge";
13+
14+
export type JobEnvironment = {
15+
type: RuntimeEnvironmentType;
16+
lastRun?: Date;
17+
version: string;
18+
enabled: boolean;
19+
};
20+
21+
type JobStatusTableProps = {
22+
environments: JobEnvironment[];
23+
};
24+
25+
export function JobStatusTable({ environments }: JobStatusTableProps) {
26+
return (
27+
<Table fullWidth>
28+
<TableHeader>
29+
<TableRow>
30+
<TableHeaderCell>Env</TableHeaderCell>
31+
<TableHeaderCell>Last Run</TableHeaderCell>
32+
<TableHeaderCell alignment="right">Version</TableHeaderCell>
33+
<TableHeaderCell alignment="right">Status</TableHeaderCell>
34+
</TableRow>
35+
</TableHeader>
36+
<TableBody>
37+
{environments.map((environment, index) => (
38+
<TableRow key={index}>
39+
<TableCell>
40+
<EnvironmentLabel environment={environment} />
41+
</TableCell>
42+
<TableCell>
43+
{environment.lastRun ? <DateTime date={environment.lastRun} /> : "Never Run"}
44+
</TableCell>
45+
<TableCell alignment="right">{environment.version}</TableCell>
46+
<TableCell alignment="right">
47+
<ActiveBadge active={environment.enabled} />
48+
</TableCell>
49+
</TableRow>
50+
))}
51+
</TableBody>
52+
</Table>
53+
);
54+
}

apps/webapp/app/components/environments/EnvironmentLabel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function EnvironmentLabel({
1313
return (
1414
<span
1515
className={cn(
16-
"flex h-4 items-center justify-center rounded-[2px] px-1 text-xxs font-medium uppercase tracking-wider text-midnight-900",
16+
"inline-flex h-4 items-center justify-center rounded-[2px] px-1 text-xxs font-medium uppercase tracking-wider text-midnight-900",
1717
environmentColorClassName(environment),
1818
className
1919
)}

apps/webapp/app/components/helpContent/HelpContentText.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,60 @@ export function HowToUseThisIntegration({ integration, help, integrationClient }
414414
);
415415
}
416416

417+
export function HowToDisableAJob({
418+
id,
419+
name,
420+
version,
421+
}: {
422+
id: string;
423+
name: string;
424+
version: string;
425+
}) {
426+
return (
427+
<>
428+
<Paragraph spacing>
429+
To disable a job, you need to set the <InlineCode>enabled</InlineCode> property to{" "}
430+
<InlineCode>false</InlineCode>.
431+
</Paragraph>
432+
<StepNumber
433+
stepNumber="1"
434+
title={
435+
<>
436+
Set <InlineCode>enabled</InlineCode> to <InlineCode>false</InlineCode>
437+
</>
438+
}
439+
/>
440+
<StepContentContainer>
441+
<CodeBlock
442+
showLineNumbers={false}
443+
className="mb-4"
444+
code={`client.defineJob({
445+
id: "${id}",
446+
name: "${name}",
447+
version: "${version}",
448+
enabled: false,
449+
// ...rest of your Job definition
450+
});`}
451+
/>
452+
</StepContentContainer>
453+
<StepNumber
454+
stepNumber="2"
455+
title={
456+
<>
457+
Run the <InlineCode>@trigger.dev/cli dev</InlineCode> command
458+
</>
459+
}
460+
/>
461+
<StepContentContainer>
462+
<Paragraph spacing>
463+
If you aren't already running the <InlineCode>dev</InlineCode> command, run it now.
464+
</Paragraph>
465+
<TriggerDevCommand />
466+
</StepContentContainer>
467+
</>
468+
);
469+
}
470+
417471
export function HowToUseApiKeysAndEndpoints() {
418472
return (
419473
<>
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { RuntimeEnvironmentType } from "@trigger.dev/database";
2+
import { cn } from "~/utils/cn";
3+
import { JobStatusTable } from "../JobsStatusTable";
4+
import { Button } from "../primitives/Buttons";
5+
import { Header1, Header2 } from "../primitives/Headers";
6+
import { NamedIcon } from "../primitives/NamedIcon";
7+
import { Paragraph } from "../primitives/Paragraph";
8+
import { TextLink } from "../primitives/TextLink";
9+
import { useFetcher } from "@remix-run/react";
10+
import { Spinner } from "../primitives/Spinner";
11+
12+
type JobEnvironment = {
13+
type: RuntimeEnvironmentType;
14+
lastRun?: Date;
15+
version: string;
16+
enabled: boolean;
17+
};
18+
19+
type DeleteJobDialogContentProps = {
20+
id: string;
21+
title: string;
22+
slug: string;
23+
environments: JobEnvironment[];
24+
redirectTo?: string;
25+
};
26+
27+
export function DeleteJobDialogContent({
28+
title,
29+
slug,
30+
environments,
31+
id,
32+
redirectTo,
33+
}: DeleteJobDialogContentProps) {
34+
const canDelete = environments.every((environment) => !environment.enabled);
35+
const fetcher = useFetcher();
36+
37+
const isLoading =
38+
fetcher.state === "submitting" ||
39+
(fetcher.state === "loading" && fetcher.formMethod === "DELETE");
40+
41+
return (
42+
<div className="flex w-full flex-col items-center gap-y-6">
43+
<div className="flex flex-col items-center justify-center gap-y-2">
44+
<Header1>{title}</Header1>
45+
<Paragraph variant="small">ID: {slug}</Paragraph>
46+
</div>
47+
<JobStatusTable environments={environments} />
48+
49+
<Header2
50+
className={cn(
51+
canDelete ? "border-rose-500 bg-rose-500/10" : "border-amber-500 bg-amber-500/10",
52+
"rounded border px-3.5 py-2 text-center text-bright"
53+
)}
54+
>
55+
{canDelete
56+
? "Are you sure you want to delete this Job?"
57+
: "You can't delete this Job until all env are disabled"}
58+
</Header2>
59+
<Paragraph variant="small" className="px-6 text-center">
60+
{canDelete ? (
61+
<>
62+
This will permanently delete the Job <span className="strong text-bright">{title}</span>
63+
. This includes the deletion of all Run history. This cannot be undone.
64+
</>
65+
) : (
66+
<>
67+
This Job is still active in an environment. You need to disable it in your Job code
68+
first before it can be deleted.{" "}
69+
<TextLink to="https://trigger.dev/docs/documentation/guides/jobs/managing#disabling-jobs">
70+
Learn how to disable a Job
71+
</TextLink>
72+
.
73+
</>
74+
)}
75+
</Paragraph>
76+
{canDelete ? (
77+
<fetcher.Form
78+
method="delete"
79+
action={`/resources/jobs/${id}${redirectTo ? `?redirectTo=${redirectTo}` : ""}`}
80+
>
81+
<Button variant="danger/large" fullWidth>
82+
{isLoading ? (
83+
<Spinner />
84+
) : (
85+
<>
86+
<NamedIcon
87+
name="trash-can"
88+
className="mr-1.5 h-4 w-4 text-bright transition group-hover:text-bright"
89+
/>
90+
Delete this job
91+
</>
92+
)}
93+
</Button>
94+
</fetcher.Form>
95+
) : (
96+
<Button variant="danger/large" fullWidth disabled>
97+
<>
98+
<NamedIcon
99+
name="trash-can"
100+
className="mr-1.5 h-4 w-4 text-bright transition group-hover:text-bright"
101+
/>
102+
Delete this job
103+
</>
104+
</Button>
105+
)}
106+
</div>
107+
);
108+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { ActiveBadge, MissingIntegrationBadge, NewBadge } from "../ActiveBadge";
2+
3+
type JobStatusBadgeProps = {
4+
enabled: boolean;
5+
hasIntegrationsRequiringAction: boolean;
6+
hasRuns: boolean;
7+
badgeSize?: "small" | "normal";
8+
};
9+
10+
export function JobStatusBadge({
11+
enabled,
12+
hasIntegrationsRequiringAction,
13+
hasRuns,
14+
badgeSize = "normal",
15+
}: JobStatusBadgeProps) {
16+
if (!enabled) {
17+
return <ActiveBadge active={false} badgeSize={badgeSize} />;
18+
}
19+
20+
if (hasIntegrationsRequiringAction) {
21+
return <MissingIntegrationBadge badgeSize={badgeSize} />;
22+
}
23+
24+
if (!hasRuns) {
25+
return <NewBadge badgeSize={badgeSize} />;
26+
}
27+
28+
return <ActiveBadge active={true} badgeSize={badgeSize} />;
29+
}

0 commit comments

Comments
 (0)