fix(ui): fix workflow loading

- Different handling for loading from library vs external
- Fix bug where only nodes and edges loaded
This commit is contained in:
psychedelicious 2023-12-03 20:13:44 +11:00
parent 4b2e3aa54d
commit 3863bd9da3
8 changed files with 50 additions and 36 deletions

View File

@ -1,7 +1,7 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { parseify } from 'common/util/serialize'; import { parseify } from 'common/util/serialize';
import { workflowLoadRequested } from 'features/nodes/store/actions'; import { workflowLoadRequested } from 'features/nodes/store/actions';
import { workflowLoaded } from 'features/nodes/store/nodesSlice'; import { workflowLoaded } from 'features/nodes/store/workflowSlice';
import { $flow } from 'features/nodes/store/reactFlowInstance'; import { $flow } from 'features/nodes/store/reactFlowInstance';
import { import {
WorkflowMigrationError, WorkflowMigrationError,
@ -21,7 +21,7 @@ export const addWorkflowLoadRequestedListener = () => {
actionCreator: workflowLoadRequested, actionCreator: workflowLoadRequested,
effect: (action, { dispatch, getState }) => { effect: (action, { dispatch, getState }) => {
const log = logger('nodes'); const log = logger('nodes');
const workflow = action.payload; const { workflow, asCopy } = action.payload;
const nodeTemplates = getState().nodes.nodeTemplates; const nodeTemplates = getState().nodes.nodeTemplates;
try { try {
@ -29,6 +29,12 @@ export const addWorkflowLoadRequestedListener = () => {
workflow, workflow,
nodeTemplates nodeTemplates
); );
if (asCopy) {
// If we're loading a copy, we need to remove the ID so that the backend will create a new workflow
delete validatedWorkflow.id;
}
dispatch(workflowLoaded(validatedWorkflow)); dispatch(workflowLoaded(validatedWorkflow));
if (!warnings.length) { if (!warnings.length) {
dispatch( dispatch(

View File

@ -17,9 +17,10 @@ export const isAnyGraphBuilt = isAnyOf(
nodesGraphBuilt nodesGraphBuilt
); );
export const workflowLoadRequested = createAction<unknown>( export const workflowLoadRequested = createAction<{
'nodes/workflowLoadRequested' workflow: unknown;
); asCopy: boolean;
}>('nodes/workflowLoadRequested');
export const updateAllNodesRequested = createAction( export const updateAllNodesRequested = createAction(
'nodes/updateAllNodesRequested' 'nodes/updateAllNodesRequested'

View File

@ -1,4 +1,5 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { workflowLoaded } from 'features/nodes/store/workflowSlice';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants'; import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import { import {
BoardFieldValue, BoardFieldValue,
@ -28,7 +29,6 @@ import {
NodeExecutionState, NodeExecutionState,
zNodeStatus, zNodeStatus,
} from 'features/nodes/types/invocation'; } from 'features/nodes/types/invocation';
import { WorkflowV2 } from 'features/nodes/types/workflow';
import { cloneDeep, forEach } from 'lodash-es'; import { cloneDeep, forEach } from 'lodash-es';
import { import {
addEdge, addEdge,
@ -654,30 +654,6 @@ const nodesSlice = createSlice({
nodeOpacityChanged: (state, action: PayloadAction<number>) => { nodeOpacityChanged: (state, action: PayloadAction<number>) => {
state.nodeOpacity = action.payload; state.nodeOpacity = action.payload;
}, },
workflowLoaded: (state, action: PayloadAction<WorkflowV2>) => {
const { nodes, edges } = action.payload;
state.nodes = applyNodeChanges(
nodes.map((node) => ({
item: { ...node, ...SHARED_NODE_PROPERTIES },
type: 'add',
})),
[]
);
state.edges = applyEdgeChanges(
edges.map((edge) => ({ item: edge, type: 'add' })),
[]
);
state.nodeExecutionStates = nodes.reduce<
Record<string, NodeExecutionState>
>((acc, node) => {
acc[node.id] = {
nodeId: node.id,
...initialNodeExecutionState,
};
return acc;
}, {});
},
viewportChanged: (state, action: PayloadAction<Viewport>) => { viewportChanged: (state, action: PayloadAction<Viewport>) => {
state.viewport = action.payload; state.viewport = action.payload;
}, },
@ -823,6 +799,32 @@ const nodesSlice = createSlice({
builder.addCase(receivedOpenAPISchema.pending, (state) => { builder.addCase(receivedOpenAPISchema.pending, (state) => {
state.isReady = false; state.isReady = false;
}); });
builder.addCase(workflowLoaded, (state, action) => {
const { nodes, edges } = action.payload;
state.nodes = applyNodeChanges(
nodes.map((node) => ({
item: { ...node, ...SHARED_NODE_PROPERTIES },
type: 'add',
})),
[]
);
state.edges = applyEdgeChanges(
edges.map((edge) => ({ item: edge, type: 'add' })),
[]
);
state.nodeExecutionStates = nodes.reduce<
Record<string, NodeExecutionState>
>((acc, node) => {
acc[node.id] = {
nodeId: node.id,
...initialNodeExecutionState,
};
return acc;
}, {});
});
builder.addCase(appSocketInvocationStarted, (state, action) => { builder.addCase(appSocketInvocationStarted, (state, action) => {
const { source_node_id } = action.payload.data; const { source_node_id } = action.payload.data;
const node = state.nodeExecutionStates[source_node_id]; const node = state.nodeExecutionStates[source_node_id];
@ -931,7 +933,6 @@ export const {
shouldSnapToGridChanged, shouldSnapToGridChanged,
shouldValidateGraphChanged, shouldValidateGraphChanged,
viewportChanged, viewportChanged,
workflowLoaded,
edgeAdded, edgeAdded,
} = nodesSlice.actions; } = nodesSlice.actions;

View File

@ -34,7 +34,9 @@ export const useGetAndLoadEmbeddedWorkflow: UseGetAndLoadEmbeddedWorkflow = ({
async (imageName: string) => { async (imageName: string) => {
try { try {
const workflow = await _getAndLoadEmbeddedWorkflow(imageName); const workflow = await _getAndLoadEmbeddedWorkflow(imageName);
dispatch(workflowLoadRequested(workflow.data)); dispatch(
workflowLoadRequested({ workflow: workflow.data, asCopy: true })
);
// No toast - the listener for this action does that after the workflow is loaded // No toast - the listener for this action does that after the workflow is loaded
onSuccess && onSuccess(); onSuccess && onSuccess();
} catch { } catch {

View File

@ -32,7 +32,9 @@ export const useGetAndLoadLibraryWorkflow: UseGetAndLoadLibraryWorkflow = ({
async (workflow_id: string) => { async (workflow_id: string) => {
try { try {
const data = await _getAndLoadWorkflow(workflow_id).unwrap(); const data = await _getAndLoadWorkflow(workflow_id).unwrap();
dispatch(workflowLoadRequested(data.workflow)); dispatch(
workflowLoadRequested({ workflow: data.workflow, asCopy: false })
);
// No toast - the listener for this action does that after the workflow is loaded // No toast - the listener for this action does that after the workflow is loaded
onSuccess && onSuccess(); onSuccess && onSuccess();
} catch { } catch {

View File

@ -31,7 +31,9 @@ export const useLoadWorkflowFromFile: UseLoadWorkflowFromFile = ({
try { try {
const parsedJSON = JSON.parse(String(rawJSON)); const parsedJSON = JSON.parse(String(rawJSON));
dispatch(workflowLoadRequested(parsedJSON)); dispatch(
workflowLoadRequested({ workflow: parsedJSON, asCopy: true })
);
} catch (e) { } catch (e) {
// There was a problem reading the file // There was a problem reading the file
logger.error(t('nodes.unableToLoadWorkflow')); logger.error(t('nodes.unableToLoadWorkflow'));

View File

@ -1,7 +1,7 @@
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useWorkflow } from 'features/nodes/hooks/useWorkflow'; import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
import { workflowLoaded } from 'features/nodes/store/nodesSlice'; import { workflowLoaded } from 'features/nodes/store/workflowSlice';
import { zWorkflowV2 } from 'features/nodes/types/workflow'; import { zWorkflowV2 } from 'features/nodes/types/workflow';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';

View File

@ -1,7 +1,7 @@
import { useAppToaster } from 'app/components/Toaster'; import { useAppToaster } from 'app/components/Toaster';
import { useAppDispatch } from 'app/store/storeHooks'; import { useAppDispatch } from 'app/store/storeHooks';
import { useWorkflow } from 'features/nodes/hooks/useWorkflow'; import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
import { workflowLoaded } from 'features/nodes/store/nodesSlice'; import { workflowLoaded } from 'features/nodes/store/workflowSlice';
import { zWorkflowV2 } from 'features/nodes/types/workflow'; import { zWorkflowV2 } from 'features/nodes/types/workflow';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';