Skip to content

Commit 7506ece

Browse files
authored
fix(editor): Show the right state on metadata update (no-changelog) (#25816)
1 parent 08e5c74 commit 7506ece

5 files changed

Lines changed: 91 additions & 4 deletions

File tree

packages/frontend/editor-ui/src/app/components/MainHeader/WorkflowDetails.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ function onTagsBlur() {
175175
workflowDocumentStore.value.setTags(tags);
176176
}
177177
workflowState.setWorkflowTagIds(tags);
178-
uiStore.markStateDirty();
178+
uiStore.markStateDirty('metadata');
179179
180180
telemetry.track('User edited workflow tags', {
181181
workflow_id: props.id,

packages/frontend/editor-ui/src/app/components/MainHeader/WorkflowHeaderDraftPublishActions.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ const workflowPublishState = computed((): WorkflowPublishState => {
9999
const hasBeenPublished = !!workflowsStore.workflow.activeVersion;
100100
const hasChanges =
101101
workflowsStore.workflow.versionId !== workflowsStore.workflow.activeVersion?.versionId ||
102-
uiStore.stateIsDirty;
102+
uiStore.hasUnsavedWorkflowChanges;
103103
104104
// Not published states
105105
if (!hasBeenPublished) {

packages/frontend/editor-ui/src/app/composables/useWorkflowState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function useWorkflowState() {
6262

6363
function setWorkflowName(data: { newName: string; setStateDirty: boolean }) {
6464
if (data.setStateDirty) {
65-
uiStore.markStateDirty();
65+
uiStore.markStateDirty('metadata');
6666
}
6767
ws.workflow.name = data.newName;
6868
ws.workflowObject.name = data.newName;
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { createPinia, setActivePinia } from 'pinia';
2+
import { useUIStore } from '@/app/stores/ui.store';
3+
4+
describe('UI Store', () => {
5+
beforeEach(() => {
6+
setActivePinia(createPinia());
7+
});
8+
9+
describe('markStateDirty', () => {
10+
it('should mark state as dirty and set hasUnsavedWorkflowChanges when type is workflow', () => {
11+
const uiStore = useUIStore();
12+
13+
uiStore.markStateDirty('workflow');
14+
15+
expect(uiStore.stateIsDirty).toBe(true);
16+
expect(uiStore.hasUnsavedWorkflowChanges).toBe(true);
17+
});
18+
19+
it('should mark state as dirty but not set hasUnsavedWorkflowChanges when type is metadata', () => {
20+
const uiStore = useUIStore();
21+
22+
uiStore.markStateDirty('metadata');
23+
24+
expect(uiStore.stateIsDirty).toBe(true);
25+
expect(uiStore.hasUnsavedWorkflowChanges).toBe(false);
26+
});
27+
28+
it('should default to workflow type when no type is provided', () => {
29+
const uiStore = useUIStore();
30+
31+
uiStore.markStateDirty();
32+
33+
expect(uiStore.stateIsDirty).toBe(true);
34+
expect(uiStore.hasUnsavedWorkflowChanges).toBe(true);
35+
});
36+
37+
it('should increment dirtyStateSetCount each time it is called', () => {
38+
const uiStore = useUIStore();
39+
const initialCount = uiStore.dirtyStateSetCount;
40+
41+
uiStore.markStateDirty('metadata');
42+
expect(uiStore.dirtyStateSetCount).toBe(initialCount + 1);
43+
44+
uiStore.markStateDirty('workflow');
45+
expect(uiStore.dirtyStateSetCount).toBe(initialCount + 2);
46+
});
47+
48+
it('should not override hasUnsavedWorkflowChanges once set to true', () => {
49+
const uiStore = useUIStore();
50+
51+
uiStore.markStateDirty('workflow');
52+
expect(uiStore.hasUnsavedWorkflowChanges).toBe(true);
53+
54+
uiStore.markStateDirty('metadata');
55+
expect(uiStore.hasUnsavedWorkflowChanges).toBe(true);
56+
});
57+
});
58+
59+
describe('markStateClean', () => {
60+
it('should mark state as clean', () => {
61+
const uiStore = useUIStore();
62+
63+
uiStore.markStateDirty('workflow');
64+
expect(uiStore.stateIsDirty).toBe(true);
65+
66+
uiStore.markStateClean();
67+
expect(uiStore.stateIsDirty).toBe(false);
68+
});
69+
70+
it('should clear hasUnsavedWorkflowChanges when marking state clean', () => {
71+
const uiStore = useUIStore();
72+
73+
uiStore.markStateDirty('workflow');
74+
expect(uiStore.hasUnsavedWorkflowChanges).toBe(true);
75+
76+
uiStore.markStateClean();
77+
expect(uiStore.hasUnsavedWorkflowChanges).toBe(false);
78+
});
79+
});
80+
});

packages/frontend/editor-ui/src/app/stores/ui.store.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ export const useUIStore = defineStore(STORES.UI, () => {
275275
});
276276
const currentView = ref<string>('');
277277
const stateIsDirty = ref<boolean>(false);
278+
// This tracks only structural changes without metadata (name or tags)
279+
const hasUnsavedWorkflowChanges = ref<boolean>(false);
278280
const dirtyStateSetCount = ref<number>(0);
279281
const lastSelectedNode = ref<string | null>(null);
280282
const nodeViewOffsetPosition = ref<[number, number]>([0, 0]);
@@ -617,13 +619,17 @@ export const useUIStore = defineStore(STORES.UI, () => {
617619
processingExecutionResults.value = value;
618620
};
619621

620-
const markStateDirty = () => {
622+
const markStateDirty = (type: 'workflow' | 'metadata' = 'workflow') => {
621623
dirtyStateSetCount.value++;
622624
stateIsDirty.value = true;
625+
if (type === 'workflow') {
626+
hasUnsavedWorkflowChanges.value = true;
627+
}
623628
};
624629

625630
const markStateClean = () => {
626631
stateIsDirty.value = false;
632+
hasUnsavedWorkflowChanges.value = false;
627633
};
628634

629635
/**
@@ -686,6 +692,7 @@ export const useUIStore = defineStore(STORES.UI, () => {
686692
headerHeight,
687693
dirtyStateSetCount: computed(() => dirtyStateSetCount.value),
688694
stateIsDirty: computed(() => stateIsDirty.value),
695+
hasUnsavedWorkflowChanges: computed(() => hasUnsavedWorkflowChanges.value),
689696
isBlankRedirect,
690697
activeCredentialType,
691698
lastSelectedNode,

0 commit comments

Comments
 (0)