orcheo-canvas 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.bumpversion.cfg CHANGED
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 0.2.7
2
+ current_version = 0.2.9
3
3
  commit = true
4
4
  tag = true
5
5
  tag_name = canvas-v{new_version}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "orcheo-canvas",
3
3
  "private": false,
4
- "version": "0.2.7",
4
+ "version": "0.2.9",
5
5
  "type": "module",
6
6
  "description": "Canvas UI for Orcheo workflow orchestration platform",
7
7
  "keywords": [
package/vite.config.ts CHANGED
@@ -18,26 +18,34 @@ export default defineConfig({
18
18
  }
19
19
  },
20
20
  // Fix for CJS/ESM compatibility issues with React 19
21
- // These packages are CommonJS but imported by ESM modules, causing issues in dev server
21
+ // Force pre-bundling of all dependencies to handle CJS->ESM conversion
22
22
  optimizeDeps: {
23
+ // Force re-optimization to ensure all deps are bundled
24
+ force: true,
25
+ // Only exclude test-related packages that shouldn't be in production
26
+ exclude: ['vitest', '@testing-library/react', '@testing-library/jest-dom', '@testing-library/user-event'],
27
+ // Explicitly include all known problematic CJS packages and their subpaths
23
28
  include: [
24
- // Used by zustand, swr, @radix-ui
29
+ // React ecosystem
30
+ 'react',
31
+ 'react-dom',
32
+ 'react-dom/client',
33
+ 'react/jsx-runtime',
34
+ 'react/jsx-dev-runtime',
35
+ // use-sync-external-store (used by zustand, swr, @radix-ui)
36
+ 'use-sync-external-store',
25
37
  'use-sync-external-store/shim',
26
38
  'use-sync-external-store/shim/with-selector',
27
- // Used by react-split, react-big-calendar, etc.
39
+ 'use-sync-external-store/shim/with-selector.js',
40
+ // prop-types (used by many React libs)
28
41
  'prop-types',
29
- 'react-split',
30
- // Used by @rjsf/utils and @rjsf/validator-ajv8
31
- 'jsonpointer',
32
- 'json-schema-merge-allof',
33
42
  'react-is',
43
+ // @rjsf dependencies
34
44
  'ajv',
35
45
  'ajv-formats',
36
- // Used by react-big-calendar, antd, etc.
37
- 'dayjs',
38
- 'invariant',
39
- 'dom-helpers',
40
- // Lodash submodules used by @rjsf
46
+ 'jsonpointer',
47
+ 'json-schema-merge-allof',
48
+ // lodash submodules used by @rjsf
41
49
  'lodash/get',
42
50
  'lodash/set',
43
51
  'lodash/has',
@@ -66,11 +74,27 @@ export default defineConfig({
66
74
  'lodash/setWith',
67
75
  'lodash/pickBy',
68
76
  'lodash/unset',
77
+ 'lodash/defaultsDeep',
78
+ 'lodash/flatten',
79
+ // Other CJS packages
80
+ 'react-split',
81
+ 'dom-helpers',
82
+ 'dayjs',
83
+ 'invariant',
84
+ 'copy-to-clipboard',
85
+ 'moment',
86
+ 'moment-timezone',
87
+ 'papaparse',
88
+ // zustand and related
89
+ 'zustand',
90
+ 'zustand/traditional',
91
+ 'zustand/middleware',
69
92
  ]
70
93
  },
71
94
  build: {
72
95
  commonjsOptions: {
73
- include: [/use-sync-external-store/, /node_modules/]
96
+ include: [/node_modules/],
97
+ transformMixedEsModules: true,
74
98
  }
75
99
  },
76
100
  server: {
package/src/App.test.tsx DELETED
@@ -1,10 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { render, screen } from "@testing-library/react";
3
- import App from "./App";
4
-
5
- describe("App", () => {
6
- it("renders the Orcheo Canvas navigation", () => {
7
- render(<App />);
8
- expect(screen.getByText(/Orcheo Canvas/i)).toBeInTheDocument();
9
- });
10
- });
@@ -1,117 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { buildPublicChatFetch } from "./chatkit-client";
3
-
4
- const originalFetch = window.fetch;
5
-
6
- afterEach(() => {
7
- window.fetch = originalFetch;
8
- vi.restoreAllMocks();
9
- });
10
-
11
- const createResponse = (status: number, body: unknown) =>
12
- new Response(JSON.stringify(body), {
13
- status,
14
- headers: { "Content-Type": "application/json" },
15
- });
16
-
17
- describe("buildPublicChatFetch", () => {
18
- it("injects workflow id into JSON bodies", async () => {
19
- const fetchMock = vi.fn(async () => createResponse(200, { ok: true }));
20
- window.fetch = fetchMock as unknown as typeof window.fetch;
21
-
22
- const handler = buildPublicChatFetch({
23
- workflowId: "wf-123",
24
- backendBaseUrl: "http://localhost:8000",
25
- metadata: { workflow_name: "LangGraph" },
26
- });
27
-
28
- await handler("http://localhost:8000/api/chatkit", {
29
- method: "POST",
30
- headers: { "Content-Type": "application/json" },
31
- body: JSON.stringify({ foo: "bar" }),
32
- });
33
-
34
- expect(fetchMock).toHaveBeenCalledTimes(1);
35
- const [, options] = fetchMock.mock.calls[0]!;
36
- expect(options?.credentials).toBe("include");
37
-
38
- const payload = JSON.parse((options?.body as string) ?? "{}");
39
- expect(payload.workflow_id).toBe("wf-123");
40
- expect(payload.foo).toBe("bar");
41
- expect(payload.metadata.workflow_id).toBe("wf-123");
42
- expect(payload.metadata.workflow_name).toBe("LangGraph");
43
- });
44
-
45
- it("emits structured errors when the backend rejects a request", async () => {
46
- const fetchMock = vi.fn(async () =>
47
- createResponse(401, {
48
- code: "chatkit.auth.oauth_required",
49
- message: "login first",
50
- }),
51
- );
52
- window.fetch = fetchMock as unknown as typeof window.fetch;
53
-
54
- const onHttpError = vi.fn();
55
- const handler = buildPublicChatFetch({
56
- workflowId: "wf-123",
57
- onHttpError,
58
- });
59
-
60
- await handler("http://localhost:8000/api/chatkit", {
61
- method: "POST",
62
- headers: { "Content-Type": "application/json" },
63
- body: JSON.stringify({}),
64
- });
65
-
66
- expect(onHttpError).toHaveBeenCalledWith({
67
- status: 401,
68
- message: "login first",
69
- code: "chatkit.auth.oauth_required",
70
- });
71
- });
72
-
73
- it("merges existing metadata without overwriting it", async () => {
74
- const fetchMock = vi.fn(async () => createResponse(200, { ok: true }));
75
- window.fetch = fetchMock as unknown as typeof window.fetch;
76
-
77
- const handler = buildPublicChatFetch({
78
- workflowId: "wf-789",
79
- metadata: { injected: "value" },
80
- });
81
-
82
- await handler("http://localhost:8000/api/chatkit", {
83
- method: "POST",
84
- headers: { "Content-Type": "application/json" },
85
- body: JSON.stringify({
86
- metadata: { existing: "field" },
87
- }),
88
- });
89
-
90
- const [, options] = fetchMock.mock.calls[0]!;
91
- const payload = JSON.parse((options?.body as string) ?? "{}");
92
- expect(payload.metadata).toMatchObject({
93
- existing: "field",
94
- injected: "value",
95
- workflow_id: "wf-789",
96
- });
97
- });
98
-
99
- it("does not inject Authorization headers by default", async () => {
100
- const fetchMock = vi.fn(async () => createResponse(200, { ok: true }));
101
- window.fetch = fetchMock as unknown as typeof window.fetch;
102
-
103
- const handler = buildPublicChatFetch({
104
- workflowId: "wf-222",
105
- });
106
-
107
- await handler("http://localhost:8000/api/chatkit", {
108
- method: "POST",
109
- headers: { "Content-Type": "application/json" },
110
- body: JSON.stringify({}),
111
- });
112
-
113
- const [, options] = fetchMock.mock.calls[0]!;
114
- const headers = new Headers(options?.headers ?? {});
115
- expect(headers.has("Authorization")).toBe(false);
116
- });
117
- });
@@ -1,183 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { cleanup, render, screen } from "@testing-library/react";
3
- import userEvent from "@testing-library/user-event";
4
- import type { TraceRecord, TraceSpan } from "@evilmartians/agent-prism-types";
5
-
6
- type LayoutMockProps = {
7
- selectedTraceId?: string;
8
- filteredSpans: TraceSpan[];
9
- handleTraceSelect: (trace: TraceRecord) => void;
10
- traceRecords: TraceRecord[];
11
- };
12
-
13
- const desktopLayoutMock = vi.fn((props: unknown) => {
14
- const { selectedTraceId, filteredSpans, handleTraceSelect, traceRecords } =
15
- props as LayoutMockProps;
16
-
17
- return (
18
- <div data-testid="desktop-layout">
19
- <span data-testid="selected-trace-id">{selectedTraceId ?? "none"}</span>
20
- <span data-testid="span-count">{filteredSpans.length}</span>
21
- <button
22
- type="button"
23
- onClick={() => handleTraceSelect(traceRecords.at(-1)!)}
24
- >
25
- select-last-trace
26
- </button>
27
- </div>
28
- );
29
- });
30
-
31
- vi.mock("../shared", () => ({
32
- useIsMobile: () => false,
33
- }));
34
-
35
- vi.mock("./TraceViewerDesktopLayout", () => ({
36
- TraceViewerDesktopLayout: (props: unknown) => desktopLayoutMock(props),
37
- }));
38
-
39
- vi.mock("./TraceViewerMobileLayout", () => ({
40
- TraceViewerMobileLayout: () => null,
41
- }));
42
-
43
- import { TraceViewer, type TraceViewerData } from "./TraceViewer";
44
-
45
- const createSpan = (id: string): TraceSpan => ({
46
- id,
47
- title: `Span ${id}`,
48
- startTime: new Date("2024-01-01T00:00:00Z"),
49
- endTime: new Date("2024-01-01T00:00:01Z"),
50
- duration: 1000,
51
- type: "llm_call",
52
- raw: "{}",
53
- status: "success",
54
- });
55
-
56
- const createViewerData = (id: string, spanCount: number): TraceViewerData => {
57
- const spans = Array.from({ length: spanCount }, (_, index) =>
58
- createSpan(`${id}-span-${index}`),
59
- );
60
-
61
- return {
62
- traceRecord: {
63
- id,
64
- name: `Trace ${id}`,
65
- spansCount: spanCount,
66
- durationMs: spanCount * 100,
67
- agentDescription: "agent",
68
- totalTokens: spanCount * 10,
69
- startTime: Date.now(),
70
- },
71
- spans,
72
- };
73
- };
74
-
75
- describe("TraceViewer", () => {
76
- afterEach(() => {
77
- cleanup();
78
- desktopLayoutMock.mockClear();
79
- });
80
-
81
- it("focuses the active trace id and refreshes spans when data updates", () => {
82
- const initialData = [
83
- createViewerData("trace-1", 1),
84
- createViewerData("trace-2", 1),
85
- ];
86
-
87
- const { rerender } = render(
88
- <TraceViewer data={initialData} activeTraceId="trace-2" />,
89
- );
90
-
91
- const selectedTraceIds = () =>
92
- screen
93
- .getAllByTestId("selected-trace-id")
94
- .map((element) => element.textContent);
95
- const spanCounts = () =>
96
- screen.getAllByTestId("span-count").map((element) => element.textContent);
97
-
98
- expect(selectedTraceIds()).toContain("trace-2");
99
- expect(spanCounts()).toContain("1");
100
-
101
- const updatedTrace = {
102
- ...initialData[1],
103
- traceRecord: {
104
- ...initialData[1].traceRecord,
105
- spansCount: initialData[1].traceRecord.spansCount + 1,
106
- },
107
- spans: [...initialData[1].spans, createSpan("trace-2-span-1")],
108
- } satisfies TraceViewerData;
109
-
110
- rerender(
111
- <TraceViewer
112
- data={[initialData[0], updatedTrace]}
113
- activeTraceId="trace-2"
114
- />,
115
- );
116
-
117
- expect(selectedTraceIds()).toContain("trace-2");
118
- expect(spanCounts()).toContain("2");
119
- });
120
-
121
- it("preserves manual selections across data refreshes", async () => {
122
- const user = userEvent.setup();
123
- const initialData = [
124
- createViewerData("trace-1", 1),
125
- createViewerData("trace-2", 1),
126
- ];
127
-
128
- const { rerender } = render(<TraceViewer data={initialData} />);
129
-
130
- const [selectLastTraceButton] = screen.getAllByRole("button", {
131
- name: /select-last-trace/i,
132
- });
133
-
134
- await user.click(selectLastTraceButton);
135
-
136
- expect(
137
- screen
138
- .getAllByTestId("selected-trace-id")
139
- .map((element) => element.textContent),
140
- ).toContain("trace-2");
141
-
142
- const refreshedTrace = {
143
- ...initialData[1],
144
- traceRecord: {
145
- ...initialData[1].traceRecord,
146
- spansCount: initialData[1].traceRecord.spansCount + 1,
147
- },
148
- spans: [...initialData[1].spans, createSpan("trace-2-span-1")],
149
- } satisfies TraceViewerData;
150
-
151
- rerender(<TraceViewer data={[initialData[0], refreshedTrace]} />);
152
-
153
- expect(
154
- screen
155
- .getAllByTestId("selected-trace-id")
156
- .map((element) => element.textContent),
157
- ).toContain("trace-2");
158
- expect(
159
- screen.getAllByTestId("span-count").map((element) => element.textContent),
160
- ).toContain("2");
161
- });
162
-
163
- it("invokes onTraceSelect when a user picks a trace", () => {
164
- const initialData = [
165
- createViewerData("trace-1", 1),
166
- createViewerData("trace-2", 1),
167
- ];
168
- const handleTraceSelect = vi.fn();
169
-
170
- render(
171
- <TraceViewer data={initialData} onTraceSelect={handleTraceSelect} />,
172
- );
173
-
174
- const lastCall = desktopLayoutMock.mock.calls.at(
175
- -1,
176
- )?.[0] as LayoutMockProps;
177
- lastCall.handleTraceSelect(lastCall.traceRecords.at(-1)!);
178
-
179
- expect(handleTraceSelect).toHaveBeenCalledWith(
180
- expect.objectContaining({ id: "trace-2" }),
181
- );
182
- });
183
- });
@@ -1,168 +0,0 @@
1
- import type { Edge, Node } from "@xyflow/react";
2
- import { describe, expect, it } from "vitest";
3
-
4
- import { buildGraphConfigFromCanvas } from "./graph-config";
5
-
6
- describe("buildGraphConfigFromCanvas integration - logic and utility nodes", () => {
7
- it("serializes logic and utility nodes for backend consumption", async () => {
8
- const nodes: Node[] = [
9
- {
10
- id: "prep-1",
11
- type: "utility",
12
- position: { x: -1, y: 0 },
13
- data: {
14
- label: "Prepare score",
15
- backendType: "SetVariableNode",
16
- variables: [
17
- { name: "state.user.score", valueType: "number", value: "8" },
18
- ],
19
- },
20
- } as Node,
21
- {
22
- id: "if-1",
23
- type: "logic",
24
- position: { x: 0, y: 0 },
25
- data: {
26
- label: "Decision",
27
- backendType: "IfElseNode",
28
- conditions: [
29
- {
30
- id: "cond-1",
31
- left: "{{ state.user.score }}",
32
- operator: "greater_than",
33
- right: 5,
34
- caseSensitive: false,
35
- },
36
- {
37
- id: "cond-2",
38
- left: true,
39
- operator: "is_truthy",
40
- right: null,
41
- caseSensitive: true,
42
- },
43
- ],
44
- conditionLogic: "and",
45
- },
46
- } as Node,
47
- {
48
- id: "set-1",
49
- type: "utility",
50
- position: { x: 1, y: 0 },
51
- data: {
52
- label: "Assign",
53
- backendType: "SetVariableNode",
54
- variables: [
55
- { name: "profile.name", valueType: "string", value: "Ada" },
56
- { name: "profile.score", valueType: "number", value: "42" },
57
- {
58
- name: "preferences",
59
- valueType: "object",
60
- value: { theme: "dark" },
61
- },
62
- {
63
- name: "flags",
64
- valueType: "array",
65
- value: ["beta", "ops"],
66
- },
67
- { name: "isActive", valueType: "boolean", value: "true" },
68
- ],
69
- },
70
- } as Node,
71
- {
72
- id: "delay-1",
73
- type: "utility",
74
- position: { x: 2, y: 0 },
75
- data: {
76
- label: "Delay",
77
- backendType: "DelayNode",
78
- durationSeconds: "2.5",
79
- },
80
- } as Node,
81
- ];
82
-
83
- const edges: Edge[] = [
84
- { id: "prep-to-if", source: "prep-1", target: "if-1" } as Edge,
85
- {
86
- id: "if-to-set",
87
- source: "if-1",
88
- target: "set-1",
89
- sourceHandle: "true",
90
- } as Edge,
91
- {
92
- id: "if-to-delay",
93
- source: "if-1",
94
- target: "delay-1",
95
- sourceHandle: "false",
96
- } as Edge,
97
- { id: "set-to-delay", source: "set-1", target: "delay-1" } as Edge,
98
- ];
99
-
100
- const { config, canvasToGraph, graphToCanvas, warnings } =
101
- await buildGraphConfigFromCanvas(nodes, edges);
102
-
103
- expect(warnings).toHaveLength(0);
104
-
105
- const prepName = canvasToGraph["prep-1"];
106
- const ifElseName = canvasToGraph["if-1"];
107
- const setVariableName = canvasToGraph["set-1"];
108
- const delayName = canvasToGraph["delay-1"];
109
-
110
- expect(prepName).toBeDefined();
111
- expect(ifElseName).toBeDefined();
112
- expect(graphToCanvas[ifElseName]).toBe("if-1");
113
-
114
- expect(config.nodes.some((node) => node.name === ifElseName)).toBe(false);
115
- expect(config.edge_nodes).toBeDefined();
116
-
117
- const ifElseNode = config.edge_nodes?.find(
118
- (node) => node.name === ifElseName,
119
- );
120
- expect(ifElseNode).toBeDefined();
121
- expect(ifElseNode).toMatchObject({
122
- type: "IfElseNode",
123
- condition_logic: "and",
124
- });
125
- expect(ifElseNode?.conditions).toEqual([
126
- expect.objectContaining({
127
- left: "{{ state.user.score }}",
128
- operator: "greater_than",
129
- right: 5,
130
- case_sensitive: false,
131
- }),
132
- expect.objectContaining({
133
- left: true,
134
- operator: "is_truthy",
135
- case_sensitive: true,
136
- }),
137
- ]);
138
-
139
- const setVariableNode = config.nodes.find(
140
- (node) => node.name === setVariableName,
141
- );
142
- expect(setVariableNode).toBeDefined();
143
- expect(setVariableNode?.variables).toEqual({
144
- "profile.name": "Ada",
145
- "profile.score": 42,
146
- preferences: { theme: "dark" },
147
- flags: ["beta", "ops"],
148
- isActive: true,
149
- });
150
-
151
- const delayNode = config.nodes.find((node) => node.name === delayName);
152
- expect(delayNode).toMatchObject({
153
- type: "DelayNode",
154
- duration_seconds: 2.5,
155
- });
156
-
157
- expect(config.conditional_edges).toContainEqual({
158
- source: prepName,
159
- path: ifElseName,
160
- mapping: { true: setVariableName, false: delayName },
161
- });
162
-
163
- expect(config.edges).toContainEqual({
164
- source: setVariableName,
165
- target: delayName,
166
- });
167
- });
168
- });
@@ -1,68 +0,0 @@
1
- import type { Edge, Node } from "@xyflow/react";
2
- import { describe, expect, it } from "vitest";
3
-
4
- import { buildGraphConfigFromCanvas } from "./graph-config";
5
-
6
- describe("buildGraphConfigFromCanvas integration - start/end filtering", () => {
7
- it("filters out canvas start and end nodes from serialization", async () => {
8
- const nodes: Node[] = [
9
- {
10
- id: "start-node",
11
- type: "start",
12
- position: { x: 0, y: 0 },
13
- data: { label: "Workflow Start", type: "start" },
14
- } as Node,
15
- {
16
- id: "set-var",
17
- type: "function",
18
- position: { x: 100, y: 0 },
19
- data: {
20
- label: "Set Variable",
21
- backendType: "SetVariableNode",
22
- variables: [
23
- { name: "my_variable", valueType: "string", value: "sample" },
24
- { name: "num", valueType: "number", value: 2 },
25
- ],
26
- },
27
- } as Node,
28
- {
29
- id: "end-node",
30
- type: "end",
31
- position: { x: 200, y: 0 },
32
- data: { label: "Workflow End", type: "end" },
33
- } as Node,
34
- ];
35
-
36
- const edges: Edge[] = [
37
- { id: "start-to-set", source: "start-node", target: "set-var" } as Edge,
38
- { id: "set-to-end", source: "set-var", target: "end-node" } as Edge,
39
- ];
40
-
41
- const { config, canvasToGraph, warnings } =
42
- await buildGraphConfigFromCanvas(nodes, edges);
43
-
44
- expect(warnings).toHaveLength(0);
45
- expect(canvasToGraph["start-node"]).toBeUndefined();
46
- expect(canvasToGraph["end-node"]).toBeUndefined();
47
-
48
- expect(canvasToGraph["set-var"]).toBeDefined();
49
- const setVarName = canvasToGraph["set-var"];
50
-
51
- expect(config.nodes).toHaveLength(3);
52
- expect(config.nodes[0]).toMatchObject({ name: "START", type: "START" });
53
- expect(config.nodes[1]).toMatchObject({
54
- name: setVarName,
55
- type: "SetVariableNode",
56
- });
57
- expect(config.nodes[2]).toMatchObject({ name: "END", type: "END" });
58
-
59
- expect(config.edges).toContainEqual({
60
- source: "START",
61
- target: setVarName,
62
- });
63
- expect(config.edges).toContainEqual({
64
- source: setVarName,
65
- target: "END",
66
- });
67
- });
68
- });