diff --git a/.changeset/nested-editing-stories.md b/.changeset/nested-editing-stories.md new file mode 100644 index 00000000..e0279708 --- /dev/null +++ b/.changeset/nested-editing-stories.md @@ -0,0 +1,5 @@ +--- +"@openworkflowspec/diagram-editor": minor +--- + +add nested editing sample workflows diff --git a/packages/open-workflow-diagram-editor/.storybook/preview.tsx b/packages/open-workflow-diagram-editor/.storybook/preview.tsx index c1d56a04..5c81649d 100644 --- a/packages/open-workflow-diagram-editor/.storybook/preview.tsx +++ b/packages/open-workflow-diagram-editor/.storybook/preview.tsx @@ -43,7 +43,7 @@ const preview: Preview = { options: { storySort: { - order: ["Introduction", "Features", "Examples", "Use Cases"], + order: ["Introduction", "Features", "Examples", "Use Cases", "Nested Editing"], }, }, }, diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/NestedEditing.stories.tsx b/packages/open-workflow-diagram-editor/stories/nested-editing/NestedEditing.stories.tsx new file mode 100644 index 00000000..6ae8d228 --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/NestedEditing.stories.tsx @@ -0,0 +1,60 @@ +/* + * Copyright 2021-Present The Open Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { DiagramEditor } from "../features/DiagramEditor"; +import * as workflows from "./index"; + +const meta = { + title: "Nested Editing/Workflows", + component: DiagramEditor, + parameters: { + layout: "fullscreen", + }, + render: (args, { globals }) => { + return ; + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const DEFAULT_STORY_ARGS = { + isReadOnly: false, + locale: "en" as const, +} as const; + +const createWorkflowStory = (workflowContent: string): Story => ({ + args: { + ...DEFAULT_STORY_ARGS, + content: workflowContent, + }, +}); + +export const AllTaskTypes: Story = createWorkflowStory(workflows.allTaskTypes); +export const CallEndpointUnion: Story = createWorkflowStory(workflows.callEndpointUnion); +export const CallHeadersMap: Story = createWorkflowStory(workflows.callHeadersMap); +export const ListenDeepNesting: Story = createWorkflowStory(workflows.listenDeepNesting); +export const NestedValidation: Story = createWorkflowStory(workflows.nestedValidation); +export const RunTaskArray: Story = createWorkflowStory(workflows.runTaskArray); +export const SetOpenMap: Story = createWorkflowStory(workflows.setOpenMap); +export const SwitchLockedCases: Story = { + args: { + ...DEFAULT_STORY_ARGS, + isReadOnly: true, + content: workflows.switchLockedCases, + }, +}; diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/Overview.mdx b/packages/open-workflow-diagram-editor/stories/nested-editing/Overview.mdx new file mode 100644 index 00000000..708b1460 --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/Overview.mdx @@ -0,0 +1,27 @@ +{/* + * Copyright 2021-Present The Open Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */} + +import { Meta } from "@storybook/addon-docs/blocks"; + + + +# Nested Editing + +These stories provide isolated environments for building and testing the nested editing system. +Each story targets a specific interaction pattern introduced by a particular task type or shape, +making it easy to develop, review, and regression-test editing behaviour without noise from unrelated features. + +The **All Task Types** story includes all 12 task types (`call`, `set`, `run`, `wait`, `fork`, `listen`, `emit`, `for`, `try/catch`, `switch`, `do`, `raise`) in a single workflow, providing a reference for how each task appears in the diagram editor. diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/index.ts b/packages/open-workflow-diagram-editor/stories/nested-editing/index.ts new file mode 100644 index 00000000..7eefa954 --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2021-Present The Open Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { default as allTaskTypes } from "./workflows/all-task-types.yaml?raw"; +export { default as callEndpointUnion } from "./workflows/call-endpoint-union.yaml?raw"; +export { default as callHeadersMap } from "./workflows/call-headers-map.yaml?raw"; +export { default as listenDeepNesting } from "./workflows/listen-deep-nesting.yaml?raw"; +export { default as nestedValidation } from "./workflows/nested-validation.yaml?raw"; +export { default as runTaskArray } from "./workflows/run-task-array.yaml?raw"; +export { default as setOpenMap } from "./workflows/set-open-map.yaml?raw"; +export { default as switchLockedCases } from "./workflows/switch-locked-cases.yaml?raw"; diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/all-task-types.yaml b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/all-task-types.yaml new file mode 100644 index 00000000..6fac1288 --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/all-task-types.yaml @@ -0,0 +1,159 @@ +# +# Copyright 2021-Present The Open Workflow Specification Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +document: + dsl: "1.0.3" + namespace: examples + name: all-task-types + version: "0.1.0" +use: + errors: + notFound: + type: https://example.com/errors/not-found + status: 404 + title: Resource Not Found +do: + - callHttpTask: + call: http + with: + method: get + endpoint: https://api.example.com/users + headers: + Accept: application/json + X-Api-Key: ${ .apiKey } + query: + page: "1" + limit: "20" + - setContextTask: + set: + userId: ${ .users[0].id } + userName: ${ .users[0].name } + fetchedAt: ${ now } + meta: + source: api.example.com + version: v2 + - runScriptTask: + run: + script: + language: javascript + arguments: + - --user-id + - ${ .userId } + environment: + NODE_ENV: production + code: | + const [,, flag, id] = process.argv; + console.log(JSON.stringify({ processed: id })); + - waitTask: + wait: + seconds: 5 + - forkTask: + fork: + compete: false + branches: + - notifyEmail: + call: http + with: + method: post + endpoint: https://api.example.com/notifications/email + body: + userId: ${ .userId } + - notifySlack: + call: http + with: + method: post + endpoint: https://api.example.com/notifications/slack + body: + userId: ${ .userId } + - listenTask: + listen: + to: + any: + - with: + type: com.example.user.verified + correlate: + userId: + from: .subject + - with: + type: com.example.user.rejected + correlate: + userId: + from: .subject + - emitTask: + emit: + event: + with: + source: https://api.example.com + type: com.example.user.processed.v1 + data: + userId: ${ .userId } + userName: ${ .userName } + - forTask: + for: + each: role + in: ${ .roles } + at: index + do: + - assignRole: + call: http + with: + method: post + endpoint: https://api.example.com/users/${ .userId }/roles + body: + role: ${ .role } + - tryTask: + try: + - fetchPreferences: + call: http + with: + method: get + endpoint: https://api.example.com/users/${ .userId }/preferences + catch: + errors: + with: + type: https://open-workflow-specification.org/spec/1.0.0/errors/communication + status: 404 + as: error + do: + - setDefaultPreferences: + set: + preferences: + theme: default + language: en + - switchTask: + switch: + - verified: + when: ${ .eventType == "com.example.user.verified" } + then: doNestedTask + - rejected: + when: ${ .eventType == "com.example.user.rejected" } + then: raiseErrorTask + - default: + then: raiseErrorTask + - doNestedTask: + do: + - enrichProfile: + call: http + with: + method: get + endpoint: https://api.example.com/profile/${ .userId } + - storeProfile: + set: + profile: ${ .body } + enrichedAt: ${ now } + then: exit + - raiseErrorTask: + raise: + error: notFound diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/call-endpoint-union.yaml b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/call-endpoint-union.yaml new file mode 100644 index 00000000..7323693f --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/call-endpoint-union.yaml @@ -0,0 +1,66 @@ +# +# Copyright 2021-Present The Open Workflow Specification Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Nested editing focus: endpoint union — the endpoint field can be a plain string URI +# or a structured object with uri + authentication. Both forms appear here so the +# editor can be tested switching between union variants inside nested do containers. +document: + dsl: "1.0.3" + namespace: examples + name: call-endpoint-union + version: "0.1.0" +do: + - prepareRequest: + set: + serviceToken: ${ .auth.token } + inputData: ${ .payload } + - callServices: + do: + - callPublicApi: + call: http + with: + method: get + endpoint: https://public.api.example.com/catalogue + query: + category: ${ .category } + - callSecureApi: + call: http + with: + method: post + endpoint: + uri: https://secure.api.example.com/process + authentication: + bearer: + token: ${ .serviceToken } + body: + payload: ${ .inputData } + - callOAuthApi: + call: http + with: + method: get + endpoint: + uri: https://oauth.api.example.com/data + authentication: + oauth2: + authority: https://auth.example.com + grant: client_credentials + client: + id: ${ .clientId } + secret: ${ .clientSecret } + - mergeResults: + set: + catalogue: ${ .catalogue } + processed: ${ .processed } + data: ${ .data } diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/call-headers-map.yaml b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/call-headers-map.yaml new file mode 100644 index 00000000..ec7107df --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/call-headers-map.yaml @@ -0,0 +1,61 @@ +# +# Copyright 2021-Present The Open Workflow Specification Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Nested editing focus: fixed-key headers map on a call task inside a do container. +# The editor must handle drilling into the do block, then into the call task, +# then into the nested headers map — three levels of nesting. +document: + dsl: "1.0.3" + namespace: examples + name: call-headers-map + version: "0.1.0" +do: + - authenticate: + call: http + with: + method: post + endpoint: https://auth.example.com/token + headers: + Content-Type: application/x-www-form-urlencoded + Accept: application/json + body: + grant_type: client_credentials + client_id: ${ .clientId } + client_secret: ${ .clientSecret } + - fetchUserData: + do: + - getProfile: + call: http + with: + method: get + endpoint: https://api.example.com/users/${ .userId } + headers: + Accept: application/json + Authorization: Bearer ${ .accessToken } + X-Correlation-Id: ${ .correlationId } + X-Request-Source: workflow-engine + - getPermissions: + call: http + with: + method: get + endpoint: https://api.example.com/users/${ .userId }/permissions + headers: + Accept: application/json + Authorization: Bearer ${ .accessToken } + - storeResult: + set: + profile: ${ .profile } + permissions: ${ .permissions } + fetchedAt: ${ now } diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/listen-deep-nesting.yaml b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/listen-deep-nesting.yaml new file mode 100644 index 00000000..34a45881 --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/listen-deep-nesting.yaml @@ -0,0 +1,94 @@ +# +# Copyright 2021-Present The Open Workflow Specification Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Nested editing focus: listen task with deeply nested correlate maps inside +# a to.any array, followed by a foreach block containing its own nested do tasks. +# Tests drill-in navigation: top level -> listen config -> event filter array -> +# correlate map, and separately: foreach -> do -> nested call task. +document: + dsl: "1.0.3" + namespace: examples + name: listen-deep-nesting + version: "0.1.0" +do: + - awaitReadings: + listen: + to: + any: + - with: + source: https://sensors.example.com + type: com.example.sensors.temperature + data: ${ .value > 37.5 } + correlate: + deviceId: + from: .device.id + roomId: + from: .location.room + - with: + source: https://sensors.example.com + type: com.example.sensors.humidity + data: ${ .percent > 80 } + correlate: + deviceId: + from: .device.id + roomId: + from: .location.room + - with: + source: https://sensors.example.com + type: com.example.sensors.pressure + data: ${ .hpa < 980 } + correlate: + deviceId: + from: .device.id + roomId: + from: .location.room + until: ${ .readingCount >= 10 } + foreach: + item: reading + at: i + do: + - logReading: + call: http + with: + method: post + endpoint: https://ingest.example.com/readings + headers: + Content-Type: application/json + X-Device-Id: ${ .reading.deviceId } + body: + reading: ${ .reading } + index: ${ .i } + - updateState: + set: + lastReading: ${ .reading } + readingCount: ${ .i + 1 } + output: + as: .latestReadings + - processReadings: + do: + - summarise: + set: + total: ${ .latestReadings | length } + avgTemp: ${ [ .latestReadings[] | select(.type == "temperature") | .value ] | add / length } + - notifyAlert: + call: http + with: + method: post + endpoint: https://alerts.example.com/notify + headers: + Content-Type: application/json + body: + summary: ${ .summary } + avgTemp: ${ .avgTemp } diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/nested-validation.yaml b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/nested-validation.yaml new file mode 100644 index 00000000..4aa4cb06 --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/nested-validation.yaml @@ -0,0 +1,39 @@ +# +# Copyright 2021-Present The Open Workflow Specification Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +document: + dsl: "1.0.3" + namespace: examples + name: nested-validation + version: "0.1.0" +do: + - validStep: + set: + status: starting + - httpCall: + call: http + with: + method: post + endpoint: https://api.example.com/process + - processItems: + for: + each: item + in: ${ .items } + do: + - nestedCall: + call: http + with: + method: get + endpoint: https://api.example.com/items/${ .item.id } diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/run-task-array.yaml b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/run-task-array.yaml new file mode 100644 index 00000000..79ffc583 --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/run-task-array.yaml @@ -0,0 +1,84 @@ +# +# Copyright 2021-Present The Open Workflow Specification Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Nested editing focus: run task with an arguments array and environment map, +# inside a do container alongside a for loop that also contains a run task. +# Tests reordering the arguments array and editing the environment map +# at multiple nesting levels. +document: + dsl: "1.0.3" + namespace: examples + name: run-task-array + version: "0.1.0" +do: + - prepare: + set: + fileContents: ${ .input.files } + outputDir: ${ .input.outputDir } + - processFiles: + do: + - validateInput: + run: + script: + language: python + arguments: + - --mode + - validate + - --strict + environment: + LOG_LEVEL: info + TIMEOUT: "30" + code: | + import sys, json + data = json.loads(sys.stdin.read()) + assert data, "empty input" + print(json.dumps({"valid": True})) + - transformFiles: + run: + script: + language: python + stdin: ${ .fileContents } + arguments: + - --output-format + - json + - --verbose + - --max-retries + - "3" + environment: + LOG_LEVEL: info + TIMEOUT: "60" + OUTPUT_DIR: ${ .outputDir } + code: | + import sys, json, argparse + parser = argparse.ArgumentParser() + parser.add_argument('--output-format') + parser.add_argument('--verbose', action='store_true') + parser.add_argument('--max-retries', type=int) + args = parser.parse_args() + data = json.loads(sys.stdin.read()) + print(json.dumps({"processed": data, "format": args.output_format})) + - reportResults: + run: + script: + language: javascript + arguments: + - --summary + - --output + - ${ .outputDir } + environment: + NODE_ENV: production + code: | + const [,, , , dir] = process.argv; + console.log(JSON.stringify({ summary: true, dir })); diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/set-open-map.yaml b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/set-open-map.yaml new file mode 100644 index 00000000..dd06cbfe --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/set-open-map.yaml @@ -0,0 +1,55 @@ +# +# Copyright 2021-Present The Open Workflow Specification Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Nested editing focus: open map (set task) inside a for loop container. +# The editor must handle adding/removing arbitrary keys in the set map +# while navigating into the for loop's nested do block — two nesting levels. +document: + dsl: "1.0.3" + namespace: examples + name: set-open-map + version: "0.1.0" +do: + - initContext: + set: + requestedAt: ${ now } + environment: production + featureFlags: + betaEditor: true + darkMode: false + - processItems: + for: + each: item + in: ${ .items } + at: index + do: + - enrichItem: + set: + id: ${ .item.id } + label: ${ .item.name } + index: ${ .index } + processedAt: ${ now } + meta: + source: workflow + version: v2 + - tagItem: + set: + tagged: true + taggedBy: ${ $workflow.definition.document.name } + taggedAt: ${ now } + - finalise: + set: + completedAt: ${ now } + status: done diff --git a/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/switch-locked-cases.yaml b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/switch-locked-cases.yaml new file mode 100644 index 00000000..9212bc67 --- /dev/null +++ b/packages/open-workflow-diagram-editor/stories/nested-editing/workflows/switch-locked-cases.yaml @@ -0,0 +1,55 @@ +# +# Copyright 2021-Present The Open Workflow Specification Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +document: + dsl: "1.0.3" + namespace: examples + name: switch-locked-cases + version: "0.1.0" +do: + - routeOrder: + switch: + - electronicOrder: + when: ${ .orderType == "electronic" } + then: fulfillElectronic + - physicalOrder: + when: ${ .orderType == "physical" } + then: fulfillPhysical + - subscriptionOrder: + when: ${ .orderType == "subscription" } + then: fulfillSubscription + - default: + then: rejectUnknownOrder + - fulfillElectronic: + set: + deliveryMethod: email + status: queued + then: exit + - fulfillPhysical: + set: + deliveryMethod: courier + status: queued + then: exit + - fulfillSubscription: + set: + deliveryMethod: recurring + status: active + then: exit + - rejectUnknownOrder: + raise: + error: + type: https://example.com/errors/unknown-order-type + status: 400 + title: Unknown Order Type