diff --git a/packages/editor-ui/src/components/ExecutionsList.vue b/packages/editor-ui/src/components/ExecutionsList.vue index 82c15abe37..7f510a48b6 100644 --- a/packages/editor-ui/src/components/ExecutionsList.vue +++ b/packages/editor-ui/src/components/ExecutionsList.vue @@ -311,6 +311,12 @@ export default defineComponent({ ExecutionTime, ExecutionFilter, }, + props: { + autoRefreshEnabled: { + type: Boolean, + default: true, + }, + }, setup() { return { ...useToast(), @@ -326,8 +332,8 @@ export default defineComponent({ allVisibleSelected: false, allExistingSelected: false, - autoRefresh: true, - autoRefreshInterval: undefined as undefined | NodeJS.Timer, + autoRefresh: this.autoRefreshEnabled, + autoRefreshTimeout: undefined as undefined | NodeJS.Timer, filter: {} as ExecutionFilterType, @@ -343,11 +349,12 @@ export default defineComponent({ }, mounted() { setPageTitle(`n8n - ${this.pageTitle}`); + + void this.handleAutoRefreshToggle(); document.addEventListener('visibilitychange', this.onDocumentVisibilityChange); }, async created() { await this.loadWorkflows(); - this.handleAutoRefreshToggle(); void this.$externalHooks().run('executionsList.openDialog'); this.$telemetry.track('User opened Executions log', { @@ -410,9 +417,9 @@ export default defineComponent({ }); window.open(route.href, '_blank'); }, - handleAutoRefreshToggle() { + async handleAutoRefreshToggle() { this.stopAutoRefreshInterval(); // Clear any previously existing intervals (if any - there shouldn't) - this.startAutoRefreshInterval(); + void this.startAutoRefreshInterval(); }, handleCheckAllExistingChange() { this.allExistingSelected = !this.allExistingSelected; @@ -488,7 +495,7 @@ export default defineComponent({ }); this.handleClearSelection(); - this.refreshData(); + await this.refreshData(); }, handleClearSelection(): void { this.allVisibleSelected = false; @@ -501,14 +508,14 @@ export default defineComponent({ this.handleClearSelection(); this.isMounting = false; }, - handleActionItemClick(commandData: { command: string; execution: IExecutionsSummary }) { + async handleActionItemClick(commandData: { command: string; execution: IExecutionsSummary }) { if (['currentlySaved', 'original'].includes(commandData.command)) { let loadWorkflow = false; if (commandData.command === 'currentlySaved') { loadWorkflow = true; } - this.retryExecution(commandData.execution, loadWorkflow); + await this.retryExecution(commandData.execution, loadWorkflow); this.$telemetry.track('User clicked retry execution button', { workflow_id: this.workflowsStore.workflowId, @@ -517,7 +524,7 @@ export default defineComponent({ }); } if (commandData.command === 'delete') { - this.deleteExecution(commandData.execution); + await this.deleteExecution(commandData.execution); } }, getWorkflowName(workflowId: string): string | undefined { @@ -568,8 +575,11 @@ export default defineComponent({ ); let lastId = 0; const gaps = [] as number[]; - for (let i = results[0].results.length - 1; i >= 0; i--) { - const currentItem = results[0].results[i]; + + const pastExecutions = results[0] || { results: [], count: 0, estimated: false }; + + for (let i = pastExecutions.results.length - 1; i >= 0; i--) { + const currentItem = pastExecutions.results[i]; const currentId = parseInt(currentItem.id, 10); if (lastId !== 0 && !isNaN(currentId)) { // We are doing this iteration to detect possible gaps. @@ -620,8 +630,8 @@ export default defineComponent({ (execution) => !gaps.includes(parseInt(execution.id, 10)) && lastId >= parseInt(execution.id, 10), ); - this.finishedExecutionsCount = results[0].count; - this.finishedExecutionsCountEstimated = results[0].estimated; + this.finishedExecutionsCount = pastExecutions.count; + this.finishedExecutionsCountEstimated = pastExecutions.estimated; Vue.set(this, 'finishedExecutions', alreadyPresentExecutionsFiltered); this.workflowsStore.addToCurrentExecutions(alreadyPresentExecutionsFiltered); @@ -864,7 +874,7 @@ export default defineComponent({ type: 'success', }); - this.refreshData(); + await this.refreshData(); } catch (error) { this.showError( error, @@ -919,22 +929,25 @@ export default defineComponent({ this.selectAllVisibleExecutions(); } }, - startAutoRefreshInterval() { + async startAutoRefreshInterval() { if (this.autoRefresh) { - this.autoRefreshInterval = setInterval(() => this.loadAutoRefresh(), 4 * 1000); // refresh data every 4 secs + await this.loadAutoRefresh(); + this.autoRefreshTimeout = setTimeout(() => { + void this.startAutoRefreshInterval(); + }, 4 * 1000); // refresh data every 4 secs } }, stopAutoRefreshInterval() { - if (this.autoRefreshInterval) { - clearInterval(this.autoRefreshInterval); - this.autoRefreshInterval = undefined; + if (this.autoRefreshTimeout) { + clearTimeout(this.autoRefreshTimeout); + this.autoRefreshTimeout = undefined; } }, onDocumentVisibilityChange() { if (document.visibilityState === 'hidden') { this.stopAutoRefreshInterval(); } else { - this.startAutoRefreshInterval(); + void this.startAutoRefreshInterval(); } }, }, diff --git a/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue b/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue index d3dce0ab4d..3f8197094d 100644 --- a/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue +++ b/packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue @@ -5,11 +5,12 @@ :loading="loading && !executions.length" :loadingMore="loadingMore" :temporaryExecution="temporaryExecution" + :auto-refresh="autoRefresh" + @update:autoRefresh="onAutoRefreshToggle" @reloadExecutions="setExecutions" @filterUpdated="onFilterUpdated" @loadMore="onLoadMore" @retryExecution="onRetryExecution" - @refresh="loadAutoRefresh" />
{ if (loadWorkflow) { @@ -325,7 +337,7 @@ export default defineComponent({ ); } }, - async onFilterUpdated(filter: ExecutionFilterType): void { + async onFilterUpdated(filter: ExecutionFilterType) { this.filter = filter; await this.setExecutions(); }, @@ -333,6 +345,33 @@ export default defineComponent({ this.workflowsStore.currentWorkflowExecutions = await this.loadExecutions(); await this.setActiveExecution(); }, + + async startAutoRefreshInterval() { + if (this.autoRefresh) { + await this.loadAutoRefresh(); + this.autoRefreshTimeout = setTimeout(() => this.startAutoRefreshInterval(), 4000); + } + }, + stopAutoRefreshInterval() { + if (this.autoRefreshTimeout) { + clearTimeout(this.autoRefreshTimeout); + this.autoRefreshTimeout = undefined; + } + }, + onAutoRefreshToggle(value: boolean): void { + this.autoRefresh = value; + this.uiStore.executionSidebarAutoRefresh = this.autoRefresh; + + this.stopAutoRefreshInterval(); // Clear any previously existing intervals (if any - there shouldn't) + this.startAutoRefreshInterval(); + }, + onDocumentVisibilityChange() { + if (document.visibilityState === 'hidden') { + this.stopAutoRefreshInterval(); + } else { + this.startAutoRefreshInterval(); + } + }, async loadAutoRefresh(): Promise { // Most of the auto-refresh logic is taken from the `ExecutionsList` component const fetchedExecutions: IExecutionsSummary[] = await this.loadExecutions(); diff --git a/packages/editor-ui/src/components/ExecutionsView/ExecutionsSidebar.vue b/packages/editor-ui/src/components/ExecutionsView/ExecutionsSidebar.vue index 00e833ac15..afd13e6d25 100644 --- a/packages/editor-ui/src/components/ExecutionsView/ExecutionsSidebar.vue +++ b/packages/editor-ui/src/components/ExecutionsView/ExecutionsSidebar.vue @@ -11,8 +11,8 @@
{{ $locale.baseText('executionsList.autoRefresh') }} @@ -39,7 +39,6 @@ :ref="`execution-${temporaryExecution.id}`" :data-test-id="`execution-details-${temporaryExecution.id}`" :showGap="true" - @refresh="onRefresh" @retryExecution="onRetryExecution" />
@@ -85,6 +83,10 @@ export default defineComponent({ ExecutionFilter, }, props: { + autoRefresh: { + type: Boolean, + default: false, + }, executions: { type: Array as PropType, required: true, @@ -106,8 +108,6 @@ export default defineComponent({ return { VIEWS, filter: {} as ExecutionFilterType, - autoRefresh: false, - autoRefreshInterval: undefined as undefined | NodeJS.Timer, }; }, computed: { @@ -126,39 +126,12 @@ export default defineComponent({ }, }, mounted() { - this.autoRefresh = this.uiStore.executionSidebarAutoRefresh === true; - this.startAutoRefreshInterval(); - // 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(); - - document.addEventListener('visibilitychange', this.onDocumentVisibilityChange); - }, - beforeDestroy() { - this.stopAutoRefreshInterval(); - document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange); }, methods: { - startAutoRefreshInterval() { - if (this.autoRefresh) { - this.autoRefreshInterval = setInterval(() => this.onRefresh(), 4000); - } - }, - stopAutoRefreshInterval() { - if (this.autoRefreshInterval) { - clearInterval(this.autoRefreshInterval); - this.autoRefreshInterval = undefined; - } - }, - onDocumentVisibilityChange() { - if (document.visibilityState === 'hidden') { - this.stopAutoRefreshInterval(); - } else { - this.startAutoRefreshInterval(); - } - }, loadMore(limit = 20): void { if (!this.loading) { const executionsListRef = this.$refs.executionList as HTMLElement | undefined; @@ -184,12 +157,6 @@ export default defineComponent({ reloadExecutions(): void { this.$emit('reloadExecutions'); }, - onAutoRefreshToggle(): void { - this.uiStore.executionSidebarAutoRefresh = this.autoRefresh; - - this.stopAutoRefreshInterval(); // Clear any previously existing intervals (if any - there shouldn't) - this.startAutoRefreshInterval(); - }, checkListSize(): void { const sidebarContainerRef = this.$refs.container as HTMLElement | undefined; const currentExecutionCardRefs = this.$refs[ diff --git a/packages/editor-ui/src/components/__tests__/ExecutionsList.test.ts b/packages/editor-ui/src/components/__tests__/ExecutionsList.test.ts index 41ea0c1a6c..dd238c617f 100644 --- a/packages/editor-ui/src/components/__tests__/ExecutionsList.test.ts +++ b/packages/editor-ui/src/components/__tests__/ExecutionsList.test.ts @@ -13,7 +13,7 @@ import { executionHelpers } from '@/mixins/executionsHelpers'; import { i18nInstance } from '@/plugins/i18n'; import type { IWorkflowDb } from '@/Interface'; import type { IExecutionsSummary } from 'n8n-workflow'; -import { waitAllPromises } from '@/__tests__/utils'; +import { retry, waitAllPromises } from '@/__tests__/utils'; import { useWorkflowsStore } from '@/stores'; const workflowDataFactory = (): IWorkflowDb => ({ @@ -70,6 +70,9 @@ const renderOptions = { }, }, }), + propsData: { + autoRefreshEnabled: false, + }, i18n: i18nInstance, stubs: ['font-awesome-icon'], mixins: [externalHooks, genericHelpers, executionHelpers], @@ -134,16 +137,18 @@ describe('ExecutionsList.vue', () => { .mockResolvedValueOnce(executionsData[1]); const { getByTestId, getAllByTestId, queryByTestId } = await renderComponent(); - await userEvent.click(getByTestId('execution-auto-refresh-checkbox')); + + expect(storeSpy).toHaveBeenCalledTimes(1); await userEvent.click(getByTestId('select-visible-executions-checkbox')); - expect(storeSpy).toHaveBeenCalledTimes(1); - expect( - getAllByTestId('select-execution-checkbox').filter((el) => - el.contains(el.querySelector(':checked')), - ).length, - ).toBe(10); + await retry(() => + expect( + getAllByTestId('select-execution-checkbox').filter((el) => + el.contains(el.querySelector(':checked')), + ).length, + ).toBe(10), + ); expect(getByTestId('select-all-executions-checkbox')).toBeInTheDocument(); expect(getByTestId('selected-executions-info').textContent).toContain(10);