Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(editor): Connect up new project viewer role to the FE #9913

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
b7b9f68
fix(editor): Implement a more general function to get resource scopes
cstuncsik Jul 2, 2024
96c9672
fix(editor): Add type to function
cstuncsik Jul 2, 2024
23ad4c3
fix(editor): Trying to fix type issue
cstuncsik Jul 2, 2024
a58c659
fix(editor): Trying to fix type issue
cstuncsik Jul 3, 2024
72bbff2
test: Unit test getResourcePermissions
cstuncsik Jul 3, 2024
5c38c93
test: Update unit test getResourcePermissions
cstuncsik Jul 3, 2024
b18a682
fix(editor): Fix resource permissions function
cstuncsik Jul 4, 2024
178a511
fix(editor): Disable new workflow creation if project scopes not allo…
cstuncsik Jul 4, 2024
e5760eb
fix(editor): Disable new credential creation if project scopes not al…
cstuncsik Jul 4, 2024
33633f5
fix(editor): Update permissions test
cstuncsik Jul 4, 2024
a45e231
fix(editor): Update permissions
cstuncsik Jul 4, 2024
b2cbc3c
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Jul 4, 2024
ef67e39
fix(editor): Fir permissions after conflict
cstuncsik Jul 4, 2024
63bf32c
fix(editor): Fix workflow permissions after conflict
cstuncsik Jul 4, 2024
e2a7853
fix(editor): Fix roles store test
cstuncsik Jul 4, 2024
6e200bc
fix(editor): Fix opening credential edit modal
cstuncsik Jul 4, 2024
f888550
fix(editor): Disabling empty resource list create button
cstuncsik Jul 4, 2024
9cde23a
fix(editor): Update permissions
cstuncsik Jul 5, 2024
0ac4c0a
fix(editor): Add tooltip to empty credentials create button
cstuncsik Jul 5, 2024
52e34f5
fix(editor): Update permissions
cstuncsik Jul 5, 2024
5413ea1
fix(editor): Fix NodeView
cstuncsik Jul 5, 2024
85c6e9d
fix(editor): Update ActionBox test
cstuncsik Jul 5, 2024
589aa6d
fix(editor): Update permissions
cstuncsik Jul 5, 2024
d7c0747
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Jul 15, 2024
8209be3
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Jul 18, 2024
2b7c068
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Jul 19, 2024
3d4ea36
fix(editor): Restrict more actions for the viewer role
cstuncsik Jul 19, 2024
7296490
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Jul 19, 2024
ae5dc7c
fix(editor): Fix canvas readonly
cstuncsik Jul 20, 2024
360038d
fix(editor): Fix nodeview workflow permission check on project
cstuncsik Jul 20, 2024
d6db2b9
fix(editor): Fix nodeview workflow permission check on project
cstuncsik Jul 20, 2024
690edc0
fix(editor): Fix nodeview workflow permission check on project
cstuncsik Jul 20, 2024
d756e95
fix(editor): Fix nodeview workflow permission check on project
cstuncsik Jul 20, 2024
f7b58e0
fix(editor): Fix canvas store readonly mode
cstuncsik Jul 21, 2024
0e89944
fix(editor): Fix nodeview workflow permission check on project
cstuncsik Jul 21, 2024
2565e92
fix(editor): Fix sticky node readonly state
cstuncsik Jul 21, 2024
46c55cb
fix(editor): Fix sticky node readonly state
cstuncsik Jul 21, 2024
29d97dd
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Jul 22, 2024
bac6285
fix(editor): Fix new workflow duplicate
cstuncsik Jul 22, 2024
d7b3b7c
fix(editor): Fix activating new workflow
cstuncsik Jul 22, 2024
ed906c0
fix(editor): Fix nodeview route watcher
cstuncsik Jul 22, 2024
c9e97c2
fix(editor): Fix activating new workflow
cstuncsik Jul 22, 2024
1501264
fix(editor): Temp fix for having scopes on a freshly created project
cstuncsik Jul 22, 2024
415c6f1
test: E2e test unlicensed viewer role
cstuncsik Jul 22, 2024
6e3f4f9
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Jul 23, 2024
e4cbab6
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Jul 23, 2024
cd15c18
fix(editor): Remove unnecessary request for projects after creating one
cstuncsik Jul 23, 2024
3bd868d
fix(editor): Disable canvas edit and use readonly striped background
cstuncsik Jul 23, 2024
8427b29
fix(editor): Disable restore and clone WF history actions if no permi…
cstuncsik Jul 23, 2024
3d851a3
fix(editor): Refactor executions tab view and disable delete and debu…
cstuncsik Jul 23, 2024
66365c2
fix(editor): Fix debug button disabling
cstuncsik Jul 24, 2024
48527ce
fix(editor): Refactor execution components to setup script
cstuncsik Jul 24, 2024
22575ca
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Jul 25, 2024
1c959d8
fix(editor): Disable execution retry button in execution card if work…
cstuncsik Jul 25, 2024
428dd11
fix(editor): Refactor to WorkflowExecutionsLandingPage to setup script
cstuncsik Jul 25, 2024
4ea9ca1
fix(editor): Refactor to WorkflowExecutionsInfoAccordion to setup script
cstuncsik Jul 25, 2024
f600963
fix(editor): Fix type errors
cstuncsik Jul 25, 2024
6ba3872
test: Test execution card retry button for workflow permissions
cstuncsik Jul 25, 2024
9ce8d30
fix(editor): Remove unused props
cstuncsik Jul 31, 2024
a10bc5d
fix(editor): Add permission check to global executions
cstuncsik Aug 7, 2024
4f3b09c
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Aug 7, 2024
179d7fc
fix(editor): Update lock file
cstuncsik Aug 7, 2024
408a2bc
fix(editor): Fix type error
cstuncsik Aug 7, 2024
31a9e0f
fix(editor): Fix unit test
cstuncsik Aug 7, 2024
2cbaa4e
fix(editor): Fix unit test
cstuncsik Aug 7, 2024
297bcdf
fix(editor): Fix lint
cstuncsik Aug 7, 2024
0511bc2
fix(editor): Fix unit test
cstuncsik Aug 7, 2024
5fdf2df
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Aug 7, 2024
3176823
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Aug 7, 2024
8984b1c
fix(editor): Fix logged-in user area
cstuncsik Aug 7, 2024
3ea666f
fix(editor): Fix NodeView when importing template
cstuncsik Aug 7, 2024
ab34ce1
fix(editor): Fix WF executions sidebar auto scroll
cstuncsik Aug 7, 2024
a7f45ca
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Aug 8, 2024
6f802f1
test: Add e2e test for the project viewer role
cstuncsik Aug 8, 2024
5fd5fc7
test: Fix e2e test
cstuncsik Aug 8, 2024
c0cef7b
fix(editor): Update context menu to handle viewer role
cstuncsik Aug 9, 2024
f3e468b
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Aug 9, 2024
7c9e6d8
fix(editor): Fix e2e test
cstuncsik Aug 9, 2024
ad8246f
Merge remote-tracking branch 'origin/master' into pay-1659-connect-up…
cstuncsik Aug 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions cypress/composables/projects.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CredentialsModal, WorkflowPage } from '../pages';
import { getVisibleSelect } from '../utils';

const workflowPage = new WorkflowPage();
const credentialsModal = new CredentialsModal();
Expand All @@ -11,18 +12,25 @@ export const getProjectTabs = () => cy.getByTestId('project-tabs').find('a');
export const getProjectTabWorkflows = () => getProjectTabs().filter('a[href$="/workflows"]');
export const getProjectTabCredentials = () => getProjectTabs().filter('a[href$="/credentials"]');
export const getProjectTabSettings = () => getProjectTabs().filter('a[href$="/settings"]');
export const getProjectSettingsNameInput = () => cy.getByTestId('project-settings-name-input');
export const getProjectSettingsNameInput = () =>
cy.getByTestId('project-settings-name-input').find('input');
export const getProjectSettingsSaveButton = () => cy.getByTestId('project-settings-save-button');
export const getProjectSettingsCancelButton = () =>
cy.getByTestId('project-settings-cancel-button');
export const getProjectSettingsDeleteButton = () =>
cy.getByTestId('project-settings-delete-button');
export const getProjectMembersSelect = () => cy.getByTestId('project-members-select');
export const addProjectMember = (email: string) => {
export const addProjectMember = (email: string, role?: string) => {
getProjectMembersSelect().click();
getProjectMembersSelect().get('.el-select-dropdown__item').contains(email.toLowerCase()).click();

if (role) {
cy.getByTestId(`user-list-item-${email}`)
.find('[data-test-id="projects-settings-user-role-select"]')
.click();
getVisibleSelect().find('li').contains(role).click();
}
};
export const getProjectNameInput = () => cy.get('#projectName').find('input');
export const getResourceMoveModal = () => cy.getByTestId('project-move-resource-modal');
export const getResourceMoveConfirmModal = () =>
cy.getByTestId('project-move-resource-confirm-modal');
Expand All @@ -31,12 +39,7 @@ export const getProjectMoveSelect = () => cy.getByTestId('project-move-resource-
export function createProject(name: string) {
getAddProjectButton().click();

getProjectNameInput()
.should('be.visible')
.should('be.focused')
.should('have.value', 'My project')
.clear()
.type(name);
getProjectSettingsNameInput().should('be.visible').clear().type(name);
getProjectSettingsSaveButton().click();
}

Expand Down
11 changes: 10 additions & 1 deletion cypress/e2e/12-canvas-actions.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,16 @@ describe('Canvas Actions', () => {
});
});

it('should delete connections by pressing the delete button', () => {
it('should delete node by pressing keyboard backspace', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(CODE_NODE_NAME).click();
cy.get('body').type('{backspace}');
WorkflowPage.getters.nodeConnections().should('have.length', 0);
});

it('should delete connections by clicking on the delete button', () => {
WorkflowPage.actions.addNodeToCanvas(MANUAL_TRIGGER_NODE_NAME);
WorkflowPage.getters.canvasNodeByName(MANUAL_TRIGGER_NODE_DISPLAY_NAME).click();
WorkflowPage.actions.addNodeToCanvas(CODE_NODE_NAME);
Expand Down
39 changes: 29 additions & 10 deletions cypress/e2e/17-sharing.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ describe('Sharing', { disableAutoLogin: true }, () => {
describe('Credential Usage in Cross Shared Workflows', () => {
beforeEach(() => {
cy.resetDatabase();
cy.enableFeature('sharing');
cy.enableFeature('advancedPermissions');
cy.enableFeature('projectRole:admin');
cy.enableFeature('projectRole:editor');
Expand All @@ -274,11 +275,6 @@ describe('Credential Usage in Cross Shared Workflows', () => {
});

it('should only show credentials from the same team project', () => {
cy.enableFeature('advancedPermissions');
cy.enableFeature('projectRole:admin');
cy.enableFeature('projectRole:editor');
cy.changeQuota('maxTeamProjects', -1);

// Create a notion credential in the home project
credentialsPage.getters.emptyListCreateCredentialButton().click();
credentialsModal.actions.createNewCredential('Notion API');
Expand All @@ -305,10 +301,36 @@ describe('Credential Usage in Cross Shared Workflows', () => {
getVisibleSelect().find('li').should('have.length', 2);
});

it('should only show credentials in their personal project for members', () => {
// Create a notion credential as the owner
credentialsPage.getters.emptyListCreateCredentialButton().click();
credentialsModal.actions.createNewCredential('Notion API');

// Create another notion credential as the owner, but share it with member
// 0
credentialsPage.getters.createCredentialButton().click();
credentialsModal.actions.createNewCredential('Notion API', false);
credentialsModal.actions.changeTab('Sharing');
credentialsModal.actions.addUser(INSTANCE_MEMBERS[0].email);
credentialsModal.actions.saveSharing();

// As the member, create a new notion credential and a workflow
cy.signinAsMember();
cy.visit(credentialsPage.url);
credentialsPage.getters.createCredentialButton().click();
credentialsModal.actions.createNewCredential('Notion API');
cy.visit(workflowsPage.url);
workflowsPage.actions.createWorkflowFromCard();
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);

// Only the own credential the shared one (+ the 'Create new' option)
// should be in the dropdown
workflowPage.getters.nodeCredentialsSelect().click();
getVisibleSelect().find('li').should('have.length', 3);
});

it('should only show credentials in their personal project for members if the workflow was shared with them', () => {
const workflowName = 'Test workflow';
cy.enableFeature('sharing');
cy.reload();

// Create a notion credential as the owner and a workflow that is shared
// with member 0
Expand Down Expand Up @@ -339,7 +361,6 @@ describe('Credential Usage in Cross Shared Workflows', () => {

it("should show all credentials from all personal projects the workflow's been shared into for the global owner", () => {
const workflowName = 'Test workflow';
cy.enableFeature('sharing');

// As member 1, create a new notion credential. This should not show up.
cy.signinAsMember(1);
Expand Down Expand Up @@ -384,8 +405,6 @@ describe('Credential Usage in Cross Shared Workflows', () => {
});

it('should show all personal credentials if the global owner owns the workflow', () => {
cy.enableFeature('sharing');

// As member 0, create a new notion credential.
cy.signinAsMember();
cy.visit(credentialsPage.url);
Expand Down
110 changes: 101 additions & 9 deletions cypress/e2e/39-projects.cy.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import {
INSTANCE_MEMBERS,
INSTANCE_OWNER,
MANUAL_TRIGGER_NODE_NAME,
NOTION_NODE_NAME,
} from '../constants';
import { INSTANCE_MEMBERS, MANUAL_TRIGGER_NODE_NAME, NOTION_NODE_NAME } from '../constants';
import {
WorkflowsPage,
WorkflowPage,
CredentialsModal,
CredentialsPage,
WorkflowExecutionsTab,
NDV,
MainSidebar,
} from '../pages';
import * as projects from '../composables/projects';
import { getVisibleSelect } from '../utils';
import { getVisibleDropdown, getVisibleModalOverlay, getVisibleSelect } from '../utils';

const workflowsPage = new WorkflowsPage();
const workflowPage = new WorkflowPage();
const credentialsPage = new CredentialsPage();
const credentialsModal = new CredentialsModal();
const executionsTab = new WorkflowExecutionsTab();
const ndv = new NDV();
const mainSidebar = new MainSidebar();

describe('Projects', { disableAutoLogin: true }, () => {
before(() => {
Expand Down Expand Up @@ -241,6 +238,26 @@ describe('Projects', { disableAutoLogin: true }, () => {
projects.getMenuItems().should('not.exist');
});

it('should not show viewer role if not licensed', () => {
cy.signinAsOwner();
cy.visit(workflowsPage.url);

projects.getMenuItems().first().click();
projects.getProjectTabSettings().click();

cy.get(
`[data-test-id="user-list-item-${INSTANCE_MEMBERS[0].email}"] [data-test-id="projects-settings-user-role-select"]`,
).click();

cy.get('.el-select-dropdown__item.is-disabled')
.should('contain.text', 'Viewer')
.get('span:contains("Upgrade")')
.filter(':visible')
.click();

getVisibleModalOverlay().should('contain.text', 'Upgrade to unlock additional roles');
});

describe('when starting from scratch', () => {
beforeEach(() => {
cy.resetDatabase();
Expand All @@ -257,7 +274,7 @@ describe('Projects', { disableAutoLogin: true }, () => {

// Create a project and add a credential to it
cy.intercept('POST', '/rest/projects').as('projectCreate');
projects.getAddProjectButton().should('contain', 'Add project').should('be.visible').click();
projects.getAddProjectButton().click();
cy.wait('@projectCreate');
projects.getMenuItems().should('have.length', 1);
projects.getMenuItems().first().click();
Expand Down Expand Up @@ -418,7 +435,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
});

it('should move resources between projects', () => {
cy.signin(INSTANCE_OWNER);
cy.signinAsOwner();
cy.visit(workflowsPage.url);

// Create a workflow and a credential in the Home project
Expand Down Expand Up @@ -563,5 +580,80 @@ describe('Projects', { disableAutoLogin: true }, () => {
projects.getProjectTabCredentials().click();
credentialsPage.getters.credentialCards().should('have.length', 2);
});

it('should handle viewer role', () => {
cy.enableFeature('projectRole:viewer');
cy.signinAsOwner();
cy.visit(workflowsPage.url);

projects.createProject('Development');
projects.addProjectMember(INSTANCE_MEMBERS[0].email, 'Viewer');
projects.getProjectSettingsSaveButton().click();

projects.getProjectTabWorkflows().click();
workflowsPage.getters.newWorkflowButtonCard().click();
projects.createWorkflow('Test_workflow_4_executions_view.json', 'WF with random error');
executionsTab.actions.createManualExecutions(2);
executionsTab.actions.toggleNodeEnabled('Error');
executionsTab.actions.createManualExecutions(2);
workflowPage.actions.saveWorkflowUsingKeyboardShortcut();

projects.getMenuItems().first().click();
projects.getProjectTabCredentials().click();
credentialsPage.getters.emptyListCreateCredentialButton().click();
projects.createCredential('Notion API');

mainSidebar.actions.openUserMenu();
cy.getByTestId('user-menu-item-logout').click();

cy.get('input[name="email"]').type(INSTANCE_MEMBERS[0].email);
cy.get('input[name="password"]').type(INSTANCE_MEMBERS[0].password);
cy.getByTestId('form-submit-button').click();

mainSidebar.getters.executions().click();
cy.getByTestId('global-execution-list-item').first().find('td:last button').click();
getVisibleDropdown()
.find('li')
.filter(':contains("Retry")')
.should('have.class', 'is-disabled');
getVisibleDropdown()
.find('li')
.filter(':contains("Delete")')
.should('have.class', 'is-disabled');

projects.getMenuItems().first().click();
cy.getByTestId('workflow-card-name').should('be.visible').first().click();
workflowPage.getters.nodeViewRoot().should('be.visible');
workflowPage.getters.executeWorkflowButton().should('not.exist');
workflowPage.getters.nodeCreatorPlusButton().should('not.exist');
workflowPage.getters.canvasNodes().should('have.length', 3).last().click();
cy.get('body').type('{backspace}');
workflowPage.getters.canvasNodes().should('have.length', 3).last().rightclick();
getVisibleDropdown()
.find('li')
.should('be.visible')
.filter(
':contains("Open"), :contains("Copy"), :contains("Select all"), :contains("Clear selection")',
)
.should('not.have.class', 'is-disabled');
cy.get('body').type('{esc}');

executionsTab.actions.switchToExecutionsTab();
cy.getByTestId('retry-execution-button')
.should('be.visible')
.find('.is-disabled')
.should('exist');
cy.get('button:contains("Debug")').should('be.disabled');
cy.get('button[title="Retry execution"]').should('be.disabled');
cy.get('button[title="Delete this execution"]').should('be.disabled');

projects.getMenuItems().first().click();
projects.getProjectTabCredentials().click();
credentialsPage.getters.credentialCards().filter(':contains("Notion")').click();
cy.getByTestId('node-credentials-config-container')
.should('be.visible')
.find('input')
.should('not.have.length');
});
});
});
23 changes: 23 additions & 0 deletions packages/@n8n/permissions/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const DEFAULT_OPERATIONS = ['create', 'read', 'update', 'delete', 'list'] as const;
export const RESOURCES = {
auditLogs: ['manage'] as const,
banner: ['dismiss'] as const,
communityPackage: ['install', 'uninstall', 'update', 'list', 'manage'] as const,
credential: ['share', 'move', ...DEFAULT_OPERATIONS] as const,
externalSecretsProvider: ['sync', ...DEFAULT_OPERATIONS] as const,
externalSecret: ['list', 'use'] as const,
eventBusDestination: ['test', ...DEFAULT_OPERATIONS] as const,
ldap: ['sync', 'manage'] as const,
license: ['manage'] as const,
logStreaming: ['manage'] as const,
orchestration: ['read', 'list'] as const,
project: [...DEFAULT_OPERATIONS] as const,
saml: ['manage'] as const,
securityAudit: ['generate'] as const,
sourceControl: ['pull', 'push', 'manage'] as const,
tag: [...DEFAULT_OPERATIONS] as const,
user: ['resetPassword', 'changeRole', ...DEFAULT_OPERATIONS] as const,
variable: [...DEFAULT_OPERATIONS] as const,
workersView: ['manage'] as const,
workflow: ['share', 'execute', 'move', ...DEFAULT_OPERATIONS] as const,
} as const;
1 change: 1 addition & 0 deletions packages/@n8n/permissions/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type * from './types';
export * from './constants';
export * from './hasScope';
export * from './combineScopes';
26 changes: 4 additions & 22 deletions packages/@n8n/permissions/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,7 @@
export type DefaultOperations = 'create' | 'read' | 'update' | 'delete' | 'list';
export type Resource =
| 'auditLogs'
| 'banner'
| 'communityPackage'
| 'credential'
| 'externalSecretsProvider'
| 'externalSecret'
| 'eventBusDestination'
| 'ldap'
| 'license'
| 'logStreaming'
| 'orchestration'
| 'project'
| 'saml'
| 'securityAudit'
| 'sourceControl'
| 'tag'
| 'user'
| 'variable'
| 'workersView'
| 'workflow';
import type { DEFAULT_OPERATIONS, RESOURCES } from './constants';

export type DefaultOperations = (typeof DEFAULT_OPERATIONS)[number];
export type Resource = keyof typeof RESOURCES;

export type ResourceScope<
R extends Resource,
Expand Down
Loading
Loading