diff --git a/public/tour-pipelines/Hello World.pipeline.component.yaml b/public/tour-pipelines/Hello World.pipeline.component.yaml new file mode 100644 index 000000000..6649a0690 --- /dev/null +++ b/public/tour-pipelines/Hello World.pipeline.component.yaml @@ -0,0 +1,134 @@ +name: Hello World +description: A simple pipeline that demonstrates the hello world component. +inputs: + - name: name + type: String + description: The name to greet. + default: '' + annotations: + editor.position: '{"x":140,"y":0}' + value: '' + optional: false +outputs: + - name: greeting + type: Text + description: The generated greeting message. + annotations: + editor.position: '{"x": 850, "y": 0}' +implementation: + graph: + tasks: + Greet: + componentRef: + name: Hello world + digest: 8cf1bcc31ce9cd9be6982a7ba95cf233f23efcb7769842b6b03e917ad5dfdd0a + spec: + name: Hello world + description: A simple hello world component that generates a greeting. + metadata: + annotations: + cloud_pipelines.net: 'true' + component_yaml_path: hello_world.component.yaml + python_original_code: | + from cloud_pipelines import components + + + def hello_world( + name: str, + greeting_output: components.OutputPath("Text"), + greeting_prefix: str = "Hello", + ): + """A simple hello world component that generates a greeting. + + Args: + name: The name to greet. + greeting_output: Output file containing the greeting message. + greeting_prefix: Prefix for the greeting (default: Hello). + """ + greeting = f"{greeting_prefix}, {name}! Welcome to Tangle." + print(greeting) + with open(greeting_output, "w") as f: + f.write(greeting) + components new regenerate python-function-component: 'true' + inputs: + - name: name + type: String + description: The name to greet. + - name: greeting_prefix + type: String + description: 'Prefix for the greeting (default: Hello).' + default: Hello + optional: true + outputs: + - name: greeting_output + type: Text + description: Output file containing the greeting message. + implementation: + container: + image: python:3.12 + command: + - sh + - '-ec' + - | + program_path=$(mktemp) + printf "%s" "$0" > "$program_path" + python3 -u "$program_path" "$@" + - | + def _make_parent_dirs_and_return_path(file_path: str): + import os + + os.makedirs(os.path.dirname(file_path), exist_ok=True) + return file_path + + def hello_world( + name, + greeting_output, + greeting_prefix = "Hello", + ): + """A simple hello world component that generates a greeting. + + Args: + name: The name to greet. + greeting_output: Output file containing the greeting message. + greeting_prefix: Prefix for the greeting (default: Hello). + """ + greeting = f"{greeting_prefix}, {name}! Welcome to Tangle." + print(greeting) + with open(greeting_output, "w") as f: + f.write(greeting) + + import argparse + _parser = argparse.ArgumentParser(prog='Hello world', description='A simple hello world component that generates a greeting.') + _parser.add_argument("--name", dest="name", type=str, required=True, default=argparse.SUPPRESS) + _parser.add_argument("--greeting-prefix", dest="greeting_prefix", type=str, required=False, default=argparse.SUPPRESS) + _parser.add_argument("--greeting-output", dest="greeting_output", type=_make_parent_dirs_and_return_path, required=True, default=argparse.SUPPRESS) + _parsed_args = vars(_parser.parse_args()) + + _outputs = hello_world(**_parsed_args) + args: + - '--name' + - inputValue: name + - if: + cond: + isPresent: greeting_prefix + then: + - '--greeting-prefix' + - inputValue: greeting_prefix + - '--greeting-output' + - outputPath: greeting_output + arguments: + name: + graphInput: + inputName: name + annotations: + editor.position: '{"x":430,"y":0}' + editor.collapsed: 'true' + outputValues: + greeting: + taskOutput: + outputName: greeting_output + taskId: Greet +metadata: + annotations: + editor.flow-direction: left-to-right + notes: Enter a name in the Input Node or configure it when submitting the pipeline. diff --git a/src/components/Learn/tours/navigatingEditor.tour.json b/src/components/Learn/tours/navigatingEditor.tour.json new file mode 100644 index 000000000..d697e759c --- /dev/null +++ b/src/components/Learn/tours/navigatingEditor.tour.json @@ -0,0 +1,113 @@ +{ + "id": "navigating-the-editor", + "displayName": "Guided Tour: Navigating the Editor", + "requiresEditor": true, + "starterPipelineUrl": "tour-pipelines/Hello World.pipeline.component.yaml", + "steps": [ + { + "selector": "[data-tour-anchor=\"no-spotlight\"]", + "content": "Welcome to the pipeline editor! This quick tour will show you around so you know where to find everything: the menu bar, canvas, dockable panels, and floating windows.", + "position": "center" + }, + { + "selector": "[data-tour=\"editor-top-bar-left\"]", + "highlightedSelectors": [ + "[data-tour=\"editor-top-bar-left\"]", + "[data-tour=\"editor-menu-items\"]" + ], + "content": "The top bar holds your pipeline name and editor menus.\n\nEach menu groups one type of command: **File** for pipeline operations, **View** for layout presets, **Runs** for submissions, **Components** for your libraries, and **Windows** for panels. A **Node** menu also appears here when you have a task selected.", + "position": "bottom" + }, + { + "selector": "[data-tour=\"editor-top-bar-actions\"]", + "content": "Over on the right are your quick actions. Submit a run, check autosave status, jump to Settings, or open the documentation.", + "position": "bottom" + }, + { + "selector": "[data-tour=\"editor-canvas\"]", + "content": "This is your workspace. Drag components onto it, connect tasks by linking their input and output handles, and pan or zoom around larger graphs.\n\nUseful controls sit along the bottom: a **minimap** in the bottom-left, and **viewport controls** with **undo/redo** in the bottom-right.", + "position": "center", + "resizeObservables": ["[data-tour=\"editor-canvas\"]"] + }, + { + "selector": "[data-dock-area=\"left\"]", + "highlightedSelectors": [ + "[data-dock-window=\"component-library\"]", + "[data-dock-window-content=\"component-library\"]", + "[data-dock-window=\"runs-and-submission\"]", + "[data-dock-window-content=\"runs-and-submission\"]", + "[data-dock-window=\"recent-runs\"]", + "[data-dock-window-content=\"recent-runs\"]" + ], + "content": "The left sidebar holds your docked panels.\n\n**Components** lets you browse and drag tasks onto the canvas. **Runs and submission** lets you submit your pipeline, and **Recent runs** shows the latest runs of this pipeline.", + "position": "right", + "resizeObservables": ["[data-dock-area=\"left\"]"] + }, + { + "selector": "[data-dock-area=\"right\"]", + "content": "The right sidebar holds Pipeline Details and its properties. Set the pipeline description, tags, and notes here, and review any validation warnings.", + "resizeObservables": ["[data-dock-area=\"right\"]"] + }, + { + "selector": "[data-tour-node=\"task\"][data-task-name=\"Greet\"]", + "content": "Try clicking the **Greet** task on the canvas to select it.", + "position": "top", + "stepInteraction": true, + "interaction": "select-task", + "targetTaskName": "Greet" + }, + { + "selector": "[data-window-id=\"context-panel\"]", + "position": "left", + "highlightedSelectors": [ + "[data-window-id=\"context-panel\"]", + "[data-dock-window-content=\"context-panel\"]" + ], + "mutationObservables": [ + "[data-window-id=\"context-panel\"]", + "[data-dock-window-content=\"context-panel\"]" + ], + "resizeObservables": [ + "[data-window-id=\"context-panel\"]", + "[data-dock-window-content=\"context-panel\"]" + ], + "targetWindowId": "context-panel", + "content": "Selecting a task opens its Task Properties panel here in the right sidebar. From this panel you can edit input arguments, configure node settings, define annotations, and inspect the component spec." + }, + { + "selector": "[data-window-id=\"context-panel\"]", + "content": "Windows are flexible. Try grabbing the Task Properties header and dragging it out of the dock to float it as its own window.", + "position": "left", + "stepInteraction": true, + "interaction": "undock-window", + "targetWindowId": "context-panel", + "targetWindowName": "Task Properties", + "fallbackContent": "Windows are flexible. Try grabbing the Task Properties header and dragging it around the canvas." + }, + { + "selector": "[data-window-id=\"context-panel\"]", + "content": "Nice! Now drag that floating window back onto the left or right sidebar to re-dock it.", + "position": "left", + "stepInteraction": true, + "interaction": "redock-window", + "targetWindowId": "context-panel", + "targetWindowName": "Task Properties", + "fallbackContent": "Windows can be docked in either sidebar. Create the perfect layout that suits you!" + }, + { + "selector": "[data-tracking-id=\"v2.pipeline_editor.windows_menu\"]", + "highlightedSelectors": [ + "[data-tracking-id=\"v2.pipeline_editor.windows_menu\"]", + "[data-tour=\"windows-menu-content\"]", + "[data-tour=\"windows-menu-submenu-content\"]" + ], + "mutationObservables": [ + "[data-tour=\"windows-menu-content\"]", + "[data-tour=\"windows-menu-submenu-content\"]" + ], + "content": "Last one. If you ever need to reconfigure your layout, the Windows menu lets you toggle panels on or off and apply a layout preset.", + "position": "right", + "stepInteraction": true + } + ] +} diff --git a/src/routes/v2/pages/Editor/EditorV2.tsx b/src/routes/v2/pages/Editor/EditorV2.tsx index 3989bd85d..070daffef 100644 --- a/src/routes/v2/pages/Editor/EditorV2.tsx +++ b/src/routes/v2/pages/Editor/EditorV2.tsx @@ -122,7 +122,10 @@ const PipelineEditor = withSuspenseWrapper( data-editor-ready="true" > -
+
)} - + @@ -159,6 +165,7 @@ export const EditorMenuBar = observer(function EditorMenuBar() { wrap="nowrap" blockAlign="center" className="shrink-0" + data-tour="editor-top-bar-actions" > {displayMenu && ( <> diff --git a/src/routes/v2/pages/Editor/components/EditorMenuBar/components/WindowsMenu.tsx b/src/routes/v2/pages/Editor/components/EditorMenuBar/components/WindowsMenu.tsx index 938af2a5d..93affabb3 100644 --- a/src/routes/v2/pages/Editor/components/EditorMenuBar/components/WindowsMenu.tsx +++ b/src/routes/v2/pages/Editor/components/EditorMenuBar/components/WindowsMenu.tsx @@ -47,7 +47,11 @@ export const WindowsMenu = observer(function WindowsMenu() { Windows - + {sortedWindows.map((win) => ( Views - + {viewPresetsForComponentSearchMode(componentSearchV2Enabled).map( (preset) => ( { const target = event.target as Element | null; - if (target?.closest(".react-flow__node")) { + if (target?.closest('[data-tour-node="task"]')) { advance(); } }; diff --git a/src/routes/v2/pages/Editor/components/FlowCanvas/components/CanvasUndoRedo.tsx b/src/routes/v2/pages/Editor/components/FlowCanvas/components/CanvasUndoRedo.tsx index d62871fde..d3864ddba 100644 --- a/src/routes/v2/pages/Editor/components/FlowCanvas/components/CanvasUndoRedo.tsx +++ b/src/routes/v2/pages/Editor/components/FlowCanvas/components/CanvasUndoRedo.tsx @@ -27,7 +27,10 @@ export const CanvasUndoRedo = observer(function CanvasUndoRedo() { }; return ( -
+
diff --git a/src/routes/v2/shared/nodes/IONode/inputManifestBase.ts b/src/routes/v2/shared/nodes/IONode/inputManifestBase.ts index fb69a9f9d..af5fb77df 100644 --- a/src/routes/v2/shared/nodes/IONode/inputManifestBase.ts +++ b/src/routes/v2/shared/nodes/IONode/inputManifestBase.ts @@ -64,11 +64,17 @@ export const inputManifestBase: ManifestPartial = { buildNodes(spec) { return [...spec.inputs].map((input, index) => - createEntityNode(input, "input", ioDefaultPosition(index, -200), { - entityId: input.$id, - ioType: "input", - name: input.name, - } satisfies IONodeData), + createEntityNode( + input, + "input", + ioDefaultPosition(index, -200), + { + entityId: input.$id, + ioType: "input", + name: input.name, + } satisfies IONodeData, + { "data-task-name": input.name }, + ), ); }, diff --git a/src/routes/v2/shared/nodes/IONode/outputManifestBase.ts b/src/routes/v2/shared/nodes/IONode/outputManifestBase.ts index c3655dd9a..a544d8740 100644 --- a/src/routes/v2/shared/nodes/IONode/outputManifestBase.ts +++ b/src/routes/v2/shared/nodes/IONode/outputManifestBase.ts @@ -59,11 +59,17 @@ export const outputManifestBase: ManifestPartial = { buildNodes(spec) { return [...spec.outputs].map((output, index) => - createEntityNode(output, "output", ioDefaultPosition(index, 800), { - entityId: output.$id, - ioType: "output", - name: output.name, - } satisfies IONodeData), + createEntityNode( + output, + "output", + ioDefaultPosition(index, 800), + { + entityId: output.$id, + ioType: "output", + name: output.name, + } satisfies IONodeData, + { "data-task-name": output.name }, + ), ); }, diff --git a/src/routes/v2/shared/nodes/TaskNode/taskManifestBase.ts b/src/routes/v2/shared/nodes/TaskNode/taskManifestBase.ts index 49780b917..66a2684a6 100644 --- a/src/routes/v2/shared/nodes/TaskNode/taskManifestBase.ts +++ b/src/routes/v2/shared/nodes/TaskNode/taskManifestBase.ts @@ -72,10 +72,16 @@ export const taskManifestBase: ManifestPartial = { buildNodes(spec) { return [...spec.tasks].map((task, index) => - createEntityNode(task, "task", taskDefaultPosition(index), { - entityId: task.$id, - name: task.name, - } satisfies TaskNodeData), + createEntityNode( + task, + "task", + taskDefaultPosition(index), + { + entityId: task.$id, + name: task.name, + } satisfies TaskNodeData, + { "data-task-name": task.name, "data-tour-node": "task" }, + ), ); }, diff --git a/src/routes/v2/shared/nodes/buildUtils.ts b/src/routes/v2/shared/nodes/buildUtils.ts index abb3c83e3..bbc5849c3 100644 --- a/src/routes/v2/shared/nodes/buildUtils.ts +++ b/src/routes/v2/shared/nodes/buildUtils.ts @@ -47,6 +47,7 @@ export function createEntityNode( nodeType: string, fallback: { x: number; y: number }, data: Record, + domAttributes?: Record, ): Node { const position = entity.annotations.get(EDITOR_POSITION_ANNOTATION) as { x: number; @@ -59,6 +60,7 @@ export function createEntityNode( position: resolvePosition(position, fallback), zIndex, data, + domAttributes, }; } diff --git a/src/routes/v2/shared/windows/components/DockedWindow.tsx b/src/routes/v2/shared/windows/components/DockedWindow.tsx index 19811290a..7c4294488 100644 --- a/src/routes/v2/shared/windows/components/DockedWindow.tsx +++ b/src/routes/v2/shared/windows/components/DockedWindow.tsx @@ -134,6 +134,7 @@ export const DockedWindow = observer(function DockedWindow() {
- +