Skip to content

Commit

Permalink
fix(editor): Turn off executions list auto-refresh after leaving the …
Browse files Browse the repository at this point in the history
…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>
  • Loading branch information
2 people authored and ivov committed Dec 15, 2023
1 parent 9933fce commit b866a94
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 19 deletions.
43 changes: 42 additions & 1 deletion cypress/e2e/20-workflow-executions.cy.ts
Original file line number Diff line number Diff line change
@@ -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');

Expand All @@ -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 = () => {
Expand Down
2 changes: 2 additions & 0 deletions cypress/pages/workflow-executions-tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
17 changes: 11 additions & 6 deletions packages/editor-ui/src/components/ExecutionsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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(),
};
Expand All @@ -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,
Expand All @@ -361,6 +364,9 @@ export default defineComponent({
workflows: [] as IWorkflowShortResponse[],
};
},
created() {
this.autoRefresh = this.autoRefreshEnabled;
},
mounted() {
setPageTitle(`n8n - ${this.pageTitle}`);
Expand All @@ -376,6 +382,7 @@ export default defineComponent({
});
},
beforeUnmount() {
this.autoRefresh = false;
this.stopAutoRefreshInterval();
document.removeEventListener('visibilitychange', this.onDocumentVisibilityChange);
},
Expand Down Expand Up @@ -938,18 +945,16 @@ 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();
}, 4 * 1000); // refresh data every 4 secs
}
},
stopAutoRefreshInterval() {
if (this.autoRefreshTimeout) {
clearTimeout(this.autoRefreshTimeout);
this.autoRefreshTimeout = undefined;
}
clearTimeout(this.autoRefreshTimeout);
this.autoRefreshTimeout = undefined;
},
onDocumentVisibilityChange() {
if (document.visibilityState === 'hidden') {
Expand Down
21 changes: 10 additions & 11 deletions packages/editor-ui/src/components/ExecutionsView/ExecutionsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ export default defineComponent({
},
},
async beforeRouteLeave(to, from, next) {
this.stopAutoRefreshInterval();
if (getNodeViewTab(to) === MAIN_HEADER_TABS.WORKFLOW) {
next();
return;
Expand Down Expand Up @@ -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;
Expand All @@ -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> {
Expand Down Expand Up @@ -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 },
})
Expand All @@ -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 },
});
Expand Down Expand Up @@ -363,10 +364,8 @@ export default defineComponent({
}
},
stopAutoRefreshInterval() {
if (this.autoRefreshTimeout) {
clearTimeout(this.autoRefreshTimeout);
this.autoRefreshTimeout = undefined;
}
clearTimeout(this.autoRefreshTimeout);
this.autoRefreshTimeout = undefined;
},
onAutoRefreshToggle(value: boolean): void {
this.autoRefresh = value;
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 = () => {
Expand Down

0 comments on commit b866a94

Please sign in to comment.