Compare commits

...

28 Commits

Author SHA1 Message Date
Eric Allam 93ecbc5e05 Fix pnpm lock file 2024-04-24 11:52:12 +01:00
Eric Allam 60a8a5777b Merge pull request #1057 from triggerdotdev/changeset-release/main
chore: Update version for release (beta)
2024-04-24 11:50:13 +01:00
github-actions[bot] ed1cd16753 chore: Update version for release (beta) 2024-04-24 10:43:28 +00:00
Eric Allam 6bb4dd7b6f Fixed messaging when trying to login with an existing invalid access token 2024-04-24 11:41:38 +01:00
nicktrn 0b5a0be807 update CI deploy error 2024-04-24 11:23:20 +01:00
nicktrn 74d1e61e42 v3: fix for fresh CLI logins after CLI token revoke (#1056)
* prettier login command output

* fix for trying to login again after revoking cli token

* changeset

* improve errors when logging in with revoked or invalid token

* fix builds.. again
2024-04-24 10:44:34 +01:00
Eric Allam 12c83a56af Skip e2e github workflows for now because playwright is broken 2024-04-24 10:33:11 +01:00
nicktrn 75a54540a4 use full image refs for init containers 2024-04-24 10:13:28 +01:00
Eric Allam b68012f81c v3: Various fixes for Next.js projects and projects that use v2 and v3 together (#1051)
* Fixes an issue that was treating v2 trigger directories as v3

* Make msw a normal dependency (for now) to fix Module Not Found error in Next.js.

* Extract out all the zod* stuff from core so the SDK does not import it

* Add a changeset

* Fixing typecheck errors in the webapp

* Export the Task and TaskOptions types

* Extract additional exports from core/v3 that aren’t used in the SDK

* Move to our global system from AsyncLocalStorage for the current task context storage

* Update the esbuild core bundling plugin for the new core v3 exports

* Fix v3 CLI telemetry

* Add support for tasks located in subdirectories inside trigger dirs

* Remove the env var check during deploy (too many false negatives)
2024-04-24 10:09:36 +01:00
nicktrn fb83d58703 fix builds 2024-04-24 08:48:32 +01:00
nicktrn 52b2a8289c fix private package installs (#1055) 2024-04-24 07:58:14 +01:00
nicktrn 1e93ec4216 Revert "change action runner location" (resolved)
This reverts commit cb27b7278a.
2024-04-24 07:45:08 +01:00
nicktrn cb27b7278a change action runner location ⬆️⬆️⬇️⬇️⬅️➡️⬅️➡️🅱️🅰️ 2024-04-23 18:13:04 +01:00
Eric Allam e85fc501a6 Fix pnpm lock file 2024-04-23 17:03:53 +01:00
github-actions[bot] 0bfeb0816f chore: Update version for release (beta) (#1054)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-23 16:58:48 +01:00
nicktrn b207601732 v3: semi-automatic package update command (#1052)
* fix package manager detection

* fix cli update check while in beta

* add update command and warn on mismatches

* update fixes and better integration with dev and deploy

* fix for ci

* update docs

* changeset

* be more specific about prompts before updates

* fail on version mismatch during ci

* update github actions docs

* fix package manager copypasta

* remove ncu

* ignore companyicons

* improve warnings and errors
2024-04-23 16:08:46 +01:00
Matt Aitken 3913e57ef4 Trace view fixes and improvements (#1046)
* Query param for span using history.replaceState is working

* When clicking again on a node, don’t collapse it

* Close the span view using the same replacing of the search param

* Conditional rendering of the resize panels was causing the tree view re-rendering and collapsing…

* Live reloading moved to where the parent label is

* Span action bar is now deeper

* WIP on trace view navigation changes with shortcuts

* Shortcuts for expanding and collapsing en masse

* Number keys expand/collapse levels

* Changed duration toggle to a shortcut key

* Option + click expands/collapse at that level

* Option/alt left/right expands/collapse at that level

* Removed unused imports

* Link from the runs table to the specific span

* Latest lockfile

* Sorted imports

* When doing a test link directly to a span

* Replay links to the span

* CLI log links go directly to a span

* Keyboard shortcuts are in a popover if the width is narrow

* If holding alt only collapse level

* Don’t expand the individual node if you’re holding alt
2024-04-22 13:46:08 +01:00
James Ritchie 26f310397a Fixed the grid layout for v2 runs page 2024-04-22 13:06:56 +01:00
github-actions[bot] 0a845767a0 chore: Update version for release (beta) (#1049)
* chore: Update version for release (beta)

* update lockfile

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
2024-04-21 15:07:11 +01:00
nicktrn ed2a26c865 v3: fix additionalFiles, uncaught exceptions, and improve errors (#1048)
* fix additionalFiles for paths up the tree

* warn if additionalFiles pattern produced no matches

* improve deploy warnings and errors, fail early

* changeset
2024-04-21 15:03:00 +01:00
Eric Allam 4a68e71583 Fix pnpm lock file 2024-04-19 19:20:01 +01:00
github-actions[bot] b657eb6555 chore: Update version for release (beta) (#1044)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-04-19 19:19:11 +01:00
Eric Allam 62c9a5b712 Fix restoring after waiting for task/batch 2024-04-19 18:59:02 +01:00
Matt Aitken f339b41ef3 Added some basic Defer migration details for CRON 2024-04-19 18:30:58 +01:00
Eric Allam ae40ce3995 Fix the management file 2024-04-19 16:14:57 +01:00
Eric Allam 374edef020 Updates the trigger, batchTrigger and their *AndWait variants to use the first parameter for the payload/items, and the second parameter for options (#1045)
Also always returns a `TaskRunResult` object from `triggerAndWait` instead of rethrowing subtask errors in the parent
2024-04-19 14:51:51 +01:00
Eric Allam b82db67b81 Add additional logging around cleaning up dev workers, and always kill them after 5 seconds if they haven't already exited 2024-04-19 11:14:20 +01:00
Eric Allam 26093896d2 v3: Fixes for using (batch)triggerAndWait with idempotency keys (#1043)
* Fixes various issues with triggerAndWait and batchTriggerAndWait

When using idempotency keys, triggerAndWait and batchTriggerAndWait will still work even if the existing runs have already been completed (or even partially completed, in the case of batchTriggerAndWait)

- TaskRunExecutionResult.id is now the run friendlyId, not the attempt friendlyId
- A single TaskRun can now have many batchItems, in the case of batchTriggerAndWait while using idempotency keys
- A run’s idempotencyKey is now added to the ctx as well as the TaskEvent and displayed in the span view
- When resolving batchTriggerAndWait, the runtimes no longer reject promises, leading to an error in the parent task

* Remove the default queue concurrency limit as we now have env and org concurrency limits

* Use the run friendlyId in the completion result id

* Added some error logging
2024-04-19 10:54:43 +01:00
230 changed files with 4857 additions and 1837 deletions
+9
View File
@@ -0,0 +1,9 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
- Fix additionalFiles that aren't decendants
- Stop swallowing uncaught exceptions in prod
- Improve warnings and errors, fail early on critical warnings
- New arg to --save-logs even for successful builds
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
v3 CLI update command and package manager detection fix
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Remove the env var check during deploy (too many false negatives)
+9
View File
@@ -0,0 +1,9 @@
---
"@trigger.dev/sdk": patch
---
Make msw a normal dependency (for now) to fix Module Not Found error in Next.js.
It turns out that webpack will "hoist" dynamically imported modules and attempt to resolve them at build time, even though it's an optional peer dep:
https://x.com/maverickdotdev/status/1782465214308319404
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Fixes an issue that was treating v2 trigger directories as v3
+15 -1
View File
@@ -44,6 +44,8 @@
"@trigger.dev/yalt": "2.3.18"
},
"changesets": [
"angry-eagles-trade",
"beige-pens-dance",
"breezy-gorillas-mate",
"chilled-hornets-move",
"clean-pianos-listen",
@@ -51,7 +53,9 @@
"cuddly-feet-approve",
"dry-walls-check",
"eight-pumas-float",
"eleven-paws-join",
"few-students-share",
"funny-swans-destroy",
"green-bags-wink",
"khaki-apricots-design",
"khaki-poems-lay",
@@ -63,6 +67,7 @@
"loud-actors-remember",
"many-ligers-pump",
"mighty-camels-joke",
"nasty-jars-pump",
"new-rivers-tell",
"ninety-pets-travel",
"odd-poets-own",
@@ -70,9 +75,15 @@
"poor-flowers-cross",
"rare-roses-float",
"real-planets-stare",
"rich-kangaroos-unite",
"rotten-beers-refuse",
"rotten-dryers-exercise",
"shaggy-spoons-taste",
"sharp-emus-compare",
"sharp-zebras-serve",
"shiny-coats-cry",
"silly-suits-switch",
"slow-buses-own",
"smart-needles-move",
"smart-olives-eat",
"spicy-lamps-smoke",
@@ -85,8 +96,11 @@
"tame-guests-know",
"tender-oranges-rhyme",
"tidy-balloons-suffer",
"tidy-dryers-sleep",
"tiny-doors-type",
"tiny-elephants-scream",
"tricky-bulldogs-heal"
"tricky-bulldogs-heal",
"tricky-ladybugs-unite",
"two-pumas-wait"
]
}
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Fix a bug where revoking the CLI token would prevent you from ever logging in again with the CLI.
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Add git to prod worker image which fixes private package installs
+56
View File
@@ -0,0 +1,56 @@
---
"@trigger.dev/sdk": patch
"@trigger.dev/core": patch
---
Updates the `trigger`, `batchTrigger` and their `*AndWait` variants to use the first parameter for the payload/items, and the second parameter for options.
Before:
```ts
await yourTask.trigger({ payload: { foo: "bar" }, options: { idempotencyKey: "key_1234" } });
await yourTask.triggerAndWait({ payload: { foo: "bar" }, options: { idempotencyKey: "key_1234" } });
await yourTask.batchTrigger({ items: [{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }] });
await yourTask.batchTriggerAndWait({ items: [{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }] });
```
After:
```ts
await yourTask.trigger({ foo: "bar" }, { idempotencyKey: "key_1234" });
await yourTask.triggerAndWait({ foo: "bar" }, { idempotencyKey: "key_1234" });
await yourTask.batchTrigger([{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }]);
await yourTask.batchTriggerAndWait([{ payload: { foo: "bar" } }, { payload: { foo: "baz" } }]);
```
We've also changed the API of the `triggerAndWait` result. Before, if the subtask that was triggered finished with an error, we would automatically "rethrow" the error in the parent task.
Now instead we're returning a `TaskRunResult` object that allows you to discriminate between successful and failed runs in the subtask:
Before:
```ts
try {
const result = await yourTask.triggerAndWait({ foo: "bar" });
// result is the output of your task
console.log("result", result);
} catch (error) {
// handle subtask errors here
}
```
After:
```ts
const result = await yourTask.triggerAndWait({ foo: "bar" });
if (result.ok) {
console.log(`Run ${result.id} succeeded with output`, result.output);
} else {
console.log(`Run ${result.id} failed with error`, result.error);
}
```
+13
View File
@@ -0,0 +1,13 @@
---
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
When using idempotency keys, triggerAndWait and batchTriggerAndWait will still work even if the existing runs have already been completed (or even partially completed, in the case of batchTriggerAndWait)
- TaskRunExecutionResult.id is now the run friendlyId, not the attempt friendlyId
- A single TaskRun can now have many batchItems, in the case of batchTriggerAndWait while using idempotency keys
- A runs idempotencyKey is now added to the ctx as well as the TaskEvent and displayed in the span view
- When resolving batchTriggerAndWait, the runtimes no longer reject promises, leading to an error in the parent task
+5
View File
@@ -0,0 +1,5 @@
---
"trigger.dev": patch
---
Add additional logging around cleaning up dev workers, and always kill them after 5 seconds if they haven't already exited
+8
View File
@@ -0,0 +1,8 @@
---
"@trigger.dev/sdk": patch
"trigger.dev": patch
"@trigger.dev/core": patch
"@trigger.dev/cli": patch
---
Move to our global system from AsyncLocalStorage for the current task context storage
+10
View File
@@ -0,0 +1,10 @@
---
"trigger.dev": patch
"@trigger.dev/core": patch
---
Fixes an issue that caused failed tasks when resuming after calling `triggerAndWait` or `batchTriggerAndWait` in prod/staging (this doesn't effect dev).
The version of Node.js we use for deployed workers (latest 20) would crash with an out-of-memory error when the checkpoint was restored. This crash does not happen on Node 18x or Node21x, so we've decided to upgrade the worker version to Node.js21x, to mitigate this issue.
You'll need to re-deploy to production to fix the issue.
+8
View File
@@ -0,0 +1,8 @@
---
"@trigger.dev/sdk": patch
"@trigger.dev/core-apps": patch
"trigger.dev": patch
"@trigger.dev/core": patch
---
Extracting out all the non-SDK related features from the main @trigger.dev/core/v3 export
+6
View File
@@ -0,0 +1,6 @@
---
"trigger.dev": patch
"@trigger.dev/cli": patch
---
Add support for tasks located in subdirectories inside trigger dirs
+3 -3
View File
@@ -27,6 +27,6 @@ jobs:
uses: ./.github/workflows/unit-tests.yml
secrets: inherit
e2e:
uses: ./.github/workflows/e2e.yml
secrets: inherit
# e2e:
# uses: ./.github/workflows/e2e.yml
# secrets: inherit
+4 -4
View File
@@ -49,11 +49,11 @@ jobs:
uses: ./.github/workflows/unit-tests.yml
secrets: inherit
e2e:
uses: ./.github/workflows/e2e.yml
secrets: inherit
# e2e:
# uses: ./.github/workflows/e2e.yml
# secrets: inherit
publish:
needs: [typecheck, units, e2e]
needs: [typecheck, units]
uses: ./.github/workflows/publish-docker.yml
secrets: inherit
+2 -2
View File
@@ -21,8 +21,8 @@
"execa": "^8.0.1",
"nanoid": "^5.0.6",
"prom-client": "^15.1.0",
"socket.io": "^4.7.4",
"socket.io-client": "^4.7.4"
"socket.io": "4.7.4",
"socket.io-client": "4.7.4"
},
"devDependencies": {
"@types/node": "^18",
+2 -2
View File
@@ -8,9 +8,9 @@ import {
PlatformToCoordinatorMessages,
ProdWorkerSocketData,
ProdWorkerToCoordinatorMessages,
ZodNamespace,
ZodSocketConnection,
} from "@trigger.dev/core/v3";
import { ZodNamespace } from "@trigger.dev/core/v3/zodNamespace";
import { ZodSocketConnection } from "@trigger.dev/core/v3/zodSocket";
import { HttpReply, getTextBody, SimpleLogger } from "@trigger.dev/core-apps";
import { collectDefaultMetrics, register, Gauge } from "prom-client";
+1 -1
View File
@@ -211,7 +211,7 @@ class KubernetesTaskOperations implements TaskOperations {
},
{
name: "populate-taskinfo",
image: "busybox",
image: "docker.io/library/busybox",
command: ["/bin/sh", "-c"],
args: ["printenv COORDINATOR_HOST | tee /etc/taskinfo/coordinator-host"],
env: [
+2 -2
View File
@@ -408,7 +408,7 @@ export class TaskMonitor {
const createNonZeroExitPod = async (name: string, labels?: Record<string, string>) => {
const container = {
name,
image: "busybox",
image: "docker.io/library/busybox",
command: ["sh"],
args: ["-c", "exit 1"],
} satisfies k8s.V1Container;
@@ -419,7 +419,7 @@ export class TaskMonitor {
const createOoDiskPod = async (name: string, labels?: Record<string, string>) => {
const container = {
name,
image: "busybox",
image: "docker.io/library/busybox",
command: ["sh"],
args: [
"-c",
@@ -2,8 +2,14 @@ import { Fragment } from "react";
import { Modifier, ShortcutDefinition } from "~/hooks/useShortcutKeys";
import { cn } from "~/utils/cn";
import { useOperatingSystem } from "./OperatingSystemProvider";
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronUpIcon,
} from "@heroicons/react/20/solid";
const variants = {
export const variants = {
small:
"text-[0.6rem] font-medium min-w-[17px] rounded-[2px] px-1 ml-1 -mr-0.5 grid place-content-center border border-dimmed/40 text-text-dimmed group-hover:text-text-bright/80 group-hover:border-dimmed/60 transition uppercase",
medium:
@@ -23,7 +29,7 @@ export function ShortcutKey({ shortcut, variant, className }: ShortcutKeyProps)
const isMac = platform === "mac";
let relevantShortcut = "mac" in shortcut ? (isMac ? shortcut.mac : shortcut.windows) : shortcut;
const modifiers = relevantShortcut.modifiers ?? [];
const character = keyString(relevantShortcut.key, isMac);
const character = keyString(relevantShortcut.key, isMac, variant);
return (
<span className={cn(variants[variant], className)}>
@@ -35,10 +41,22 @@ export function ShortcutKey({ shortcut, variant, className }: ShortcutKeyProps)
);
}
function keyString(key: String, isMac: boolean) {
function keyString(key: String, isMac: boolean, size: "small" | "medium") {
key = key.toLowerCase();
const className = size === "small" ? "w-2.5 h-4" : "w-3 h-5";
switch (key) {
case "enter":
return isMac ? "↵" : key;
case "arrowdown":
return <ChevronDownIcon className={className} />;
case "arrowup":
return <ChevronUpIcon className={className} />;
case "arrowleft":
return <ChevronLeftIcon className={className} />;
case "arrowright":
return <ChevronRightIcon className={className} />;
default:
return key;
}
@@ -1,10 +1,10 @@
import { VirtualItem, Virtualizer, useVirtualizer } from "@tanstack/react-virtual";
import { motion } from "framer-motion";
import { MutableRefObject, RefObject, useCallback, useEffect, useReducer, useRef } from "react";
import { UnmountClosed } from "react-collapse";
import { cn } from "~/utils/cn";
import { NodeState, NodesState, reducer } from "./reducer";
import { applyFilterToState, concreteStateFromInput, selectedIdFromState } from "./utils";
import { motion } from "framer-motion";
export type TreeViewProps<TData> = {
tree: FlatTree<TData>;
@@ -165,6 +165,11 @@ export type UseTreeStateOutput = {
expandNode: (id: string, scrollToNode?: boolean) => void;
collapseNode: (id: string) => void;
toggleExpandNode: (id: string, scrollToNode?: boolean) => void;
expandAllBelowDepth: (depth: number) => void;
collapseAllBelowDepth: (depth: number) => void;
expandLevel: (level: number) => void;
collapseLevel: (level: number) => void;
toggleExpandLevel: (level: number) => void;
selectFirstVisibleNode: (scrollToNode?: boolean) => void;
selectLastVisibleNode: (scrollToNode?: boolean) => void;
selectNextVisibleNode: (scrollToNode?: boolean) => void;
@@ -333,6 +338,41 @@ export function useTree<TData>({
[state]
);
const expandAllBelowDepth = useCallback(
(depth: number) => {
dispatch({ type: "EXPAND_ALL_BELOW_DEPTH", payload: { tree, depth } });
},
[state]
);
const collapseAllBelowDepth = useCallback(
(depth: number) => {
dispatch({ type: "COLLAPSE_ALL_BELOW_DEPTH", payload: { tree, depth } });
},
[state]
);
const expandLevel = useCallback(
(level: number) => {
dispatch({ type: "EXPAND_LEVEL", payload: { tree, level } });
},
[state]
);
const collapseLevel = useCallback(
(level: number) => {
dispatch({ type: "COLLAPSE_LEVEL", payload: { tree, level } });
},
[state]
);
const toggleExpandLevel = useCallback(
(level: number) => {
dispatch({ type: "TOGGLE_EXPAND_LEVEL", payload: { tree, level } });
},
[state]
);
const getTreeProps = useCallback(() => {
return {
role: "tree",
@@ -368,25 +408,48 @@ export function useTree<TData>({
}
case "Left":
case "ArrowLeft": {
e.preventDefault();
const selected = selectedIdFromState(state.nodes);
if (selected) {
const treeNode = tree.find((node) => node.id === selected);
if (treeNode && treeNode.hasChildren && state.nodes[selected].expanded) {
if (e.altKey) {
if (treeNode && treeNode.hasChildren) {
collapseLevel(treeNode.level);
}
break;
}
const shouldCollapse =
treeNode && treeNode.hasChildren && state.nodes[selected].expanded;
if (shouldCollapse) {
collapseNode(selected);
} else {
selectParentNode(true);
}
}
e.preventDefault();
break;
}
case "Right":
case "ArrowRight": {
e.preventDefault();
const selected = selectedIdFromState(state.nodes);
if (selected) {
const treeNode = tree.find((node) => node.id === selected);
if (e.altKey) {
if (treeNode && treeNode.hasChildren) {
expandLevel(treeNode.level);
}
break;
}
expandNode(selected, true);
}
e.preventDefault();
break;
}
case "Escape": {
@@ -427,6 +490,11 @@ export function useTree<TData>({
expandNode,
collapseNode,
toggleExpandNode,
expandAllBelowDepth,
collapseAllBelowDepth,
expandLevel,
collapseLevel,
toggleExpandLevel,
selectFirstVisibleNode,
selectLastVisibleNode,
selectNextVisibleNode,
@@ -91,6 +91,46 @@ type ToggleExpandNodeAction = {
} & WithScrollToNode;
};
type ExpandAllBelowDepthAction = {
type: "EXPAND_ALL_BELOW_DEPTH";
payload: {
depth: number;
tree: FlatTree<any>;
};
};
type CollapseAllBelowDepthAction = {
type: "COLLAPSE_ALL_BELOW_DEPTH";
payload: {
depth: number;
tree: FlatTree<any>;
};
};
type ExpandLevelAction = {
type: "EXPAND_LEVEL";
payload: {
level: number;
tree: FlatTree<any>;
};
};
type CollapseLevelAction = {
type: "COLLAPSE_LEVEL";
payload: {
level: number;
tree: FlatTree<any>;
};
};
type ToggleExpandLevelAction = {
type: "TOGGLE_EXPAND_LEVEL";
payload: {
level: number;
tree: FlatTree<any>;
};
};
type SelectFirstVisibleNodeAction = {
type: "SELECT_FIRST_VISIBLE_NODE";
payload: {
@@ -135,6 +175,11 @@ export type Action =
| ExpandNodeAction
| CollapseNodeAction
| ToggleExpandNodeAction
| ExpandAllBelowDepthAction
| CollapseAllBelowDepthAction
| ExpandLevelAction
| CollapseLevelAction
| ToggleExpandLevelAction
| SelectFirstVisibleNodeAction
| SelectLastVisibleNodeAction
| SelectNextVisibleNodeAction
@@ -229,6 +274,109 @@ export function reducer(state: TreeState, action: Action): TreeState {
});
}
}
case "EXPAND_ALL_BELOW_DEPTH": {
const nodesToExpand = action.payload.tree.filter(
(n) => n.level >= action.payload.depth && n.hasChildren
);
const newNodes = Object.fromEntries(
Object.entries(state.nodes).map(([key, value]) => [
key,
{
...value,
expanded: nodesToExpand.find((n) => n.id === key) ? true : value.expanded,
},
])
);
const visibleNodes = applyVisibility(action.payload.tree, newNodes);
return { nodes: visibleNodes, changes: generateChanges(state.nodes, visibleNodes) };
}
case "COLLAPSE_ALL_BELOW_DEPTH": {
const nodesToCollapse = action.payload.tree.filter(
(n) => n.level >= action.payload.depth && n.hasChildren
);
const newNodes = Object.fromEntries(
Object.entries(state.nodes).map(([key, value]) => [
key,
{
...value,
expanded: nodesToCollapse.find((n) => n.id === key) ? false : value.expanded,
},
])
);
const visibleNodes = applyVisibility(action.payload.tree, newNodes);
return { nodes: visibleNodes, changes: generateChanges(state.nodes, visibleNodes) };
}
case "EXPAND_LEVEL": {
const nodesToExpand = action.payload.tree.filter(
(n) => n.level <= action.payload.level && n.hasChildren
);
const newNodes = Object.fromEntries(
Object.entries(state.nodes).map(([key, value]) => [
key,
{
...value,
expanded: nodesToExpand.find((n) => n.id === key) ? true : value.expanded,
},
])
);
const visibleNodes = applyVisibility(action.payload.tree, newNodes);
return { nodes: visibleNodes, changes: generateChanges(state.nodes, visibleNodes) };
}
case "COLLAPSE_LEVEL": {
const nodesToCollapse = action.payload.tree.filter(
(n) => n.level === action.payload.level && n.hasChildren
);
const newNodes = Object.fromEntries(
Object.entries(state.nodes).map(([key, value]) => [
key,
{
...value,
expanded: nodesToCollapse.find((n) => n.id === key) ? false : value.expanded,
},
])
);
const visibleNodes = applyVisibility(action.payload.tree, newNodes);
return { nodes: visibleNodes, changes: generateChanges(state.nodes, visibleNodes) };
}
case "TOGGLE_EXPAND_LEVEL": {
//first get the first item at that level in the tree. If it is expanded, collapse all nodes at that level
//if it is collapsed, expand all nodes at that level
const nodesAtLevel = action.payload.tree.filter(
(n) => n.level === action.payload.level && n.hasChildren
);
const firstNode = nodesAtLevel[0];
if (!firstNode) {
return state;
}
const currentlyExpanded = state.nodes[firstNode.id]?.expanded ?? true;
const currentVisible = state.nodes[firstNode.id]?.visible ?? true;
if (currentlyExpanded && currentVisible) {
return reducer(state, {
type: "COLLAPSE_LEVEL",
payload: {
level: action.payload.level,
tree: action.payload.tree,
},
});
} else {
return reducer(state, {
type: "EXPAND_LEVEL",
payload: {
level: action.payload.level,
tree: action.payload.tree,
},
});
}
}
case "SELECT_FIRST_VISIBLE_NODE": {
const node = firstVisibleNode(action.payload.tree, state.nodes);
if (node) {
@@ -32,6 +32,7 @@ import {
import { TimeFrameFilter } from "./TimeFrameFilter";
import { Button } from "../primitives/Buttons";
import { useCallback } from "react";
import assertNever from "assert-never";
export function RunsFilters() {
const navigate = useNavigate();
@@ -182,8 +183,7 @@ export function FilterStatusIcon({
case "FAILED":
return <XCircleIcon className={cn(filterStatusClassNameColor(status), className)} />;
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -205,8 +205,7 @@ export function filterStatusTitle(status: FilterableStatus): string {
case "TIMEDOUT":
return "Timed out";
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -228,8 +227,7 @@ export function filterStatusClassNameColor(status: FilterableStatus): string {
case "TIMEDOUT":
return "text-amber-300";
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -11,6 +11,7 @@ import type { JobRunStatus } from "@trigger.dev/database";
import { cn } from "~/utils/cn";
import { Spinner } from "../primitives/Spinner";
import { z } from "zod";
import assertNever from "assert-never";
export function RunStatus({ status }: { status: JobRunStatus }) {
return (
@@ -51,8 +52,7 @@ export function RunStatusIcon({ status, className }: { status: JobRunStatus; cla
case "CANCELED":
return <NoSymbolIcon className={cn(runStatusClassNameColor(status), className)} />;
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -89,8 +89,7 @@ export function runStatusTitle(status: JobRunStatus): string {
case "INVALID_PAYLOAD":
return "Invalid payload";
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -123,8 +122,7 @@ export function runStatusClassNameColor(status: JobRunStatus): string {
case "CANCELED":
return "text-charcoal-500";
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -5,6 +5,7 @@ import {
XCircleIcon,
} from "@heroicons/react/20/solid";
import { WorkerDeploymentStatus } from "@trigger.dev/database";
import assertNever from "assert-never";
import { Spinner } from "~/components/primitives/Spinner";
import { cn } from "~/utils/cn";
@@ -54,8 +55,7 @@ export function DeploymentStatusIcon({
/>
);
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -74,8 +74,7 @@ export function deploymentStatusClassNameColor(status: WorkerDeploymentStatus):
case "FAILED":
return "text-error";
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -97,8 +96,7 @@ export function deploymentStatusTitle(status: WorkerDeploymentStatus): string {
case "FAILED":
return "Failed";
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -8,6 +8,7 @@ import {
} from "@heroicons/react/20/solid";
import type { TaskRunAttemptStatus as TaskRunAttemptStatusType } from "@trigger.dev/database";
import { TaskRunAttemptStatus } from "@trigger.dev/database";
import assertNever from "assert-never";
import { SnowflakeIcon } from "lucide-react";
import { Spinner } from "~/components/primitives/Spinner";
import { cn } from "~/utils/cn";
@@ -72,8 +73,7 @@ export function TaskRunAttemptStatusIcon({
case "COMPLETED":
return <CheckCircleIcon className={cn(runAttemptStatusClassNameColor(status), className)} />;
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -99,8 +99,7 @@ export function runAttemptStatusClassNameColor(status: ExtendedTaskAttemptStatus
case "COMPLETED":
return "text-success";
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -126,8 +125,7 @@ export function runAttemptStatusTitle(status: ExtendedTaskAttemptStatus | null):
case "COMPLETED":
return "Completed";
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -10,6 +10,7 @@ import {
XCircleIcon,
} from "@heroicons/react/20/solid";
import { TaskRunStatus } from "@trigger.dev/database";
import assertNever from "assert-never";
import { SnowflakeIcon } from "lucide-react";
import { Spinner } from "~/components/primitives/Spinner";
import { cn } from "~/utils/cn";
@@ -88,8 +89,7 @@ export function TaskRunStatusIcon({
return <FireIcon className={cn(runStatusClassNameColor(status), className)} />;
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -120,8 +120,7 @@ export function runStatusClassNameColor(status: TaskRunStatus): string {
case "CRASHED":
return "text-error";
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -153,8 +152,7 @@ export function runStatusTitle(status: TaskRunStatus): string {
case "CRASHED":
return "Crashed";
default: {
const _exhaustiveCheck: never = status;
throw new Error(`Non-exhaustive match for value: ${status}`);
assertNever(status);
}
}
}
@@ -1,10 +1,16 @@
import { ArrowPathIcon, StopCircleIcon } from "@heroicons/react/20/solid";
import { StopIcon } from "@heroicons/react/24/outline";
import { BeakerIcon, BookOpenIcon, CheckIcon } from "@heroicons/react/24/solid";
import { useLocation } from "@remix-run/react";
import { formatDuration } from "@trigger.dev/core/v3";
import { User } from "@trigger.dev/database";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
import { useEnvironments } from "~/hooks/useEnvironments";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { RunListAppliedFilters, RunListItem } from "~/presenters/v3/RunListPresenter.server";
import { docsPath, v3RunPath, v3TestPath } from "~/utils/pathBuilder";
import { docsPath, v3RunSpanPath, v3TestPath } from "~/utils/pathBuilder";
import { EnvironmentLabel } from "../../environments/EnvironmentLabel";
import { DateTime } from "../../primitives/DateTime";
import { Paragraph } from "../../primitives/Paragraph";
@@ -14,21 +20,14 @@ import {
TableBlankRow,
TableBody,
TableCell,
TableCellChevron,
TableCellMenu,
TableHeader,
TableHeaderCell,
TableRow,
} from "../../primitives/Table";
import { formatDuration } from "@trigger.dev/core/v3";
import { TaskRunStatusCombo } from "./TaskRunStatus";
import { useEnvironments } from "~/hooks/useEnvironments";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { ArrowPathIcon, StopCircleIcon } from "@heroicons/react/20/solid";
import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
import { CancelRunDialog } from "./CancelRunDialog";
import { useLocation } from "@remix-run/react";
import { ReplayRunDialog } from "./ReplayRunDialog";
import { TaskRunStatusCombo } from "./TaskRunStatus";
type RunsTableProps = {
total: number;
@@ -78,7 +77,7 @@ export function TaskRunsTable({
<BlankState isLoading={isLoading} filters={filters} />
) : (
runs.map((run) => {
const path = v3RunPath(organization, project, run);
const path = v3RunSpanPath(organization, project, run, { spanId: run.spanId });
const usernameForEnv =
currentUser.id !== run.environment.userId ? run.environment.userName : undefined;
return (
-1
View File
@@ -76,7 +76,6 @@ const EnvironmentSchema = z.object({
REDIS_PASSWORD: z.string().optional(),
REDIS_TLS_DISABLED: z.string().optional(),
DEFAULT_QUEUE_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(5),
DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(10),
DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT: z.coerce.number().int().default(10),
DEFAULT_DEV_ENV_EXECUTION_ATTEMPTS: z.coerce.number().int().positive().default(1),
@@ -0,0 +1,32 @@
import { useCallback, useState } from "react";
import { useOptimisticLocation } from "./useOptimisticLocation";
import type { Location } from "@remix-run/react";
export function useReplaceLocation() {
const optimisticLocation = useOptimisticLocation();
const [location, setLocation] = useState(optimisticLocation);
const replaceLocation = useCallback((location: Location<any>) => {
const fullPath = location.pathname + location.search + location.hash;
//replace the URL in the browser
history.replaceState(null, "", fullPath);
//update the state (new object in case the same location ref was modified)
const newLocation = { ...location };
setLocation(newLocation);
}, []);
const replaceSearchParam = useCallback(
(key: string, value?: string) => {
const searchParams = new URLSearchParams(location.search);
if (value) {
searchParams.set(key, value);
} else {
searchParams.delete(key);
}
replaceLocation({ ...optimisticLocation, search: "?" + searchParams.toString() });
},
[optimisticLocation, replaceLocation]
);
return { location, replaceLocation, replaceSearchParam };
}
+128
View File
@@ -0,0 +1,128 @@
import {
TaskRunError,
TaskRunExecutionResult,
TaskRunFailedExecutionResult,
TaskRunSuccessfulExecutionResult,
} from "@trigger.dev/core/v3";
import {
BatchTaskRunItemStatus,
TaskRun,
TaskRunAttempt,
TaskRunAttemptStatus,
TaskRunStatus,
} from "@trigger.dev/database";
import { assertNever } from "assert-never";
import { logger } from "~/services/logger.server";
const SUCCESSFUL_STATUSES = [TaskRunStatus.COMPLETED_SUCCESSFULLY];
const FAILURE_STATUSES = [
TaskRunStatus.CANCELED,
TaskRunStatus.INTERRUPTED,
TaskRunStatus.COMPLETED_WITH_ERRORS,
TaskRunStatus.SYSTEM_FAILURE,
TaskRunStatus.CRASHED,
];
export type TaskRunWithAttempts = TaskRun & {
attempts: TaskRunAttempt[];
};
export function executionResultForTaskRun(
taskRun: TaskRunWithAttempts
): TaskRunExecutionResult | undefined {
if (SUCCESSFUL_STATUSES.includes(taskRun.status)) {
// find the last attempt that was successful
const attempt = taskRun.attempts.find((a) => a.status === TaskRunAttemptStatus.COMPLETED);
if (!attempt) {
logger.error("Task run is successful but no successful attempt found", {
taskRunId: taskRun.id,
taskRunStatus: taskRun.status,
taskRunAttempts: taskRun.attempts.map((a) => a.status),
});
return undefined;
}
return {
ok: true,
id: taskRun.friendlyId,
output: attempt.output ?? undefined,
outputType: attempt.outputType,
} satisfies TaskRunSuccessfulExecutionResult;
}
if (FAILURE_STATUSES.includes(taskRun.status)) {
if (taskRun.status === TaskRunStatus.CANCELED) {
return {
ok: false,
id: taskRun.friendlyId,
error: {
type: "INTERNAL_ERROR",
code: "TASK_RUN_CANCELLED",
},
} satisfies TaskRunFailedExecutionResult;
}
const attempt = taskRun.attempts.find((a) => a.status === TaskRunAttemptStatus.FAILED);
if (!attempt) {
logger.error("Task run is failed but no failed attempt found", {
taskRunId: taskRun.id,
taskRunStatus: taskRun.status,
taskRunAttempts: taskRun.attempts.map((a) => a.status),
});
return undefined;
}
const error = TaskRunError.safeParse(attempt.error);
if (!error.success) {
logger.error("Failed to parse error from failed task run attempt", {
taskRunId: taskRun.id,
taskRunStatus: taskRun.status,
taskRunAttempts: taskRun.attempts.map((a) => a.status),
error: attempt.error,
});
return {
ok: false,
id: taskRun.friendlyId,
error: {
type: "INTERNAL_ERROR",
code: "CONFIGURED_INCORRECTLY",
},
} satisfies TaskRunFailedExecutionResult;
}
return {
ok: false,
id: taskRun.friendlyId,
error: error.data,
} satisfies TaskRunFailedExecutionResult;
}
}
export function batchTaskRunItemStatusForRunStatus(status: TaskRunStatus): BatchTaskRunItemStatus {
switch (status) {
case TaskRunStatus.COMPLETED_SUCCESSFULLY:
return BatchTaskRunItemStatus.COMPLETED;
case TaskRunStatus.CANCELED:
case TaskRunStatus.INTERRUPTED:
case TaskRunStatus.COMPLETED_WITH_ERRORS:
case TaskRunStatus.SYSTEM_FAILURE:
case TaskRunStatus.CRASHED:
case TaskRunStatus.COMPLETED_WITH_ERRORS:
return BatchTaskRunItemStatus.FAILED;
case TaskRunStatus.PENDING:
case TaskRunStatus.WAITING_FOR_DEPLOY:
case TaskRunStatus.WAITING_TO_RESUME:
case TaskRunStatus.RETRYING_AFTER_FAILURE:
case TaskRunStatus.EXECUTING:
case TaskRunStatus.PAUSED:
return BatchTaskRunItemStatus.PENDING;
default:
assertNever(status);
}
}
@@ -0,0 +1,46 @@
import { BatchTaskRunExecutionResult } from "@trigger.dev/core/v3";
import { executionResultForTaskRun } from "~/models/taskRun.server";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { BasePresenter } from "./basePresenter.server";
export class ApiBatchResultsPresenter extends BasePresenter {
public async call(
friendlyId: string,
env: AuthenticatedEnvironment
): Promise<BatchTaskRunExecutionResult | undefined> {
return this.traceWithEnv("call", env, async (span) => {
const batchRun = await this._prisma.batchTaskRun.findUnique({
where: {
friendlyId,
runtimeEnvironmentId: env.id,
},
include: {
items: {
include: {
taskRun: {
include: {
attempts: {
orderBy: {
createdAt: "desc",
},
},
},
},
},
},
},
});
if (!batchRun) {
return undefined;
}
return {
id: batchRun.friendlyId,
items: batchRun.items
.map((item) => executionResultForTaskRun(item.taskRun))
.filter(Boolean),
};
});
}
}
@@ -0,0 +1,33 @@
import { TaskRunExecutionResult } from "@trigger.dev/core/v3";
import { executionResultForTaskRun } from "~/models/taskRun.server";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { BasePresenter } from "./basePresenter.server";
export class ApiRunResultPresenter extends BasePresenter {
public async call(
friendlyId: string,
env: AuthenticatedEnvironment
): Promise<TaskRunExecutionResult | undefined> {
return this.traceWithEnv("call", env, async (span) => {
const taskRun = await this._prisma.taskRun.findUnique({
where: {
friendlyId,
runtimeEnvironmentId: env.id,
},
include: {
attempts: {
orderBy: {
createdAt: "desc",
},
},
},
});
if (!taskRun) {
return undefined;
}
return executionResultForTaskRun(taskRun);
});
}
}
@@ -106,6 +106,7 @@ export class RunListPresenter {
lockedAt: Date | null;
completedAt: Date | null;
isTest: boolean;
spanId: string;
attempts: BigInt;
}[]
>`
@@ -121,6 +122,7 @@ export class RunListPresenter {
tr."lockedAt" AS "lockedAt",
tra."completedAt" AS "completedAt",
tr."isTest" AS "isTest",
tr."spanId" AS "spanId",
COUNT(tra.id) AS attempts
FROM
${sqlDatabaseSchema}."TaskRun" tr
@@ -225,6 +227,7 @@ export class RunListPresenter {
status: run.status,
version: run.version,
taskIdentifier: run.taskIdentifier,
spanId: run.spanId,
attempts: Number(run.attempts),
isReplayable: true,
isCancellable: CANCELLABLE_STATUSES.includes(run.status),
@@ -0,0 +1,34 @@
import { Span, SpanKind } from "@opentelemetry/api";
import { PrismaClientOrTransaction, prisma } from "~/db.server";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { attributesFromAuthenticatedEnv, tracer } from "../../v3/tracer.server";
export abstract class BasePresenter {
constructor(protected readonly _prisma: PrismaClientOrTransaction = prisma) {}
protected async traceWithEnv<T>(
trace: string,
env: AuthenticatedEnvironment,
fn: (span: Span) => Promise<T>
): Promise<T> {
return tracer.startActiveSpan(
`${this.constructor.name}.${trace}`,
{ attributes: attributesFromAuthenticatedEnv(env), kind: SpanKind.SERVER },
async (span) => {
try {
return await fn(span);
} catch (e) {
if (e instanceof Error) {
span.recordException(e);
} else {
span.recordException(new Error(String(e)));
}
throw e;
} finally {
span.end();
}
}
);
}
}
@@ -60,7 +60,7 @@ export default function Page() {
const user = useUser();
return (
<>
<div className="overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
{job.hasIntegrationsRequiringAction && (
<Callout variant="error" to={organizationIntegrationsPath(organization)} className="mb-2">
{simplur`This Job has ${
@@ -96,6 +96,6 @@ export default function Page() {
</div>
)}
</Help>
</>
</div>
);
}
@@ -1,10 +1,13 @@
import {
ArrowsPointingInIcon,
ArrowsPointingOutIcon,
ChevronDownIcon,
ChevronRightIcon,
MagnifyingGlassMinusIcon,
MagnifyingGlassPlusIcon,
} from "@heroicons/react/20/solid";
import { Outlet, useNavigate, useParams, useRevalidator } from "@remix-run/react";
import type { Location } from "@remix-run/react";
import { useParams, useRevalidator } from "@remix-run/react";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { Virtualizer } from "@tanstack/react-virtual";
import {
@@ -23,7 +26,7 @@ import { InlineCode } from "~/components/code/InlineCode";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { MainCenteredContainer, PageBody } from "~/components/layout/AppLayout";
import { Badge } from "~/components/primitives/Badge";
import { LinkButton } from "~/components/primitives/Buttons";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { Input } from "~/components/primitives/Input";
import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/PageHeader";
@@ -33,6 +36,7 @@ import {
ResizablePanel,
ResizablePanelGroup,
} from "~/components/primitives/Resizable";
import { ShortcutKey, variants } from "~/components/primitives/ShortcutKey";
import { Slider } from "~/components/primitives/Slider";
import { Switch } from "~/components/primitives/Switch";
import * as Timeline from "~/components/primitives/Timeline";
@@ -45,8 +49,9 @@ import { useDebounce } from "~/hooks/useDebounce";
import { useEventSource } from "~/hooks/useEventSource";
import { useInitialDimensions } from "~/hooks/useInitialDimensions";
import { useOrganization } from "~/hooks/useOrganizations";
import { usePathName } from "~/hooks/usePathName";
import { useProject } from "~/hooks/useProject";
import { useReplaceLocation } from "~/hooks/useReplaceLocation";
import { Shortcut, useShortcutKeys } from "~/hooks/useShortcutKeys";
import { useUser } from "~/hooks/useUser";
import { RunEvent, RunPresenter } from "~/presenters/v3/RunPresenter.server";
import { getResizableRunSettings, setResizableRunSettings } from "~/services/resizablePanel";
@@ -60,6 +65,11 @@ import {
v3RunStreamingPath,
v3RunsPath,
} from "~/utils/pathBuilder";
import { SpanView } from "../resources.orgs.$organizationSlug.projects.v3.$projectParam.runs.$runParam.spans.$spanParam/route";
import { number } from "zod";
import { useHotkeys } from "react-hotkeys-hook";
import { Popover, PopoverArrowTrigger, PopoverContent } from "~/components/primitives/Popover";
import { Header3 } from "~/components/primitives/Headers";
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
const userId = await requireUserId(request);
@@ -82,19 +92,18 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
});
};
function getSpanId(path: string): string | undefined {
const regex = /spans\/([^\/]*)/;
const match = path.match(regex);
return match ? match[1] : undefined;
function getSpanId(location: Location<any>): string | undefined {
const search = new URLSearchParams(location.search);
return search.get("span") ?? undefined;
}
export default function Page() {
const { run, trace, resizeSettings } = useTypedLoaderData<typeof loader>();
const navigate = useNavigate();
const organization = useOrganization();
const pathName = usePathName();
const project = useProject();
const user = useUser();
const { location, replaceSearchParam } = useReplaceLocation();
const selectedSpanId = getSpanId(location);
const usernameForEnv = user.id !== run.environment.userId ? run.environment.userName : undefined;
@@ -133,10 +142,8 @@ export default function Page() {
const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt } = trace;
const selectedSpanId = getSpanId(pathName);
const changeToSpan = useDebounce((selectedSpan: string) => {
navigate(v3RunSpanPath(organization, project, run, { spanId: selectedSpan }));
replaceSearchParam("span", selectedSpan);
}, 250);
const revalidator = useRevalidator();
@@ -166,62 +173,47 @@ export default function Page() {
</NavBar>
<PageBody scrollable={false}>
<div className={cn("grid h-full max-h-full grid-cols-1 overflow-hidden")}>
{selectedSpanId === undefined ? (
<TasksTreeView
selectedId={selectedSpanId}
key={events[0]?.id ?? "-"}
events={events}
parentRunFriendlyId={parentRunFriendlyId}
onSelectedIdChanged={(selectedSpan) => {
//instantly close the panel if no span is selected
if (!selectedSpan) {
navigate(v3RunPath(organization, project, run));
return;
}
<ResizablePanelGroup
direction="horizontal"
className="h-full max-h-full"
onLayout={(layout) => {
if (layout.length !== 2) return;
if (!selectedSpanId) return;
setResizableRunSettings(document, layout);
}}
>
<ResizablePanel order={1} minSize={30} defaultSize={resizeSettings.layout?.[0]}>
<TasksTreeView
selectedId={selectedSpanId}
key={events[0]?.id ?? "-"}
events={events}
parentRunFriendlyId={parentRunFriendlyId}
onSelectedIdChanged={(selectedSpan) => {
//instantly close the panel if no span is selected
if (!selectedSpan) {
replaceSearchParam("span");
return;
}
changeToSpan(selectedSpan);
}}
totalDuration={duration}
rootSpanStatus={rootSpanStatus}
rootStartedAt={rootStartedAt}
environmentType={run.environment.type}
/>
) : (
<ResizablePanelGroup
direction="horizontal"
className="h-full max-h-full"
onLayout={(layout) => {
if (layout.length !== 2) return;
setResizableRunSettings(document, layout);
}}
>
<ResizablePanel order={1} minSize={30} defaultSize={resizeSettings.layout?.[0]}>
<TasksTreeView
selectedId={selectedSpanId}
key={events[0]?.id ?? "-"}
events={events}
parentRunFriendlyId={parentRunFriendlyId}
onSelectedIdChanged={(selectedSpan) => {
//instantly close the panel if no span is selected
if (!selectedSpan) {
navigate(v3RunPath(organization, project, run));
return;
}
changeToSpan(selectedSpan);
}}
totalDuration={duration}
rootSpanStatus={rootSpanStatus}
rootStartedAt={rootStartedAt}
environmentType={run.environment.type}
changeToSpan(selectedSpan);
}}
totalDuration={duration}
rootSpanStatus={rootSpanStatus}
rootStartedAt={rootStartedAt}
environmentType={run.environment.type}
/>
</ResizablePanel>
<ResizableHandle withHandle />
{selectedSpanId && (
<ResizablePanel order={2} minSize={30} defaultSize={resizeSettings.layout?.[1]}>
<SpanView
runParam={run.friendlyId}
spanId={selectedSpanId}
closePanel={() => replaceSearchParam("span")}
/>
</ResizablePanel>
<ResizableHandle withHandle />
<ResizablePanel order={2} minSize={30} defaultSize={resizeSettings.layout?.[1]}>
<Outlet key={selectedSpanId} />
</ResizablePanel>
</ResizablePanelGroup>
)}
)}
</ResizablePanelGroup>
</div>
</PageBody>
</>
@@ -263,6 +255,9 @@ function TasksTreeView({
getNodeProps,
toggleNodeSelection,
toggleExpandNode,
expandAllBelowDepth,
toggleExpandLevel,
collapseAllBelowDepth,
selectNode,
scrollToNode,
virtualizer,
@@ -286,7 +281,7 @@ function TasksTreeView({
});
return (
<div className="grid h-full grid-rows-[2.5rem_1fr] overflow-hidden">
<div className="grid h-full grid-rows-[2.5rem_1fr_3.25rem] overflow-hidden">
<div className="mx-3 flex items-center justify-between gap-2 border-b border-grid-dimmed">
<Input
placeholder="Search log"
@@ -297,30 +292,12 @@ function TasksTreeView({
onChange={(e) => setFilterText(e.target.value)}
/>
<div className="flex items-center gap-2">
<LiveReloadingStatus rootSpanCompleted={rootSpanStatus !== "executing"} />
<Switch
variant="small"
label="Errors only"
checked={errorsOnly}
onCheckedChange={(e) => setErrorsOnly(e.valueOf())}
/>
<Switch
variant="small"
label="Show durations"
checked={showDurations}
onCheckedChange={(e) => setShowDurations(e.valueOf())}
/>
<Slider
variant={"tertiary"}
className="w-20"
LeadingIcon={MagnifyingGlassMinusIcon}
TrailingIcon={MagnifyingGlassPlusIcon}
value={[scale]}
onValueChange={(value) => setScale(value[0])}
min={0}
max={1}
step={0.05}
/>
</div>
</div>
<ResizablePanelGroup
@@ -333,14 +310,15 @@ function TasksTreeView({
{/* Tree list */}
<ResizablePanel order={1} minSize={20} defaultSize={50} className="pl-3">
<div className="grid h-full grid-rows-[2rem_1fr] overflow-hidden">
<div className="flex items-center">
<div className="flex items-center pr-2">
{parentRunFriendlyId ? (
<ShowParentLink runFriendlyId={parentRunFriendlyId} />
) : (
<Paragraph variant="small" className="text-charcoal-500">
<Paragraph variant="small" className="flex-1 text-charcoal-500">
This is the root task
</Paragraph>
)}
<LiveReloadingStatus rootSpanCompleted={rootSpanStatus !== "executing"} />
</div>
<TreeView
parentRef={parentRef}
@@ -355,13 +333,13 @@ function TasksTreeView({
<>
<div
className={cn(
"delay-[25ms] flex h-8 cursor-pointer items-center overflow-hidden rounded-l-sm pr-2 transition-colors",
"flex h-8 cursor-pointer items-center overflow-hidden rounded-l-sm pr-2",
state.selected
? "bg-grid-dimmed hover:bg-grid-bright"
: "bg-transparent hover:bg-grid-dimmed"
)}
onClick={() => {
toggleNodeSelection(node.id);
selectNode(node.id);
}}
>
<div className="flex h-8 items-center">
@@ -379,7 +357,15 @@ function TasksTreeView({
)}
onClick={(e) => {
e.stopPropagation();
toggleExpandNode(node.id);
if (e.altKey) {
if (state.expanded) {
collapseAllBelowDepth(node.level);
} else {
expandAllBelowDepth(node.level);
}
} else {
toggleExpandNode(node.id);
}
scrollToNode(node.id);
}}
>
@@ -445,6 +431,50 @@ function TasksTreeView({
/>
</ResizablePanel>
</ResizablePanelGroup>
<div className="flex items-center justify-between gap-2 border-t border-grid-dimmed px-2">
<div className="grow @container">
<div className="hidden items-center gap-4 @[42rem]:flex">
<KeyboardShortcuts
expandAllBelowDepth={expandAllBelowDepth}
collapseAllBelowDepth={collapseAllBelowDepth}
toggleExpandLevel={toggleExpandLevel}
setShowDurations={setShowDurations}
/>
</div>
<div className="@[42rem]:hidden">
<Popover>
<PopoverArrowTrigger>Shortcuts</PopoverArrowTrigger>
<PopoverContent
className="min-w-[20rem] overflow-y-auto p-2 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
align="start"
>
<Header3 spacing>Keyboard shortcuts</Header3>
<div className="flex flex-col gap-2">
<KeyboardShortcuts
expandAllBelowDepth={expandAllBelowDepth}
collapseAllBelowDepth={collapseAllBelowDepth}
toggleExpandLevel={toggleExpandLevel}
setShowDurations={setShowDurations}
/>
</div>
</PopoverContent>
</Popover>
</div>
</div>
<div className="flex items-center gap-4">
<Slider
variant={"tertiary"}
className="w-20"
LeadingIcon={MagnifyingGlassMinusIcon}
TrailingIcon={MagnifyingGlassPlusIcon}
value={[scale]}
onValueChange={(value) => setScale(value[0])}
min={0}
max={1}
step={0.05}
/>
</div>
</div>
</div>
);
}
@@ -738,6 +768,7 @@ function ShowParentLink({ runFriendlyId }: { runFriendlyId: string }) {
fullWidth
textAlignLeft
shortcut={{ key: "p" }}
className="flex-1"
>
{mouseOver ? (
<ShowParentIconSelected className="h-4 w-4 text-indigo-500" />
@@ -884,3 +915,92 @@ function ConnectedDevWarning() {
</div>
);
}
function KeyboardShortcuts({
expandAllBelowDepth,
collapseAllBelowDepth,
toggleExpandLevel,
setShowDurations,
}: {
expandAllBelowDepth: (depth: number) => void;
collapseAllBelowDepth: (depth: number) => void;
toggleExpandLevel: (depth: number) => void;
setShowDurations: (show: (show: boolean) => boolean) => void;
}) {
return (
<>
<ArrowKeyShortcuts />
<ShortcutWithAction
shortcut={{ key: "e" }}
action={() => expandAllBelowDepth(0)}
title="Expand all"
/>
<ShortcutWithAction
shortcut={{ key: "c" }}
action={() => collapseAllBelowDepth(1)}
title="Collapse all"
/>
<NumberShortcuts toggleLevel={(number) => toggleExpandLevel(number)} />
<ShortcutWithAction
shortcut={{ key: "d" }}
action={() => setShowDurations((d) => !d)}
title="Toggle durations"
/>
</>
);
}
function ArrowKeyShortcuts() {
return (
<div className="flex items-center gap-0.5">
<ShortcutKey shortcut={{ key: "arrowup" }} variant="medium" className="ml-0 mr-0" />
<ShortcutKey shortcut={{ key: "arrowdown" }} variant="medium" className="ml-0 mr-0" />
<ShortcutKey shortcut={{ key: "arrowleft" }} variant="medium" className="ml-0 mr-0" />
<ShortcutKey shortcut={{ key: "arrowright" }} variant="medium" className="ml-0 mr-0" />
<Paragraph variant="extra-small" className="ml-1.5 whitespace-nowrap">
Navigate
</Paragraph>
</div>
);
}
function ShortcutWithAction({
shortcut,
title,
action,
}: {
shortcut: Shortcut;
title: string;
action: () => void;
}) {
useShortcutKeys({
shortcut,
action,
});
return (
<div className="flex items-center gap-0.5">
<ShortcutKey shortcut={shortcut} variant="medium" className="ml-0 mr-0" />
<Paragraph variant="extra-small" className="ml-1.5 whitespace-nowrap">
{title}
</Paragraph>
</div>
);
}
function NumberShortcuts({ toggleLevel }: { toggleLevel: (depth: number) => void }) {
useHotkeys(["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], (event, hotkeysEvent) => {
toggleLevel(Number(event.key));
});
return (
<div className="flex items-center gap-0.5">
<span className={cn(variants.medium, "ml-0 mr-0")}>0</span>
<span className="text-[0.75rem] text-text-dimmed"></span>
<span className={cn(variants.medium, "ml-0 mr-0")}>9</span>
<Paragraph variant="extra-small" className="ml-1.5 whitespace-nowrap">
Toggle level
</Paragraph>
</div>
);
}
@@ -6,7 +6,6 @@ import { ActionFunction, LoaderFunctionArgs, json } from "@remix-run/server-runt
import { TaskRunStatus } from "@trigger.dev/database";
import { useCallback, useEffect, useRef, useState } from "react";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod";
import { JSONEditor } from "~/components/code/JSONEditor";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
import { Button } from "~/components/primitives/Buttons";
@@ -37,7 +36,7 @@ import {
TestTaskPresenter,
} from "~/presenters/v3/TestTaskPresenter.server";
import { requireUserId } from "~/services/session.server";
import { docsPath, v3RunPath, v3TaskParamsSchema } from "~/utils/pathBuilder";
import { docsPath, v3RunSpanPath, v3TaskParamsSchema } from "~/utils/pathBuilder";
import { TestTaskService } from "~/v3/services/testTask.server";
import { TestTaskData } from "~/v3/testTask";
@@ -77,7 +76,12 @@ export const action: ActionFunction = async ({ request, params }) => {
}
return redirectWithSuccessMessage(
v3RunPath({ slug: organizationSlug }, { slug: projectParam }, { friendlyId: run.friendlyId }),
v3RunSpanPath(
{ slug: organizationSlug },
{ slug: projectParam },
{ friendlyId: run.friendlyId },
{ spanId: run.spanId }
),
request,
"Test run created"
);
@@ -0,0 +1,45 @@
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { ApiBatchResultsPresenter } from "~/presenters/v3/ApiBatchResultsPresenter.server";
import { ApiRunResultPresenter } from "~/presenters/v3/ApiRunResultPresenter.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
const ParamsSchema = z.object({
/* This is the batch friendly ID */
batchParam: z.string(),
});
export async function loader({ request, params }: LoaderFunctionArgs) {
// Authenticate the request
const authenticationResult = await authenticateApiRequest(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API Key" }, { status: 401 });
}
const parsed = ParamsSchema.safeParse(params);
if (!parsed.success) {
return json({ error: "Invalid or missing run ID" }, { status: 400 });
}
const { batchParam } = parsed.data;
try {
const presenter = new ApiBatchResultsPresenter();
const result = await presenter.call(batchParam, authenticationResult.environment);
if (!result) {
return json({ error: "Batch not found" }, { status: 404 });
}
return json(result);
} catch (error) {
if (error instanceof Error) {
return json({ error: error.message }, { status: 500 });
} else {
return json({ error: JSON.stringify(error) }, { status: 500 });
}
}
}
@@ -1,12 +1,10 @@
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { PrismaErrorSchema, prisma } from "~/db.server";
import { z } from "zod";
import { prisma } from "~/db.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { CancelRunService } from "~/services/runs/cancelRun.server";
import { ApiRunPresenter } from "~/presenters/ApiRunPresenter.server";
import { ReplayTaskRunService } from "~/v3/services/replayTaskRun.server";
import { logger } from "~/services/logger.server";
import { ReplayTaskRunService } from "~/v3/services/replayTaskRun.server";
const ParamsSchema = z.object({
/* This is the run friendly ID */
@@ -0,0 +1,44 @@
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { ApiRunResultPresenter } from "~/presenters/v3/ApiRunResultPresenter.server";
import { authenticateApiRequest } from "~/services/apiAuth.server";
const ParamsSchema = z.object({
/* This is the run friendly ID */
runParam: z.string(),
});
export async function loader({ request, params }: LoaderFunctionArgs) {
// Authenticate the request
const authenticationResult = await authenticateApiRequest(request);
if (!authenticationResult) {
return json({ error: "Invalid or Missing API Key" }, { status: 401 });
}
const parsed = ParamsSchema.safeParse(params);
if (!parsed.success) {
return json({ error: "Invalid or missing run ID" }, { status: 400 });
}
const { runParam } = parsed.data;
try {
const presenter = new ApiRunResultPresenter();
const result = await presenter.call(runParam, authenticationResult.environment);
if (!result) {
return json({ error: "Run either doesn't exist or is not finished" }, { status: 404 });
}
return json(result);
} catch (error) {
if (error instanceof Error) {
return json({ error: error.message }, { status: 500 });
} else {
return json({ error: JSON.stringify(error) }, { status: 500 });
}
}
}
@@ -1,6 +1,6 @@
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { parseBatchTriggerTaskRequestBody } from "@trigger.dev/core/v3";
import { BatchTriggerTaskRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import { MAX_BATCH_TRIGGER_ITEMS } from "~/consts";
import { authenticateApiRequest } from "~/services/apiAuth.server";
@@ -46,7 +46,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
// Now parse the request body
const anyBody = await request.json();
const body = parseBatchTriggerTaskRequestBody(anyBody);
const body = BatchTriggerTaskRequestBody.safeParse(anyBody);
if (!body.success) {
return json({ error: "Invalid request body" }, { status: 400 });
@@ -1,6 +1,6 @@
import type { ActionFunctionArgs } from "@remix-run/server-runtime";
import { json } from "@remix-run/server-runtime";
import { parseTriggerTaskRequestBody } from "@trigger.dev/core/v3";
import { TriggerTaskRequestBody } from "@trigger.dev/core/v3";
import { z } from "zod";
import { authenticateApiRequest } from "~/services/apiAuth.server";
import { logger } from "~/services/logger.server";
@@ -52,7 +52,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
// Now parse the request body
const anyBody = await request.json();
const body = parseTriggerTaskRequestBody(anyBody);
const body = TriggerTaskRequestBody.safeParse(anyBody);
if (!body.success) {
return json({ error: "Invalid request body" }, { status: 400 });
@@ -2,6 +2,7 @@ import { LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
import { requireUserId } from "~/services/session.server";
import { v3RunSpanPath } from "~/utils/pathBuilder";
const ParamsSchema = z.object({
projectRef: z.string(),
@@ -33,8 +34,20 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
return new Response("Not found", { status: 404 });
}
const run = await prisma.taskRun.findUnique({
where: {
friendlyId: validatedParams.runParam,
},
});
if (!run) {
throw new Response("Not found", { status: 404 });
}
// Redirect to the project's runs page
return redirect(
`/orgs/${project.organization.slug}/projects/v3/${project.slug}/runs/${validatedParams.runParam}`
v3RunSpanPath({ slug: project.organization.slug }, { slug: project.slug }, run, {
spanId: run.spanId,
})
);
}
@@ -4,10 +4,11 @@ import {
QueueListIcon,
StopCircleIcon,
} from "@heroicons/react/20/solid";
import { useParams } from "@remix-run/react";
import { useFetcher, useParams } from "@remix-run/react";
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
import { formatDurationNanoseconds, nanosecondsToMilliseconds } from "@trigger.dev/core/v3";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { useEffect } from "react";
import { typedjson, useTypedFetcher, useTypedLoaderData } from "remix-typedjson";
import { ExitIcon } from "~/assets/icons/ExitIcon";
import { CodeBlock } from "~/components/code/CodeBlock";
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
@@ -17,6 +18,7 @@ import { Dialog, DialogTrigger } from "~/components/primitives/Dialog";
import { Header2 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Property, PropertyTable } from "~/components/primitives/PropertyTable";
import { Spinner } from "~/components/primitives/Spinner";
import { CancelRunDialog } from "~/components/runs/v3/CancelRunDialog";
import { LiveTimer } from "~/components/runs/v3/LiveTimer";
import { ReplayRunDialog } from "~/components/runs/v3/ReplayRunDialog";
@@ -58,19 +60,57 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
return typedjson({ span });
};
export default function Page() {
const {
span: { event },
} = useTypedLoaderData<typeof loader>();
export function SpanView({
runParam,
spanId,
closePanel,
}: {
runParam: string;
spanId: string | undefined;
closePanel: () => void;
}) {
const organization = useOrganization();
const project = useProject();
const { runParam } = useParams();
const fetcher = useTypedFetcher<typeof loader>();
useEffect(() => {
if (spanId === undefined) return;
fetcher.load(
`/resources/orgs/${organization.slug}/projects/v3/${project.slug}/runs/${runParam}/spans/${spanId}`
);
}, [organization.slug, project.slug, runParam, spanId]);
if (spanId === undefined) {
return null;
}
if (fetcher.state !== "idle" || fetcher.data === undefined) {
return (
<div
className={cn(
"grid h-full max-h-full grid-rows-[2.5rem_1fr] overflow-hidden bg-background-bright"
)}
>
<div className="mx-3 flex items-center gap-2 overflow-x-hidden border-b border-grid-dimmed">
<div className="size-4 bg-grid-dimmed" />
<div className="h-6 w-[60%] bg-grid-dimmed" />
</div>
<div className="flex items-center justify-center">
<Spinner />
</div>
</div>
);
}
const {
span: { event },
} = fetcher.data;
return (
<div
className={cn(
"grid h-full max-h-full overflow-hidden bg-background-bright",
event.showActionBar ? "grid-rows-[2.5rem_1fr_2.5rem]" : "grid-rows-[2.5rem_1fr]"
event.showActionBar ? "grid-rows-[2.5rem_1fr_3.25rem]" : "grid-rows-[2.5rem_1fr]"
)}
>
<div className="mx-3 flex items-center justify-between gap-2 overflow-x-hidden border-b border-grid-dimmed">
@@ -85,8 +125,8 @@ export default function Page() {
</Header2>
</div>
{runParam && (
<LinkButton
to={v3RunPath(organization, project, { friendlyId: runParam })}
<Button
onClick={closePanel}
variant="minimal/medium"
LeadingIcon={ExitIcon}
shortcut={{ key: "esc" }}
@@ -130,6 +170,9 @@ export default function Page() {
)}
<Property label="Message">{event.message}</Property>
<Property label="Task ID">{event.taskSlug}</Property>
{event.idempotencyKey && (
<Property label="Idempotency key">{event.idempotencyKey}</Property>
)}
{event.taskPath && event.taskExportName && (
<Property label="Task">
<TaskPath
@@ -185,7 +228,7 @@ export default function Page() {
{ friendlyId: event.runId },
{ spanId: event.spanId }
)}
variant="minimal/small"
variant="minimal/medium"
LeadingIcon={QueueListIcon}
shortcut={{ key: "f" }}
>
@@ -213,7 +256,7 @@ function RunActionButtons({ span }: { span: Span }) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="danger/small" LeadingIcon={StopCircleIcon}>
<Button variant="danger/medium" LeadingIcon={StopCircleIcon}>
Cancel run
</Button>
</DialogTrigger>
@@ -233,7 +276,7 @@ function RunActionButtons({ span }: { span: Span }) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="tertiary/small" LeadingIcon={ArrowPathIcon}>
<Button variant="tertiary/medium" LeadingIcon={ArrowPathIcon}>
Replay run
</Button>
</DialogTrigger>
@@ -4,7 +4,7 @@ import { z } from "zod";
import { prisma } from "~/db.server";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import { logger } from "~/services/logger.server";
import { v3RunPath } from "~/utils/pathBuilder";
import { v3RunSpanPath } from "~/utils/pathBuilder";
import { ReplayTaskRunService } from "~/v3/services/replayTaskRun.server";
const FormSchema = z.object({
@@ -54,12 +54,13 @@ export const action: ActionFunction = async ({ request, params }) => {
);
}
const runPath = v3RunPath(
const runPath = v3RunSpanPath(
{
slug: taskRun.project.organization.slug,
},
{ slug: taskRun.project.slug },
{ friendlyId: newRun.friendlyId }
{ friendlyId: newRun.friendlyId },
{ spanId: newRun.spanId }
);
return redirectWithSuccessMessage(runPath, request, `Replaying run`);
@@ -134,20 +134,27 @@ export async function authenticatePersonalAccessToken(
const hashedToken = hashToken(token);
const personalAccessToken = await prisma.personalAccessToken.update({
const personalAccessToken = await prisma.personalAccessToken.findFirst({
where: {
hashedToken,
revokedAt: null,
},
});
if (!personalAccessToken) {
// The token may have been revoked or is entirely invalid
return;
}
await prisma.personalAccessToken.update({
where: {
id: personalAccessToken.id,
},
data: {
lastAccessedAt: new Date(),
},
});
if (!personalAccessToken) {
return;
}
const decryptedToken = decryptPersonalAccessToken(personalAccessToken);
if (decryptedToken !== token) {
@@ -210,6 +217,18 @@ export async function createPersonalAccessTokenFromAuthorizationCode(
},
});
if (existingCliPersonalAccessToken.revokedAt) {
// re-activate revoked CLI PAT so we can use it again
await prisma.personalAccessToken.update({
where: {
id: existingCliPersonalAccessToken.id,
},
data: {
revokedAt: null,
},
});
}
//we don't return the decrypted token
return {
id: existingCliPersonalAccessToken.id,
+1 -3
View File
@@ -14,8 +14,6 @@ import { Job } from "~/models/job.server";
import type { Organization } from "~/models/organization.server";
import type { Project } from "~/models/project.server";
import { objectToSearchParams } from "./searchParams";
import { ScheduleListFilters } from "~/components/runs/v3/ScheduleFilters";
import { useLocation } from "@remix-run/react";
export type OrgForPath = Pick<Organization, "slug">;
export type ProjectForPath = Pick<Project, "slug">;
@@ -368,7 +366,7 @@ export function v3RunSpanPath(
run: v3RunForPath,
span: v3SpanForPath
) {
return `${v3RunPath(organization, project, run)}/spans/${span.spanId}`;
return `${v3RunPath(organization, project, run)}?span=${span.spanId}`;
}
export function v3TraceSpanPath(
@@ -1,9 +1,5 @@
import {
ZodMessageHandler,
ZodMessageSender,
clientWebsocketMessages,
serverWebsocketMessages,
} from "@trigger.dev/core/v3";
import { clientWebsocketMessages, serverWebsocketMessages } from "@trigger.dev/core/v3";
import { ZodMessageHandler, ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler";
import { Evt } from "evt";
import { randomUUID } from "node:crypto";
import type { CloseEvent, ErrorEvent, MessageEvent, WebSocket } from "ws";
+4 -1
View File
@@ -63,6 +63,7 @@ export type TraceAttributes = Partial<
| "batchId"
| "payload"
| "payloadType"
| "idempotencyKey"
>
>;
@@ -371,6 +372,7 @@ export class EventRepository {
id: event.spanId,
parentId: event.parentId ?? undefined,
runId: event.runId,
idempotencyKey: event.idempotencyKey,
data: {
message: event.message,
style: event.style,
@@ -459,7 +461,7 @@ export class EventRepository {
const links: SpanLink[] = [];
if (messagingEvent.success && messagingEvent.data) {
if ("id" in messagingEvent.data.message) {
if (messagingEvent.data.message && "id" in messagingEvent.data.message) {
if (messagingEvent.data.message.id.startsWith("run_")) {
links.push({
type: "run",
@@ -719,6 +721,7 @@ export class EventRepository {
links: links as unknown as Prisma.InputJsonValue,
payload: options.attributes.payload,
payloadType: options.attributes.payloadType,
idempotencyKey: options.attributes.idempotencyKey,
};
if (options.immediate) {
+6 -1
View File
@@ -5,8 +5,8 @@ import {
PlatformToProviderMessages,
ProviderToPlatformMessages,
SharedQueueToClientMessages,
ZodNamespace,
} from "@trigger.dev/core/v3";
import { ZodNamespace } from "@trigger.dev/core/v3/zodNamespace";
import { Server } from "socket.io";
import { env } from "~/env.server";
import { singleton } from "~/utils/singleton";
@@ -71,6 +71,7 @@ function initializeSocketIOServerInstance() {
function createCoordinatorNamespace(io: Server) {
const coordinator = new ZodNamespace({
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
io,
name: "coordinator",
authToken: env.COORDINATOR_SECRET,
@@ -147,6 +148,7 @@ function createCoordinatorNamespace(io: Server) {
function createProviderNamespace(io: Server) {
const provider = new ZodNamespace({
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
io,
name: "provider",
authToken: env.PROVIDER_SECRET,
@@ -181,6 +183,7 @@ function createProviderNamespace(io: Server) {
function createSharedQueueConsumerNamespace(io: Server) {
const sharedQueue = new ZodNamespace({
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
io,
name: "shared-queue",
authToken: env.PROVIDER_SECRET,
@@ -188,7 +191,9 @@ function createSharedQueueConsumerNamespace(io: Server) {
serverMessages: SharedQueueToClientMessages,
onConnection: async (socket, handler, sender, logger) => {
const sharedSocketConnection = new SharedSocketConnection({
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
namespace: sharedQueue.namespace,
// @ts-ignore - for some reason the built ZodNamespace Server type is not compatible with the Server type here, but only when doing typechecking
socket,
logger,
poolSize: env.SHARED_QUEUE_CONSUMER_POOL_SIZE,
@@ -3,9 +3,9 @@ import {
TaskRunExecution,
TaskRunExecutionPayload,
TaskRunExecutionResult,
ZodMessageSender,
serverWebsocketMessages,
} from "@trigger.dev/core/v3";
import { ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler";
import { BackgroundWorker, BackgroundWorkerTask } from "@trigger.dev/database";
import { z } from "zod";
import { prisma } from "~/db.server";
@@ -118,7 +118,7 @@ export class DevQueueConsumer {
completion: TaskRunExecutionResult,
execution: TaskRunExecution
) {
this._inProgressAttempts.delete(completion.id);
this._inProgressAttempts.delete(execution.attempt.id);
if (completion.ok) {
this._taskSuccesses++;
@@ -424,7 +424,7 @@ export class DevQueueConsumer {
orderBy: { number: "desc" },
},
tags: true,
batchItem: {
batchItems: {
include: {
batchTaskRun: true,
},
@@ -499,6 +499,7 @@ export class DevQueueConsumer {
createdAt: lockedTaskRun.createdAt,
tags: lockedTaskRun.tags.map((tag) => tag.name),
isTest: lockedTaskRun.isTest,
idempotencyKey: lockedTaskRun.idempotencyKey ?? undefined,
},
queue: {
id: queue.friendlyId,
@@ -520,9 +521,10 @@ export class DevQueueConsumer {
slug: this.env.project.slug,
name: this.env.project.name,
},
batch: lockedTaskRun.batchItem?.batchTaskRun
? { id: lockedTaskRun.batchItem.batchTaskRun.friendlyId }
: undefined,
batch:
lockedTaskRun.batchItems[0] && lockedTaskRun.batchItems[0].batchTaskRun
? { id: lockedTaskRun.batchItems[0].batchTaskRun.friendlyId }
: undefined,
};
const environmentRepository = new EnvironmentVariablesRepository();
+20 -20
View File
@@ -39,7 +39,6 @@ const SemanticAttributes = {
export type MarQSOptions = {
redis: RedisOptions;
defaultQueueConcurrency: number;
defaultEnvConcurrency: number;
defaultOrgConcurrency: number;
windowSize?: number;
@@ -92,7 +91,7 @@ export class MarQS {
public async getQueueConcurrencyLimit(env: AuthenticatedEnvironment, queue: string) {
const result = await this.redis.get(this.keys.queueConcurrencyLimitKey(env, queue));
return result ? Number(result) : this.options.defaultQueueConcurrency;
return result ? Number(result) : undefined;
}
public async getEnvConcurrencyLimit(env: AuthenticatedEnvironment) {
@@ -860,7 +859,6 @@ export class MarQS {
messageQueue,
String(this.options.visibilityTimeoutInMs ?? 300000), // 5 minutes
String(Date.now()),
String(this.options.defaultQueueConcurrency),
String(this.options.defaultEnvConcurrency),
String(this.options.defaultOrgConcurrency)
);
@@ -1015,16 +1013,22 @@ export class MarQS {
concurrencyLimitKey,
envConcurrencyLimitKey,
orgConcurrencyLimitKey,
String(this.options.defaultQueueConcurrency),
String(this.options.defaultEnvConcurrency),
String(this.options.defaultOrgConcurrency)
);
const queueCurrent = Number(capacities[0]);
const envLimit = Number(capacities[3]);
const orgLimit = Number(capacities[5]);
const queueLimit = capacities[1] ? Number(capacities[1]) : Math.min(envLimit, orgLimit);
const envCurrent = Number(capacities[2]);
const orgCurrent = Number(capacities[4]);
// [queue current, queue limit, env current, env limit, org current, org limit]
return {
queue: { current: Number(capacities[0]), limit: Number(capacities[1]) },
env: { current: Number(capacities[2]), limit: Number(capacities[3]) },
org: { current: Number(capacities[4]), limit: Number(capacities[5]) },
queue: { current: queueCurrent, limit: queueLimit },
env: { current: envCurrent, limit: envLimit },
org: { current: orgCurrent, limit: orgLimit },
};
}
@@ -1119,13 +1123,12 @@ local currentConcurrencyKey = KEYS[7]
local envCurrentConcurrencyKey = KEYS[8]
local orgCurrentConcurrencyKey = KEYS[9]
-- Args: childQueueName, visibilityQueue, currentTime, defaultConcurrencyLimit, defaultEnvConcurrencyLimit, defaultOrgConcurrencyLimit
-- Args: childQueueName, visibilityQueue, currentTime, defaultEnvConcurrencyLimit, defaultOrgConcurrencyLimit
local childQueueName = ARGV[1]
local visibilityTimeout = tonumber(ARGV[2])
local currentTime = tonumber(ARGV[3])
local defaultConcurrencyLimit = ARGV[4]
local defaultEnvConcurrencyLimit = ARGV[5]
local defaultOrgConcurrencyLimit = ARGV[6]
local defaultEnvConcurrencyLimit = ARGV[4]
local defaultOrgConcurrencyLimit = ARGV[5]
-- Check current org concurrency against the limit
local orgCurrentConcurrency = tonumber(redis.call('SCARD', orgCurrentConcurrencyKey) or '0')
@@ -1145,8 +1148,9 @@ end
-- Check current queue concurrency against the limit
local currentConcurrency = tonumber(redis.call('SCARD', currentConcurrencyKey) or '0')
local concurrencyLimit = tonumber(redis.call('GET', concurrencyLimitKey) or defaultConcurrencyLimit)
local concurrencyLimit = tonumber(redis.call('GET', concurrencyLimitKey) or '1000000')
-- Check condition only if concurrencyLimit exists
if currentConcurrency >= concurrencyLimit then
return nil
end
@@ -1304,10 +1308,9 @@ local concurrencyLimitKey = KEYS[4]
local envConcurrencyLimitKey = KEYS[5]
local orgConcurrencyLimitKey = KEYS[6]
-- Args defaultConcurrencyLimit, defaultEnvConcurrencyLimit, defaultOrgConcurrencyLimit
local defaultConcurrencyLimit = tonumber(ARGV[1])
local defaultEnvConcurrencyLimit = tonumber(ARGV[2])
local defaultOrgConcurrencyLimit = tonumber(ARGV[3])
-- Args defaultEnvConcurrencyLimit, defaultOrgConcurrencyLimit
local defaultEnvConcurrencyLimit = tonumber(ARGV[1])
local defaultOrgConcurrencyLimit = tonumber(ARGV[2])
local currentOrgConcurrency = tonumber(redis.call('SCARD', currentOrgConcurrencyKey) or '0')
local orgConcurrencyLimit = tonumber(redis.call('GET', orgConcurrencyLimitKey) or defaultOrgConcurrencyLimit)
@@ -1316,7 +1319,7 @@ local currentEnvConcurrency = tonumber(redis.call('SCARD', currentEnvConcurrency
local envConcurrencyLimit = tonumber(redis.call('GET', envConcurrencyLimitKey) or defaultEnvConcurrencyLimit)
local currentConcurrency = tonumber(redis.call('SCARD', currentConcurrencyKey) or '0')
local concurrencyLimit = tonumber(redis.call('GET', concurrencyLimitKey) or defaultConcurrencyLimit)
local concurrencyLimit = redis.call('GET', concurrencyLimitKey)
-- Return current capacity and concurrency limits for the queue, env, org
return { currentConcurrency, concurrencyLimit, currentEnvConcurrency, envConcurrencyLimit, currentOrgConcurrency, orgConcurrencyLimit }
@@ -1398,7 +1401,6 @@ declare module "ioredis" {
childQueueName: string,
visibilityTimeout: string,
currentTime: string,
defaultConcurrencyLimit: string,
defaultEnvConcurrencyLimit: string,
defaultOrgConcurrencyLimit: string,
callback?: Callback<[string, string]>
@@ -1447,7 +1449,6 @@ declare module "ioredis" {
concurrencyLimitKey: string,
envConcurrencyLimitKey: string,
orgConcurrencyLimitKey: string,
defaultConcurrencyLimit: string,
defaultEnvConcurrencyLimit: string,
defaultOrgConcurrencyLimit: string,
callback?: Callback<number[]>
@@ -1492,7 +1493,6 @@ function getMarQSClient() {
envQueuePriorityStrategy: new SimpleWeightedChoiceStrategy({ queueSelectionCount: 12 }),
workers: 1,
redis: redisOptions,
defaultQueueConcurrency: env.DEFAULT_QUEUE_EXECUTION_CONCURRENCY_LIMIT,
defaultEnvConcurrency: env.DEFAULT_ENV_EXECUTION_CONCURRENCY_LIMIT,
defaultOrgConcurrency: env.DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT,
visibilityTimeoutInMs: 120 * 1000, // 2 minutes,
@@ -8,9 +8,9 @@ import {
TaskRunExecutionResult,
TaskRunFailedExecutionResult,
TaskRunSuccessfulExecutionResult,
ZodMessageSender,
serverWebsocketMessages,
} from "@trigger.dev/core/v3";
import { ZodMessageSender } from "@trigger.dev/core/v3/zodMessageHandler";
import {
BackgroundWorker,
BackgroundWorkerTask,
@@ -26,7 +26,6 @@ import { EnvironmentVariablesRepository } from "../environmentVariables/environm
import { generateFriendlyId } from "../friendlyIdentifiers";
import { socketIo } from "../handleSocketIo.server";
import { findCurrentWorkerDeployment } from "../models/workerDeployment.server";
import { CancelAttemptService } from "../services/cancelAttempt.server";
import { RestoreCheckpointService } from "../services/restoreCheckpoint.server";
import { tracer } from "../tracer.server";
@@ -812,7 +811,7 @@ class SharedQueueTasks {
if (ok) {
const success: TaskRunSuccessfulExecutionResult = {
ok,
id: attempt.friendlyId,
id: attempt.taskRun.friendlyId,
output: attempt.output ?? undefined,
outputType: attempt.outputType,
};
@@ -820,7 +819,7 @@ class SharedQueueTasks {
} else {
const failure: TaskRunFailedExecutionResult = {
ok,
id: attempt.friendlyId,
id: attempt.taskRun.friendlyId,
error: attempt.error as TaskRunError,
};
return failure;
@@ -848,7 +847,7 @@ class SharedQueueTasks {
taskRun: {
include: {
tags: true,
batchItem: {
batchItems: {
include: {
batchTaskRun: true,
},
@@ -956,6 +955,7 @@ class SharedQueueTasks {
createdAt: taskRun.createdAt,
tags: taskRun.tags.map((tag) => tag.name),
isTest: taskRun.isTest,
idempotencyKey: taskRun.idempotencyKey ?? undefined,
},
queue: {
id: queue.friendlyId,
@@ -977,9 +977,10 @@ class SharedQueueTasks {
slug: attempt.runtimeEnvironment.project.slug,
name: attempt.runtimeEnvironment.project.name,
},
batch: taskRun.batchItem?.batchTaskRun
? { id: taskRun.batchItem.batchTaskRun.friendlyId }
: undefined,
batch:
taskRun.batchItems[0] && taskRun.batchItems[0].batchTaskRun
? { id: taskRun.batchItems[0].batchTaskRun.friendlyId }
: undefined,
worker: {
id: attempt.backgroundWorkerId,
contentHash: attempt.backgroundWorker.contentHash,
-1
View File
@@ -1,4 +1,3 @@
import { RedisOptions } from "ioredis";
import { z } from "zod";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
@@ -352,6 +352,7 @@ function extractResourceProperties(attributes: KeyValue[]) {
queueId: extractStringAttribute(attributes, SemanticInternalAttributes.QUEUE_ID),
queueName: extractStringAttribute(attributes, SemanticInternalAttributes.QUEUE_NAME),
batchId: extractStringAttribute(attributes, SemanticInternalAttributes.BATCH_ID),
idempotencyKey: extractStringAttribute(attributes, SemanticInternalAttributes.IDEMPOTENCY_KEY),
};
}
@@ -1,9 +1,9 @@
import { BatchTriggerTaskRequestBody } from "@trigger.dev/core/v3";
import { nanoid } from "nanoid";
import { AuthenticatedEnvironment } from "~/services/apiAuth.server";
import { generateFriendlyId } from "../friendlyIdentifiers";
import { BaseService } from "./baseService.server";
import { TriggerTaskService } from "./triggerTask.server";
import { batchTaskRunItemStatusForRunStatus } from "~/models/taskRun.server";
export type BatchTriggerTaskServiceOptions = {
idempotencyKey?: string;
@@ -22,23 +22,23 @@ export class BatchTriggerTaskService extends BaseService {
return await this.traceWithEnv("call()", environment, async (span) => {
span.setAttribute("taskId", taskId);
const idempotencyKey = options.idempotencyKey ?? nanoid();
const existingBatch = await this._prisma.batchTaskRun.findUnique({
where: {
runtimeEnvironmentId_idempotencyKey: {
runtimeEnvironmentId: environment.id,
idempotencyKey,
},
},
include: {
items: {
include: {
taskRun: true,
const existingBatch = options.idempotencyKey
? await this._prisma.batchTaskRun.findUnique({
where: {
runtimeEnvironmentId_idempotencyKey: {
runtimeEnvironmentId: environment.id,
idempotencyKey: options.idempotencyKey,
},
},
},
},
});
include: {
items: {
include: {
taskRun: true,
},
},
},
})
: undefined;
if (existingBatch) {
span.setAttribute("batchId", existingBatch.friendlyId);
@@ -58,7 +58,7 @@ export class BatchTriggerTaskService extends BaseService {
data: {
friendlyId: generateFriendlyId("batch"),
runtimeEnvironmentId: environment.id,
idempotencyKey,
idempotencyKey: options.idempotencyKey,
taskIdentifier: taskId,
dependentTaskAttemptId: dependentAttempt?.id,
},
@@ -70,8 +70,6 @@ export class BatchTriggerTaskService extends BaseService {
let index = 0;
for (const item of body.items) {
const idempotencyKey = nanoid();
const run = await triggerTaskService.call(
taskId,
environment,
@@ -83,7 +81,6 @@ export class BatchTriggerTaskService extends BaseService {
},
},
{
idempotencyKey,
triggerVersion: options.triggerVersion,
traceContext: options.traceContext,
spanParentAsLink: options.spanParentAsLink,
@@ -96,6 +93,7 @@ export class BatchTriggerTaskService extends BaseService {
data: {
batchTaskRunId: batch.id,
taskRunId: run.id,
status: batchTaskRunItemStatusForRunStatus(run.status),
},
});
@@ -4,9 +4,9 @@ import { marqs } from "~/v3/marqs/index.server";
import { devPubSub } from "../marqs/devPubSub.server";
import { BaseService } from "./baseService.server";
import { socketIo } from "../handleSocketIo.server";
import { assertUnreachable } from "../utils/asserts.server";
import { CancelAttemptService } from "./cancelAttempt.server";
import { logger } from "~/services/logger.server";
import assertNever from "assert-never";
export const CANCELLABLE_STATUSES: Array<TaskRunStatus> = [
"PENDING",
@@ -148,7 +148,7 @@ export class CancelTaskRunService extends BaseService {
break;
}
default: {
assertUnreachable(attempt.status);
assertNever(attempt.status);
}
}
}
@@ -39,10 +39,12 @@ export class CompleteAttemptService extends BaseService {
env?: AuthenticatedEnvironment;
checkpoint?: CheckpointData;
}): Promise<"COMPLETED" | "RETRIED"> {
const taskRunAttempt = await findAttempt(this._prisma, completion.id);
const taskRunAttempt = await findAttempt(this._prisma, execution.attempt.id);
if (!taskRunAttempt) {
logger.error("[CompleteAttemptService] Task run attempt not found", { id: completion.id });
logger.error("[CompleteAttemptService] Task run attempt not found", {
id: execution.attempt.id,
});
// Update the task run to be failed
await this._prisma.taskRun.update({
@@ -76,7 +78,7 @@ export class CompleteAttemptService extends BaseService {
env?: AuthenticatedEnvironment
): Promise<"COMPLETED"> {
await this._prisma.taskRunAttempt.update({
where: { friendlyId: completion.id },
where: { id: taskRunAttempt.id },
data: {
status: "COMPLETED",
completedAt: new Date(),
@@ -144,7 +146,7 @@ export class CompleteAttemptService extends BaseService {
}
await this._prisma.taskRunAttempt.update({
where: { friendlyId: completion.id },
where: { id: taskRunAttempt.id },
data: {
status: "FAILED",
completedAt: new Date(),
@@ -1,4 +1,5 @@
import { CoordinatorToPlatformMessages, InferSocketMessageSchema } from "@trigger.dev/core/v3";
import { CoordinatorToPlatformMessages } from "@trigger.dev/core/v3";
import type { InferSocketMessageSchema } from "@trigger.dev/core/v3/zodSocket";
import type {
CheckpointRestoreEvent,
TaskRunAttemptStatus,
@@ -1,9 +1,9 @@
import {
CoordinatorToPlatformMessages,
InferSocketMessageSchema,
TaskRunExecution,
TaskRunExecutionResult,
} from "@trigger.dev/core/v3";
import type { InferSocketMessageSchema } from "@trigger.dev/core/v3/zodSocket";
import { $transaction, PrismaClientOrTransaction } from "~/db.server";
import { logger } from "~/services/logger.server";
import { marqs } from "~/v3/marqs/index.server";
@@ -12,7 +12,7 @@ export class ResumeTaskRunDependenciesService extends BaseService {
include: {
taskRun: {
include: {
batchItem: true,
batchItems: true,
dependency: {
include: {
dependentAttempt: true,
@@ -34,14 +34,16 @@ export class ResumeTaskRunDependenciesService extends BaseService {
return;
}
const { batchItem, dependency } = taskAttempt.taskRun;
const { batchItems, dependency } = taskAttempt.taskRun;
if (!batchItem && !dependency) {
if (!batchItems.length && !dependency) {
return;
}
if (batchItem) {
await this.#resumeBatchItem(batchItem, taskAttempt);
if (batchItems.length) {
for (const batchItem of batchItems) {
await this.#resumeBatchItem(batchItem, taskAttempt);
}
return;
}
@@ -34,18 +34,20 @@ export class TriggerTaskService extends BaseService {
return await this.traceWithEnv("call()", environment, async (span) => {
span.setAttribute("taskId", taskId);
const idempotencyKey = options.idempotencyKey ?? body.options?.idempotencyKey ?? nanoid();
const idempotencyKey = options.idempotencyKey ?? body.options?.idempotencyKey;
const existingRun = await this._prisma.taskRun.findUnique({
where: {
runtimeEnvironmentId_idempotencyKey: {
runtimeEnvironmentId: environment.id,
idempotencyKey,
},
},
});
const existingRun = idempotencyKey
? await this._prisma.taskRun.findUnique({
where: {
runtimeEnvironmentId_idempotencyKey: {
runtimeEnvironmentId: environment.id,
idempotencyKey,
},
},
})
: undefined;
if (existingRun) {
if (existingRun && existingRun.taskIdentifier === taskId) {
span.setAttribute("runId", existingRun.friendlyId);
return existingRun;
}
@@ -68,6 +70,7 @@ export class TriggerTaskService extends BaseService {
},
runIsTest: body.options?.test ?? false,
batchId: options.batchId,
idempotencyKey,
},
incomplete: true,
immediate: true,
+4 -5
View File
@@ -1,11 +1,10 @@
import { clientWebsocketMessages, serverWebsocketMessages } from "@trigger.dev/core/v3";
import {
MessageCatalogToSocketIoEvents,
StructuredLogger,
ZodMessageHandler,
ZodMessageSender,
clientWebsocketMessages,
serverWebsocketMessages,
} from "@trigger.dev/core/v3";
MessageCatalogToSocketIoEvents,
} from "@trigger.dev/core/v3/zodMessageHandler";
import type { StructuredLogger } from "@trigger.dev/core/v3/utils/structuredLogger";
import { Evt } from "evt";
import { randomUUID } from "node:crypto";
import { logger } from "~/services/logger.server";
@@ -1,3 +0,0 @@
export function assertUnreachable(x: never): never {
throw new Error("Didn't expect to get here");
}
+1 -1
View File
@@ -1,5 +1,5 @@
import { Logger } from "@trigger.dev/core-backend";
import { ZodMessageCatalogSchema, ZodMessageHandler, ZodMessageSender } from "@trigger.dev/core/v3";
import { ZodMessageCatalogSchema, ZodMessageHandler } from "@trigger.dev/core/v3/zodMessageHandler";
import { Evt } from "evt";
import Redis, { RedisOptions } from "ioredis";
import { z } from "zod";
+2 -1
View File
@@ -96,6 +96,7 @@
"@uiw/react-codemirror": "^4.19.5",
"@upstash/ratelimit": "^1.0.1",
"@whatwg-node/fetch": "^0.9.14",
"assert-never": "^1.2.1",
"aws4fetch": "^1.0.18",
"class-variance-authority": "^0.5.2",
"clsx": "^1.2.1",
@@ -149,7 +150,7 @@
"simple-oauth2": "^5.0.0",
"simplur": "^3.0.1",
"slug": "^6.0.0",
"socket.io": "^4.7.4",
"socket.io": "4.7.4",
"socket.io-adapter": "^2.5.4",
"sonner": "^1.0.3",
"sqs-consumer": "^7.4.0",
+1 -1
View File
@@ -6,4 +6,4 @@
"publishConfig": {
"access": "public"
}
}
}
+2
View File
@@ -2,6 +2,8 @@
The CLI `dev` command runs a server for your tasks. It will watches for changes in your `/trigger` directory and communicates with the Trigger.dev platform to register your tasks, perform runs, and send data back and forth.
It can also update your `@trigger.dev/*` packages to prevent version mismatches and failed deploys. You will always be prompted first.
<CodeGroup>
```bash npm
+8 -5
View File
@@ -21,13 +21,16 @@ yarn dlx trigger.dev@beta deploy
</CodeGroup>
<Warning>Will fail in CI if any version mismatches are detected. Ensure everything runs locally first using the [dev](/v3/cli-dev) command and don't bypass the version checks!</Warning>
It performs a few steps to deploy:
1. Typechecks the code.
2. Compiles and bundles the code.
3. Checks that [environment variables](/v3/deploy-environment-variables) are set.
4. Deploys the code to the cloud.
5. Registers the tasks as a new version in the environment (prod by default).
1. Optionally updates packages when running locally.
2. Typechecks the code.
3. Compiles and bundles the code.
4. Checks that [environment variables](/v3/deploy-environment-variables) are set.
5. Deploys the code to the cloud.
6. Registers the tasks as a new version in the environment (prod by default).
## Options
+2
View File
@@ -21,6 +21,8 @@ yarn dlx trigger.dev@beta dev
</CodeGroup>
It will first perform an update check to prevent version mismatches, failed deploys, and other errors. You will always be prompted first.
You will see in the terminal that the server is running and listening for requests. When you run a task, you will see it in the terminal along with a link to view it in the dashboard.
It is worth noting that each task runs in a separate Node process. This means that if you have a long-running task, it will not block other tasks from running.
+1 -1
View File
@@ -35,7 +35,7 @@ export const myTask = task({
maxAttempts: 10,
},
run: async (payload: string) => {
const result = await otherTask.triggerAndWait({ payload: "some data" });
const result = await otherTask.triggerAndWait("some data");
//...do other stuff
},
});
+18
View File
@@ -5,6 +5,8 @@ description: "You can easily deploy your tasks with GitHub actions."
This simple GitHub action file will deploy you Trigger.dev tasks when new code is pushed to the `main` branch and the `trigger` directory has changes in it.
<Warning>The deploy step will fail if any version mismatches are detected. Please see the [version pinning](/v3/github-actions#version-pinning) section for more details.</Warning>
```yaml .github/workflows/release-trigger.yml
name: Deploy to Trigger.dev
@@ -42,3 +44,19 @@ If you already have a GitHub action file, you can just add the final step "🚀
You need to add the `TRIGGER_ACCESS_TOKEN` secret to your repository. You can create a new access token by going to your profile page and then clicking on the "Personal Access Tokens" tab.
To set it in GitHub go to your repository, click on "Settings", "Secrets and variables" and then "Actions". Add a new secret with the name `TRIGGER_ACCESS_TOKEN` and use the value of your access token.
## Version pinning
The CLI and `@trigger.dev/*` package versions need to be in sync, otherwise there will be errors and unpredictable behavior. Hence, the `deploy` command will automatically fail during CI on any version mismatches.
To ensure a smooth CI experience you can pin the CLI version in the deploy step, like so:
```yaml .github/workflows/release-trigger.yml
- name: 🚀 Deploy Trigger.dev
env:
TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }}
run: |
npx trigger.dev@3.0.0-beta.16 deploy
```
You should use the version you run locally during dev and manual deploy. The current version is displayed in the banner, but you can also check it by appending `--version` to any command.
+36 -7
View File
@@ -11,7 +11,6 @@ This guide highlights the differences and should help you migrate your project.
Here are some features you might be using in Defer that are coming this month to v3:
- [Scheduled tasks (including CRON)](/v3/tasks-scheduled) will be available in mid-April.
- Triggering a task with a delay (like `assignOptions` delay in Defer) will be available soon there is [an alternative](#delay) you can use for now.
You can view the full feature matrix [here](/v3/feature-matrix).
@@ -71,7 +70,7 @@ export async function runLongRunningTask() {
}
```
In Trigger.dev your logic goes in the `run` function of a task. You can then `trigger` and `batchTrigger` that task, with a payload and options.
In Trigger.dev your logic goes in the `run` function of a task. You can then `trigger` and `batchTrigger` that task, with a payload as the first argument.
```ts /app/actions/actions.ts
"use server";
@@ -79,7 +78,7 @@ In Trigger.dev your logic goes in the `run` function of a task. You can then `tr
import { longRunningTask } from "@/trigger/someTasks";
export async function runLongRunningTask() {
return await longRunningTask.trigger({ payload: { foo: "bar" } });
return await longRunningTask.trigger({ foo: "bar" });
}
```
@@ -243,7 +242,7 @@ export const longRunningTask = task({
import { longRunningTask } from "@/trigger/longRunningTask";
export async function runLongRunningTask() {
return await longRunningTask.trigger({ payload: { foo: "bar" } });
return await longRunningTask.trigger({ foo: "bar" });
}
```
@@ -256,6 +255,36 @@ export async function runLongRunningTask() {
#### Example 2: A CRON task
<Warning>
"Scheduled" tasks will be available by mid-April. This will allow you to replace Defer CRON tasks.
</Warning>
We call these [scheduled tasks](/v3/tasks-scheduled) in Trigger.dev.
In Defer you might have a function like this:
```ts
import { defer } from "@defer/client";
async function sendMondayNewletter() {
// business logic here
}
export default defer.cron(sendMondayNewletter, "0 0 * * 1");
```
In Trigger.dev the task looks like this:
```ts
import { schedules } from "@trigger.dev/sdk/v3";
//this task will run when any of the attached schedules trigger
export const sendMondayNewletter = schedules.task({
id: "send-monday-newsletter",
run: async (payload) => {
// business logic here
},
});
```
Then you need to attach a schedule to the task, either using the dashboard or in your code. You can attach unlimited schedules to a task.
<Card title="Attaching schedules" icon="clock" href="/v3/tasks-scheduled">
How to attach a schedule to a task
</Card>
+19 -30
View File
@@ -107,24 +107,19 @@ export async function POST(request: Request) {
if (data.branch === "main") {
//trigger the task, with a different queue
const handle = await generatePullRequest.trigger({
payload: data,
options: {
queue: {
//the "main-branch" queue will have a concurrency limit of 10
//this triggered run will use that queue
name: "main-branch",
concurrencyLimit: 10,
},
const handle = await generatePullRequest.trigger(data, {
queue: {
//the "main-branch" queue will have a concurrency limit of 10
//this triggered run will use that queue
name: "main-branch",
concurrencyLimit: 10,
},
});
return Response.json(handle);
} else {
//triggered with the default (concurrency of 1)
const handle = await generatePullRequest.trigger({
payload: data,
});
const handle = await generatePullRequest.trigger(data);
return Response.json(handle);
}
}
@@ -146,32 +141,26 @@ export async function POST(request: Request) {
if (data.isFreeUser) {
//free users can only have 1 PR generated at a time
const handle = await generatePullRequest.trigger({
payload: data,
options: {
queue: {
//every free user gets a queue with a concurrency limit of 1
name: "free-users",
concurrencyLimit: 1,
},
concurrencyKey: data.userId,
const handle = await generatePullRequest.trigger(data, {
queue: {
//every free user gets a queue with a concurrency limit of 1
name: "free-users",
concurrencyLimit: 1,
},
concurrencyKey: data.userId,
});
//return a success response with the handle
return Response.json(handle);
} else {
//trigger the task, with a different queue
const handle = await generatePullRequest.trigger({
payload: data,
options: {
queue: {
//every paid user gets a queue with a concurrency limit of 10
name: "paid-users",
concurrencyLimit: 10,
},
concurrencyKey: data.userId,
const handle = await generatePullRequest.trigger(data, {
queue: {
//every paid user gets a queue with a concurrency limit of 10
name: "paid-users",
concurrencyLimit: 10,
},
concurrencyKey: data.userId,
});
//return a success response with the handle
+1 -1
View File
@@ -37,7 +37,7 @@ import { helloWorldTask } from "./trigger/hello-world";
async function triggerHelloWorld() {
//This triggers the task and return a handle
const handle = await helloWorld.trigger({ payload: { message: "Hello world!" } });
const handle = await helloWorld.trigger({ message: "Hello world!" });
//You can use the handle to check the status of the task, cancel and retry it.
console.log("Task is running with handle", handle.id);
+20 -2
View File
@@ -53,7 +53,7 @@ export const config: TriggerConfig = {
## ESM-only packages
We'll let you know when run the CLI dev command if this is a problem. Some packages are ESM-only so they don't work directly from CJS when using Node.js. In that case you need to add them to the `dependenciesToBundle` array in your `trigger.config.ts` file.
We'll let you know when running the CLI dev command if this is a problem. Some packages are ESM-only so they don't work directly from CJS when using Node.js. In that case you need to add them to the `dependenciesToBundle` array in your `trigger.config.ts` file.
```ts trigger.config.ts
import type { TriggerConfig } from "@trigger.dev/sdk/v3";
@@ -92,7 +92,10 @@ Prisma works by generating a client from your `prisma.schema` file. This means y
<Step title="package.json postinstall `prisma generate`">
```json
<CodeGroup>
```json default path
{
"scripts": {
"postinstall": "prisma generate"
@@ -100,6 +103,16 @@ Prisma works by generating a client from your `prisma.schema` file. This means y
}
```
```json custom path
{
"scripts": {
"postinstall": "prisma generate --schema=./custom/path/to/schema.prisma"
}
}
```
</CodeGroup>
Anything you put in `postinstall` will be run as part of the install step. This is how Next.js recommends you set up Prisma anyway.
</Step>
@@ -111,7 +124,12 @@ Prisma works by generating a client from your `prisma.schema` file. This means y
export const config: TriggerConfig = {
//..other stuff
// using the default path
additionalFiles: ["./prisma/schema.prisma"],
// or a custom path, for example in a monorepo
additionalFiles: ["../../custom/path/to/schema.prisma"],
additionalPackages: ["prisma@5.11.0"],
};
```
+36 -29
View File
@@ -48,7 +48,7 @@ export async function POST(request: Request) {
const data = await request.json();
//trigger your task
const handle = await emailSequence.trigger({ payload: { to: data.email, name: data.name } });
const handle = await emailSequence.trigger({ to: data.email, name: data.name });
//return a success response with the handle
return Response.json(handle);
@@ -67,7 +67,7 @@ export async function action({ request, params }: ActionFunctionArgs) {
const data = await request.json();
//trigger your task
const handle = await emailSequence.trigger({ payload: { to: data.email, name: data.name } });
const handle = await emailSequence.trigger({ to: data.email, name: data.name });
//return a success response with the handle
return json(handle);
@@ -91,9 +91,9 @@ export async function POST(request: Request) {
const data = await request.json();
//batch trigger your task
const batchHandle = await emailSequence.batchTrigger({
items: data.users.map((u) => ({ payload: { to: u.email, name: u.name } })),
});
const batchHandle = await emailSequence.batchTrigger(
data.users.map((u) => ({ payload: { to: u.email, name: u.name } }))
);
//return a success response with the handle
return Response.json(batchHandle);
@@ -112,9 +112,9 @@ export async function action({ request, params }: ActionFunctionArgs) {
const data = await request.json();
//batch trigger your task
const batchHandle = await emailSequence.batchTrigger({
items: data.users.map((u) => ({ payload: { to: u.email, name: u.name } })),
});
const batchHandle = await emailSequence.batchTrigger(
data.users.map((u) => ({ payload: { to: u.email, name: u.name } }))
);
//return a success response with the handle
return json(batchHandle);
@@ -137,7 +137,7 @@ import { myOtherTask } from "~/trigger/my-other-task";
export const myTask = task({
id: "my-task",
run: async (payload: string) => {
const handle = await myOtherTask.trigger({ payload: "some data" });
const handle = await myOtherTask.trigger("some data");
//...do other stuff
},
@@ -154,7 +154,7 @@ import { myOtherTask } from "~/trigger/my-other-task";
export const myTask = task({
id: "my-task",
run: async (payload: string) => {
const batchHandle = await myOtherTask.batchTrigger({ items: [{ payload: "some data" }] });
const batchHandle = await myOtherTask.batchTrigger([{ payload: "some data" }]);
//...do other stuff
},
@@ -168,16 +168,18 @@ This is where it gets interesting. You can trigger a task and then wait for the
<Accordion title="Don't use this in parallel, e.g. with `Promise.all()`">
Instead, use `batchTriggerAndWait()` if you can, or a for loop if you can't.
To control concurrency using batch triggers, you can set `queue.concurrencyLimit` on the child task.
To control concurrency using batch triggers, you can set `queue.concurrencyLimit` on the child task.
<CodeGroup>
```ts /trigger/batch.ts
export const batchTask = task({
id: "batch-task",
run: async (payload: string) => {
const results = await childTask.batchTriggerAndWait({
items: [{ payload: "item1" }, { payload: "item2" }],
});
const results = await childTask.batchTriggerAndWait([
{ payload: "item1" },
{ payload: "item2" },
]);
console.log("Results", results);
//...do stuff with the results
@@ -192,7 +194,7 @@ export const loopTask = task({
//this will be slower than the batch version
//as we have to resume the parent after each iteration
for (let i = 0; i < 2; i++) {
const result = await childTask.triggerAndWait({ payload: `item${i}` });
const result = await childTask.triggerAndWait(`item${i}`);
console.log("Result", result);
//...do stuff with the result
@@ -200,6 +202,7 @@ export const loopTask = task({
},
});
```
</CodeGroup>
</Accordion>
@@ -208,7 +211,7 @@ export const loopTask = task({
export const parentTask = task({
id: "parent-task",
run: async (payload: string) => {
const result = await batchChildTask.triggerAndWait({ payload: "some-data" });
const result = await batchChildTask.triggerAndWait("some-data");
console.log("Result", result);
//...do stuff with the result
@@ -223,16 +226,18 @@ You can batch trigger a task and wait for all the results. This is useful for th
<Accordion title="Don't use this in parallel, e.g. with `Promise.all()`">
Instead, pass in all items at once and set an appropriate `maxConcurrency`. Alternatively, use sequentially with a for loop.
To control concurrency, you can set `queue.concurrencyLimit` on the child task.
To control concurrency, you can set `queue.concurrencyLimit` on the child task.
<CodeGroup>
```ts /trigger/batch.ts
export const batchTask = task({
id: "batch-task",
run: async (payload: string) => {
const results = await childTask.batchTriggerAndWait({
items: [{ payload: "item1" }, { payload: "item2" }],
});
const results = await childTask.batchTriggerAndWait([
{ payload: "item1" },
{ payload: "item2" },
]);
console.log("Results", results);
//...do stuff with the results
@@ -247,9 +252,10 @@ export const loopTask = task({
//this will be slower than a single batchTriggerAndWait()
//as we have to resume the parent after each iteration
for (let i = 0; i < 2; i++) {
const result = await childTask.batchTriggerAndWait({
items: [{ payload: `itemA${i}` }, { payload: `itemB${i}` }],
});
const result = await childTask.batchTriggerAndWait([
{ payload: `itemA${i}` },
{ payload: `itemB${i}` },
]);
console.log("Result", result);
//...do stuff with the result
@@ -257,6 +263,7 @@ export const loopTask = task({
},
});
```
</CodeGroup>
</Accordion>
@@ -265,9 +272,11 @@ export const loopTask = task({
export const batchParentTask = task({
id: "parent-task",
run: async (payload: string) => {
const results = await childTask.batchTriggerAndWait({
items: [{ payload: "item4" }, { payload: "item5" }, { payload: "item6" }],
});
const results = await childTask.batchTriggerAndWait([
{ payload: "item4" },
{ payload: "item5" },
{ payload: "item6" },
]);
console.log("Results", results);
//...do stuff with the result
@@ -326,9 +335,7 @@ import { createAvatar } from "@/trigger/create-avatar";
export async function create() {
try {
const handle = await createAvatar.trigger({
payload: {
userImage: "http://...",
},
userImage: "http://...",
});
return { handle };
+2 -22
View File
@@ -164,34 +164,14 @@ We've unified triggering in v3. You use `trigger()` or `batchTrigger()` which yo
async function yourBackendFunction() {
//call `trigger()` on any task
const handle = await openaiTask.trigger({
payload: {
prompt: "Tell me a programming joke",
},
prompt: "Tell me a programming joke",
});
}
```
## Upgrading your project
<Steps>
<Step title="Upgrade the v2 Trigger.dev packages">
You can run this command to upgrade all the packages to the beta:
```bash
npx @trigger.dev/cli@beta update --to beta
```
</Step>
<Step title="Follow the v3 quick start">
Follow the [v3 quick start](/v3/quick-start) to get started with v3.
</Step>
</Steps>
Just follow the [v3 quick start](/v3/quick-start) to get started with v3. Our new CLI will take care of the rest.
## Using v2 together with v3
+33
View File
@@ -1,5 +1,38 @@
# @trigger.dev/airtable
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
- Updated dependencies [374edef02]
- Updated dependencies [26093896d]
- @trigger.dev/sdk@3.0.0-beta.15
- @trigger.dev/integration-kit@3.0.0-beta.15
## 3.0.0-beta.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/airtable",
"version": "3.0.0-beta.14",
"version": "3.0.0-beta.18",
"description": "Trigger.dev integration for airtable",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.14",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.14",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.18",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.18",
"airtable": "^0.12.1",
"zod": "3.22.3"
},
+33
View File
@@ -1,5 +1,38 @@
# @trigger.dev/github
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
- Updated dependencies [374edef02]
- Updated dependencies [26093896d]
- @trigger.dev/sdk@3.0.0-beta.15
- @trigger.dev/integration-kit@3.0.0-beta.15
## 3.0.0-beta.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/github",
"version": "3.0.0-beta.14",
"version": "3.0.0-beta.18",
"description": "The official GitHub integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -30,8 +30,8 @@
"@octokit/request-error": "^5.0.1",
"@octokit/webhooks": "^12.0.10",
"octokit": "^3.1.2",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.14",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.14",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.18",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.18",
"zod": "3.22.3"
},
"engines": {
+33
View File
@@ -1,5 +1,38 @@
# @trigger.dev/linear
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
- Updated dependencies [374edef02]
- Updated dependencies [26093896d]
- @trigger.dev/sdk@3.0.0-beta.15
- @trigger.dev/integration-kit@3.0.0-beta.15
## 3.0.0-beta.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/linear",
"version": "3.0.0-beta.14",
"version": "3.0.0-beta.18",
"description": "Trigger.dev integration for @linear/sdk",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@linear/sdk": "^8.0.0",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.14",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.14",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.18",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.18",
"zod": "3.22.3"
},
"engines": {
+33
View File
@@ -1,5 +1,38 @@
# @trigger.dev/slack
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
- Updated dependencies [374edef02]
- Updated dependencies [26093896d]
- @trigger.dev/sdk@3.0.0-beta.15
- @trigger.dev/integration-kit@3.0.0-beta.15
## 3.0.0-beta.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/openai",
"version": "3.0.0-beta.14",
"version": "3.0.0-beta.18",
"description": "The official OpenAI integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -42,8 +42,8 @@
},
"dependencies": {
"openai": "^4.16.1",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.14",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.14"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.18",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.18"
},
"engines": {
"node": ">=18.0.0"
+33
View File
@@ -1,5 +1,38 @@
# @trigger.dev/plain
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
- Updated dependencies [374edef02]
- Updated dependencies [26093896d]
- @trigger.dev/sdk@3.0.0-beta.15
- @trigger.dev/integration-kit@3.0.0-beta.15
## 3.0.0-beta.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/plain",
"version": "3.0.0-beta.14",
"version": "3.0.0-beta.18",
"description": "The official Plain.com integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"build:tsup": "tsup"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.14",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.14",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.18",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.18",
"@team-plain/typescript-sdk": "^2.7.0"
},
"engines": {
+33
View File
@@ -1,5 +1,38 @@
# @trigger.dev/replicate
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
- Updated dependencies [374edef02]
- Updated dependencies [26093896d]
- @trigger.dev/sdk@3.0.0-beta.15
- @trigger.dev/integration-kit@3.0.0-beta.15
## 3.0.0-beta.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/replicate",
"version": "3.0.0-beta.14",
"version": "3.0.0-beta.18",
"description": "Trigger.dev integration for replicate",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -25,8 +25,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.14",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.14",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.18",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.18",
"replicate": "^0.18.1",
"zod": "3.22.3"
},
+33
View File
@@ -1,5 +1,38 @@
# @trigger.dev/resend
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
- Updated dependencies [374edef02]
- Updated dependencies [26093896d]
- @trigger.dev/sdk@3.0.0-beta.15
- @trigger.dev/integration-kit@3.0.0-beta.15
## 3.0.0-beta.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/resend",
"version": "3.0.0-beta.14",
"version": "3.0.0-beta.18",
"description": "The official Resend.com integration for Trigger.dev",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -24,8 +24,8 @@
"build:tsup": "tsup"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.14",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.14",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.18",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.18",
"resend": "^2.1.0"
},
"engines": {
+33
View File
@@ -1,5 +1,38 @@
# @trigger.dev/sendgrid
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
- Updated dependencies [374edef02]
- Updated dependencies [26093896d]
- @trigger.dev/sdk@3.0.0-beta.15
- @trigger.dev/integration-kit@3.0.0-beta.15
## 3.0.0-beta.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/sendgrid",
"version": "3.0.0-beta.14",
"version": "3.0.0-beta.18",
"description": "Trigger.dev integration for @sendgrid/mail",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@sendgrid/mail": "^7.7.0",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.14",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.14"
"@trigger.dev/sdk": "workspace:^3.0.0-beta.18",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.18"
},
"engines": {
"node": ">=16.8.0"
+33
View File
@@ -1,5 +1,38 @@
# @trigger.dev/shopify
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
- @trigger.dev/integration-kit@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.17
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/integration-kit@3.0.0-beta.16
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
- Updated dependencies [374edef02]
- Updated dependencies [26093896d]
- @trigger.dev/sdk@3.0.0-beta.15
- @trigger.dev/integration-kit@3.0.0-beta.15
## 3.0.0-beta.14
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@trigger.dev/shopify",
"version": "3.0.0-beta.14",
"version": "3.0.0-beta.18",
"description": "Trigger.dev integration for @shopify/shopify-api",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -26,8 +26,8 @@
},
"dependencies": {
"@shopify/shopify-api": "^8.0.2",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.14",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.14",
"@trigger.dev/sdk": "workspace:^3.0.0-beta.18",
"@trigger.dev/integration-kit": "workspace:^3.0.0-beta.18",
"zod": "3.22.3"
},
"engines": {
+29
View File
@@ -1,5 +1,34 @@
# @trigger.dev/slack
## 3.0.0-beta.18
### Patch Changes
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- Updated dependencies [b68012f81]
- @trigger.dev/sdk@3.0.0-beta.18
## 3.0.0-beta.17
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.17
## 3.0.0-beta.16
### Patch Changes
- @trigger.dev/sdk@3.0.0-beta.16
## 3.0.0-beta.15
### Patch Changes
- Updated dependencies [374edef02]
- Updated dependencies [26093896d]
- @trigger.dev/sdk@3.0.0-beta.15
## 3.0.0-beta.14
### Patch Changes

Some files were not shown because too many files have changed in this diff Show More