mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
fix(editor): Turn off executions list auto-refresh after leaving the page (#8005)
## Summary Fixes the bug when users leave the executions page but there is still an ongoing request for executions. --------- Co-authored-by: Alex Grozav <alex@grozav.com>
This commit is contained in:
parent
b29b4d442b
commit
e3c363d72c
|
@ -1,18 +1,20 @@
|
|||
import { WorkflowPage } from '../pages';
|
||||
import { WorkflowExecutionsTab } from '../pages/workflow-executions-tab';
|
||||
import type { RouteHandler } from 'cypress/types/net-stubbing';
|
||||
|
||||
const workflowPage = new WorkflowPage();
|
||||
const executionsTab = new WorkflowExecutionsTab();
|
||||
const executionsRefreshInterval = 4000;
|
||||
|
||||
// Test suite for executions tab
|
||||
describe('Current Workflow Executions', () => {
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', `My test workflow`);
|
||||
createMockExecutions();
|
||||
});
|
||||
|
||||
it('should render executions tab correctly', () => {
|
||||
createMockExecutions();
|
||||
cy.intercept('GET', '/rest/executions?filter=*').as('getExecutions');
|
||||
cy.intercept('GET', '/rest/executions-current?filter=*').as('getCurrentExecutions');
|
||||
|
||||
|
@ -29,6 +31,45 @@ describe('Current Workflow Executions', () => {
|
|||
.invoke('attr', 'class')
|
||||
.should('match', /_active_/);
|
||||
});
|
||||
|
||||
it('should not redirect back to execution tab when request is not done before leaving the page', () => {
|
||||
cy.intercept('GET', '/rest/executions?filter=*');
|
||||
cy.intercept('GET', '/rest/executions-current?filter=*');
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
cy.wait(1000);
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
});
|
||||
|
||||
it('should not redirect back to execution tab when slow request is not done before leaving the page', () => {
|
||||
const throttleResponse: RouteHandler = (req) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve(req.continue()), 2000);
|
||||
});
|
||||
};
|
||||
|
||||
cy.intercept('GET', '/rest/executions?filter=*', throttleResponse);
|
||||
cy.intercept('GET', '/rest/executions-current?filter=*', throttleResponse);
|
||||
|
||||
executionsTab.actions.switchToExecutionsTab();
|
||||
executionsTab.actions.switchToEditorTab();
|
||||
cy.wait(executionsRefreshInterval);
|
||||
cy.url().should('not.include', '/executions');
|
||||
});
|
||||
});
|
||||
|
||||
const createMockExecutions = () => {
|
||||
|
|
|
@ -39,9 +39,11 @@ export class WorkflowExecutionsTab extends BasePage {
|
|||
},
|
||||
switchToExecutionsTab: () => {
|
||||
this.getters.executionsTabButton().click();
|
||||
cy.url().should('include', '/executions');
|
||||
},
|
||||
switchToEditorTab: () => {
|
||||
workflowPage.getters.editorTabButton().click();
|
||||
cy.url().should('match', /\/workflow\/[^\/]+$/);
|
||||
},
|
||||
deleteExecutionInPreview: () => {
|
||||
this.getters.executionPreviewDeleteButton().click();
|
||||
|
|
|
@ -310,6 +310,7 @@ import { isEmpty } from '@/utils/typesUtils';
|
|||
import { setPageTitle } from '@/utils/htmlUtils';
|
||||
import { executionFilterToQueryFilter } from '@/utils/executionUtils';
|
||||
import { useExternalHooks } from '@/composables/useExternalHooks';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ExecutionsList',
|
||||
|
@ -328,11 +329,13 @@ export default defineComponent({
|
|||
const i18n = useI18n();
|
||||
const telemetry = useTelemetry();
|
||||
const externalHooks = useExternalHooks();
|
||||
const route = useRoute();
|
||||
|
||||
return {
|
||||
i18n,
|
||||
telemetry,
|
||||
externalHooks,
|
||||
route,
|
||||
...useToast(),
|
||||
...useMessage(),
|
||||
};
|
||||
|
@ -346,7 +349,7 @@ export default defineComponent({
|
|||
|
||||
allVisibleSelected: false,
|
||||
allExistingSelected: false,
|
||||
autoRefresh: this.autoRefreshEnabled,
|
||||
autoRefresh: false,
|
||||
autoRefreshTimeout: undefined as undefined | NodeJS.Timer,
|
||||
|
||||
filter: {} as ExecutionFilterType,
|
||||
|
@ -361,6 +364,9 @@ export default defineComponent({
|
|||
workflows: [] as IWorkflowShortResponse[],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.autoRefresh = this.autoRefreshEnabled;
|
||||
},
|
||||
mounted() {
|
||||
setPageTitle(`n8n - ${this.pageTitle}`);
|
||||
|
||||
|
@ -376,6 +382,7 @@ export default defineComponent({
|
|||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.autoRefresh = false;
|
||||
this.stopAutoRefreshInterval();
|
||||
document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);
|
||||
},
|
||||
|
@ -938,7 +945,7 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
async startAutoRefreshInterval() {
|
||||
if (this.autoRefresh) {
|
||||
if (this.autoRefresh && this.route.name === VIEWS.WORKFLOW_EXECUTIONS) {
|
||||
await this.loadAutoRefresh();
|
||||
this.autoRefreshTimeout = setTimeout(() => {
|
||||
void this.startAutoRefreshInterval();
|
||||
|
@ -946,10 +953,8 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
stopAutoRefreshInterval() {
|
||||
if (this.autoRefreshTimeout) {
|
||||
clearTimeout(this.autoRefreshTimeout);
|
||||
this.autoRefreshTimeout = undefined;
|
||||
}
|
||||
},
|
||||
onDocumentVisibilityChange() {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
|
|
|
@ -145,7 +145,6 @@ export default defineComponent({
|
|||
},
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
this.stopAutoRefreshInterval();
|
||||
if (getNodeViewTab(to) === MAIN_HEADER_TABS.WORKFLOW) {
|
||||
next();
|
||||
return;
|
||||
|
@ -181,6 +180,9 @@ export default defineComponent({
|
|||
next();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.autoRefresh = this.uiStore.executionSidebarAutoRefresh;
|
||||
},
|
||||
async mounted() {
|
||||
this.loading = true;
|
||||
const workflowUpdated = this.$route.params.name !== this.workflowsStore.workflowId;
|
||||
|
@ -198,16 +200,15 @@ export default defineComponent({
|
|||
await this.setExecutions();
|
||||
}
|
||||
}
|
||||
|
||||
this.autoRefresh = this.uiStore.executionSidebarAutoRefresh;
|
||||
void this.startAutoRefreshInterval();
|
||||
document.addEventListener('visibilitychange', this.onDocumentVisibilityChange);
|
||||
|
||||
this.loading = false;
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.stopAutoRefreshInterval();
|
||||
document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);
|
||||
this.autoRefresh = false;
|
||||
this.stopAutoRefreshInterval();
|
||||
},
|
||||
methods: {
|
||||
async initView(loadWorkflow: boolean): Promise<void> {
|
||||
|
@ -292,7 +293,7 @@ export default defineComponent({
|
|||
}
|
||||
if (this.executions.length > 0) {
|
||||
await this.$router
|
||||
.push({
|
||||
.replace({
|
||||
name: VIEWS.EXECUTION_PREVIEW,
|
||||
params: { name: this.currentWorkflow, executionId: nextExecution.id },
|
||||
})
|
||||
|
@ -302,7 +303,7 @@ export default defineComponent({
|
|||
} else {
|
||||
// If there are no executions left, show empty state and clear active execution from the store
|
||||
this.workflowsStore.activeWorkflowExecution = null;
|
||||
await this.$router.push({
|
||||
await this.$router.replace({
|
||||
name: VIEWS.EXECUTION_HOME,
|
||||
params: { name: this.currentWorkflow },
|
||||
});
|
||||
|
@ -363,10 +364,8 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
stopAutoRefreshInterval() {
|
||||
if (this.autoRefreshTimeout) {
|
||||
clearTimeout(this.autoRefreshTimeout);
|
||||
this.autoRefreshTimeout = undefined;
|
||||
}
|
||||
},
|
||||
onAutoRefreshToggle(value: boolean): void {
|
||||
this.autoRefresh = value;
|
||||
|
@ -448,7 +447,7 @@ export default defineComponent({
|
|||
params: { name: this.currentWorkflow, executionId: this.executions[0].id },
|
||||
})
|
||||
.catch(() => {});
|
||||
} else if (this.executions.length === 0) {
|
||||
} else if (this.executions.length === 0 && this.$route.name === VIEWS.EXECUTION_PREVIEW) {
|
||||
this.$router
|
||||
.push({
|
||||
name: VIEWS.EXECUTION_HOME,
|
||||
|
|
|
@ -3,7 +3,7 @@ import { merge } from 'lodash-es';
|
|||
import { createTestingPinia } from '@pinia/testing';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { STORES } from '@/constants';
|
||||
import { STORES, VIEWS } from '@/constants';
|
||||
import ExecutionsList from '@/components/ExecutionsList.vue';
|
||||
import type { IWorkflowDb } from '@/Interface';
|
||||
import type { IExecutionsSummary } from 'n8n-workflow';
|
||||
|
@ -12,6 +12,12 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
|
|||
import type { RenderOptions } from '@/__tests__/render';
|
||||
import { createComponentRenderer } from '@/__tests__/render';
|
||||
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: vi.fn().mockReturnValue({
|
||||
name: VIEWS.WORKFLOW_EXECUTIONS,
|
||||
}),
|
||||
}));
|
||||
|
||||
let pinia: ReturnType<typeof createTestingPinia>;
|
||||
|
||||
const generateUndefinedNullOrString = () => {
|
||||
|
|
Loading…
Reference in a new issue