FN-6073: preserve pending workflow value edits during save
Preserve unsaved workflow value edits made while a save request is in flight. - track the keys included in the current save request before sending it - clear only the saved keys after a successful save so newer edits remain pending - add regression coverage for mid-flight edits that trigger a follow-up save Files changed: packages/dashboard/app/components/WorkflowSettingsPanel.tsx | 7 +++- packages/dashboard/app/components/__tests__/WorkflowSettingsPanel.test.tsx | 41 +++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-6073 Fusion-Task-Lineage: 31e05269-d51d-40c0-9691-87e61d464992
This commit is contained in:
@@ -721,11 +721,16 @@ function ValuesTab({
|
||||
|
||||
const save = useCallback(async () => {
|
||||
if (!dirty) return;
|
||||
const savedKeys = new Set(Object.keys(pending));
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await updateWorkflowSettingValues(workflowId, pending, boundProjectId);
|
||||
setPayload(res);
|
||||
setPending({});
|
||||
setPending((prev) => {
|
||||
const next = { ...prev };
|
||||
for (const k of savedKeys) delete next[k];
|
||||
return next;
|
||||
});
|
||||
setRejections({});
|
||||
addToast(t("workflowSettings.valuesSaved", "Setting values saved"), "success");
|
||||
} catch (err) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* `updateWorkflowSettingValues`).
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { render, screen, fireEvent, waitFor, cleanup, within } from "@testing-library/react";
|
||||
import { render, screen, fireEvent, waitFor, cleanup, within, act } from "@testing-library/react";
|
||||
import { useState } from "react";
|
||||
import * as jestDomMatchers from "@testing-library/jest-dom/matchers";
|
||||
|
||||
@@ -227,6 +227,45 @@ describe("WorkflowSettingsPanel — Values tab", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves mid-flight edits after a successful save and resaves only the new keys", async () => {
|
||||
mockFetchValues.mockResolvedValue(payload({ effective: { "timeout-ms": 1000, "new-sessions": false, label: "" } }));
|
||||
let resolveSave!: (value: WorkflowSettingValuesPayload) => void;
|
||||
mockUpdateValues
|
||||
.mockReturnValueOnce(
|
||||
new Promise<WorkflowSettingValuesPayload>((resolve) => {
|
||||
resolveSave = resolve;
|
||||
}),
|
||||
)
|
||||
.mockResolvedValueOnce(
|
||||
payload({
|
||||
stored: { "timeout-ms": 5000, label: "mid-flight-edit" },
|
||||
effective: { "timeout-ms": 5000, "new-sessions": false, label: "mid-flight-edit" },
|
||||
}),
|
||||
);
|
||||
|
||||
render(<Host initial={decls} />);
|
||||
openValues();
|
||||
await waitFor(() => expect(mockFetchValues).toHaveBeenCalled());
|
||||
|
||||
fireEvent.change(screen.getByLabelText("Timeout"), { target: { value: "5000" } });
|
||||
fireEvent.click(screen.getByTestId("wf-settings-save-values"));
|
||||
await waitFor(() => expect(mockUpdateValues).toHaveBeenCalledTimes(1));
|
||||
|
||||
fireEvent.change(screen.getByLabelText("Label"), { target: { value: "mid-flight-edit" } });
|
||||
await act(async () => {
|
||||
resolveSave(payload({ stored: { "timeout-ms": 5000 }, effective: { "timeout-ms": 5000, "new-sessions": false } }));
|
||||
});
|
||||
|
||||
expect(mockUpdateValues).toHaveBeenNthCalledWith(1, "wf-1", { "timeout-ms": 5000 }, "proj-1");
|
||||
expect(screen.getByLabelText("Label")).toHaveValue("mid-flight-edit");
|
||||
expect(screen.getByLabelText("Timeout")).toHaveValue(5000);
|
||||
|
||||
await waitFor(() => expect(screen.getByTestId("wf-settings-save-values")).not.toBeDisabled());
|
||||
fireEvent.click(screen.getByTestId("wf-settings-save-values"));
|
||||
await waitFor(() => expect(mockUpdateValues).toHaveBeenCalledTimes(2));
|
||||
expect(mockUpdateValues).toHaveBeenNthCalledWith(2, "wf-1", { label: "mid-flight-edit" }, "proj-1");
|
||||
});
|
||||
|
||||
it("renders a per-field rejection on the matching row and keeps other edits applied", async () => {
|
||||
mockFetchValues.mockResolvedValue(payload({ effective: { "timeout-ms": 1000, "new-sessions": false } }));
|
||||
mockUpdateValues.mockRejectedValueOnce(
|
||||
|
||||
Reference in New Issue
Block a user