mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(editor): Workflow history [WIP] - Remove pinned data from workflow history version preview (no-changelog) (#7406)
This commit is contained in:
parent
82129694c6
commit
c7c8048430
|
@ -34,8 +34,9 @@ const workflowVersionPreview = computed<IWorkflowDb | undefined>(() => {
|
||||||
if (!props.workflowVersion || !props.workflow) {
|
if (!props.workflowVersion || !props.workflow) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const { pinData, ...workflow } = props.workflow;
|
||||||
return {
|
return {
|
||||||
...props.workflow,
|
...workflow,
|
||||||
nodes: props.workflowVersion.nodes,
|
nodes: props.workflowVersion.nodes,
|
||||||
connections: props.workflowVersion.connections,
|
connections: props.workflowVersion.connections,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
|
import { vi } from 'vitest';
|
||||||
import { createPinia, setActivePinia } from 'pinia';
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
|
import { waitFor } from '@testing-library/vue';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import type { UserAction } from 'n8n-design-system';
|
import type { UserAction } from 'n8n-design-system';
|
||||||
import { createComponentRenderer } from '@/__tests__/render';
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
import WorkflowHistoryContent from '@/components/WorkflowHistory/WorkflowHistoryContent.vue';
|
import WorkflowHistoryContent from '@/components/WorkflowHistory/WorkflowHistoryContent.vue';
|
||||||
import type { WorkflowHistoryActionTypes } from '@/types/workflowHistory';
|
import type { WorkflowHistoryActionTypes } from '@/types/workflowHistory';
|
||||||
import { workflowHistoryDataFactory } from '@/stores/__tests__/utils/workflowHistoryTestUtils';
|
import { workflowVersionDataFactory } from '@/stores/__tests__/utils/workflowHistoryTestUtils';
|
||||||
|
import type { IWorkflowDb } from '@/Interface';
|
||||||
|
|
||||||
const actionTypes: WorkflowHistoryActionTypes = ['restore', 'clone', 'open', 'download'];
|
const actionTypes: WorkflowHistoryActionTypes = ['restore', 'clone', 'open', 'download'];
|
||||||
const actions: UserAction[] = actionTypes.map((value) => ({
|
const actions: UserAction[] = actionTypes.map((value) => ({
|
||||||
|
@ -16,15 +19,24 @@ const actions: UserAction[] = actionTypes.map((value) => ({
|
||||||
const renderComponent = createComponentRenderer(WorkflowHistoryContent);
|
const renderComponent = createComponentRenderer(WorkflowHistoryContent);
|
||||||
|
|
||||||
let pinia: ReturnType<typeof createPinia>;
|
let pinia: ReturnType<typeof createPinia>;
|
||||||
|
let postMessageSpy: vi.SpyInstance;
|
||||||
|
|
||||||
describe('WorkflowHistoryContent', () => {
|
describe('WorkflowHistoryContent', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
pinia = createPinia();
|
pinia = createPinia();
|
||||||
setActivePinia(pinia);
|
setActivePinia(pinia);
|
||||||
|
|
||||||
|
postMessageSpy = vi.fn();
|
||||||
|
Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', {
|
||||||
|
writable: true,
|
||||||
|
value: {
|
||||||
|
postMessage: postMessageSpy,
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use the list item component to render version data', () => {
|
it('should use the list item component to render version data', () => {
|
||||||
const workflowVersion = workflowHistoryDataFactory();
|
const workflowVersion = workflowVersionDataFactory();
|
||||||
const { getByTestId } = renderComponent({
|
const { getByTestId } = renderComponent({
|
||||||
pinia,
|
pinia,
|
||||||
props: {
|
props: {
|
||||||
|
@ -38,7 +50,7 @@ describe('WorkflowHistoryContent', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each(actionTypes)('should emit %s event', async (action) => {
|
test.each(actionTypes)('should emit %s event', async (action) => {
|
||||||
const workflowVersion = workflowHistoryDataFactory();
|
const workflowVersion = workflowVersionDataFactory();
|
||||||
const { getByTestId, emitted } = renderComponent({
|
const { getByTestId, emitted } = renderComponent({
|
||||||
pinia,
|
pinia,
|
||||||
props: {
|
props: {
|
||||||
|
@ -56,4 +68,23 @@ describe('WorkflowHistoryContent', () => {
|
||||||
[{ action, id: workflowVersion.versionId, data: { formattedCreatedAt: expect.any(String) } }],
|
[{ action, id: workflowVersion.versionId, data: { formattedCreatedAt: expect.any(String) } }],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should pass proper workflow data to WorkflowPreview component', async () => {
|
||||||
|
const workflowVersion = workflowVersionDataFactory();
|
||||||
|
const workflow = { pinData: {} } as IWorkflowDb;
|
||||||
|
renderComponent({
|
||||||
|
pinia,
|
||||||
|
props: {
|
||||||
|
workflow,
|
||||||
|
workflowVersion,
|
||||||
|
actions,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
window.postMessage('{"command":"n8nReady"}', '*');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(postMessageSpy).toHaveBeenCalledWith(expect.not.stringContaining('pinData'), '*');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,202 +8,194 @@
|
||||||
</div>
|
</div>
|
||||||
<iframe
|
<iframe
|
||||||
:class="{
|
:class="{
|
||||||
[$style.workflow]: !this.nodeViewDetailsOpened,
|
[$style.workflow]: !nodeViewDetailsOpened,
|
||||||
[$style.executionPreview]: mode === 'execution',
|
[$style.executionPreview]: mode === 'execution',
|
||||||
[$style.openNDV]: this.nodeViewDetailsOpened,
|
[$style.openNDV]: nodeViewDetailsOpened,
|
||||||
[$style.show]: this.showPreview,
|
[$style.show]: showPreview,
|
||||||
}"
|
}"
|
||||||
ref="preview_iframe"
|
ref="iframeRef"
|
||||||
:src="`${rootStore.baseUrl}workflows/demo`"
|
:src="`${rootStore.baseUrl}workflows/demo`"
|
||||||
@mouseenter="onMouseEnter"
|
@mouseenter="onMouseEnter"
|
||||||
@mouseleave="onMouseLeave"
|
@mouseleave="onMouseLeave"
|
||||||
></iframe>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, onBeforeUnmount, ref, computed, watch } from 'vue';
|
||||||
import { useToast } from '@/composables';
|
import { useI18n, useToast } from '@/composables';
|
||||||
import type { IWorkflowDb } from '@/Interface';
|
import type { IWorkflowDb } from '@/Interface';
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import { useRootStore } from '@/stores/n8nRoot.store';
|
import { useRootStore } from '@/stores/n8nRoot.store';
|
||||||
import { useWorkflowsStore } from '@/stores';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
|
||||||
export default defineComponent({
|
const props = withDefaults(
|
||||||
name: 'WorkflowPreview',
|
defineProps<{
|
||||||
props: {
|
loading?: boolean;
|
||||||
loading: {
|
mode?: 'workflow' | 'execution';
|
||||||
type: Boolean,
|
workflow?: IWorkflowDb;
|
||||||
default: false,
|
executionId?: string;
|
||||||
},
|
executionMode?: string;
|
||||||
mode: {
|
loaderType?: 'image' | 'spinner';
|
||||||
type: String,
|
}>(),
|
||||||
default: 'workflow',
|
{
|
||||||
validator: (value: string): boolean => ['workflow', 'execution'].includes(value),
|
loading: false,
|
||||||
},
|
mode: 'workflow',
|
||||||
workflow: {
|
loaderType: 'image',
|
||||||
type: Object as () => IWorkflowDb,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
executionId: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
executionMode: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
loaderType: {
|
|
||||||
type: String,
|
|
||||||
default: 'image',
|
|
||||||
validator: (value: string): boolean => ['image', 'spinner'].includes(value),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
setup() {
|
);
|
||||||
return {
|
|
||||||
...useToast(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
nodeViewDetailsOpened: false,
|
|
||||||
ready: false,
|
|
||||||
insideIframe: false,
|
|
||||||
scrollX: 0,
|
|
||||||
scrollY: 0,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useRootStore, useWorkflowsStore),
|
|
||||||
showPreview(): boolean {
|
|
||||||
return (
|
|
||||||
!this.loading &&
|
|
||||||
((this.mode === 'workflow' && !!this.workflow) ||
|
|
||||||
(this.mode === 'execution' && !!this.executionId)) &&
|
|
||||||
this.ready
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onMouseEnter() {
|
|
||||||
this.insideIframe = true;
|
|
||||||
this.scrollX = window.scrollX;
|
|
||||||
this.scrollY = window.scrollY;
|
|
||||||
},
|
|
||||||
onMouseLeave() {
|
|
||||||
this.insideIframe = false;
|
|
||||||
},
|
|
||||||
loadWorkflow() {
|
|
||||||
try {
|
|
||||||
if (!this.workflow) {
|
|
||||||
throw new Error(this.$locale.baseText('workflowPreview.showError.missingWorkflow'));
|
|
||||||
}
|
|
||||||
if (!this.workflow.nodes || !Array.isArray(this.workflow.nodes)) {
|
|
||||||
throw new Error(this.$locale.baseText('workflowPreview.showError.arrayEmpty'));
|
|
||||||
}
|
|
||||||
|
|
||||||
const iframeRef = this.$refs.preview_iframe as HTMLIFrameElement | undefined;
|
const emit = defineEmits<{
|
||||||
if (iframeRef?.contentWindow) {
|
(event: 'close'): void;
|
||||||
iframeRef.contentWindow.postMessage(
|
}>();
|
||||||
JSON.stringify({
|
|
||||||
command: 'openWorkflow',
|
|
||||||
workflow: this.workflow,
|
|
||||||
}),
|
|
||||||
'*',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.showError(
|
|
||||||
error,
|
|
||||||
this.$locale.baseText('workflowPreview.showError.previewError.title'),
|
|
||||||
this.$locale.baseText('workflowPreview.showError.previewError.message'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
loadExecution() {
|
|
||||||
try {
|
|
||||||
if (!this.executionId) {
|
|
||||||
throw new Error(this.$locale.baseText('workflowPreview.showError.missingExecution'));
|
|
||||||
}
|
|
||||||
const iframeRef = this.$refs.preview_iframe as HTMLIFrameElement | undefined;
|
|
||||||
if (iframeRef?.contentWindow) {
|
|
||||||
iframeRef.contentWindow.postMessage(
|
|
||||||
JSON.stringify({
|
|
||||||
command: 'openExecution',
|
|
||||||
executionId: this.executionId,
|
|
||||||
executionMode: this.executionMode || '',
|
|
||||||
}),
|
|
||||||
'*',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.workflowsStore.activeWorkflowExecution) {
|
const i18n = useI18n();
|
||||||
iframeRef.contentWindow.postMessage(
|
const toast = useToast();
|
||||||
JSON.stringify({
|
const rootStore = useRootStore();
|
||||||
command: 'setActiveExecution',
|
const workflowsStore = useWorkflowsStore();
|
||||||
execution: this.workflowsStore.activeWorkflowExecution,
|
|
||||||
}),
|
const iframeRef = ref<HTMLIFrameElement | null>(null);
|
||||||
'*',
|
const nodeViewDetailsOpened = ref(false);
|
||||||
);
|
const ready = ref(false);
|
||||||
}
|
const insideIframe = ref(false);
|
||||||
}
|
const scrollX = ref(0);
|
||||||
} catch (error) {
|
const scrollY = ref(0);
|
||||||
this.showError(
|
|
||||||
error,
|
const showPreview = computed(() => {
|
||||||
this.$locale.baseText('workflowPreview.showError.previewError.title'),
|
return (
|
||||||
this.$locale.baseText('workflowPreview.executionMode.showError.previewError.message'),
|
!props.loading &&
|
||||||
);
|
((props.mode === 'workflow' && props.workflow) ||
|
||||||
}
|
(props.mode === 'execution' && props.executionId)) &&
|
||||||
},
|
ready.value
|
||||||
receiveMessage({ data }: MessageEvent) {
|
);
|
||||||
try {
|
|
||||||
const json = JSON.parse(data);
|
|
||||||
if (json.command === 'n8nReady') {
|
|
||||||
this.ready = true;
|
|
||||||
} else if (json.command === 'openNDV') {
|
|
||||||
this.nodeViewDetailsOpened = true;
|
|
||||||
} else if (json.command === 'closeNDV') {
|
|
||||||
this.nodeViewDetailsOpened = false;
|
|
||||||
} else if (json.command === 'error') {
|
|
||||||
this.$emit('close');
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
},
|
|
||||||
onDocumentScroll() {
|
|
||||||
if (this.insideIframe) {
|
|
||||||
window.scrollTo(this.scrollX, this.scrollY);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
showPreview(show) {
|
|
||||||
if (show) {
|
|
||||||
if (this.mode === 'workflow') {
|
|
||||||
this.loadWorkflow();
|
|
||||||
} else if (this.mode === 'execution') {
|
|
||||||
this.loadExecution();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
executionId(value) {
|
|
||||||
if (this.mode === 'execution' && this.executionId) {
|
|
||||||
this.loadExecution();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
workflow() {
|
|
||||||
if (this.mode === 'workflow' && this.workflow) {
|
|
||||||
this.loadWorkflow();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
window.addEventListener('message', this.receiveMessage);
|
|
||||||
document.addEventListener('scroll', this.onDocumentScroll);
|
|
||||||
},
|
|
||||||
beforeUnmount() {
|
|
||||||
window.removeEventListener('message', this.receiveMessage);
|
|
||||||
document.removeEventListener('scroll', this.onDocumentScroll);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const loadWorkflow = () => {
|
||||||
|
try {
|
||||||
|
if (!props.workflow) {
|
||||||
|
throw new Error(i18n.baseText('workflowPreview.showError.missingWorkflow'));
|
||||||
|
}
|
||||||
|
if (!props.workflow.nodes || !Array.isArray(props.workflow.nodes)) {
|
||||||
|
throw new Error(i18n.baseText('workflowPreview.showError.arrayEmpty'));
|
||||||
|
}
|
||||||
|
iframeRef.value?.contentWindow?.postMessage?.(
|
||||||
|
JSON.stringify({
|
||||||
|
command: 'openWorkflow',
|
||||||
|
workflow: props.workflow,
|
||||||
|
}),
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
toast.showError(
|
||||||
|
error,
|
||||||
|
i18n.baseText('workflowPreview.showError.previewError.title'),
|
||||||
|
i18n.baseText('workflowPreview.showError.previewError.message'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadExecution = () => {
|
||||||
|
try {
|
||||||
|
if (!props.executionId) {
|
||||||
|
throw new Error(i18n.baseText('workflowPreview.showError.missingExecution'));
|
||||||
|
}
|
||||||
|
iframeRef.value?.contentWindow?.postMessage?.(
|
||||||
|
JSON.stringify({
|
||||||
|
command: 'openExecution',
|
||||||
|
executionId: props.executionId,
|
||||||
|
executionMode: props.executionMode || '',
|
||||||
|
}),
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (workflowsStore.activeWorkflowExecution) {
|
||||||
|
iframeRef.value?.contentWindow?.postMessage?.(
|
||||||
|
JSON.stringify({
|
||||||
|
command: 'setActiveExecution',
|
||||||
|
execution: workflowsStore.activeWorkflowExecution,
|
||||||
|
}),
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.showError(
|
||||||
|
error,
|
||||||
|
i18n.baseText('workflowPreview.showError.previewError.title'),
|
||||||
|
i18n.baseText('workflowPreview.executionMode.showError.previewError.message'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseEnter = () => {
|
||||||
|
insideIframe.value = true;
|
||||||
|
scrollX.value = window.scrollX;
|
||||||
|
scrollY.value = window.scrollY;
|
||||||
|
};
|
||||||
|
const onMouseLeave = () => {
|
||||||
|
insideIframe.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const receiveMessage = ({ data }: MessageEvent) => {
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(data);
|
||||||
|
if (json.command === 'n8nReady') {
|
||||||
|
ready.value = true;
|
||||||
|
} else if (json.command === 'openNDV') {
|
||||||
|
nodeViewDetailsOpened.value = true;
|
||||||
|
} else if (json.command === 'closeNDV') {
|
||||||
|
nodeViewDetailsOpened.value = false;
|
||||||
|
} else if (json.command === 'error') {
|
||||||
|
emit('close');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onDocumentScroll = () => {
|
||||||
|
if (insideIframe.value) {
|
||||||
|
window.scrollTo(scrollX.value, scrollY.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('message', receiveMessage);
|
||||||
|
document.addEventListener('scroll', onDocumentScroll);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('message', receiveMessage);
|
||||||
|
document.removeEventListener('scroll', onDocumentScroll);
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => showPreview.value,
|
||||||
|
() => {
|
||||||
|
if (showPreview.value) {
|
||||||
|
if (props.mode === 'workflow') {
|
||||||
|
loadWorkflow();
|
||||||
|
} else if (props.mode === 'execution') {
|
||||||
|
loadExecution();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.executionId,
|
||||||
|
() => {
|
||||||
|
if (props.mode === 'execution' && props.executionId) {
|
||||||
|
loadExecution();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.workflow,
|
||||||
|
() => {
|
||||||
|
if (props.mode === 'workflow' && props.workflow) {
|
||||||
|
loadWorkflow();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
import { vi } from 'vitest';
|
||||||
|
import { createPinia, setActivePinia } from 'pinia';
|
||||||
|
import { waitFor } from '@testing-library/vue';
|
||||||
|
import type { IExecutionsSummary } from 'n8n-workflow';
|
||||||
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import type { INodeUi, IWorkflowDb } from '@/Interface';
|
||||||
|
import WorkflowPreview from '@/components/WorkflowPreview.vue';
|
||||||
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
|
||||||
|
const renderComponent = createComponentRenderer(WorkflowPreview);
|
||||||
|
|
||||||
|
let pinia: ReturnType<typeof createPinia>;
|
||||||
|
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
|
||||||
|
let postMessageSpy: vi.SpyInstance;
|
||||||
|
|
||||||
|
const sendPostMessageCommand = (command: string) => {
|
||||||
|
window.postMessage(`{"command":"${command}"}`, '*');
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('WorkflowPreview', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
pinia = createPinia();
|
||||||
|
setActivePinia(pinia);
|
||||||
|
workflowsStore = useWorkflowsStore();
|
||||||
|
|
||||||
|
postMessageSpy = vi.fn();
|
||||||
|
Object.defineProperty(HTMLIFrameElement.prototype, 'contentWindow', {
|
||||||
|
writable: true,
|
||||||
|
value: {
|
||||||
|
postMessage: postMessageSpy,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call iframe postMessage when it is ready and no workflow or executionId props', async () => {
|
||||||
|
renderComponent({
|
||||||
|
pinia,
|
||||||
|
props: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
sendPostMessageCommand('n8nReady');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(postMessageSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call iframe postMessage when it is ready and there are no nodes in the workflow', async () => {
|
||||||
|
const workflow = {} as IWorkflowDb;
|
||||||
|
renderComponent({
|
||||||
|
pinia,
|
||||||
|
props: {
|
||||||
|
workflow,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
sendPostMessageCommand('n8nReady');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(postMessageSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call iframe postMessage when it is ready and nodes is not an array', async () => {
|
||||||
|
const workflow = { nodes: {} } as IWorkflowDb;
|
||||||
|
renderComponent({
|
||||||
|
pinia,
|
||||||
|
props: {
|
||||||
|
workflow,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
sendPostMessageCommand('n8nReady');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(postMessageSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call iframe postMessage with "openWorkflow" when it is ready and the workflow has nodes', async () => {
|
||||||
|
const nodes = [{ name: 'Start' }] as INodeUi[];
|
||||||
|
const workflow = { nodes } as IWorkflowDb;
|
||||||
|
renderComponent({
|
||||||
|
pinia,
|
||||||
|
props: {
|
||||||
|
workflow,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
sendPostMessageCommand('n8nReady');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||||
|
JSON.stringify({
|
||||||
|
command: 'openWorkflow',
|
||||||
|
workflow,
|
||||||
|
}),
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call iframe postMessage with "openExecution" when executionId is passed but mode not set to "execution"', async () => {
|
||||||
|
const executionId = '123';
|
||||||
|
renderComponent({
|
||||||
|
pinia,
|
||||||
|
props: {
|
||||||
|
executionId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
sendPostMessageCommand('n8nReady');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(postMessageSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call iframe postMessage with "openExecution" when executionId is passed and mode is set', async () => {
|
||||||
|
const executionId = '123';
|
||||||
|
renderComponent({
|
||||||
|
pinia,
|
||||||
|
props: {
|
||||||
|
executionId,
|
||||||
|
mode: 'execution',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
sendPostMessageCommand('n8nReady');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||||
|
JSON.stringify({
|
||||||
|
command: 'openExecution',
|
||||||
|
executionId,
|
||||||
|
executionMode: '',
|
||||||
|
}),
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call also iframe postMessage with "setActiveExecution" if active execution is set', async () => {
|
||||||
|
vi.spyOn(workflowsStore, 'activeWorkflowExecution', 'get').mockReturnValue({
|
||||||
|
id: 'abc',
|
||||||
|
} as IExecutionsSummary);
|
||||||
|
|
||||||
|
const executionId = '123';
|
||||||
|
renderComponent({
|
||||||
|
pinia,
|
||||||
|
props: {
|
||||||
|
executionId,
|
||||||
|
mode: 'execution',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
sendPostMessageCommand('n8nReady');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||||
|
JSON.stringify({
|
||||||
|
command: 'openExecution',
|
||||||
|
executionId,
|
||||||
|
executionMode: '',
|
||||||
|
}),
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||||
|
JSON.stringify({
|
||||||
|
command: 'setActiveExecution',
|
||||||
|
execution: { id: 'abc' },
|
||||||
|
}),
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('iframe should toggle "openNDV" class with postmessages', async () => {
|
||||||
|
const nodes = [{ name: 'Start' }] as INodeUi[];
|
||||||
|
const workflow = { nodes } as IWorkflowDb;
|
||||||
|
const { container } = renderComponent({
|
||||||
|
pinia,
|
||||||
|
props: {
|
||||||
|
workflow,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const iframe = container.querySelector('iframe');
|
||||||
|
|
||||||
|
expect(iframe?.classList.toString()).not.toContain('openNDV');
|
||||||
|
|
||||||
|
sendPostMessageCommand('n8nReady');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(postMessageSpy).toHaveBeenCalledWith(
|
||||||
|
JSON.stringify({
|
||||||
|
command: 'openWorkflow',
|
||||||
|
workflow,
|
||||||
|
}),
|
||||||
|
'*',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
sendPostMessageCommand('openNDV');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(iframe?.classList.toString()).toContain('openNDV');
|
||||||
|
});
|
||||||
|
|
||||||
|
sendPostMessageCommand('closeNDV');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(iframe?.classList.toString()).not.toContain('openNDV');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit "close" event if iframe sends "error" command', async () => {
|
||||||
|
const { emitted } = renderComponent({
|
||||||
|
pinia,
|
||||||
|
props: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
sendPostMessageCommand('error');
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(emitted().close).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -81,18 +81,16 @@ export const useWorkflowHistoryStore = defineStore('workflowHistory', () => {
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
workflowVersionId: string,
|
workflowVersionId: string,
|
||||||
shouldDeactivate: boolean,
|
shouldDeactivate: boolean,
|
||||||
) => {
|
): Promise<IWorkflowDb> => {
|
||||||
const workflowVersion = await getWorkflowVersion(workflowId, workflowVersionId);
|
const workflowVersion = await getWorkflowVersion(workflowId, workflowVersionId);
|
||||||
if (workflowVersion?.nodes && workflowVersion?.connections) {
|
const { connections, nodes } = workflowVersion;
|
||||||
const { connections, nodes } = workflowVersion;
|
const updateData: IWorkflowDataUpdate = { connections, nodes };
|
||||||
const updateData: IWorkflowDataUpdate = { connections, nodes };
|
|
||||||
|
|
||||||
if (shouldDeactivate) {
|
if (shouldDeactivate) {
|
||||||
updateData.active = false;
|
updateData.active = false;
|
||||||
}
|
|
||||||
|
|
||||||
await workflowsStore.updateWorkflow(workflowId, updateData, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return workflowsStore.updateWorkflow(workflowId, updateData, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -199,7 +199,7 @@ const restoreWorkflowVersion = async (
|
||||||
if (modalAction === WorkflowHistoryVersionRestoreModalActions.cancel) {
|
if (modalAction === WorkflowHistoryVersionRestoreModalActions.cancel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await workflowHistoryStore.restoreWorkflow(
|
activeWorkflow.value = await workflowHistoryStore.restoreWorkflow(
|
||||||
route.params.workflowId,
|
route.params.workflowId,
|
||||||
id,
|
id,
|
||||||
modalAction === WorkflowHistoryVersionRestoreModalActions.deactivateAndRestore,
|
modalAction === WorkflowHistoryVersionRestoreModalActions.deactivateAndRestore,
|
||||||
|
|
Loading…
Reference in a new issue