feat(editor): Refactor and unify executions views (no-changelog) (#8538)
This commit is contained in:
294
packages/editor-ui/src/stores/executions.store.ts
Normal file
294
packages/editor-ui/src/stores/executions.store.ts
Normal file
@@ -0,0 +1,294 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import type { ExecutionStatus, IDataObject, ExecutionSummary } from 'n8n-workflow';
|
||||
import type {
|
||||
ExecutionFilterType,
|
||||
ExecutionsQueryFilter,
|
||||
IExecutionDeleteFilter,
|
||||
IExecutionFlattedResponse,
|
||||
IExecutionResponse,
|
||||
IExecutionsListResponse,
|
||||
IExecutionsStopData,
|
||||
} from '@/Interface';
|
||||
import { useRootStore } from '@/stores/n8nRoot.store';
|
||||
import { makeRestApiRequest, unflattenExecutionData } from '@/utils/apiUtils';
|
||||
import { executionFilterToQueryFilter, getDefaultExecutionFilters } from '@/utils/executionUtils';
|
||||
|
||||
export const useExecutionsStore = defineStore('executions', () => {
|
||||
const rootStore = useRootStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const itemsPerPage = ref(10);
|
||||
|
||||
const activeExecution = ref<ExecutionSummary | null>(null);
|
||||
|
||||
const filters = ref<ExecutionFilterType>(getDefaultExecutionFilters());
|
||||
const executionsFilters = computed<ExecutionsQueryFilter>(() =>
|
||||
executionFilterToQueryFilter(filters.value),
|
||||
);
|
||||
const currentExecutionsFilters = computed<Partial<ExecutionFilterType>>(() => ({
|
||||
...(filters.value.workflowId !== 'all' ? { workflowId: filters.value.workflowId } : {}),
|
||||
}));
|
||||
|
||||
const autoRefresh = ref(true);
|
||||
const autoRefreshTimeout = ref<NodeJS.Timeout | null>(null);
|
||||
const autoRefreshDelay = ref(4 * 1000); // Refresh data every 4 secs
|
||||
|
||||
const executionsById = ref<Record<string, ExecutionSummary>>({});
|
||||
const executionsCount = ref(0);
|
||||
const executionsCountEstimated = ref(false);
|
||||
const executions = computed(() => {
|
||||
const data = Object.values(executionsById.value);
|
||||
|
||||
data.sort((a, b) => {
|
||||
return new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime();
|
||||
});
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
const executionsByWorkflowId = computed(() =>
|
||||
executions.value.reduce<Record<string, ExecutionSummary[]>>((acc, execution) => {
|
||||
if (!acc[execution.workflowId]) {
|
||||
acc[execution.workflowId] = [];
|
||||
}
|
||||
acc[execution.workflowId].push(execution);
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
|
||||
const currentExecutionsById = ref<Record<string, ExecutionSummary>>({});
|
||||
const currentExecutions = computed(() => {
|
||||
const data = Object.values(currentExecutionsById.value);
|
||||
|
||||
data.sort((a, b) => {
|
||||
return new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime();
|
||||
});
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
const currentExecutionsByWorkflowId = computed(() =>
|
||||
currentExecutions.value.reduce<Record<string, ExecutionSummary[]>>((acc, execution) => {
|
||||
if (!acc[execution.workflowId]) {
|
||||
acc[execution.workflowId] = [];
|
||||
}
|
||||
acc[execution.workflowId].push(execution);
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
|
||||
const allExecutions = computed(() => [...currentExecutions.value, ...executions.value]);
|
||||
|
||||
function addExecution(execution: ExecutionSummary) {
|
||||
executionsById.value[execution.id] = {
|
||||
...execution,
|
||||
status: execution.status ?? getExecutionStatus(execution),
|
||||
mode: execution.mode,
|
||||
};
|
||||
}
|
||||
|
||||
function addCurrentExecution(execution: ExecutionSummary) {
|
||||
currentExecutionsById.value[execution.id] = {
|
||||
...execution,
|
||||
status: execution.status ?? getExecutionStatus(execution),
|
||||
mode: execution.mode,
|
||||
};
|
||||
}
|
||||
|
||||
function removeExecution(id: string) {
|
||||
const { [id]: _, ...rest } = executionsById.value;
|
||||
executionsById.value = rest;
|
||||
}
|
||||
|
||||
function setFilters(value: ExecutionFilterType) {
|
||||
filters.value = value;
|
||||
}
|
||||
|
||||
async function initialize(workflowId?: string) {
|
||||
if (workflowId) {
|
||||
filters.value.workflowId = workflowId;
|
||||
}
|
||||
await fetchExecutions();
|
||||
await startAutoRefreshInterval(workflowId);
|
||||
}
|
||||
|
||||
function getExecutionStatus(execution: ExecutionSummary): ExecutionStatus {
|
||||
if (execution.status) {
|
||||
return execution.status;
|
||||
} else {
|
||||
if (execution.waitTill) {
|
||||
return 'waiting';
|
||||
} else if (execution.stoppedAt === undefined) {
|
||||
return 'running';
|
||||
} else if (execution.finished) {
|
||||
return 'success';
|
||||
} else if (execution.stoppedAt !== null) {
|
||||
return 'error';
|
||||
} else {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchExecutions(
|
||||
filter = executionsFilters.value,
|
||||
lastId?: string,
|
||||
firstId?: string,
|
||||
) {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await makeRestApiRequest<IExecutionsListResponse>(
|
||||
rootStore.getRestApiContext,
|
||||
'GET',
|
||||
'/executions',
|
||||
{
|
||||
...(filter ? { filter } : {}),
|
||||
...(firstId ? { firstId } : {}),
|
||||
...(lastId ? { lastId } : {}),
|
||||
limit: itemsPerPage.value,
|
||||
},
|
||||
);
|
||||
|
||||
currentExecutionsById.value = {};
|
||||
data.results.forEach((execution) => {
|
||||
if (['new', 'running'].includes(execution.status as string)) {
|
||||
addCurrentExecution(execution);
|
||||
} else {
|
||||
addExecution(execution);
|
||||
}
|
||||
});
|
||||
|
||||
executionsCount.value = data.count;
|
||||
executionsCountEstimated.value = data.estimated;
|
||||
} catch (e) {
|
||||
throw e;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchExecution(id: string): Promise<IExecutionResponse | undefined> {
|
||||
const response = await makeRestApiRequest<IExecutionFlattedResponse>(
|
||||
rootStore.getRestApiContext,
|
||||
'GET',
|
||||
`/executions/${id}`,
|
||||
);
|
||||
|
||||
return response ? unflattenExecutionData(response) : undefined;
|
||||
}
|
||||
|
||||
async function loadAutoRefresh(workflowId?: string): Promise<void> {
|
||||
const autoRefreshExecutionFilters = {
|
||||
...executionsFilters.value,
|
||||
...(workflowId ? { workflowId } : {}),
|
||||
};
|
||||
|
||||
autoRefreshTimeout.value = setTimeout(async () => {
|
||||
if (autoRefresh.value) {
|
||||
await fetchExecutions(autoRefreshExecutionFilters);
|
||||
void startAutoRefreshInterval(workflowId);
|
||||
}
|
||||
}, autoRefreshDelay.value);
|
||||
}
|
||||
|
||||
async function startAutoRefreshInterval(workflowId?: string) {
|
||||
stopAutoRefreshInterval();
|
||||
await loadAutoRefresh(workflowId);
|
||||
}
|
||||
|
||||
function stopAutoRefreshInterval() {
|
||||
if (autoRefreshTimeout.value) {
|
||||
clearTimeout(autoRefreshTimeout.value);
|
||||
autoRefreshTimeout.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function stopCurrentExecution(executionId: string): Promise<IExecutionsStopData> {
|
||||
return await makeRestApiRequest(
|
||||
rootStore.getRestApiContext,
|
||||
'POST',
|
||||
`/executions/${executionId}/stop`,
|
||||
);
|
||||
}
|
||||
|
||||
async function retryExecution(id: string, loadWorkflow?: boolean): Promise<boolean> {
|
||||
return await makeRestApiRequest(
|
||||
rootStore.getRestApiContext,
|
||||
'POST',
|
||||
`/executions/${id}/retry`,
|
||||
loadWorkflow
|
||||
? {
|
||||
loadWorkflow: true,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
||||
async function deleteExecutions(sendData: IExecutionDeleteFilter): Promise<void> {
|
||||
await makeRestApiRequest(
|
||||
rootStore.getRestApiContext,
|
||||
'POST',
|
||||
'/executions/delete',
|
||||
sendData as unknown as IDataObject,
|
||||
);
|
||||
|
||||
if (sendData.ids) {
|
||||
sendData.ids.forEach(removeExecution);
|
||||
}
|
||||
|
||||
if (sendData.deleteBefore) {
|
||||
const deleteBefore = new Date(sendData.deleteBefore);
|
||||
allExecutions.value.forEach((execution) => {
|
||||
if (new Date(execution.startedAt) < deleteBefore) {
|
||||
removeExecution(execution.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetData() {
|
||||
executionsById.value = {};
|
||||
currentExecutionsById.value = {};
|
||||
executionsCount.value = 0;
|
||||
executionsCountEstimated.value = false;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
itemsPerPage.value = 10;
|
||||
filters.value = getDefaultExecutionFilters();
|
||||
autoRefresh.value = true;
|
||||
resetData();
|
||||
stopAutoRefreshInterval();
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
executionsById,
|
||||
executions,
|
||||
executionsCount,
|
||||
executionsCountEstimated,
|
||||
executionsByWorkflowId,
|
||||
currentExecutions,
|
||||
currentExecutionsByWorkflowId,
|
||||
activeExecution,
|
||||
fetchExecutions,
|
||||
fetchExecution,
|
||||
getExecutionStatus,
|
||||
autoRefresh,
|
||||
autoRefreshTimeout,
|
||||
startAutoRefreshInterval,
|
||||
stopAutoRefreshInterval,
|
||||
initialize,
|
||||
filters,
|
||||
setFilters,
|
||||
executionsFilters,
|
||||
currentExecutionsFilters,
|
||||
allExecutions,
|
||||
stopCurrentExecution,
|
||||
retryExecution,
|
||||
deleteExecutions,
|
||||
resetData,
|
||||
reset,
|
||||
};
|
||||
});
|
||||
@@ -179,7 +179,6 @@ export const useUIStore = defineStore(STORES.UI, {
|
||||
selectedNodes: [],
|
||||
nodeViewInitialized: false,
|
||||
addFirstStepOnLoad: false,
|
||||
executionSidebarAutoRefresh: true,
|
||||
bannersHeight: 0,
|
||||
bannerStack: [],
|
||||
suggestedTemplates: undefined,
|
||||
|
||||
@@ -11,12 +11,10 @@ import {
|
||||
} from '@/constants';
|
||||
import type {
|
||||
ExecutionsQueryFilter,
|
||||
IExecutionDeleteFilter,
|
||||
IExecutionPushResponse,
|
||||
IExecutionResponse,
|
||||
IExecutionsCurrentSummaryExtended,
|
||||
IExecutionsListResponse,
|
||||
IExecutionsStopData,
|
||||
INewWorkflowData,
|
||||
INodeMetadata,
|
||||
INodeUi,
|
||||
@@ -1245,34 +1243,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||
setActiveExecutions(newActiveExecutions: IExecutionsCurrentSummaryExtended[]): void {
|
||||
this.activeExecutions = newActiveExecutions;
|
||||
},
|
||||
|
||||
async retryExecution(id: string, loadWorkflow?: boolean): Promise<boolean> {
|
||||
let sendData;
|
||||
if (loadWorkflow === true) {
|
||||
sendData = {
|
||||
loadWorkflow: true,
|
||||
};
|
||||
}
|
||||
const rootStore = useRootStore();
|
||||
return await makeRestApiRequest(
|
||||
rootStore.getRestApiContext,
|
||||
'POST',
|
||||
`/executions/${id}/retry`,
|
||||
sendData,
|
||||
);
|
||||
},
|
||||
|
||||
// Deletes executions
|
||||
async deleteExecutions(sendData: IExecutionDeleteFilter): Promise<void> {
|
||||
const rootStore = useRootStore();
|
||||
return await makeRestApiRequest(
|
||||
rootStore.getRestApiContext,
|
||||
'POST',
|
||||
'/executions/delete',
|
||||
sendData as unknown as IDataObject,
|
||||
);
|
||||
},
|
||||
|
||||
// TODO: For sure needs some kind of default filter like last day, with max 10 results, ...
|
||||
async getPastExecutions(
|
||||
filter: IDataObject,
|
||||
@@ -1301,12 +1271,14 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||
};
|
||||
}
|
||||
const rootStore = useRootStore();
|
||||
return await makeRestApiRequest(
|
||||
const output = await makeRestApiRequest(
|
||||
rootStore.getRestApiContext,
|
||||
'GET',
|
||||
'/executions/active',
|
||||
'/executions',
|
||||
sendData,
|
||||
);
|
||||
|
||||
return output.results;
|
||||
},
|
||||
|
||||
async getExecution(id: string): Promise<IExecutionResponse | undefined> {
|
||||
@@ -1376,16 +1348,6 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||
`/test-webhook/${workflowId}`,
|
||||
);
|
||||
},
|
||||
|
||||
async stopCurrentExecution(executionId: string): Promise<IExecutionsStopData> {
|
||||
const rootStore = useRootStore();
|
||||
return await makeRestApiRequest(
|
||||
rootStore.getRestApiContext,
|
||||
'POST',
|
||||
`/executions/active/${executionId}/stop`,
|
||||
);
|
||||
},
|
||||
|
||||
async loadCurrentWorkflowExecutions(
|
||||
requestFilter: ExecutionsQueryFilter,
|
||||
): Promise<ExecutionSummary[]> {
|
||||
|
||||
Reference in New Issue
Block a user