From a19db9a8d36fd07ff123e09ee079128e353273ad Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 10 Sep 2024 20:16:43 +0200 Subject: [PATCH] feat: add ability to edit id in flows (#4364) * all * all * nit mailto * fix * fix --- backend/windmill-api/openapi.yaml | 20 --- .../src/lib/components/FlowMetadata.svelte | 4 +- .../src/lib/components/IdEditorInput.svelte | 85 ++++++++++++ .../contextPanel/components/IdEditor.svelte | 81 ++---------- .../DecisionTreeGraphNode.svelte | 2 +- .../InsertDecisionTreeNode.svelte | 4 +- .../ConfirmationModal.svelte | 2 +- .../flows/content/DynamicInputHelpBox.svelte | 5 +- .../lib/components/flows/flowModuleNextId.ts | 2 +- .../src/lib/components/flows/flowStore.ts | 6 + .../flows/map/FlowModuleSchemaItem.svelte | 121 +++++++++++++++--- .../flows/map/FlowModuleSchemaMap.svelte | 30 +++++ .../flows/map/InsertModuleButton.svelte | 10 +- .../flows/map/InsertTriggerButton.svelte | 4 +- .../lib/components/flows/map/MapItem.svelte | 4 + .../components/flows/map/VirtualItem.svelte | 22 +++- .../lib/components/graph/FlowGraphV2.svelte | 4 + .../src/lib/components/graph/graphBuilder.ts | 8 +- .../renderers/nodes/BranchAllStart.svelte | 2 +- .../renderers/nodes/BranchOneStart.svelte | 6 +- .../graph/renderers/nodes/InputNode.svelte | 4 +- .../graph/renderers/nodes/ModuleNode.svelte | 7 +- frontend/src/lib/components/graph/util.ts | 8 +- .../settings/WorkspaceUserSettings.svelte | 2 +- 24 files changed, 298 insertions(+), 145 deletions(-) create mode 100644 frontend/src/lib/components/IdEditorInput.svelte diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 52a69329f7110..3c027316403fe 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -4737,26 +4737,6 @@ paths: schema: type: string - /w/{workspace}/flows/input_history/p/{path}: - get: - summary: list inputs for previous completed flow jobs - operationId: getFlowInputHistoryByPath - tags: - - flow - parameters: - - $ref: "#/components/parameters/WorkspaceId" - - $ref: "#/components/parameters/ScriptPath" - - $ref: "#/components/parameters/Page" - - $ref: "#/components/parameters/PerPage" - responses: - "200": - description: input history for completed jobs with this flow path - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/Input" /w/{workspace}/raw_apps/list: get: diff --git a/frontend/src/lib/components/FlowMetadata.svelte b/frontend/src/lib/components/FlowMetadata.svelte index 0ff92493e1765..46e0a6fda9215 100644 --- a/frontend/src/lib/components/FlowMetadata.svelte +++ b/frontend/src/lib/components/FlowMetadata.svelte @@ -51,10 +51,10 @@ {#if job.is_flow_step}
- + Step of flow - {job.parent_job} + {truncateRev(job.parent_job, 18)}
diff --git a/frontend/src/lib/components/IdEditorInput.svelte b/frontend/src/lib/components/IdEditorInput.svelte new file mode 100644 index 0000000000000..1db5bf686f574 --- /dev/null +++ b/frontend/src/lib/components/IdEditorInput.svelte @@ -0,0 +1,85 @@ + + + diff --git a/frontend/src/lib/components/apps/editor/contextPanel/components/IdEditor.svelte b/frontend/src/lib/components/apps/editor/contextPanel/components/IdEditor.svelte index 90827ead53d9d..2db599831615c 100644 --- a/frontend/src/lib/components/apps/editor/contextPanel/components/IdEditor.svelte +++ b/frontend/src/lib/components/apps/editor/contextPanel/components/IdEditor.svelte @@ -1,40 +1,18 @@ @@ -50,44 +28,13 @@ - + close(null)} + on:save={(e) => { + dispatch('save', e.detail) + close(null) + }} + {reservedIds} + /> diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/DecisionTreeGraphNode.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/DecisionTreeGraphNode.svelte index 53d23e7a884bb..a72d54356bc98 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/DecisionTreeGraphNode.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/DecisionTreeGraphNode.svelte @@ -47,7 +47,7 @@ style="width: 275px; height: 34px; background-color: {getStateColor( undefined, darkMode, - '#fff' + true )};" on:click={() => { selected = true diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/decisionTree/InsertDecisionTreeNode.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/decisionTree/InsertDecisionTreeNode.svelte index c5bac15603d89..9a7dba0feb72e 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/decisionTree/InsertDecisionTreeNode.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/decisionTree/InsertDecisionTreeNode.svelte @@ -17,7 +17,7 @@ dispatch('node') }} type="button" - class="text-primary bg-surface border-[1px] mx-[1px] border-gray-300 dark:border-gray-500 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-full text-sm w-[25px] h-[25px] flex items-center justify-center" + class="text-primary bg-surface outline-[1px] outline dark:outline-gray-500 outline-gray-300 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-full text-sm w-[25px] h-[25px] flex items-center justify-center" > @@ -28,7 +28,7 @@ type="button" on:click={() => dispatch('addBranch')} class={twMerge( - 'text-primary bg-surface border-[1px] mx-[1px] border-gray-300 dark:border-gray-500 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-full text-sm w-[25px] h-[25px] flex items-center justify-center', + 'text-secondary bg-surface outline-[1px] outline dark:outline-gray-500 outline-gray-300 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-full text-sm w-[25px] h-[25px] flex items-center justify-center', !canAddNode && 'ml-16 mb-2' )} > diff --git a/frontend/src/lib/components/common/confirmationModal/ConfirmationModal.svelte b/frontend/src/lib/components/common/confirmationModal/ConfirmationModal.svelte index 5d9985b7df7ce..85c72b6dc32a4 100644 --- a/frontend/src/lib/components/common/confirmationModal/ConfirmationModal.svelte +++ b/frontend/src/lib/components/common/confirmationModal/ConfirmationModal.svelte @@ -44,7 +44,7 @@
diff --git a/frontend/src/lib/components/flows/content/DynamicInputHelpBox.svelte b/frontend/src/lib/components/flows/content/DynamicInputHelpBox.svelte index 1f649d205a011..0e71ce9175f15 100644 --- a/frontend/src/lib/components/flows/content/DynamicInputHelpBox.svelte +++ b/frontend/src/lib/components/flows/content/DynamicInputHelpBox.svelte @@ -29,7 +29,10 @@ > Single JavaScript expression. The following functions and objects are available:
    -
  • {'results.'}: the result of step at id 'id'
  • +
  • {'results.'}: the result of step at id 'id' (use {'results?.'} if id may + not exist because branch was not chosen)
  • flow_input: the object containing the flow input arguments
  • params: the object containing the current step static values
  • diff --git a/frontend/src/lib/components/flows/flowModuleNextId.ts b/frontend/src/lib/components/flows/flowModuleNextId.ts index d7d072820541d..428ceccf470b6 100644 --- a/frontend/src/lib/components/flows/flowModuleNextId.ts +++ b/frontend/src/lib/components/flows/flowModuleNextId.ts @@ -7,7 +7,7 @@ import { charsToNumber, numberToChars } from './idUtils' export function nextId(flowState: FlowState, fullFlow: OpenFlow): string { const allIds = dfs(fullFlow.value.modules, (fm) => fm.id) const max = allIds.concat(Object.keys(flowState)).reduce((acc, key) => { - if (key === 'failure' || key.includes('branch') || key.includes('loop')) { + if (key.length >= 4) { return acc } else { const num = charsToNumber(key) diff --git a/frontend/src/lib/components/flows/flowStore.ts b/frontend/src/lib/components/flows/flowStore.ts index 0fe8b26136cb7..39f3e2fc51655 100644 --- a/frontend/src/lib/components/flows/flowStore.ts +++ b/frontend/src/lib/components/flows/flowStore.ts @@ -34,3 +34,9 @@ export async function copyFirstStepSchema(flowState: FlowState, flowStore: Writa return flow }) } + +export function replaceId(expr: string, id: string, newId: string): string { + return expr + .replaceAll(`results.${id}`, `results.${newId}`) + .replaceAll(`results?.${id}`, `results?.${newId}`) +} diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte index 4d9e884a41c54..c5e3f62c65568 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte @@ -9,6 +9,7 @@ Database, Gauge, Move, + Pencil, PhoneIncoming, Repeat, Square, @@ -17,9 +18,15 @@ } from 'lucide-svelte' import { createEventDispatcher, getContext } from 'svelte' import { fade } from 'svelte/transition' - import type { FlowInput } from '../types' - import type { Writable } from 'svelte/store' + import type { FlowEditorContext, FlowInput } from '../types' + import { get, type Writable } from 'svelte/store' import { twMerge } from 'tailwind-merge' + import IdEditorInput from '$lib/components/IdEditorInput.svelte' + import { dfs } from '../dfs' + import { Drawer } from '$lib/components/common' + import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte' + import { getDependeeAndDependentComponents } from '../flowExplorer' + import { replaceId } from '../flowStore' export let selected: boolean = false export let deletable: boolean = false @@ -41,22 +48,85 @@ const { flowInputsStore } = getContext<{ flowInputsStore: Writable }>( 'FlowGraphContext' ) + + const flowEditorContext = getContext('FlowEditorContext') const dispatch = createEventDispatcher() const { currentStepStore: copilotCurrentStepStore } = getContext('FlowCopilotContext') || {} + + let editId = false + + let newId: string = id ?? '' + + let hover = false +{#if deletable && id && editId} + {@const flowStore = flowEditorContext?.flowStore ? get(flowEditorContext?.flowStore) : undefined} + {@const getDeps = getDependeeAndDependentComponents( + id, + flowStore?.value.modules ?? [], + flowStore?.value.failure_module + )} + + (editId = false)}> +
    + x.id)} + bind:value={newId} + on:save={(e) => { + dispatch('changeId', { id, newId: e.detail, deps: getDeps?.dependents ?? {} }) + editId = false + }} + on:close={() => { + editId = false + }} + /> +
    +

    Step Inputs Replacements

    +
    + Replace all occurrences of `results.{id}` with{' '} + results.{newId} in the step inputs of all steps that depend + on it. +
    +
    + {#if Object.keys(getDeps?.dependents ?? {})?.length > 0} + {#each Object.entries(getDeps?.dependents ?? {}) as dependents} +
    +

    {dependents[0]}

    + {#each dependents?.[1] as d} + {d} → + {replaceId(d, id, newId)} + {/each} +
    + {/each} + {:else} +
    No dependents
    + {/if} +
    +
    +
    +
    +
    +{/if} +
    (hover = true)} + on:mouseleave={() => (hover = false)} on:click >
    @@ -67,7 +137,7 @@ class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5" > {#if retries}{retries}{/if} - +
    Retries @@ -79,7 +149,7 @@ transition:fade|local={{ duration: 200 }} class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5" > - +
    Concurrency Limits @@ -90,7 +160,7 @@ transition:fade|local={{ duration: 200 }} class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5" > - +
Cached @@ -101,7 +171,7 @@ transition:fade|local={{ duration: 200 }} class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5" > - + Early stop/break @@ -112,7 +182,7 @@ transition:fade|local={{ duration: 200 }} class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5" > - + Suspend @@ -123,7 +193,7 @@ transition:fade|local={{ duration: 200 }} class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5" > - + Sleep @@ -134,7 +204,7 @@ transition:fade|local={{ duration: 200 }} class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5" > - + Mocked @@ -143,38 +213,49 @@
{#if $$slots.icon} {/if}
{label}
-
+
{#if id} {id} + + {#if deletable} + + {/if} {/if}
{#if deletable} {#if (id && Object.values($flowInputsStore?.[id]?.flowStepWarnings || {}).length > 0) || Boolean(warningMessage)} diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index c38f25ccbc2f5..618d6c763a34b 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -30,6 +30,7 @@ import { ignoredTutorials } from '$lib/components/tutorials/ignoredTutorials' import { tutorialInProgress } from '$lib/tutorialUtils' import FlowGraphV2 from '$lib/components/graph/FlowGraphV2.svelte' + import { replaceId } from '../flowStore' export let modules: FlowModule[] | undefined export let sidebarSize: number | undefined = undefined @@ -328,6 +329,35 @@ $flowStore = $flowStore } }} + on:changeId={({ detail }) => { + let { id, newId, deps } = detail + dfs($flowStore.value.modules, (mod) => { + if (deps[mod.id]) { + deps[mod.id].forEach((dep) => { + if ( + mod.value.type == 'rawscript' || + mod.value.type == 'script' || + mod.value.type == 'flow' + ) { + mod.value.input_transforms = Object.fromEntries( + Object.entries(mod.value.input_transforms).map(([k, v]) => { + if (v.type == 'javascript') { + return [k, { ...v, expr: replaceId(v.expr, id, newId) }] + } else { + return [k, v] + } + }) + ) + } + }) + } + if (mod.id == id) { + mod.id = newId + } + }) + $flowStore = $flowStore + $selectedId = newId + }} on:deleteBranch={async ({ detail }) => { if (detail.module) { await removeBranch(detail.module, detail.index) diff --git a/frontend/src/lib/components/flows/map/InsertModuleButton.svelte b/frontend/src/lib/components/flows/map/InsertModuleButton.svelte index e0de72a068d2e..40cce9648fd0a 100644 --- a/frontend/src/lib/components/flows/map/InsertModuleButton.svelte +++ b/frontend/src/lib/components/flows/map/InsertModuleButton.svelte @@ -35,13 +35,13 @@ id={`flow-editor-add-step-${index}`} type="button" class={twMerge( - 'w-6 h-6 flex items-center justify-center', - 'border border-gray-300 dark:border-gray-500', - 'text-primary text-sm', - 'bg-surface focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected rounded-full ' + 'w-5 h-5 flex items-center justify-center', + 'outline-[1px] outline dark:outline-gray-500 outline-gray-300', + 'text-secondary', + 'bg-surface focus:outline-none hover:bg-surface-hover rounded ' )} > - +
diff --git a/frontend/src/lib/components/flows/map/InsertTriggerButton.svelte b/frontend/src/lib/components/flows/map/InsertTriggerButton.svelte index 03d819b2c53c9..35dc3311612e9 100644 --- a/frontend/src/lib/components/flows/map/InsertTriggerButton.svelte +++ b/frontend/src/lib/components/flows/map/InsertTriggerButton.svelte @@ -27,9 +27,9 @@ title="Add a Trigger" slot="trigger" type="button" - class="text-primary bg-surface border-[1px] mx-[1px] border-gray-300 dark:border-gray-500 rotate-180 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-gray-200 font-medium rounded-full text-sm w-[25px] h-[25px] flex items-center justify-center" + class="text-secondary bg-surface outline-[1px] outline dark:outline-gray-500 outline-gray-300 rotate-180 focus:outline-none hover:bg-surface-hover focus:ring-4 focus:ring-gray-200 font-medium rounded text-sm w-[20px] h-[20px] flex items-center justify-center" > - + {#if !disableAi} diff --git a/frontend/src/lib/components/flows/map/MapItem.svelte b/frontend/src/lib/components/flows/map/MapItem.svelte index 6efca43558210..c7c78e6a84882 100644 --- a/frontend/src/lib/components/flows/map/MapItem.svelte +++ b/frontend/src/lib/components/flows/map/MapItem.svelte @@ -102,6 +102,7 @@ mod.value.skip_failures ? '(skip failures)' : '' }`} id={mod.id} + on:changeId on:move={() => dispatch('move')} on:delete={onDelete} on:click={() => dispatch('select', mod.id)} @@ -120,6 +121,7 @@ {:else if mod.value.type === 'branchone'} dispatch('move')} on:click={() => dispatch('select', mod.id)} @@ -135,6 +137,7 @@ {:else if mod.value.type === 'branchall'} dispatch('move')} on:click={() => dispatch('select', mod.id)} @@ -150,6 +153,7 @@ {:else} dispatch('select', mod.id)} on:delete={onDelete} on:move={() => dispatch('move')} diff --git a/frontend/src/lib/components/flows/map/VirtualItem.svelte b/frontend/src/lib/components/flows/map/VirtualItem.svelte index 4ab598bf9c359..c69e743d7fd64 100644 --- a/frontend/src/lib/components/flows/map/VirtualItem.svelte +++ b/frontend/src/lib/components/flows/map/VirtualItem.svelte @@ -5,7 +5,7 @@ import { createEventDispatcher, getContext } from 'svelte' import type { FlowCopilotContext } from '$lib/components/copilot/flow' - export let label: string + export let label: string | undefined = undefined export let bgColor: string = '' export let selected: boolean export let selectable: boolean @@ -13,6 +13,7 @@ export let center = true export let borderColor: string | undefined = undefined export let hideId: boolean = false + export let preLabel: string | undefined = undefined const dispatch = createEventDispatcher<{ insert: { @@ -35,8 +36,7 @@ 'w-full flex relative overflow-hidden rounded-sm', selectable ? 'cursor-pointer' : '', selected ? 'outline outline-offset-1 outline-2 outline-gray-600' : '', - label === 'Input' && $copilotCurrentStepStore === 'Input' ? 'z-[901]' : '', - 'bg-surface' + label === 'Input' && $copilotCurrentStepStore === 'Input' ? 'z-[901]' : '' )} style="width: 275px; max-height: 34px; background-color: {bgColor} !important;" on:click={() => { @@ -44,14 +44,15 @@ if (id) { dispatch('select', id) } else { - dispatch('select', label) + dispatch('select', label || label || '') } } }} - id={`flow-editor-virtual-${encodeURIComponent(label)}`} + title={(label ? label + ' ' : '') + (label ?? '')} + id={`flow-editor-virtual-${encodeURIComponent(label || label || '')}`} >
{/if}
-
{label}
+
+ {#if label} +
{label}
+ {/if} + {#if preLabel} +
{preLabel}
+ {/if} +
{#if id && !hideId} {id} diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index 9ccd540c6d4c4..56b693dc11c39 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -146,6 +146,9 @@ dispatch('select', modId) } }, + changeId: (detail) => { + dispatch('changeId', detail) + }, delete: (detail, label) => { $selectedId = label @@ -274,6 +277,7 @@ showLock={false} showZoom={false} showFitView={false} + class="!shadow-none" > {#if showDataflow} void move: (module: FlowModule, modules: FlowModule[]) => void selectedIteration: (detail, moduleId: string) => void + changeId: (newId: string) => void } export default function graphBuilder( @@ -354,11 +355,8 @@ export default function graphBuilder( id: `${module.id}-branch-${branchIndex}`, data: { offset: currentOffset, - label: - defaultIfEmptyString(branch.summary, 'Branch ' + (branchIndex + 1)) + - '\n`' + - branch.expr + - '`', + label: defaultIfEmptyString(branch.summary, 'Branch ' + (branchIndex + 1)), + preLabel: branch.summary ? '' : branch.expr, id: module.id, branchIndex: branchIndex, modules: modules, diff --git a/frontend/src/lib/components/graph/renderers/nodes/BranchAllStart.svelte b/frontend/src/lib/components/graph/renderers/nodes/BranchAllStart.svelte index 34a3ab27f5180..cc0bd3bd3bc48 100644 --- a/frontend/src/lib/components/graph/renderers/nodes/BranchAllStart.svelte +++ b/frontend/src/lib/components/graph/renderers/nodes/BranchAllStart.svelte @@ -47,7 +47,7 @@ {#if !$copilotInfo.exists_openai_resource_path}
diff --git a/frontend/src/lib/components/graph/renderers/nodes/ModuleNode.svelte b/frontend/src/lib/components/graph/renderers/nodes/ModuleNode.svelte index 67d3ad2ff3660..dd66cc4051ff0 100644 --- a/frontend/src/lib/components/graph/renderers/nodes/ModuleNode.svelte +++ b/frontend/src/lib/components/graph/renderers/nodes/ModuleNode.svelte @@ -56,7 +56,7 @@ '/' + (state?.iteration_total ?? '?') : ''} - bgColor={getStateColor(type, darkMode, '#fff')} + bgColor={getStateColor(type, darkMode, true)} modules={data.modules ?? []} moving={data.moving} duration_ms={state?.duration_ms} @@ -68,6 +68,9 @@ on:insert={(e) => { data.eventHandlers.insert(e.detail) }} + on:changeId={(e) => { + data.eventHandlers.changeId(e.detail) + }} on:move={(e) => { data.eventHandlers.move(data.module, data.modules) }} @@ -87,7 +90,7 @@ {#if (data.value.type === 'branchall' || data.value.type === 'branchone') && data.insertable}