mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-23 10:32:17 -08:00
feat(editor): Redirect users to canvas if they don't have any workflows (#5629)
* feat(editor): Bring new users to empty canvas * Fix failing e2e tests and revert CLI implementation * Revert editor-ui Interface changes * Try to mock /settings and /active * Revert canvas test changes, reload after executions in 20-workflow-executions * Make sure we include manual executiosn before running them in 20-workflow-executions * Make sure to re-init node view when replacing empty workflows route, show phantom loader
This commit is contained in:
parent
0b6fa6b20e
commit
354edf6886
|
@ -27,13 +27,12 @@ describe('Workflows', () => {
|
|||
});
|
||||
|
||||
cy.signin({ email, password });
|
||||
cy.visit(WorkflowsPage.url);
|
||||
cy.visit('/');
|
||||
cy.waitForLoad();
|
||||
});
|
||||
|
||||
it('should create a new workflow using empty state card', () => {
|
||||
WorkflowsPage.getters.newWorkflowButtonCard().should('be.visible');
|
||||
WorkflowsPage.getters.newWorkflowButtonCard().click();
|
||||
|
||||
it('should land on empty canvas after registration', () => {
|
||||
cy.url().should('include', WorkflowPage.url);
|
||||
cy.createFixtureWorkflow('Test_workflow_1.json', `Empty State Card Workflow ${uuid()}`);
|
||||
|
||||
WorkflowPage.getters.workflowTags().should('contain.text', 'some-tag-1');
|
||||
|
@ -41,8 +40,6 @@ describe('Workflows', () => {
|
|||
});
|
||||
|
||||
it('should create multiple new workflows using add workflow button', () => {
|
||||
WorkflowsPage.getters.newWorkflowButtonCard().should('not.exist');
|
||||
|
||||
[...Array(multipleWorkflowsCount).keys()].forEach(() => {
|
||||
cy.visit(WorkflowsPage.url);
|
||||
WorkflowsPage.getters.createWorkflowButton().click();
|
||||
|
@ -95,8 +92,10 @@ describe('Workflows', () => {
|
|||
WorkflowsPage.getters.newWorkflowTemplateCard().should('be.visible');
|
||||
});
|
||||
|
||||
it('should contain empty state cards', () => {
|
||||
WorkflowsPage.getters.newWorkflowButtonCard().should('be.visible');
|
||||
WorkflowsPage.getters.newWorkflowTemplateCard().should('be.visible');
|
||||
it('should redirect to new canvas if no workflows', () => {
|
||||
cy.wait(1000);
|
||||
cy.visit(WorkflowsPage.url);
|
||||
cy.wait(1000);
|
||||
cy.url().should('include', WorkflowPage.url);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -17,7 +17,6 @@ describe('Undo/Redo', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
});
|
||||
|
||||
it('should undo/redo adding nodes', () => {
|
||||
|
|
|
@ -28,7 +28,6 @@ describe('Canvas Actions', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
});
|
||||
|
||||
it('should render canvas', () => {
|
||||
|
|
|
@ -11,7 +11,6 @@ describe('Data pinning', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
});
|
||||
|
||||
it('Should be able to pin node output', () => {
|
||||
|
|
|
@ -16,7 +16,6 @@ describe('Data mapping', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
|
||||
cy.window()
|
||||
// @ts-ignore
|
||||
|
|
|
@ -8,12 +8,9 @@ describe('Schedule Trigger node', async () => {
|
|||
beforeEach(() => {
|
||||
cy.resetAll();
|
||||
cy.skipSetup();
|
||||
cy.visit(workflowsPage.url);
|
||||
});
|
||||
|
||||
it('should execute and return the execution timestamp', () => {
|
||||
workflowsPage.actions.createWorkflowFromCard();
|
||||
cy.waitForLoad();
|
||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
|
||||
workflowPage.actions.openNode('Schedule Trigger');
|
||||
ndv.actions.execute();
|
||||
|
@ -22,8 +19,6 @@ describe('Schedule Trigger node', async () => {
|
|||
});
|
||||
|
||||
it('should execute once per second when activated', () => {
|
||||
workflowsPage.actions.createWorkflowFromCard();
|
||||
cy.waitForLoad();
|
||||
workflowPage.actions.renameWorkflow('Schedule Trigger Workflow');
|
||||
workflowPage.actions.addInitialNodeToCanvas('Schedule Trigger');
|
||||
workflowPage.actions.openNode('Schedule Trigger');
|
||||
|
|
|
@ -98,7 +98,6 @@ describe('Webhook Trigger node', async () => {
|
|||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
|
||||
cy.window()
|
||||
// @ts-ignore
|
||||
|
|
|
@ -80,9 +80,7 @@ describe('Sharing', () => {
|
|||
credentialsModal.actions.save();
|
||||
credentialsModal.actions.close();
|
||||
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
cy.waitForLoad();
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.setWorkflowName('Workflow W1');
|
||||
workflowPage.actions.addInitialNodeToCanvas('Manual Trigger');
|
||||
workflowPage.actions.addNodeToCanvas('Notion', true, true);
|
||||
|
|
|
@ -8,8 +8,6 @@ describe('Workflow tags', () => {
|
|||
beforeEach(() => {
|
||||
cy.resetAll();
|
||||
cy.skipSetup();
|
||||
wf.actions.visit();
|
||||
cy.waitForLoad();
|
||||
});
|
||||
|
||||
it('should create and attach tags inline', () => {
|
||||
|
|
|
@ -9,9 +9,6 @@ describe('Execution', () => {
|
|||
beforeEach(() => {
|
||||
cy.resetAll();
|
||||
cy.skipSetup();
|
||||
// Import workflow
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
cy.waitForLoad();
|
||||
});
|
||||
|
||||
it('should test manual workflow', () => {
|
||||
|
|
|
@ -112,7 +112,6 @@ describe('Credentials', () => {
|
|||
|
||||
it('should create credentials from NDV for node with multiple auth options', () => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(GMAIL_NODE_NAME);
|
||||
workflowPage.getters.canvasNodes().last().click();
|
||||
|
@ -129,7 +128,6 @@ describe('Credentials', () => {
|
|||
|
||||
it('should show multiple credential types in the same dropdown', () => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(GMAIL_NODE_NAME);
|
||||
workflowPage.getters.canvasNodes().last().click();
|
||||
|
@ -155,7 +153,6 @@ describe('Credentials', () => {
|
|||
|
||||
it('should correctly render required and optional credentials', () => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
workflowPage.actions.addNodeToCanvas(PIPEDRIVE_NODE_NAME, true, true);
|
||||
cy.get('body').type('{downArrow}');
|
||||
cy.get('body').type('{enter}');
|
||||
|
@ -180,7 +177,6 @@ describe('Credentials', () => {
|
|||
|
||||
it('should create credentials from NDV for node with no auth options', () => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(TRELLO_NODE_NAME);
|
||||
workflowPage.getters.canvasNodes().last().click();
|
||||
|
@ -194,7 +190,6 @@ describe('Credentials', () => {
|
|||
|
||||
it('should delete credentials from NDV', () => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME);
|
||||
workflowPage.getters.canvasNodes().last().click();
|
||||
|
@ -214,7 +209,6 @@ describe('Credentials', () => {
|
|||
|
||||
it('should rename credentials from NDV', () => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(TRELLO_NODE_NAME);
|
||||
workflowPage.getters.canvasNodes().last().click();
|
||||
|
@ -235,7 +229,6 @@ describe('Credentials', () => {
|
|||
|
||||
it('should setup generic authentication for HTTP node', () => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
workflowPage.actions.addNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
|
||||
workflowPage.actions.addNodeToCanvas(HTTP_REQUEST_NODE_NAME);
|
||||
workflowPage.getters.canvasNodes().last().click();
|
||||
|
|
|
@ -13,7 +13,6 @@ describe('Current Workflow Executions', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
cy.createFixtureWorkflow('Test_workflow_4_executions_view.json', `My test workflow`);
|
||||
createMockExecutions();
|
||||
});
|
||||
|
|
|
@ -37,11 +37,6 @@ describe('Default owner', () => {
|
|||
it('should be able to create workflows', () => {
|
||||
cy.resetAll();
|
||||
cy.skipSetup();
|
||||
cy.visit('/');
|
||||
workflowsPage.getters.newWorkflowButtonCard().should('be.visible');
|
||||
workflowsPage.getters.newWorkflowButtonCard().click();
|
||||
|
||||
cy.waitForLoad();
|
||||
cy.createFixtureWorkflow('Test_workflow_1.json', `Test workflow`);
|
||||
|
||||
// reload page, ensure owner still has access
|
||||
|
|
|
@ -13,8 +13,7 @@ describe('Node Creator', () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.visit(nodeCreatorFeature.url);
|
||||
cy.waitForLoad();
|
||||
WorkflowPage.actions.visit();
|
||||
});
|
||||
|
||||
it('should open node creator on trigger tab if no trigger is on canvas', () => {
|
||||
|
|
|
@ -5,12 +5,13 @@ const workflowPage = new WorkflowPage();
|
|||
const ndv = new NDV();
|
||||
|
||||
describe('NDV', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
before(() => {
|
||||
cy.resetAll();
|
||||
cy.skipSetup();
|
||||
cy.visit(workflowPage.url)
|
||||
cy.waitForLoad();
|
||||
|
||||
});
|
||||
beforeEach(() => {
|
||||
workflowPage.actions.visit();
|
||||
workflowPage.actions.renameWorkflow(uuid());
|
||||
workflowPage.actions.saveWorkflowOnButtonClick();
|
||||
});
|
||||
|
|
|
@ -11,7 +11,6 @@ describe('Code node', () => {
|
|||
});
|
||||
|
||||
it('should execute the placeholder in all-items mode successfully', () => {
|
||||
WorkflowPage.actions.visit();
|
||||
WorkflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||
WorkflowPage.actions.addNodeToCanvas('Code');
|
||||
WorkflowPage.actions.openNode('Code');
|
||||
|
|
|
@ -21,7 +21,6 @@ describe('Workflow Actions', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
WorkflowPage.actions.visit();
|
||||
cy.waitForLoad();
|
||||
});
|
||||
|
||||
it('should be able to save on button click', () => {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { WorkflowPage, WorkflowsPage, NDV } from '../pages';
|
||||
import { WorkflowPage, NDV } from '../pages';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const ndv = new NDV();
|
||||
|
||||
|
@ -11,9 +10,6 @@ describe('HTTP Request node', () => {
|
|||
});
|
||||
|
||||
it('should make a request with a URL and receive a response', () => {
|
||||
cy.visit(workflowsPage.url);
|
||||
|
||||
workflowsPage.actions.createWorkflowFromCard();
|
||||
workflowPage.actions.addInitialNodeToCanvas('Manual');
|
||||
workflowPage.actions.addNodeToCanvas('HTTP Request');
|
||||
workflowPage.actions.openNode('HTTP Request');
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { SettingsSidebar } from './sidebar/settings-sidebar';
|
||||
import { MainSidebar } from './sidebar/main-sidebar';
|
||||
import { WorkflowsPage } from './workflows';
|
||||
import { WorkflowPage } from './workflow';
|
||||
import { BasePage } from './base';
|
||||
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const mainSidebar = new MainSidebar();
|
||||
const settingsSidebar = new SettingsSidebar();
|
||||
|
||||
|
@ -30,7 +30,7 @@ export class SettingsUsersPage extends BasePage {
|
|||
goToOwnerSetup: () => this.getters.setUpOwnerButton().click(),
|
||||
loginAndVisit: (email: string, password: string, isOwner: boolean) => {
|
||||
cy.signin({ email, password });
|
||||
cy.visit(workflowsPage.url);
|
||||
workflowPage.actions.visit();
|
||||
mainSidebar.actions.goToSettings();
|
||||
if (isOwner) {
|
||||
settingsSidebar.getters.menuItem('Users').click();
|
||||
|
@ -39,7 +39,7 @@ export class SettingsUsersPage extends BasePage {
|
|||
settingsSidebar.getters.menuItem('Users').should('not.exist');
|
||||
// Should be redirected to workflows page if trying to access UM url
|
||||
cy.visit('/settings/users');
|
||||
cy.url().should('match', new RegExp(workflowsPage.url));
|
||||
cy.url().should('match', new RegExp(workflowPage.url));
|
||||
}
|
||||
},
|
||||
opedDeleteDialog: (email: string) => {
|
||||
|
|
|
@ -256,12 +256,20 @@ export class WorkflowPage extends BasePage {
|
|||
turnOnManualExecutionSaving: () => {
|
||||
this.getters.workflowMenu().click();
|
||||
this.getters.workflowMenuItemSettings().click();
|
||||
cy.get('.el-loading-mask').should('not.be.visible');
|
||||
this.getters
|
||||
.workflowSettingsSaveManualExecutionsSelect()
|
||||
.find('li:contains("Yes")')
|
||||
.click({ force: true });
|
||||
|
||||
this.getters.workflowSettingsSaveManualExecutionsSelect().should('contain', 'Yes');
|
||||
this.getters.workflowSettingsSaveButton().click();
|
||||
this.getters.successToast().should('exist');
|
||||
|
||||
this.getters.workflowMenu().click();
|
||||
this.getters.workflowMenuItemSettings().click();
|
||||
this.getters.workflowSettingsSaveManualExecutionsSelect().should('contain', 'Yes');
|
||||
this.getters.workflowSettingsSaveButton().click();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
import 'cypress-real-events';
|
||||
import { WorkflowsPage, SigninPage, SignupPage, SettingsUsersPage } from '../pages';
|
||||
import { WorkflowsPage, SigninPage, SignupPage, SettingsUsersPage, WorkflowPage } from '../pages';
|
||||
import { N8N_AUTH_COOKIE } from '../constants';
|
||||
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
|
||||
import { MessageBox } from '../pages/modals/message-box';
|
||||
|
@ -54,13 +54,13 @@ Cypress.Commands.add(
|
|||
);
|
||||
|
||||
Cypress.Commands.add('waitForLoad', () => {
|
||||
cy.getByTestId('node-view-loader', { timeout: 10000 }).should('not.exist');
|
||||
cy.get('.el-loading-mask', { timeout: 10000 }).should('not.exist');
|
||||
cy.getByTestId('node-view-loader', { timeout: 20000 }).should('not.exist');
|
||||
cy.get('.el-loading-mask', { timeout: 20000 }).should('not.exist');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('signin', ({ email, password }) => {
|
||||
const signinPage = new SigninPage();
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
|
||||
cy.session(
|
||||
[email, password],
|
||||
|
@ -74,7 +74,10 @@ Cypress.Commands.add('signin', ({ email, password }) => {
|
|||
});
|
||||
|
||||
// we should be redirected to /workflows
|
||||
cy.url().should('include', workflowsPage.url);
|
||||
cy.visit(workflowPage.url);
|
||||
cy.url().should('include', workflowPage.url);
|
||||
cy.intercept('GET', '/rest/workflows/new').as('loading');
|
||||
cy.wait('@loading');
|
||||
},
|
||||
{
|
||||
validate() {
|
||||
|
@ -158,7 +161,7 @@ Cypress.Commands.add('inviteUsers', ({ instanceOwner, users }) => {
|
|||
|
||||
Cypress.Commands.add('skipSetup', () => {
|
||||
const signupPage = new SignupPage();
|
||||
const workflowsPage = new WorkflowsPage();
|
||||
const workflowPage = new WorkflowPage();
|
||||
const Confirmation = new MessageBox();
|
||||
|
||||
cy.visit(signupPage.url);
|
||||
|
@ -171,8 +174,10 @@ Cypress.Commands.add('skipSetup', () => {
|
|||
Confirmation.getters.header().should('contain.text', 'Skip owner account setup?');
|
||||
Confirmation.actions.confirm();
|
||||
|
||||
// we should be redirected to /workflows
|
||||
cy.url().should('include', workflowsPage.url);
|
||||
// we should be redirected to empty canvas
|
||||
cy.intercept('GET', '/rest/workflows/new').as('loading');
|
||||
cy.url().should('include', workflowPage.url);
|
||||
cy.wait('@loading');
|
||||
} else {
|
||||
cy.log('User already signed up');
|
||||
}
|
||||
|
|
|
@ -22,22 +22,20 @@ import CustomCredential from '../fixtures/Custom_credential.json';
|
|||
// Load custom nodes and credentials fixtures
|
||||
beforeEach(() => {
|
||||
cy.intercept('GET', '/types/nodes.json', (req) => {
|
||||
req.continue((res) => {
|
||||
const nodes = res.body;
|
||||
req.on('response', (res) => {
|
||||
const nodes = res.body || [];
|
||||
|
||||
res.headers['cache-control'] = 'no-cache, no-store';
|
||||
nodes.push(CustomNodeFixture, CustomNodeWithN8nCredentialFixture, CustomNodeWithCustomCredentialFixture);
|
||||
res.send(nodes);
|
||||
});
|
||||
}).as('nodesIntercept');
|
||||
|
||||
cy.intercept('GET', '/types/credentials.json', (req) => {
|
||||
req.continue((res) => {
|
||||
const credentials = res.body;
|
||||
req.on('response', (res) => {
|
||||
const credentials = res.body || [];
|
||||
|
||||
res.headers['cache-control'] = 'no-cache, no-store';
|
||||
credentials.push(CustomCredential);
|
||||
res.send(credentials);
|
||||
});
|
||||
})
|
||||
}).as('credentialsIntercept');
|
||||
})
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
ref="layout"
|
||||
resource-key="workflows"
|
||||
:resources="allWorkflows"
|
||||
:initialize="initialize"
|
||||
:filters="filters"
|
||||
:additional-filters-handler="onFilter"
|
||||
:show-aside="allWorkflows.length > 0"
|
||||
:shareable="isShareable"
|
||||
:initialize="initialize"
|
||||
@click:add="addWorkflow"
|
||||
@update:filters="filters = $event"
|
||||
>
|
||||
|
@ -148,7 +148,7 @@ const StatusFilter = {
|
|||
ALL: '',
|
||||
};
|
||||
|
||||
export default mixins(showMessage, debounceHelper, newVersions).extend({
|
||||
const WorkflowsView = mixins(showMessage, debounceHelper, newVersions).extend({
|
||||
name: 'WorkflowsView',
|
||||
components: {
|
||||
ResourcesListLayout,
|
||||
|
@ -224,12 +224,20 @@ export default mixins(showMessage, debounceHelper, newVersions).extend({
|
|||
}
|
||||
},
|
||||
async initialize() {
|
||||
this.usersStore.fetchUsers(); // Can be loaded in the background, used for filtering
|
||||
|
||||
return await Promise.all([
|
||||
await Promise.all([
|
||||
this.usersStore.fetchUsers(),
|
||||
this.workflowsStore.fetchAllWorkflows(),
|
||||
this.workflowsStore.fetchActiveWorkflows(),
|
||||
]);
|
||||
|
||||
// If the user has no workflows and is not participating in the demo experiment,
|
||||
// redirect to the new workflow view
|
||||
if (!this.isDemoTest && this.allWorkflows.length === 0) {
|
||||
this.uiStore.nodeViewInitialized = false;
|
||||
this.$router.replace({ name: VIEWS.NEW_WORKFLOW });
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
onClickTag(tagId: string, event: PointerEvent) {
|
||||
if (!this.filters.tags.includes(tagId)) {
|
||||
|
@ -273,6 +281,8 @@ export default mixins(showMessage, debounceHelper, newVersions).extend({
|
|||
this.usersStore.showPersonalizationSurvey();
|
||||
},
|
||||
});
|
||||
|
||||
export default WorkflowsView;
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
Loading…
Reference in a new issue