mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
fix(editor): Fix for loading executions that are not on the current executions list (#5035)
* fix(editor): fixed executions list scroll not working on large screens * ⚡ Use limit to only load necessary number of additional executions * 🐛 Fixing loading execution that is not on the current execution list * ✔️ Fixing lint error * ⚡ Fixing more executions count logic * 📚 Updating comments * 🔥 Removing leftover `console.log`
This commit is contained in:
parent
d113977b10
commit
d0865e28ff
|
@ -27,3 +27,7 @@ export async function getCurrentExecutions(context: IRestApiContext, filter: IDa
|
||||||
export async function getFinishedExecutions(context: IRestApiContext, filter: IDataObject) {
|
export async function getFinishedExecutions(context: IRestApiContext, filter: IDataObject) {
|
||||||
return await makeRestApiRequest(context, 'GET', '/executions', { filter });
|
return await makeRestApiRequest(context, 'GET', '/executions', { filter });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getExecutionData(context: IRestApiContext, executionId: string) {
|
||||||
|
return await makeRestApiRequest(context, 'GET', `/executions/${executionId}`);
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="['executions-sidebar', $style.container]">
|
<div :class="['executions-sidebar', $style.container]" ref="container">
|
||||||
<div :class="$style.heading">
|
<div :class="$style.heading">
|
||||||
<n8n-heading tag="h2" size="medium" color="text-dark">
|
<n8n-heading tag="h2" size="medium" color="text-dark">
|
||||||
{{ $locale.baseText('generic.executions') }}
|
{{ $locale.baseText('generic.executions') }}
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
</n8n-link>
|
</n8n-link>
|
||||||
</n8n-info-tip>
|
</n8n-info-tip>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.executionList" ref="executionList" @scroll="loadMore">
|
<div :class="$style.executionList" ref="executionList" @scroll="loadMore(20)">
|
||||||
<div v-if="loading" class="mr-m">
|
<div v-if="loading" class="mr-m">
|
||||||
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
||||||
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
<n8n-loading :class="$style.loader" variant="p" :rows="1" />
|
||||||
|
@ -149,12 +149,19 @@ export default Vue.extend({
|
||||||
this.$router.go(-1);
|
this.$router.go(-1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'workflowsStore.activeWorkflowExecution'() {
|
||||||
|
this.checkListSize();
|
||||||
|
this.scrollToActiveCard();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.autoRefresh = this.uiStore.executionSidebarAutoRefresh === true;
|
this.autoRefresh = this.uiStore.executionSidebarAutoRefresh === true;
|
||||||
if (this.autoRefresh) {
|
if (this.autoRefresh) {
|
||||||
this.autoRefreshInterval = setInterval(() => this.onRefresh(), 4000);
|
this.autoRefreshInterval = setInterval(() => this.onRefresh(), 4000);
|
||||||
}
|
}
|
||||||
|
// On larger screens, we need to load more then first page of executions
|
||||||
|
// for the scroll bar to appear and infinite scrolling is enabled
|
||||||
|
this.checkListSize();
|
||||||
this.scrollToActiveCard();
|
this.scrollToActiveCard();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
@ -164,14 +171,14 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loadMore(): void {
|
loadMore(limit = 20): void {
|
||||||
if (!this.loading) {
|
if (!this.loading) {
|
||||||
const executionsList = this.$refs.executionList as HTMLElement;
|
const executionsList = this.$refs.executionList as HTMLElement;
|
||||||
if (executionsList) {
|
if (executionsList) {
|
||||||
const diff =
|
const diff =
|
||||||
executionsList.offsetHeight - (executionsList.scrollHeight - executionsList.scrollTop);
|
executionsList.offsetHeight - (executionsList.scrollHeight - executionsList.scrollTop);
|
||||||
if (diff > -10 && diff < 10) {
|
if (diff > -10 && diff < 10) {
|
||||||
this.$emit('loadMore');
|
this.$emit('loadMore', limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -209,6 +216,23 @@ export default Vue.extend({
|
||||||
status: this.filter.status,
|
status: this.filter.status,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
checkListSize(): void {
|
||||||
|
const sidebarContainer = this.$refs.container as HTMLElement;
|
||||||
|
const currentExecutionCard = this.$refs[
|
||||||
|
`execution-${this.workflowsStore.activeWorkflowExecution?.id}`
|
||||||
|
] as Vue[];
|
||||||
|
|
||||||
|
// Find out how many execution card can fit into list
|
||||||
|
// and load more if needed
|
||||||
|
if (sidebarContainer && currentExecutionCard) {
|
||||||
|
const cardElement = currentExecutionCard[0].$el as HTMLElement;
|
||||||
|
const listCapacity = Math.ceil(sidebarContainer.clientHeight / cardElement.clientHeight);
|
||||||
|
|
||||||
|
if (listCapacity > this.executions.length) {
|
||||||
|
this.$emit('loadMore', listCapacity - this.executions.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
scrollToActiveCard(): void {
|
scrollToActiveCard(): void {
|
||||||
const executionsList = this.$refs.executionList as HTMLElement;
|
const executionsList = this.$refs.executionList as HTMLElement;
|
||||||
const currentExecutionCard = this.$refs[
|
const currentExecutionCard = this.$refs[
|
||||||
|
@ -218,8 +242,9 @@ export default Vue.extend({
|
||||||
if (executionsList && currentExecutionCard && this.workflowsStore.activeWorkflowExecution) {
|
if (executionsList && currentExecutionCard && this.workflowsStore.activeWorkflowExecution) {
|
||||||
const cardElement = currentExecutionCard[0].$el as HTMLElement;
|
const cardElement = currentExecutionCard[0].$el as HTMLElement;
|
||||||
const cardRect = cardElement.getBoundingClientRect();
|
const cardRect = cardElement.getBoundingClientRect();
|
||||||
|
const LIST_HEADER_OFFSET = 200;
|
||||||
if (cardRect.top > executionsList.offsetHeight) {
|
if (cardRect.top > executionsList.offsetHeight) {
|
||||||
executionsList.scrollTo({ top: cardRect.top });
|
executionsList.scrollTo({ top: cardRect.top - LIST_HEADER_OFFSET });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -177,7 +177,7 @@ export default mixins(
|
||||||
if (this.workflowsStore.currentWorkflowExecutions.length > 0) {
|
if (this.workflowsStore.currentWorkflowExecutions.length > 0) {
|
||||||
const workflowExecutions = await this.loadExecutions();
|
const workflowExecutions = await this.loadExecutions();
|
||||||
this.workflowsStore.addToCurrentExecutions(workflowExecutions);
|
this.workflowsStore.addToCurrentExecutions(workflowExecutions);
|
||||||
this.setActiveExecution();
|
await this.setActiveExecution();
|
||||||
} else {
|
} else {
|
||||||
await this.setExecutions();
|
await this.setExecutions();
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ export default mixins(
|
||||||
this.callDebounced('loadMore', { debounceTime: 1000 });
|
this.callDebounced('loadMore', { debounceTime: 1000 });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async loadMore(): Promise<void> {
|
async loadMore(limit = 20): Promise<void> {
|
||||||
if (
|
if (
|
||||||
this.filter.status === 'running' ||
|
this.filter.status === 'running' ||
|
||||||
this.loadedFinishedExecutionsCount >= this.totalFinishedExecutionsCount
|
this.loadedFinishedExecutionsCount >= this.totalFinishedExecutionsCount
|
||||||
|
@ -233,7 +233,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
let data: IExecutionsListResponse;
|
let data: IExecutionsListResponse;
|
||||||
try {
|
try {
|
||||||
data = await this.restApi().getPastExecutions(requestFilter, 20, lastId);
|
data = await this.restApi().getPastExecutions(requestFilter, limit, lastId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.loadingMore = false;
|
this.loadingMore = false;
|
||||||
this.$showError(error, this.$locale.baseText('executionsList.showError.loadMore.title'));
|
this.$showError(error, this.$locale.baseText('executionsList.showError.loadMore.title'));
|
||||||
|
@ -316,7 +316,7 @@ export default mixins(
|
||||||
async setExecutions(): Promise<void> {
|
async setExecutions(): Promise<void> {
|
||||||
const workflowExecutions = await this.loadExecutions();
|
const workflowExecutions = await this.loadExecutions();
|
||||||
this.workflowsStore.currentWorkflowExecutions = workflowExecutions;
|
this.workflowsStore.currentWorkflowExecutions = workflowExecutions;
|
||||||
this.setActiveExecution();
|
await this.setActiveExecution();
|
||||||
},
|
},
|
||||||
async loadAutoRefresh(): Promise<void> {
|
async loadAutoRefresh(): Promise<void> {
|
||||||
// Most of the auto-refresh logic is taken from the `ExecutionsList` component
|
// Most of the auto-refresh logic is taken from the `ExecutionsList` component
|
||||||
|
@ -404,14 +404,17 @@ export default mixins(
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setActiveExecution(): void {
|
async setActiveExecution(): Promise<void> {
|
||||||
const activeExecutionId = this.$route.params.executionId;
|
const activeExecutionId = this.$route.params.executionId;
|
||||||
if (activeExecutionId) {
|
if (activeExecutionId) {
|
||||||
const execution = this.workflowsStore.getExecutionDataById(activeExecutionId);
|
const execution = this.workflowsStore.getExecutionDataById(activeExecutionId);
|
||||||
if (execution) {
|
if (execution) {
|
||||||
this.workflowsStore.activeWorkflowExecution = execution;
|
this.workflowsStore.activeWorkflowExecution = execution;
|
||||||
|
} else {
|
||||||
|
await this.tryToFindExecution(activeExecutionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no execution in the route, select the first one
|
// If there is no execution in the route, select the first one
|
||||||
if (this.workflowsStore.activeWorkflowExecution === null && this.executions.length > 0) {
|
if (this.workflowsStore.activeWorkflowExecution === null && this.executions.length > 0) {
|
||||||
this.workflowsStore.activeWorkflowExecution = this.executions[0];
|
this.workflowsStore.activeWorkflowExecution = this.executions[0];
|
||||||
|
@ -423,6 +426,37 @@ export default mixins(
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async tryToFindExecution(executionId: string, skipCheck = false): Promise<void> {
|
||||||
|
// First check if executions exists in the DB at all
|
||||||
|
if (!skipCheck) {
|
||||||
|
const executionExists = await this.workflowsStore.fetchExecutionDataById(executionId);
|
||||||
|
if (!executionExists) {
|
||||||
|
this.workflowsStore.activeWorkflowExecution = null;
|
||||||
|
this.$showError(
|
||||||
|
new Error(
|
||||||
|
this.$locale.baseText('executionView.notFound.message', {
|
||||||
|
interpolate: { executionId },
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
this.$locale.baseText('nodeView.showError.openExecution.title'),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fetch next batch of executions
|
||||||
|
await this.loadMore(100);
|
||||||
|
const execution = this.workflowsStore.getExecutionDataById(executionId);
|
||||||
|
if (!execution) {
|
||||||
|
// If it's not there load next until found
|
||||||
|
await this.$nextTick();
|
||||||
|
// But skip fetching execution data since we at this point know it exists
|
||||||
|
await this.tryToFindExecution(executionId, true);
|
||||||
|
} else {
|
||||||
|
// When found set execution as active
|
||||||
|
this.workflowsStore.activeWorkflowExecution = execution;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
async openWorkflow(workflowId: string): Promise<void> {
|
async openWorkflow(workflowId: string): Promise<void> {
|
||||||
await this.loadActiveWorkflows();
|
await this.loadActiveWorkflows();
|
||||||
|
|
||||||
|
|
|
@ -487,6 +487,7 @@
|
||||||
"executionSidebar.searchPlaceholder": "Search executions...",
|
"executionSidebar.searchPlaceholder": "Search executions...",
|
||||||
"executionView.onPaste.title": "Cannot paste here",
|
"executionView.onPaste.title": "Cannot paste here",
|
||||||
"executionView.onPaste.message": "This view is read-only. Switch to <i>Workflow</i> tab to be able to edit the current workflow",
|
"executionView.onPaste.message": "This view is read-only. Switch to <i>Workflow</i> tab to be able to edit the current workflow",
|
||||||
|
"executionView.notFound.message": "Execution with id '{executionId}' could not be found!",
|
||||||
"expressionEdit.anythingInside": "Anything inside",
|
"expressionEdit.anythingInside": "Anything inside",
|
||||||
"expressionEdit.isJavaScript": "is JavaScript.",
|
"expressionEdit.isJavaScript": "is JavaScript.",
|
||||||
"expressionEdit.learnMore": "Learn more",
|
"expressionEdit.learnMore": "Learn more",
|
||||||
|
|
|
@ -46,6 +46,7 @@ import { useRootStore } from './n8nRootStore';
|
||||||
import {
|
import {
|
||||||
getActiveWorkflows,
|
getActiveWorkflows,
|
||||||
getCurrentExecutions,
|
getCurrentExecutions,
|
||||||
|
getExecutionData,
|
||||||
getFinishedExecutions,
|
getFinishedExecutions,
|
||||||
getNewWorkflow,
|
getNewWorkflow,
|
||||||
getWorkflows,
|
getWorkflows,
|
||||||
|
@ -941,6 +942,10 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async fetchExecutionDataById(executionId: string): Promise<IExecutionResponse | null> {
|
||||||
|
const rootStore = useRootStore();
|
||||||
|
return await getExecutionData(rootStore.getRestApiContext, executionId);
|
||||||
|
},
|
||||||
deleteExecution(execution: IExecutionsSummary): void {
|
deleteExecution(execution: IExecutionsSummary): void {
|
||||||
this.currentWorkflowExecutions.splice(this.currentWorkflowExecutions.indexOf(execution), 1);
|
this.currentWorkflowExecutions.splice(this.currentWorkflowExecutions.indexOf(execution), 1);
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue