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
14 changed files with 366 additions and 97 deletions

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);