feat(editor): Workflow history [WIP]- Create workflow history item preview component (no-changelog) (#7378)

Co-authored-by: Giulio Andreini <g.andreini@gmail.com>
This commit is contained in:
Csaba Tuncsik 2023-10-11 10:13:04 +02:00 committed by GitHub
parent 965db8f7f2
commit 53c3379282
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 366 additions and 97 deletions

View file

@ -122,7 +122,11 @@ export default defineComponent({
.activator {
cursor: pointer;
padding: var(--spacing-2xs);
display: flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
margin: 0;
border-radius: var(--border-radius-base);
line-height: normal !important;
@ -133,7 +137,7 @@ export default defineComponent({
&:hover {
background-color: var(--color-background-base);
color: initial !important;
color: var(--color-primary);
}
}

View file

@ -7,12 +7,14 @@
@command="onCommand"
@visible-change="onVisibleChange"
>
<span :class="{ [$style.button]: true, [$style[theme]]: !!theme }">
<n8n-icon
:icon="iconOrientation === 'horizontal' ? 'ellipsis-h' : 'ellipsis-v'"
:size="iconSize"
/>
</span>
<slot>
<span :class="{ [$style.button]: true, [$style[theme]]: !!theme }">
<n8n-icon
:icon="iconOrientation === 'horizontal' ? 'ellipsis-h' : 'ellipsis-v'"
:size="iconSize"
/>
</span>
</slot>
<template #dropdown>
<el-dropdown-menu data-test-id="action-toggle-dropdown">

View file

@ -722,8 +722,16 @@ $--header-spacing: 20px;
}
.workflowHistoryButton {
margin-left: var(--spacing-l);
width: 30px;
height: 30px;
margin-left: var(--spacing-m);
margin-right: var(--spacing-4xs);
color: var(--color-text-dark);
border-radius: var(--border-radius-base);
&:hover {
background-color: var(--color-background-base);
}
:disabled {
background: transparent;

View file

@ -1,14 +1,110 @@
<script setup lang="ts">
import type { WorkflowVersion } from '@/types/workflowHistory';
import { computed } from 'vue';
import type { IWorkflowDb, UserAction } from '@/Interface';
import type {
WorkflowVersion,
WorkflowHistoryActionTypes,
WorkflowVersionId,
} from '@/types/workflowHistory';
import WorkflowPreview from '@/components/WorkflowPreview.vue';
import WorkflowHistoryListItem from '@/components/WorkflowHistory/WorkflowHistoryListItem.vue';
import { useI18n } from '@/composables';
const i18n = useI18n();
const props = defineProps<{
workflow: IWorkflowDb | null;
workflowVersion: WorkflowVersion | null;
actions: UserAction[];
isListLoading?: boolean;
}>();
const emit = defineEmits<{
(
event: 'action',
value: {
action: WorkflowHistoryActionTypes[number];
id: WorkflowVersionId;
data: { formattedCreatedAt: string };
},
): void;
}>();
const workflowVersionPreview = computed<IWorkflowDb | undefined>(() => {
if (!props.workflowVersion || !props.workflow) {
return;
}
return {
...props.workflow,
nodes: props.workflowVersion.nodes,
connections: props.workflowVersion.connections,
};
});
const onAction = ({
action,
id,
data,
}: {
action: WorkflowHistoryActionTypes[number];
id: WorkflowVersionId;
data: { formattedCreatedAt: string };
}) => {
emit('action', { action, id, data });
};
</script>
<template>
<div :class="$style.content">
{{ props.workflowVersion }}
<WorkflowPreview
v-if="props.workflowVersion"
:workflow="workflowVersionPreview"
:loading="props.isListLoading"
loaderType="spinner"
/>
<ul :class="$style.info">
<workflow-history-list-item
:class="$style.card"
v-if="props.workflowVersion"
:full="true"
:index="-1"
:item="props.workflowVersion"
:isActive="false"
:actions="props.actions"
@action="onAction"
>
<template #default="{ formattedCreatedAt }">
<section :class="$style.text">
<p>
<span :class="$style.label">
{{ i18n.baseText('workflowHistory.content.title') }}:
</span>
<time :datetime="props.workflowVersion.createdAt">{{ formattedCreatedAt }}</time>
</p>
<p>
<span :class="$style.label">
{{ i18n.baseText('workflowHistory.content.editedBy') }}:
</span>
<span>{{ props.workflowVersion.authors }}</span>
</p>
<p>
<span :class="$style.label">
{{ i18n.baseText('workflowHistory.content.versionId') }}:
</span>
<data :value="props.workflowVersion.versionId">{{
props.workflowVersion.versionId
}}</data>
</p>
</section>
</template>
<template #action-toggle-button>
<n8n-button type="tertiary" size="small" data-test-id="action-toggle-button">
{{ i18n.baseText('workflowHistory.content.actions') }}
<n8n-icon class="ml-3xs" icon="chevron-down" size="small" />
</n8n-button>
</template>
</workflow-history-list-item>
</ul>
</div>
</template>
@ -20,5 +116,63 @@ const props = defineProps<{
top: 0;
width: 100%;
height: 100%;
overflow: auto;
}
.info {
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
}
.card {
padding: var(--spacing-s) var(--spacing-l) 0 var(--spacing-xl);
border: 0;
align-items: start;
.text {
display: flex;
flex-direction: column;
flex: 1 1 auto;
p {
display: flex;
align-items: center;
padding: 0;
cursor: default;
&:first-child {
padding-top: var(--spacing-3xs);
padding-bottom: var(--spacing-3xs);
* {
font-size: var(--font-size-m);
}
}
&:last-child {
padding-top: var(--spacing-3xs);
* {
font-size: var(--font-size-2xs);
}
}
.label {
padding-right: var(--spacing-4xs);
}
* {
max-width: unset;
justify-self: unset;
white-space: unset;
overflow: hidden;
text-overflow: unset;
padding: 0;
font-size: var(--font-size-s);
}
}
}
}
</style>

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import { ref } from 'vue';
import type { UserAction } from 'n8n-design-system';
import { useI18n } from '@/composables';
import type {
@ -13,7 +13,7 @@ import WorkflowHistoryListItem from '@/components/WorkflowHistory/WorkflowHistor
const props = defineProps<{
items: WorkflowHistory[];
activeItem: WorkflowHistory | null;
actionTypes: WorkflowHistoryActionTypes;
actions: UserAction[];
requestNumberOfItems: number;
lastReceivedItemsLength: number;
evaluatedPruneTime: number;
@ -41,14 +41,6 @@ const listElement = ref<Element | null>(null);
const shouldAutoScroll = ref(true);
const observer = ref<IntersectionObserver | null>(null);
const actions = computed<UserAction[]>(() =>
props.actionTypes.map((value) => ({
label: i18n.baseText(`workflowHistory.item.actions.${value}`),
disabled: false,
value,
})),
);
const observeElement = (element: Element) => {
observer.value = new IntersectionObserver(
([entry]) => {
@ -116,8 +108,8 @@ const onItemMounted = ({
:key="item.versionId"
:index="index"
:item="item"
:is-active="item.versionId === props.activeItem?.versionId"
:actions="actions"
:isActive="item.versionId === props.activeItem?.versionId"
:actions="props.actions"
@action="onAction"
@preview="onPreview"
@mounted="onItemMounted"

View file

@ -39,7 +39,7 @@ const formattedCreatedAt = computed<string>(() => {
const currentYear = new Date().getFullYear().toString();
const [date, time] = dateformat(
props.item.createdAt,
`${props.item.createdAt.startsWith(currentYear) ? '' : 'yyyy '}mmm d"#"HH:MM`,
`${props.item.createdAt.startsWith(currentYear) ? '' : 'yyyy '}mmm d"#"HH:MM:ss`,
).split('#');
return i18n.baseText('workflowHistory.item.createdAt', { interpolate: { date, time } });
@ -99,14 +99,19 @@ onMounted(() => {
[$style.actionsVisible]: actionsVisible,
}"
>
<p @click="onItemClick">
<time :datetime="item.createdAt">{{ formattedCreatedAt }}</time>
<n8n-tooltip placement="right-end" :disabled="authors.size < 2 && !isAuthorElementTruncated">
<template #content>{{ props.item.authors }}</template>
<span ref="authorElement">{{ authors.label }}</span>
</n8n-tooltip>
<data :value="item.versionId">{{ idLabel }}</data>
</p>
<slot :formattedCreatedAt="formattedCreatedAt">
<p @click="onItemClick">
<time :datetime="item.createdAt">{{ formattedCreatedAt }}</time>
<n8n-tooltip
placement="right-end"
:disabled="authors.size < 2 && !isAuthorElementTruncated"
>
<template #content>{{ props.item.authors }}</template>
<span ref="authorElement">{{ authors.label }}</span>
</n8n-tooltip>
<data :value="item.versionId">{{ idLabel }}</data>
</p>
</slot>
<div :class="$style.tail">
<n8n-badge v-if="props.index === 0">
{{ i18n.baseText('workflowHistory.item.latest') }}
@ -115,10 +120,13 @@ onMounted(() => {
theme="dark"
:class="$style.actions"
:actions="props.actions"
placement="bottom-end"
@action="onAction"
@click.stop
@visible-change="onVisibleChange"
/>
>
<slot name="action-toggle-button" />
</n8n-action-toggle>
</div>
</li>
</template>
@ -136,11 +144,11 @@ onMounted(() => {
p {
display: grid;
padding: var(--spacing-s);
line-height: unset;
cursor: pointer;
flex: 1 1 auto;
time {
padding: 0 0 var(--spacing-3xs);
padding: 0 0 var(--spacing-5xs);
color: var(--color-text-dark);
font-size: var(--font-size-s);
font-weight: var(--font-weight-bold);
@ -153,7 +161,7 @@ onMounted(() => {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-top: var(--spacing-4xs);
margin-top: calc(var(--spacing-4xs) * -1);
font-size: var(--font-size-2xs);
}
}

View file

@ -0,0 +1,59 @@
import { createPinia, setActivePinia } from 'pinia';
import userEvent from '@testing-library/user-event';
import type { UserAction } from 'n8n-design-system';
import { createComponentRenderer } from '@/__tests__/render';
import WorkflowHistoryContent from '@/components/WorkflowHistory/WorkflowHistoryContent.vue';
import type { WorkflowHistoryActionTypes } from '@/types/workflowHistory';
import { workflowHistoryDataFactory } from '@/stores/__tests__/utils/workflowHistoryTestUtils';
const actionTypes: WorkflowHistoryActionTypes = ['restore', 'clone', 'open', 'download'];
const actions: UserAction[] = actionTypes.map((value) => ({
label: value,
disabled: false,
value,
}));
const renderComponent = createComponentRenderer(WorkflowHistoryContent);
let pinia: ReturnType<typeof createPinia>;
describe('WorkflowHistoryContent', () => {
beforeEach(() => {
pinia = createPinia();
setActivePinia(pinia);
});
it('should use the list item component to render version data', () => {
const workflowVersion = workflowHistoryDataFactory();
const { getByTestId } = renderComponent({
pinia,
props: {
workflow: null,
workflowVersion,
actions,
},
});
expect(getByTestId('workflow-history-list-item')).toBeInTheDocument();
});
test.each(actionTypes)('should emit %s event', async (action) => {
const workflowVersion = workflowHistoryDataFactory();
const { getByTestId, emitted } = renderComponent({
pinia,
props: {
workflow: null,
workflowVersion,
actions,
},
});
await userEvent.click(getByTestId('action-toggle-button'));
expect(getByTestId('action-toggle-dropdown')).toBeInTheDocument();
await userEvent.click(getByTestId(`action-${action}`));
expect(emitted().action).toEqual([
[{ action, id: workflowVersion.versionId, data: { formattedCreatedAt: expect.any(String) } }],
]);
});
});

View file

@ -2,6 +2,7 @@ import { within } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import { createPinia, setActivePinia } from 'pinia';
import { faker } from '@faker-js/faker';
import type { UserAction } from 'n8n-design-system';
import { createComponentRenderer } from '@/__tests__/render';
import WorkflowHistoryList from '@/components/WorkflowHistory/WorkflowHistoryList.vue';
import type { WorkflowHistoryActionTypes } from '@/types/workflowHistory';
@ -18,6 +19,11 @@ vi.stubGlobal(
);
const actionTypes: WorkflowHistoryActionTypes = ['restore', 'clone', 'open', 'download'];
const actions: UserAction[] = actionTypes.map((value) => ({
label: value,
disabled: false,
value,
}));
const renderComponent = createComponentRenderer(WorkflowHistoryList);
@ -38,7 +44,7 @@ describe('WorkflowHistoryList', () => {
pinia,
props: {
items: [],
actionTypes,
actions,
activeItem: null,
requestNumberOfItems: 20,
lastReceivedItemsLength: 0,
@ -55,7 +61,7 @@ describe('WorkflowHistoryList', () => {
pinia,
props: {
items: [],
actionTypes,
actions,
activeItem: null,
requestNumberOfItems: 20,
lastReceivedItemsLength: 0,
@ -76,7 +82,7 @@ describe('WorkflowHistoryList', () => {
pinia,
props: {
items,
actionTypes,
actions,
activeItem: null,
requestNumberOfItems: 20,
lastReceivedItemsLength: 20,
@ -108,7 +114,7 @@ describe('WorkflowHistoryList', () => {
pinia,
props: {
items,
actionTypes,
actions,
activeItem: items[0],
requestNumberOfItems: 20,
lastReceivedItemsLength: 20,
@ -126,7 +132,7 @@ describe('WorkflowHistoryList', () => {
pinia,
props: {
items,
actionTypes,
actions,
activeItem: null,
requestNumberOfItems: 20,
lastReceivedItemsLength: 20,
@ -159,7 +165,7 @@ describe('WorkflowHistoryList', () => {
pinia,
props: {
items,
actionTypes,
actions,
activeItem: items[0],
requestNumberOfItems: 20,
lastReceivedItemsLength: 20,

View file

@ -36,10 +36,10 @@ describe('WorkflowHistoryListItem', () => {
},
});
await userEvent.hover(container.querySelector('.el-tooltip__trigger'));
await userEvent.hover(container.querySelector('.el-tooltip__trigger')!);
expect(queryByRole('tooltip')).not.toBeInTheDocument();
await userEvent.click(container.querySelector('p'));
await userEvent.click(container.querySelector('p')!);
expect(emitted().preview).toEqual([
[expect.objectContaining({ id: item.versionId, event: expect.any(MouseEvent) })],
]);
@ -61,7 +61,7 @@ describe('WorkflowHistoryListItem', () => {
},
});
const authorsTag = container.querySelector('.el-tooltip__trigger');
const authorsTag = container.querySelector('.el-tooltip__trigger')!;
expect(authorsTag).toHaveTextContent(`${authors[0]} + ${authors.length - 1}`);
await userEvent.hover(authorsTag);
expect(getByRole('tooltip')).toBeInTheDocument();

View file

@ -189,6 +189,11 @@ export default defineComponent({
this.loadExecution();
}
},
workflow() {
if (this.mode === 'workflow' && this.workflow) {
this.loadWorkflow();
}
},
},
mounted() {
window.addEventListener('message', this.receiveMessage);

View file

@ -1847,6 +1847,10 @@
"workflowSettings.timeoutWorkflow": "Timeout Workflow",
"workflowSettings.timezone": "Timezone",
"workflowHistory.title": "Version History",
"workflowHistory.content.title": "Version",
"workflowHistory.content.editedBy": "Edited by",
"workflowHistory.content.versionId": "Version ID",
"workflowHistory.content.actions": "Actions",
"workflowHistory.item.id": "ID: {id}",
"workflowHistory.item.createdAt": "{date} at {time}",
"workflowHistory.item.actions.restore": "Restore this version",

View file

@ -29,23 +29,19 @@ export const useWorkflowHistoryStore = defineStore('workflowHistory', () => {
workflowId: string,
queryParams: WorkflowHistoryRequestParams,
): Promise<WorkflowHistory[]> =>
whApi
.getWorkflowHistory(rootStore.getRestApiContext, workflowId, queryParams)
.catch((error) => {
console.error(error);
return [] as WorkflowHistory[];
});
whApi.getWorkflowHistory(rootStore.getRestApiContext, workflowId, queryParams);
const getWorkflowVersion = async (
workflowId: string,
versionId: string,
): Promise<WorkflowVersion | null> =>
whApi.getWorkflowVersion(rootStore.getRestApiContext, workflowId, versionId).catch((error) => {
console.error(error);
return null;
});
whApi.getWorkflowVersion(rootStore.getRestApiContext, workflowId, versionId);
const downloadVersion = async (workflowId: string, workflowVersionId: WorkflowVersionId) => {
const downloadVersion = async (
workflowId: string,
workflowVersionId: WorkflowVersionId,
data: { formattedCreatedAt: string },
) => {
const [workflow, workflowVersion] = await Promise.all([
workflowsStore.fetchWorkflow(workflowId),
getWorkflowVersion(workflowId, workflowVersionId),
@ -55,7 +51,7 @@ export const useWorkflowHistoryStore = defineStore('workflowHistory', () => {
const blob = new Blob([JSON.stringify({ ...workflow, nodes, connections }, null, 2)], {
type: 'application/json;charset=utf-8',
});
saveAs(blob, `${workflow.name}-${workflowVersionId}.json`);
saveAs(blob, `${workflow.name}(${data.formattedCreatedAt}).json`);
}
};

View file

@ -1,7 +1,7 @@
<script setup lang="ts">
import { onBeforeMount, ref, watchEffect, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import type { IWorkflowDb } from '@/Interface';
import type { IWorkflowDb, UserAction } from '@/Interface';
import { VIEWS, WORKFLOW_HISTORY_VERSION_RESTORE } from '@/constants';
import { useI18n, useToast } from '@/composables';
import type {
@ -46,6 +46,7 @@ const workflowHistoryStore = useWorkflowHistoryStore();
const uiStore = useUIStore();
const workflowsStore = useWorkflowsStore();
const canRender = ref(true);
const isListLoading = ref(true);
const requestNumberOfItems = ref(20);
const lastReceivedItemsLength = ref(0);
@ -58,16 +59,13 @@ const editorRoute = computed(() => ({
const activeWorkflow = ref<IWorkflowDb | null>(null);
const workflowHistory = ref<WorkflowHistory[]>([]);
const activeWorkflowVersion = ref<WorkflowVersion | null>(null);
const activeWorkflowVersionPreview = computed<IWorkflowDb | null>(() => {
if (activeWorkflowVersion.value && activeWorkflow.value) {
return {
...activeWorkflow.value,
nodes: activeWorkflowVersion.value.nodes,
connections: activeWorkflowVersion.value.connections,
};
}
return null;
});
const actions = computed<UserAction[]>(() =>
workflowHistoryActionTypes.map((value) => ({
label: i18n.baseText(`workflowHistory.item.actions.${value}`),
disabled: false,
value,
})),
);
const loadMore = async (queryParams: WorkflowHistoryRequestParams) => {
const history = await workflowHistoryStore.getWorkflowHistory(
@ -75,25 +73,30 @@ const loadMore = async (queryParams: WorkflowHistoryRequestParams) => {
queryParams,
);
lastReceivedItemsLength.value = history.length;
workflowHistory.value.push(...history);
workflowHistory.value = workflowHistory.value.concat(history);
};
onBeforeMount(async () => {
const [workflow] = await Promise.all([
workflowsStore.fetchWorkflow(route.params.workflowId),
loadMore({ take: requestNumberOfItems.value }),
]);
activeWorkflow.value = workflow;
isListLoading.value = false;
try {
const [workflow] = await Promise.all([
workflowsStore.fetchWorkflow(route.params.workflowId),
loadMore({ take: requestNumberOfItems.value }),
]);
activeWorkflow.value = workflow;
isListLoading.value = false;
if (!route.params.versionId && workflowHistory.value.length) {
await router.replace({
name: VIEWS.WORKFLOW_HISTORY,
params: {
workflowId: route.params.workflowId,
versionId: workflowHistory.value[0].versionId,
},
});
if (!route.params.versionId && workflowHistory.value.length) {
await router.replace({
name: VIEWS.WORKFLOW_HISTORY,
params: {
workflowId: route.params.workflowId,
versionId: workflowHistory.value[0].versionId,
},
});
}
} catch (error) {
canRender.value = false;
toast.showError(error, i18n.baseText('workflowHistory.title'));
}
});
@ -174,7 +177,7 @@ const onAction = async ({
openInNewTab(id);
break;
case WORKFLOW_HISTORY_ACTIONS.DOWNLOAD:
await workflowHistoryStore.downloadVersion(route.params.workflowId, id);
await workflowHistoryStore.downloadVersion(route.params.workflowId, id, data);
break;
case WORKFLOW_HISTORY_ACTIONS.CLONE:
await workflowHistoryStore.cloneIntoNewWorkflow(route.params.workflowId, id, data);
@ -194,6 +197,10 @@ const onAction = async ({
id,
modalAction === WorkflowHistoryVersionRestoreModalActions.deactivateAndRestore,
);
const history = await workflowHistoryStore.getWorkflowHistory(route.params.workflowId, {
take: 1,
});
workflowHistory.value = history.concat(workflowHistory.value);
toast.showMessage({
title: i18n.baseText('workflowHistory.action.restore.success.title'),
type: 'success',
@ -231,13 +238,26 @@ const onUpgrade = () => {
};
watchEffect(async () => {
if (route.params.versionId) {
const [workflow, workflowVersion] = await Promise.all([
workflowsStore.fetchWorkflow(route.params.workflowId),
workflowHistoryStore.getWorkflowVersion(route.params.workflowId, route.params.versionId),
]);
activeWorkflow.value = workflow;
activeWorkflowVersion.value = workflowVersion;
if (!route.params.versionId) {
return;
}
try {
activeWorkflowVersion.value = await workflowHistoryStore.getWorkflowVersion(
route.params.workflowId,
route.params.versionId,
);
} catch (error) {
toast.showError(
new Error(`${error.message} "${route.params.versionId}"&nbsp;`),
i18n.baseText('workflowHistory.title'),
);
}
try {
activeWorkflow.value = await workflowsStore.fetchWorkflow(route.params.workflowId);
} catch (error) {
canRender.value = false;
toast.showError(error, i18n.baseText('workflowHistory.title'));
}
});
</script>
@ -254,15 +274,13 @@ watchEffect(async () => {
<n8n-button type="tertiary" icon="times" size="small" text square />
</router-link>
</div>
<div :class="$style.contentComponentWrapper">
<workflow-history-content :workflow-version="activeWorkflowVersionPreview" />
</div>
<div :class="$style.listComponentWrapper">
<workflow-history-list
v-if="canRender"
:items="workflowHistory"
:lastReceivedItemsLength="lastReceivedItemsLength"
:activeItem="activeWorkflowVersion"
:actionTypes="workflowHistoryActionTypes"
:actions="actions"
:requestNumberOfItems="requestNumberOfItems"
:shouldUpgrade="workflowHistoryStore.shouldUpgrade"
:evaluatedPruneTime="workflowHistoryStore.evaluatedPruneTime"
@ -273,6 +291,16 @@ watchEffect(async () => {
@upgrade="onUpgrade"
/>
</div>
<div :class="$style.contentComponentWrapper">
<workflow-history-content
v-if="canRender"
:workflow="activeWorkflow"
:workflow-version="activeWorkflowVersion"
:actions="actions"
:isListLoading="isListLoading"
@action="onAction"
/>
</div>
</div>
</template>
<style module lang="scss">
@ -308,13 +336,11 @@ watchEffect(async () => {
.contentComponentWrapper {
grid-area: content;
position: relative;
z-index: 1;
}
.listComponentWrapper {
grid-area: list;
position: relative;
z-index: 2;
&::before {
content: '';

View file

@ -9,12 +9,14 @@ import { createComponentRenderer } from '@/__tests__/render';
import { SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
import WorkflowHistoryPage from '@/views/WorkflowHistory.vue';
import { useWorkflowHistoryStore } from '@/stores/workflowHistory.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { STORES, VIEWS } from '@/constants';
import {
workflowHistoryDataFactory,
workflowVersionDataFactory,
} from '@/stores/__tests__/utils/workflowHistoryTestUtils';
import type { WorkflowVersion } from '@/types/workflowHistory';
import type { IWorkflowDb } from '@/Interface';
vi.mock('vue-router', () => {
const params = {};
@ -63,6 +65,7 @@ let pinia: ReturnType<typeof createTestingPinia>;
let router: ReturnType<typeof useRouter>;
let route: ReturnType<typeof useRoute>;
let workflowHistoryStore: ReturnType<typeof useWorkflowHistoryStore>;
let workflowsStore: ReturnType<typeof useWorkflowsStore>;
let windowOpenSpy: SpyInstance;
describe('WorkflowHistory', () => {
@ -73,9 +76,11 @@ describe('WorkflowHistory', () => {
},
});
workflowHistoryStore = useWorkflowHistoryStore();
workflowsStore = useWorkflowsStore();
route = useRoute();
router = useRouter();
vi.spyOn(workflowsStore, 'fetchWorkflow').mockResolvedValue({} as IWorkflowDb);
vi.spyOn(workflowHistoryStore, 'getWorkflowHistory').mockResolvedValue(historyData);
vi.spyOn(workflowHistoryStore, 'getWorkflowVersion').mockResolvedValue(versionData);
windowOpenSpy = vi.spyOn(window, 'open').mockImplementation(() => null);