fix: restored save on change, simplified tool_mode attribution to make it work between updates (#5599)
* Fixed flowStore to run autoSave on setNode
* Fix isToolMode from GenericNode to take already set value from node
* Removed tool_mode from the usePostTemplateValue params, inserting it into inly the payload
* Removing tool_mode from usePostTemplateValue use
* Made toolMode be passed in mutateTemplate
* Refactored activateToolMode to make it work between updates
* Added autoSaveFlow dependency into useEffect
* ✨ (general-bugs-save-changes-on-node.spec.ts): add test to verify that any changes made on the node are saved on user interaction
---------
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
parent
1f614ccca6
commit
0c2de6691d
8 changed files with 86 additions and 44 deletions
|
|
@ -49,7 +49,6 @@ export default function NodeInputField({
|
||||||
node: data.node!,
|
node: data.node!,
|
||||||
nodeId: data.id,
|
nodeId: data.id,
|
||||||
parameterId: name,
|
parameterId: name,
|
||||||
tool_mode: data.node!.tool_mode ?? false,
|
|
||||||
});
|
});
|
||||||
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
||||||
const { handleNodeClass } = useHandleNodeClass(data.id);
|
const { handleNodeClass } = useHandleNodeClass(data.id);
|
||||||
|
|
|
||||||
|
|
@ -181,8 +181,10 @@ function GenericNode({
|
||||||
() =>
|
() =>
|
||||||
data.node?.outputs?.some(
|
data.node?.outputs?.some(
|
||||||
(output) => output.name === "component_as_tool",
|
(output) => output.name === "component_as_tool",
|
||||||
) ?? false,
|
) ??
|
||||||
[data.node?.outputs],
|
data.node?.tool_mode ??
|
||||||
|
false,
|
||||||
|
[data.node?.outputs, data.node?.tool_mode],
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasToolMode = useMemo(
|
const hasToolMode = useMemo(
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,19 @@ export const mutateTemplate = debounce(
|
||||||
setErrorData,
|
setErrorData,
|
||||||
parameterName?: string,
|
parameterName?: string,
|
||||||
callback?: () => void,
|
callback?: () => void,
|
||||||
|
toolMode?: boolean,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const newNode = cloneDeep(node);
|
const newNode = cloneDeep(node);
|
||||||
const newTemplate = await postTemplateValue.mutateAsync({
|
const newTemplate = await postTemplateValue.mutateAsync({
|
||||||
value: newValue,
|
value: newValue,
|
||||||
field_name: parameterName,
|
field_name: parameterName,
|
||||||
|
tool_mode: toolMode ?? node.tool_mode,
|
||||||
});
|
});
|
||||||
if (newTemplate) {
|
if (newTemplate) {
|
||||||
newNode.template = newTemplate.template;
|
newNode.template = newTemplate.template;
|
||||||
newNode.outputs = newTemplate.outputs;
|
newNode.outputs = newTemplate.outputs;
|
||||||
|
newNode.tool_mode = toolMode;
|
||||||
}
|
}
|
||||||
setNodeClass(newNode);
|
setNodeClass(newNode);
|
||||||
callback?.();
|
callback?.();
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ interface IPostTemplateValueParams {
|
||||||
node: APIClassType;
|
node: APIClassType;
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
parameterId: string;
|
parameterId: string;
|
||||||
tool_mode: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePostTemplateValue: useMutationFunctionType<
|
export const usePostTemplateValue: useMutationFunctionType<
|
||||||
|
|
@ -25,7 +24,7 @@ export const usePostTemplateValue: useMutationFunctionType<
|
||||||
IPostTemplateValue,
|
IPostTemplateValue,
|
||||||
APIClassType,
|
APIClassType,
|
||||||
ResponseErrorDetailAPI
|
ResponseErrorDetailAPI
|
||||||
> = ({ parameterId, nodeId, node, tool_mode }, options?) => {
|
> = ({ parameterId, nodeId, node }, options?) => {
|
||||||
const { mutate } = UseRequestProcessor();
|
const { mutate } = UseRequestProcessor();
|
||||||
|
|
||||||
const postTemplateValueFn = async (
|
const postTemplateValueFn = async (
|
||||||
|
|
@ -41,7 +40,7 @@ export const usePostTemplateValue: useMutationFunctionType<
|
||||||
template: template,
|
template: template,
|
||||||
field: parameterId,
|
field: parameterId,
|
||||||
field_value: payload.value,
|
field_value: payload.value,
|
||||||
tool_mode: tool_mode,
|
tool_mode: payload.tool_mode,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ export default function Page({ view }: { view?: boolean }): JSX.Element {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
useFlowStore.setState({ autoSaveFlow });
|
useFlowStore.setState({ autoSaveFlow });
|
||||||
});
|
}, [autoSaveFlow]);
|
||||||
|
|
||||||
function handleUndo(e: KeyboardEvent) {
|
function handleUndo(e: KeyboardEvent) {
|
||||||
if (!isWrappedWithClass(e, "noflow")) {
|
if (!isWrappedWithClass(e, "noflow")) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class";
|
||||||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||||
import ToggleShadComponent from "@/components/core/parameterRenderComponent/components/toggleShadComponent";
|
import ToggleShadComponent from "@/components/core/parameterRenderComponent/components/toggleShadComponent";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { usePatchUpdateFlow } from "@/controllers/API/queries/flows/use-patch-update-flow";
|
|
||||||
import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value";
|
import { usePostTemplateValue } from "@/controllers/API/queries/nodes/use-post-template-value";
|
||||||
import { usePostRetrieveVertexOrder } from "@/controllers/API/queries/vertex";
|
import { usePostRetrieveVertexOrder } from "@/controllers/API/queries/vertex";
|
||||||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||||
|
|
@ -93,7 +92,6 @@ const NodeToolbarComponent = memo(
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const updateToolMode = useFlowStore((state) => state.updateToolMode);
|
|
||||||
|
|
||||||
const flowDataNodes = useMemo(
|
const flowDataNodes = useMemo(
|
||||||
() => currentFlow?.data?.nodes,
|
() => currentFlow?.data?.nodes,
|
||||||
|
|
@ -114,7 +112,6 @@ const NodeToolbarComponent = memo(
|
||||||
node: data.node!,
|
node: data.node!,
|
||||||
nodeId: data.id,
|
nodeId: data.id,
|
||||||
parameterId: "tool_mode",
|
parameterId: "tool_mode",
|
||||||
tool_mode: data.node!.tool_mode ?? false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const isSaved = flows?.some((flow) =>
|
const isSaved = flows?.some((flow) =>
|
||||||
|
|
@ -141,8 +138,6 @@ const NodeToolbarComponent = memo(
|
||||||
);
|
);
|
||||||
const addFlow = useAddFlow();
|
const addFlow = useAddFlow();
|
||||||
|
|
||||||
const { mutate: patchUpdateFlow } = usePatchUpdateFlow();
|
|
||||||
|
|
||||||
const isMinimal = useMemo(
|
const isMinimal = useMemo(
|
||||||
() => countHandlesFn(data) <= 1 && numberOfOutputHandles <= 1,
|
() => countHandlesFn(data) <= 1 && numberOfOutputHandles <= 1,
|
||||||
[data, numberOfOutputHandles],
|
[data, numberOfOutputHandles],
|
||||||
|
|
@ -172,13 +167,8 @@ const NodeToolbarComponent = memo(
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleActivateToolMode = () => {
|
const handleActivateToolMode = () => {
|
||||||
const newValue = !flowDataNodes![index]!.data.node.tool_mode;
|
const newValue = !toolMode;
|
||||||
|
|
||||||
updateToolMode(data.id, newValue);
|
|
||||||
data.node!.tool_mode = newValue;
|
|
||||||
|
|
||||||
setToolMode(newValue);
|
setToolMode(newValue);
|
||||||
|
|
||||||
mutateTemplate(
|
mutateTemplate(
|
||||||
newValue,
|
newValue,
|
||||||
data.node!,
|
data.node!,
|
||||||
|
|
@ -186,21 +176,9 @@ const NodeToolbarComponent = memo(
|
||||||
postToolModeValue,
|
postToolModeValue,
|
||||||
setErrorData,
|
setErrorData,
|
||||||
"tool_mode",
|
"tool_mode",
|
||||||
() => {
|
() => updateNodeInternals(data.id),
|
||||||
currentFlow!.data!.nodes[index]!.data.node.tool_mode = newValue;
|
newValue,
|
||||||
patchUpdateFlow({
|
|
||||||
id: currentFlow?.id!,
|
|
||||||
name: currentFlow?.name!,
|
|
||||||
data: currentFlow?.data!,
|
|
||||||
description: currentFlow?.description!,
|
|
||||||
folder_id: currentFlow?.folder_id!,
|
|
||||||
endpoint_name: currentFlow?.endpoint_name!,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
updateNodeInternals(data.id);
|
|
||||||
return newValue;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMinimize = useCallback(() => {
|
const handleMinimize = useCallback(() => {
|
||||||
|
|
@ -437,6 +415,7 @@ const NodeToolbarComponent = memo(
|
||||||
setLastCopiedSelection,
|
setLastCopiedSelection,
|
||||||
paste,
|
paste,
|
||||||
handleActivateToolMode,
|
handleActivateToolMode,
|
||||||
|
toolMode,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -294,32 +294,35 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||||
? change(get().nodes.find((node) => node.id === id)!)
|
? change(get().nodes.find((node) => node.id === id)!)
|
||||||
: change;
|
: change;
|
||||||
|
|
||||||
set((state) => {
|
const newNodes = get().nodes.map((node) => {
|
||||||
const newNodes = state.nodes.map((node) => {
|
if (node.id === id) {
|
||||||
if (node.id === id) {
|
if (isUserChange) {
|
||||||
if (isUserChange) {
|
if ((node.data as NodeDataType).node?.frozen) {
|
||||||
if ((node.data as NodeDataType).node?.frozen) {
|
(newChange.data as NodeDataType).node!.frozen = false;
|
||||||
(newChange.data as NodeDataType).node!.frozen = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return newChange;
|
|
||||||
}
|
}
|
||||||
return node;
|
return newChange;
|
||||||
});
|
}
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
|
||||||
const newEdges = cleanEdges(newNodes, get().edges);
|
const newEdges = cleanEdges(newNodes, get().edges);
|
||||||
|
|
||||||
|
set((state) => {
|
||||||
if (callback) {
|
if (callback) {
|
||||||
// Defer the callback execution to ensure it runs after state updates are fully applied.
|
// Defer the callback execution to ensure it runs after state updates are fully applied.
|
||||||
queueMicrotask(callback);
|
queueMicrotask(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
nodes: newNodes,
|
nodes: newNodes,
|
||||||
edges: newEdges,
|
edges: newEdges,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
get().updateCurrentFlow({ nodes: newNodes, edges: newEdges });
|
||||||
|
if (get().autoSaveFlow) {
|
||||||
|
get().autoSaveFlow!();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getNode: (id: string) => {
|
getNode: (id: string) => {
|
||||||
return get().nodes.find((node) => node.id === id);
|
return get().nodes.find((node) => node.id === id);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { expect, Page, test } from "@playwright/test";
|
||||||
|
import { awaitBootstrapTest } from "../../utils/await-bootstrap-test";
|
||||||
|
|
||||||
|
async function verifyTextareaValue(page: Page, value: string) {
|
||||||
|
await page.getByTestId("textarea_str_input_value").fill(value);
|
||||||
|
await page.getByTestId("icon-ChevronLeft").first().click();
|
||||||
|
|
||||||
|
await page.waitForSelector('[data-testid="list-card"]', {
|
||||||
|
timeout: 3000,
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByTestId("list-card").first().click();
|
||||||
|
|
||||||
|
await page.waitForSelector('[data-testid="textarea_str_input_value"]', {
|
||||||
|
timeout: 3000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputValue = await page
|
||||||
|
.getByTestId("textarea_str_input_value")
|
||||||
|
.inputValue();
|
||||||
|
expect(inputValue).toBe(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
test(
|
||||||
|
"any changes on the node must be saved on user interaction",
|
||||||
|
{ tag: ["@release", "@components"] },
|
||||||
|
async ({ page }) => {
|
||||||
|
const randomValues = Array.from({ length: 4 }, () =>
|
||||||
|
Math.random().toString(36).substring(2, 15),
|
||||||
|
);
|
||||||
|
|
||||||
|
await awaitBootstrapTest(page);
|
||||||
|
await page.getByTestId("blank-flow").click();
|
||||||
|
|
||||||
|
await page.waitForSelector('[data-testid="fit_view"]', {
|
||||||
|
timeout: 100000,
|
||||||
|
});
|
||||||
|
await page.getByTestId("sidebar-search-input").click();
|
||||||
|
await page.getByTestId("sidebar-search-input").fill("text output");
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByTestId("outputsText Output")
|
||||||
|
.hover()
|
||||||
|
.then(async () => {
|
||||||
|
await page.getByTestId("add-component-button-text-output").click();
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.waitForSelector('[data-testid="title-Text Output"]', {
|
||||||
|
timeout: 3000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify each random value
|
||||||
|
for (const value of randomValues) {
|
||||||
|
await verifyTextareaValue(page, value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue