Skip to content

Commit d3bdc30

Browse files
authored
Merge pull request #8 from myftija/improve-article-workflow-example
Improve the article workflow example
2 parents e98e873 + 6be7181 commit d3bdc30

15 files changed

+296
-359
lines changed
-948 KB
Binary file not shown.

article-summary-workflow/src/app/actions.ts

+34-25
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@
22

33
import type { articleWorkflow } from "@/trigger/articleWorkflow";
44
import type { ReviewPayload } from "@/trigger/reviewSummary";
5-
import { tasks, wait } from "@trigger.dev/sdk/v3";
6-
7-
// A user identifier that could be fetched from your auth mechanism.
8-
// This is out of scope for this example, so we just hardcode it.
9-
const user = "reactflowtest";
10-
const userTag = `user_${user}`;
5+
import { auth, tasks, wait } from "@trigger.dev/sdk";
116

127
const randomStr = (length: number) =>
138
[...Array(length)]
@@ -19,32 +14,46 @@ const randomStr = (length: number) =>
1914
)
2015
.join("");
2116

22-
export async function triggerArticleWorkflow(prevState: any, formData: FormData) {
17+
export async function triggerArticleWorkflow(
18+
prevState: any,
19+
formData: FormData
20+
) {
2321
const articleUrl = formData.get("articleUrl") as string;
24-
const uniqueTag = `reactflow_${randomStr(20)}`;
22+
const workflowTag = `reactflow_${randomStr(20)}`;
2523

2624
const reviewWaitpointToken = await wait.createToken({
27-
tags: [uniqueTag, userTag],
25+
tags: [workflowTag],
2826
timeout: "1h",
29-
idempotencyKey: `review-summary-${uniqueTag}`,
27+
idempotencyKey: `review-summary-${workflowTag}`,
3028
});
3129

32-
const handle = await tasks.trigger<typeof articleWorkflow>(
33-
"article-workflow",
34-
{
35-
articleUrl,
36-
approvalWaitpointTokenId: reviewWaitpointToken.id,
37-
},
38-
{
39-
tags: [uniqueTag, userTag],
40-
}
41-
);
30+
const [workflowPublicAccessToken] = await Promise.all([
31+
// We generate a public access token to use the Trigger.dev realtime API and listen to changes in task runs using react hooks.
32+
// This token has access to all runs tagged with the unique workflow tag.
33+
auth.createPublicToken({
34+
scopes: {
35+
read: {
36+
tags: [workflowTag],
37+
},
38+
},
39+
}),
40+
,
41+
tasks.trigger<typeof articleWorkflow>(
42+
"article-workflow",
43+
{
44+
articleUrl,
45+
approvalWaitpointTokenId: reviewWaitpointToken.id,
46+
},
47+
{
48+
tags: [workflowTag],
49+
}
50+
),
51+
]);
4252

4353
return {
4454
articleUrl,
45-
runId: handle.id,
46-
runTag: uniqueTag,
47-
reviewWaitpointTokenId: reviewWaitpointToken.id,
55+
workflowTag,
56+
workflowPublicAccessToken,
4857
};
4958
}
5059

@@ -54,7 +63,7 @@ export async function approveArticleSummary(tokenId: string) {
5463
{
5564
approved: true,
5665
approvedAt: new Date(),
57-
approvedBy: user,
66+
approvedBy: "Alice",
5867
}
5968
);
6069
}
@@ -65,7 +74,7 @@ export async function rejectArticleSummary(tokenId: string) {
6574
{
6675
approved: false,
6776
rejectedAt: new Date(),
68-
rejectedBy: user,
77+
rejectedBy: "Alice",
6978
reason: "It's no good",
7079
}
7180
);

article-summary-workflow/src/app/globals.css

-7
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,6 @@
1515
--font-mono: var(--font-geist-mono);
1616
}
1717

18-
@media (prefers-color-scheme: dark) {
19-
:root {
20-
--background: #0a0a0a;
21-
--foreground: #ededed;
22-
}
23-
}
24-
2518
body {
2619
background: var(--background);
2720
color: var(--foreground);

article-summary-workflow/src/app/page.tsx

+3-21
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,20 @@ import React from "react";
22
import Flow from "@/components/Flow";
33
import Image from "next/image";
44
import logo from "./logo.svg";
5-
import { auth } from "@trigger.dev/sdk";
65

76
export default async function Home() {
8-
// A user identifier that could be fetched from your auth mechanism.
9-
// This is out of scope for this example, so we just hardcode it.
10-
const user = "reactflowtest";
11-
const userTag = `user_${user}`;
12-
13-
// We generate a public access token to use the Trigger.dev realtime API and listen to changes in task runs.
14-
// Depending on your setup, you might want to be more granular in the scopes you grant.
15-
// Check the frontend usage docs for a comprehensive list of the approaches to authenticate:
16-
// https://trigger.dev/docs/frontend/overview#authentication
17-
const publicAccessToken = await auth.createPublicToken({
18-
scopes: {
19-
read: {
20-
tags: [userTag],
21-
},
22-
},
23-
});
24-
257
return (
268
<div className="flex flex-col items-center justify-center h-screen p-5 gap-5">
279
<div className="flex flex-col items-center justify-center gap-2">
2810
<Image src={logo} alt="Logo" width={180} />
2911
<p className="text-sm text-zinc-500 max-w-[420px] text-center">
30-
This reference project that shows a possible approach to implement workflows using{" "}
31-
<span className="font-bold">Trigger.dev</span> and{" "}
12+
This reference project that shows a possible approach to implement
13+
workflows using <span className="font-bold">Trigger.dev</span> and{" "}
3214
<span className="font-bold">ReactFlow</span>
3315
</p>
3416
</div>
3517
<div className="grow w-full max-w-[1500px]">
36-
<Flow triggerPublicAccessToken={publicAccessToken} triggerUserTag={userTag} />
18+
<Flow />
3719
</div>
3820
</div>
3921
);

article-summary-workflow/src/components/ActionNode.tsx

+14-61
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
"use client";
22

3-
import React, { useEffect } from "react";
4-
import { Handle, Position, NodeProps, Node, useReactFlow } from "@xyflow/react";
3+
import React from "react";
4+
import { Handle, Position, NodeProps, Node } from "@xyflow/react";
55
import type { LucideIcon } from "lucide-react";
66
import { Loader2, Check, X, Layers, Asterisk, RefreshCcw } from "lucide-react";
77
import { Tooltip } from "react-tippy";
8-
import { useRealtimeRunsWithTag } from "@trigger.dev/react-hooks";
8+
import type { RealtimeRun, AnyTask } from "@trigger.dev/sdk";
99
import { cn } from "@/lib/cn";
1010

1111
export type ActionNodeData = Node<
@@ -15,24 +15,7 @@ export type ActionNodeData = Node<
1515
isTerminalAction?: boolean;
1616
trigger: {
1717
taskIdentifier: string;
18-
userTag: string;
19-
currentRunTag?: string;
20-
currentRunStatus?:
21-
| "WAITING_FOR_DEPLOY"
22-
| "PENDING_VERSION"
23-
| "QUEUED"
24-
| "EXECUTING"
25-
| "REATTEMPTING"
26-
| "FROZEN"
27-
| "COMPLETED"
28-
| "CANCELED"
29-
| "FAILED"
30-
| "CRASHED"
31-
| "INTERRUPTED"
32-
| "SYSTEM_FAILURE"
33-
| "DELAYED"
34-
| "EXPIRED"
35-
| "TIMED_OUT";
18+
currentRun?: RealtimeRun<AnyTask>;
3619
};
3720
},
3821
"action"
@@ -50,65 +33,35 @@ const triggerStatusToIcon: Record<string, React.ElementType> = {
5033
FAILED: X,
5134
};
5235

53-
function ActionNode({ id, data }: NodeProps<ActionNodeData>) {
54-
const { runs } = useRealtimeRunsWithTag(data.trigger.userTag);
55-
const { updateNodeData } = useReactFlow<ActionNodeData>();
56-
57-
useEffect(() => {
58-
if (
59-
!data.trigger.currentRunTag &&
60-
data.trigger.currentRunStatus !== undefined
61-
) {
62-
updateNodeData(id, {
63-
trigger: { ...data.trigger, currentRunStatus: undefined },
64-
});
65-
return;
66-
}
67-
68-
const run = runs.find(
69-
(run) =>
70-
run.tags.includes(data.trigger.currentRunTag as string) &&
71-
run.taskIdentifier === data.trigger.taskIdentifier
72-
);
73-
if (!run) {
74-
if (data.trigger.currentRunStatus !== undefined) {
75-
updateNodeData(id, {
76-
trigger: { ...data.trigger, currentRunStatus: undefined },
77-
});
78-
}
79-
return;
80-
}
81-
updateNodeData(id, {
82-
trigger: { ...data.trigger, currentRunStatus: run.status },
83-
});
84-
}, [runs, id, updateNodeData]);
36+
function ActionNode({ data }: NodeProps<ActionNodeData>) {
37+
const { currentRun } = data.trigger;
8538

8639
return (
8740
<div className="px-4 py-2 shadow-md rounded-lg bg-white border-1 border-zinc-200 text-sm relative">
88-
{data.trigger.currentRunStatus && (
41+
{currentRun && (
8942
<div
9043
className={cn(
9144
"absolute -top-1.5 -right-1.5 flex items-center justify-center w-4 h-4 rounded-full bg-gray-400",
9245
{
93-
"bg-blue-400": data.trigger.currentRunStatus === "EXECUTING",
94-
"bg-yellow-400": data.trigger.currentRunStatus === "REATTEMPTING",
95-
"bg-emerald-400": data.trigger.currentRunStatus === "COMPLETED",
96-
"bg-red-400": data.trigger.currentRunStatus === "FAILED",
46+
"bg-blue-400": currentRun.status === "EXECUTING",
47+
"bg-yellow-400": currentRun.status === "REATTEMPTING",
48+
"bg-emerald-400": currentRun.status === "COMPLETED",
49+
"bg-red-400": currentRun.status === "FAILED",
9750
}
9851
)}
9952
>
10053
{/* @ts-ignore - there is some weird type issue with react-tippy */}
10154
<Tooltip
102-
title={data.trigger.currentRunStatus.toLowerCase()}
55+
title={currentRun.status.toLowerCase()}
10356
position="right"
10457
trigger="mouseenter"
10558
size="small"
10659
>
10760
{React.createElement(
108-
triggerStatusToIcon[data.trigger.currentRunStatus] ?? Asterisk,
61+
triggerStatusToIcon[currentRun.status] ?? Asterisk,
10962
{
11063
className: cn("size-3 text-white", {
111-
"animate-spin": data.trigger.currentRunStatus === "EXECUTING",
64+
"animate-spin": currentRun.status === "EXECUTING",
11265
}),
11366
}
11467
)}

0 commit comments

Comments
 (0)