Skip to content

Commit 4986bfd

Browse files
matt-aitkenericallamsamejr
authored
Scheduled tasks (#1036)
* Database schema and migrations for schedules * Added schedules to the side menu * The pagination can optionally hide the page numbers for a compactive mode * Filters for the schedule page * Added triggerSource (“STANDARD”, “SCHEDULED”) to BackgroundWorkerTask * Added the ability to disabled a LinkButton * Started work on the schedule page * Environment buttons * The new schedule form styles * Added a cxouple of extra fields * Allow a checkbox to have a rich label * Added cronstrue package to the webapp * WIP creating tasks using the form * Improved the form styling * Creating schedules is working in the UI * Minor improvements * Basic schedule table is displaying * Creating tasks with triggerSource = scheduled. Refactored how task metadata is stored and accessed to be cleaner * Resource route * Fixes in the form * Added a gap between the environments * WIP on OpenAI generating CRON expressions * AI generated CRON expressions is working * Fix for the CRON field being uneditable after an AI generation * Improvements * Table padding * useThrottle now behaves correctly * Added filtering to the schedules list * Improved the layout and fixed CRON search * Fixed pagination for the schedules list. Just use a regular Prisma query * Page size of 20 * Added links to the schedule rows * Get rid of the Last run column for now * Implement triggered scheduled tasks Also implemented superjson payloads and dev runtime environment “presence” with RuntimeEnvironmentSession * Move CronPattern and CreateSchedule into a common client-accessible file * Latest UI changes * Fix for creating a task schedule with a blank dedup key * Refactor the human to cron stuff into a separate file and use json_object OpenAI response format * Fix for trying to use a hook on the server-side… * A couple of fixes to the new schedule form * WIP on viewing a scheduled run * Make the filters all optional * Use the RunListPresenter from the schedule presenter * Display a table of runs… the wrong runs but still * Runs from the schedule * Deleting schedules from the UI * Tidied imports and fixed name of options object * Disabling a schedule * Editing schedules * Tidied imports * Added icons to the task list, needs some design love * Added a tooltip for CRON pattersn * Show the last run in the schedules table * Some tweaks * Added a placeholder to the CRON AI field * Improved the trigger source icon * Dim out disabled schedules * Scheduled tasks have the correct icon in a run * Added the task source icon to the test task list * Added the date field component to storybook * Style improvements to the date field * Implement Task Schedule API * added a medium sized variant to the date field * Fixed replay run for superjson payload types Also now linking from the replaying run to the original run (using span links) Also added a project metrics prometheus endpoint to detect the state of the queues * WIP on allowing different forms for testing * If you pass a string to prettyPrintPacket which is json or superjson, safeParse it first * Test page, deal json and superjson. Started splitting UI for schedules * WIP on schedule form * Removed the Label from the DateField * WIP on test schedule form * Fix for the runs page showing the wrong message when there are no runs from time filtering * Removed labels from the DateField * Fixes for the form * Test runs are working for schedules * Fix for nextScheduledTimestamps in triggerScheduledTask * Implement idempotency key support and fix issue with cancelled runs causing concurrency usage * Added API documentation for the schedule API * Remove log * Removed console log from runs page * Transform the recent runs test data on the server * Fix for hydration mismatch * Current date as the default for the test form * Recent payloads working * Delete schedule modal * Deal with empty strings from the form * Set the initial value for the scheduled test form * Add option to print console logs in the dev CLI locally (issue #1014) * Export queue from the SDK * Fix for schedules list when you have no schedule tasks * Blank states improved * Make task schedules more generic, to support additional schedule generators in the future * Removed log from maqrs * Removed “v3/schedules” export from the SDK --------- Co-authored-by: Eric Allam <[email protected]> Co-authored-by: James Ritchie <[email protected]>
1 parent a3abe4c commit 4986bfd

File tree

133 files changed

+6214
-627
lines changed

Some content is hidden

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

133 files changed

+6214
-627
lines changed

.changeset/dry-walls-check.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"trigger.dev": patch
3+
"@trigger.dev/core": patch
4+
---
5+
6+
Add option to print console logs in the dev CLI locally (issue #1014)

.changeset/khaki-poems-lay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
---
4+
5+
Export queue from the SDK

.changeset/rotten-dryers-exercise.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
"trigger.dev": patch
4+
"@trigger.dev/core": patch
5+
---
6+
7+
Adding task with a triggerSource of schedule

.changeset/tricky-bulldogs-heal.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@trigger.dev/sdk": patch
3+
"trigger.dev": patch
4+
"@trigger.dev/core": patch
5+
---
6+
7+
Added a new global - Task Catalog - to better handle task metadata
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export function AISparkleIcon({ className }: { className?: string }) {
2+
return (
3+
<svg className={className} viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
4+
<path
5+
d="M14.9806 0.803884C14.8871 0.33646 14.4767 0 14 0C13.5233 0 13.1129 0.33646 13.0194 0.803884L12.7809 1.99644C12.7017 2.3923 12.3923 2.70174 11.9964 2.78091L10.8039 3.01942C10.3365 3.1129 10 3.52332 10 4C10 4.47668 10.3365 4.8871 10.8039 4.98058L11.9964 5.21909C12.3923 5.29826 12.7017 5.6077 12.7809 6.00356L13.0194 7.19612C13.1129 7.66354 13.5233 8 14 8C14.4767 8 14.8871 7.66354 14.9806 7.19612L15.2191 6.00356C15.2983 5.6077 15.6077 5.29826 16.0036 5.21909L17.1961 4.98058C17.6635 4.8871 18 4.47668 18 4C18 3.52332 17.6635 3.1129 17.1961 3.01942L16.0036 2.78091C15.6077 2.70174 15.2983 2.3923 15.2191 1.99644L14.9806 0.803884Z"
6+
fill="url(#paint0_linear_11402_36656)"
7+
/>
8+
<path
9+
d="M5.94868 4.68377C5.81257 4.27543 5.43043 4 5 4C4.56957 4 4.18743 4.27543 4.05132 4.68377L3.36754 6.73509C3.26801 7.03369 3.03369 7.26801 2.73509 7.36754L0.683772 8.05132C0.27543 8.18743 0 8.56957 0 9C0 9.43043 0.27543 9.81257 0.683772 9.94868L2.73509 10.6325C3.03369 10.732 3.26801 10.9663 3.36754 11.2649L4.05132 13.3162C4.18743 13.7246 4.56957 14 5 14C5.43043 14 5.81257 13.7246 5.94868 13.3162L6.63246 11.2649C6.73199 10.9663 6.96631 10.732 7.26491 10.6325L9.31623 9.94868C9.72457 9.81257 10 9.43043 10 9C10 8.56957 9.72457 8.18743 9.31623 8.05132L7.26491 7.36754C6.96631 7.26801 6.73199 7.03369 6.63246 6.73509L5.94868 4.68377Z"
10+
fill="url(#paint1_linear_11402_36656)"
11+
/>
12+
<path
13+
d="M12.9487 12.6838C12.8126 12.2754 12.4304 12 12 12C11.5696 12 11.1874 12.2754 11.0513 12.6838L10.8675 13.2351C10.768 13.5337 10.5337 13.768 10.2351 13.8675L9.68377 14.0513C9.27543 14.1874 9 14.5696 9 15C9 15.4304 9.27543 15.8126 9.68377 15.9487L10.2351 16.1325C10.5337 16.232 10.768 16.4663 10.8675 16.7649L11.0513 17.3162C11.1874 17.7246 11.5696 18 12 18C12.4304 18 12.8126 17.7246 12.9487 17.3162L13.1325 16.7649C13.232 16.4663 13.4663 16.232 13.7649 16.1325L14.3162 15.9487C14.7246 15.8126 15 15.4304 15 15C15 14.5696 14.7246 14.1874 14.3162 14.0513L13.7649 13.8675C13.4663 13.768 13.232 13.5337 13.1325 13.2351L12.9487 12.6838Z"
14+
fill="url(#paint2_linear_11402_36656)"
15+
/>
16+
<defs>
17+
<linearGradient
18+
id="paint0_linear_11402_36656"
19+
x1="9"
20+
y1="0"
21+
x2="9"
22+
y2="18"
23+
gradientUnits="userSpaceOnUse"
24+
>
25+
<stop stopColor="#E543FF" />
26+
<stop offset="1" stopColor="#286399" />
27+
</linearGradient>
28+
<linearGradient
29+
id="paint1_linear_11402_36656"
30+
x1="9"
31+
y1="0"
32+
x2="9"
33+
y2="18"
34+
gradientUnits="userSpaceOnUse"
35+
>
36+
<stop stopColor="#E543FF" />
37+
<stop offset="1" stopColor="#286399" />
38+
</linearGradient>
39+
<linearGradient
40+
id="paint2_linear_11402_36656"
41+
x1="9"
42+
y1="0"
43+
x2="9"
44+
y2="18"
45+
gradientUnits="userSpaceOnUse"
46+
>
47+
<stop stopColor="#E543FF" />
48+
<stop offset="1" stopColor="#286399" />
49+
</linearGradient>
50+
</defs>
51+
</svg>
52+
);
53+
}

apps/webapp/app/components/navigation/SideMenu.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
ArrowRightOnRectangleIcon,
55
BeakerIcon,
66
ChartBarIcon,
7+
ClockIcon,
78
CursorArrowRaysIcon,
89
IdentificationIcon,
910
KeyIcon,
@@ -47,6 +48,7 @@ import {
4748
v3ProjectPath,
4849
v3ProjectSettingsPath,
4950
v3RunsPath,
51+
v3SchedulesPath,
5052
v3TestPath,
5153
} from "~/utils/pathBuilder";
5254
import { Feedback } from "../Feedback";
@@ -571,6 +573,13 @@ function V3ProjectSideMenu({
571573
to={v3TestPath(organization, project)}
572574
data-action="test"
573575
/>
576+
<SideMenuItem
577+
name="Schedules"
578+
icon={ClockIcon}
579+
iconColor="text-sun-500"
580+
to={v3SchedulesPath(organization, project)}
581+
data-action="schedules"
582+
/>
574583
<SideMenuItem
575584
name="API keys"
576585
icon={KeyIcon}

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,15 +286,15 @@ export const Button = forwardRef<HTMLButtonElement, ButtonPropsType>(
286286
type LinkPropsType = Pick<
287287
LinkProps,
288288
"to" | "target" | "onClick" | "onMouseDown" | "onMouseEnter" | "onMouseLeave" | "download"
289-
> &
290-
React.ComponentProps<typeof ButtonContent>;
289+
> & { disabled?: boolean } & React.ComponentProps<typeof ButtonContent>;
291290
export const LinkButton = ({
292291
to,
293292
onClick,
294293
onMouseDown,
295294
onMouseEnter,
296295
onMouseLeave,
297296
download,
297+
disabled = false,
298298
...props
299299
}: LinkPropsType) => {
300300
const innerRef = useRef<HTMLAnchorElement>(null);
@@ -309,6 +309,19 @@ export const LinkButton = ({
309309
});
310310
}
311311

312+
if (disabled) {
313+
return (
314+
<div
315+
className={cn(
316+
"group pointer-events-none cursor-default opacity-40 outline-none",
317+
props.fullWidth ? "w-full" : ""
318+
)}
319+
>
320+
<ButtonContent {...props} />
321+
</div>
322+
);
323+
}
324+
312325
if (to.toString().startsWith("http") || to.toString().startsWith("/resources")) {
313326
return (
314327
<ExtLink

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const variants = {
2323
},
2424
"button/small": {
2525
button:
26-
"flex items-center w-fit h-8 pl-2 pr-3 rounded border border-charcoal-800 hover:bg-charcoal-850 hover:border-charcoal-750 transition",
26+
"flex items-center w-fit h-8 pl-2 pr-3 rounded border border-charcoal-600 hover:bg-charcoal-850 hover:border-charcoal-500 transition",
2727
label: "text-sm text-text-bright select-none",
2828
description: "text-text-dimmed",
2929
inputPosition: "mt-0",
@@ -32,7 +32,7 @@ const variants = {
3232
},
3333
button: {
3434
button:
35-
"w-fit py-2 pl-3 pr-4 rounded border border-charcoal-800 hover:bg-charcoal-850 hover:border-charcoal-750 transition",
35+
"w-fit py-2 pl-3 pr-4 rounded border border-charcoal-600 hover:bg-charcoal-850 hover:border-charcoal-500 transition",
3636
label: "text-text-bright select-none",
3737
description: "text-text-dimmed",
3838
inputPosition: "mt-1",
@@ -57,7 +57,7 @@ export type CheckboxProps = Omit<
5757
name?: string;
5858
value?: string;
5959
variant?: keyof typeof variants;
60-
label?: string;
60+
label?: React.ReactNode;
6161
description?: string;
6262
badges?: string[];
6363
className?: string;
@@ -137,7 +137,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
137137
className={cn(
138138
inputPositionClasses,
139139
props.readOnly || disabled ? "cursor-default" : "cursor-pointer",
140-
"read-only:border-charcoal-650 disabled:border-charcoal-650 rounded-sm border border-charcoal-700 bg-transparent transition checked:!bg-indigo-500 read-only:!bg-charcoal-700 group-hover:bg-charcoal-900 group-hover:checked:bg-indigo-500 group-focus:ring-1 focus:ring-indigo-500 focus:ring-offset-0 focus:ring-offset-transparent focus-visible:outline-none focus-visible:ring-indigo-500 disabled:!bg-charcoal-700"
140+
"read-only:border-charcoal-650 disabled:border-charcoal-650 rounded-sm border border-charcoal-600 bg-transparent transition checked:!bg-indigo-500 read-only:!bg-charcoal-700 group-hover:bg-charcoal-900 group-hover:checked:bg-indigo-500 group-focus:ring-1 focus:ring-indigo-500 focus:ring-offset-0 focus:ring-offset-transparent focus-visible:outline-none focus-visible:ring-indigo-500 disabled:!bg-charcoal-700"
141141
)}
142142
id={id}
143143
ref={ref}

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

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
1+
import { BellAlertIcon } from "@heroicons/react/20/solid";
12
import { CalendarDateTime, createCalendar } from "@internationalized/date";
23
import { useDateField, useDateSegment } from "@react-aria/datepicker";
34
import type { DateFieldState, DateSegment } from "@react-stately/datepicker";
45
import { useDateFieldState } from "@react-stately/datepicker";
56
import { Granularity } from "@react-types/datepicker";
67
import { useEffect, useRef, useState } from "react";
78
import { cn } from "~/utils/cn";
8-
import { useLocales } from "./LocaleProvider";
99
import { Button } from "./Buttons";
1010

11+
const variants = {
12+
small: {
13+
fieldStyles: "h-5 text-sm rounded-sm px-0.5",
14+
nowButtonVariant: "tertiary/small" as const,
15+
clearButtonVariant: "minimal/small" as const,
16+
},
17+
medium: {
18+
fieldStyles: "h-7 text-base rounded px-1",
19+
nowButtonVariant: "tertiary/medium" as const,
20+
clearButtonVariant: "minimal/medium" as const,
21+
},
22+
};
23+
24+
type Variant = keyof typeof variants;
25+
1126
type DateFieldProps = {
12-
label?: string;
27+
label: string;
1328
defaultValue?: Date;
1429
minValue?: Date;
1530
maxValue?: Date;
@@ -20,6 +35,7 @@ type DateFieldProps = {
2035
showNowButton?: boolean;
2136
showClearButton?: boolean;
2237
onValueChange?: (value: Date | undefined) => void;
38+
variant?: Variant;
2339
};
2440

2541
export function DateField({
@@ -34,6 +50,7 @@ export function DateField({
3450
showGuide = false,
3551
showNowButton = false,
3652
showClearButton = false,
53+
variant = "small",
3754
}: DateFieldProps) {
3855
const [value, setValue] = useState<undefined | CalendarDateTime>(
3956
utcDateToCalendarDate(defaultValue)
@@ -90,48 +107,50 @@ export function DateField({
90107

91108
return (
92109
<div className={`flex flex-col items-start ${className || ""}`}>
93-
<span {...labelProps} className="mb-1 ml-0.5 text-xs text-charcoal-300">
94-
{label}
95-
</span>
96-
<div className="flex flex-row items-center gap-1">
110+
<div className="flex flex-row items-center gap-1" aria-label={label}>
97111
<div
98112
{...fieldProps}
99113
ref={ref}
100114
className={cn(
101-
"flex rounded-sm border border-charcoal-800 bg-charcoal-750 p-0.5 px-1.5 transition-colors focus-within:border-charcoal-500 hover:border-charcoal-700 focus-within:hover:border-charcoal-500",
115+
"flex rounded-sm border bg-charcoal-700 p-0.5 transition focus-within:border-charcoal-600 hover:border-charcoal-600",
102116
fieldClassName
103117
)}
104118
>
105-
<DateSegment segment={yearSegment} state={state} />
106-
<DateSegment segment={literalSegment("/")} state={state} />
107-
<DateSegment segment={monthSegment} state={state} />
108-
<DateSegment segment={literalSegment("/")} state={state} />
109-
<DateSegment segment={daySegment} state={state} />
110-
<DateSegment segment={literalSegment(", ")} state={state} />
111-
<DateSegment segment={hourSegment} state={state} />
112-
<DateSegment segment={literalSegment(":")} state={state} />
113-
<DateSegment segment={minuteSegment} state={state} />
114-
<DateSegment segment={literalSegment(":")} state={state} />
115-
<DateSegment segment={secondSegment} state={state} />
116-
<DateSegment segment={literalSegment(" ")} state={state} />
117-
<DateSegment segment={dayPeriodSegment} state={state} />
119+
<DateSegment segment={yearSegment} state={state} variant={variant} />
120+
<DateSegment segment={literalSegment("/")} state={state} variant={variant} />
121+
<DateSegment segment={monthSegment} state={state} variant={variant} />
122+
<DateSegment segment={literalSegment("/")} state={state} variant={variant} />
123+
<DateSegment segment={daySegment} state={state} variant={variant} />
124+
<DateSegment segment={literalSegment(", ")} state={state} variant={variant} />
125+
<DateSegment segment={hourSegment} state={state} variant={variant} />
126+
<DateSegment segment={literalSegment(":")} state={state} variant={variant} />
127+
<DateSegment segment={minuteSegment} state={state} variant={variant} />
128+
<DateSegment segment={literalSegment(":")} state={state} variant={variant} />
129+
<DateSegment segment={secondSegment} state={state} variant={variant} />
130+
<DateSegment segment={literalSegment(" ")} state={state} variant={variant} />
131+
<DateSegment segment={dayPeriodSegment} state={state} variant={variant} />
118132
</div>
119133
{showNowButton && (
120134
<Button
121-
variant="tertiary/small"
135+
type="button"
136+
variant={variants[variant].nowButtonVariant}
137+
LeadingIcon={BellAlertIcon}
138+
leadingIconClassName="text-text-dimmed group-hover:text-text-bright"
122139
onClick={() => {
123140
const now = new Date();
124141
setValue(utcDateToCalendarDate(new Date()));
125142
onValueChange?.(now);
126143
}}
127144
>
128-
Now
145+
<span className="text-text-dimmed transition group-hover:text-text-bright">Now</span>
129146
</Button>
130147
)}
131148
{showClearButton && (
132149
<Button
133-
variant="tertiary/small"
150+
type="button"
151+
variant={variants[variant].clearButtonVariant}
134152
LeadingIcon={"close"}
153+
leadingIconClassName="-mr-2"
135154
onClick={() => {
136155
setValue(undefined);
137156
onValueChange?.(undefined);
@@ -142,7 +161,9 @@ export function DateField({
142161
state.clearSegment("minute");
143162
state.clearSegment("second");
144163
}}
145-
/>
164+
>
165+
Clear
166+
</Button>
146167
)}
147168
</div>
148169
{showGuide && (
@@ -172,11 +193,13 @@ function utcDateToCalendarDate(date?: Date) {
172193
type DateSegmentProps = {
173194
segment: DateSegment;
174195
state: DateFieldState;
196+
variant: Variant;
175197
};
176198

177-
function DateSegment({ segment, state }: DateSegmentProps) {
199+
function DateSegment({ segment, state, variant }: DateSegmentProps) {
178200
const ref = useRef<null | HTMLDivElement>(null);
179201
const { segmentProps } = useDateSegment(segment, state, ref);
202+
const sizeVariant = variants[variant];
180203

181204
return (
182205
<div
@@ -186,23 +209,27 @@ function DateSegment({ segment, state }: DateSegmentProps) {
186209
...segmentProps.style,
187210
minWidth: minWidthForSegment(segment),
188211
}}
189-
className={`group box-content rounded-sm px-0.5 text-right text-sm tabular-nums outline-none focus:bg-indigo-500 focus:text-white ${
212+
className={cn(
213+
"group box-content text-center tabular-nums outline-none focus:bg-charcoal-600 focus:text-text-bright",
214+
sizeVariant.fieldStyles,
190215
!segment.isEditable ? "text-charcoal-500" : "text-text-bright"
191-
}`}
216+
)}
192217
>
193218
{/* Always reserve space for the placeholder, to prevent layout shift when editing. */}
194219
<span
195220
aria-hidden="true"
196-
className="block text-center italic text-charcoal-500 group-focus:text-white"
221+
className="flex h-full items-center justify-center text-center text-charcoal-500 group-focus:text-text-bright"
197222
style={{
198223
visibility: segment.isPlaceholder ? undefined : "hidden",
199-
height: segment.isPlaceholder ? "" : 0,
224+
height: segment.isPlaceholder ? undefined : 0,
200225
pointerEvents: "none",
201226
}}
202227
>
203228
{segment.placeholder}
204229
</span>
205-
{segment.isPlaceholder ? "" : segment.text}
230+
<span className="flex h-full items-center justify-center">
231+
{segment.isPlaceholder ? "" : segment.text}
232+
</span>
206233
</div>
207234
);
208235
}
@@ -231,7 +258,7 @@ function DateSegmentGuide({ segment }: { segment: DateSegment }) {
231258
style={{
232259
minWidth: minWidthForSegment(segment),
233260
}}
234-
className={`group box-content rounded-sm px-0.5 text-right text-sm tabular-nums outline-none ${
261+
className={`group box-content rounded-sm px-0.5 text-right text-sm tabular-nums text-rose-500 outline-none ${
235262
!segment.isEditable ? "text-charcoal-500" : "text-text-bright"
236263
}`}
237264
>

0 commit comments

Comments
 (0)