diff --git a/README.md b/README.md index b4f9f429..c89932bf 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ A couple of environment variables are required: - user_owner - user with owner role - user_orguser - Organization user - user_orgviewer - Organization viewer +- user_orgadmin - Organization admin - user_orgowner - Organization owner - user_platformowner - Platform owner diff --git a/cypress/cypress.config.ts b/cypress/cypress.config.ts index 27492a32..c5ca49da 100644 --- a/cypress/cypress.config.ts +++ b/cypress/cypress.config.ts @@ -16,6 +16,7 @@ export default defineConfig({ // orgs user_orguser: 'orguser@example.com', user_orgviewer: 'orgviewer@example.com', + user_orgadmin: 'orgadmin@example.com', user_orgowner: 'orgowner@example.com', // top level user for all default tests user_platformowner: 'platformowner@example.com', diff --git a/cypress/e2e/organizations/manage.cy.ts b/cypress/e2e/organizations/manage.cy.ts index 4c0c6332..5ba490be 100644 --- a/cypress/e2e/organizations/manage.cy.ts +++ b/cypress/e2e/organizations/manage.cy.ts @@ -17,9 +17,14 @@ describe('Org Manage page', () => { it('Adds a org viewer', () => { manage.doAddOrgViewer(testData.organizations.manage.user); }); - it('Should upgrade org viewer to owner', () => { - manage.doEditOrgViewer(testData.organizations.manage.user); + it('Should upgrade org viewer to admin', () => { + manage.doEditOrgViewerToAdmin(testData.organizations.manage.user); }); + + it('Should upgrade org admin to owner', () => { + manage.doEditOrgViewerToOwner(testData.organizations.manage.user); + }); + it('Deletes user', () => { manage.doDeleteUser(testData.organizations.manage.user); }); diff --git a/cypress/e2e/rbac/organizations/orgAdmin.cy.ts b/cypress/e2e/rbac/organizations/orgAdmin.cy.ts new file mode 100644 index 00000000..78533124 --- /dev/null +++ b/cypress/e2e/rbac/organizations/orgAdmin.cy.ts @@ -0,0 +1,170 @@ +import { testData } from 'cypress/fixtures/variables'; +import GroupAction from 'cypress/support/actions/organizations/GroupsAction'; +import NotificationsAction from 'cypress/support/actions/organizations/NotificationsAction'; +import OverviewAction from 'cypress/support/actions/organizations/OverviewAction'; +import ProjectsActions from 'cypress/support/actions/organizations/ProjectsActions'; +import { aliasMutation, aliasQuery, registerIdleHandler } from 'cypress/utils/aliasQuery'; + +const overview = new OverviewAction(); +const group = new GroupAction(); +const project = new ProjectsActions(); +const notifications = new NotificationsAction(); + +const orgAdmin = [Cypress.env('user_orgadmin')]; + +orgAdmin.forEach(admin => { + const desc = { + [Cypress.env('user_orgadmin')]: 'Org admin', + }; + + describe(`Organizations ${desc[admin]} journey`, () => { + beforeEach(() => { + // register interceptors/idle handler + cy.intercept('POST', Cypress.env('api'), req => { + aliasQuery(req, 'getOrganization'); + aliasMutation(req, 'updateOrganizationFriendlyName'); + aliasMutation(req, 'addUserToGroup'); + aliasMutation(req, 'addGroupToOrganization'); + }); + + registerIdleHandler('idle'); + + cy.login(admin, admin); + cy.visit(`${Cypress.env('url')}/organizations/lagoon-demo-organization`); + }); + + if (admin === Cypress.env('user_orgadmin')) { + it('Fails to change org name and desc - no permission for ORGADMIN', () => { + overview.doFailedChangeOrgFriendlyname(testData.organizations.overview.friendlyName); + overview.closeModal(); + overview.doFailedChangeOrgDescription(testData.organizations.overview.description); + overview.closeModal(); + }); + } else { + it('Changes org name and desc', () => { + overview.changeOrgFriendlyname(testData.organizations.overview.friendlyName); + overview.changeOrgDescription(testData.organizations.overview.description); + }); + } + + it('Navigates to groups and creates', () => { + cy.waitForNetworkIdle('@idle', 500); + + const group1 = testData.organizations.groups.newGroupName; + const group2 = testData.organizations.groups.newGroupName2; + + cy.get('.groups').click(); + cy.location('pathname').should('equal', '/organizations/lagoon-demo-organization/groups'); + + group.doAddGroup(group1, group2); + registerIdleHandler('groupQuery'); + group.doAddMemberToGroup(testData.organizations.users.email, group1); + }); + + it('Navigates to projects and creates a new one', () => { + registerIdleHandler('projectsQuery'); + cy.intercept('POST', Cypress.env('api'), req => { + aliasMutation(req, 'addProjectToOrganization'); + }); + + cy.waitForNetworkIdle('@idle', 500); + + cy.get('.projects').click(); + cy.location('pathname').should('equal', '/organizations/lagoon-demo-organization/projects'); + cy.waitForNetworkIdle('@projectsQuery', 1000); + + project.doAddProject(testData.organizations.project); + }); + + it('Navigates to notifications and creates a couple', () => { + cy.intercept('POST', Cypress.env('api'), req => { + aliasMutation(req, 'addNotificationSlack'); + aliasMutation(req, 'UpdateNotificationSlack'); + aliasMutation(req, 'addNotificationRocketChat'); + aliasMutation(req, 'addNotificationMicrosoftTeams'); + aliasMutation(req, 'addNotificationEmail'); + aliasMutation(req, 'addNotificationWebhook'); + }); + + registerIdleHandler('notificationsQuery'); + + cy.waitForNetworkIdle('@idle', 500); + cy.get('.notifications').click(); + cy.location('pathname').should('equal', '/organizations/lagoon-demo-organization/notifications'); + cy.waitForNetworkIdle('@notificationsQuery', 1000); + + const { slack: slackData, email: emailData, webhook: webhookData } = testData.organizations.notifications; + + notifications.doAddNotification('slack', slackData); + notifications.doAddNotification('email', emailData); + notifications.doAddNotification('webhook', webhookData); + }); + + it('Navigates to a project, adds a group and notifications', () => { + cy.visit( + `${Cypress.env('url')}/organizations/lagoon-demo-organization/projects/${ + testData.organizations.project.projectName + }` + ); + + cy.getBySel('addGroupToProject').click(); + + cy.get('.react-select__indicator').click({ force: true }); + cy.get('#react-select-2-option-0').click(); + + cy.getBySel('addGroupToProjectConfirm').click(); + + cy.log('add notifications'); + + cy.getBySel('addNotificationToProject').click(); + + cy.get('[class$=control]').click({ force: true }); + cy.get('#react-select-3-option-0').click(); + + cy.getBySel('addNotificationToProjectConfirm').click(); + }); + + // cleanup + after(() => { + registerIdleHandler('projectsQuery'); + registerIdleHandler('groupQuery'); + cy.intercept('POST', Cypress.env('api'), req => { + aliasMutation(req, 'removeNotification'); + aliasMutation(req, 'deleteGroup'); + aliasMutation(req, 'deleteProject'); + }); + + cy.waitForNetworkIdle('@idle', 500); + cy.get('.groups').click(); + + group.doDeleteGroup(testData.organizations.groups.newGroupName); + cy.wait('@gqldeleteGroupMutation'); + + group.doDeleteGroup(testData.organizations.groups.newGroupName2); + cy.wait('@gqldeleteGroupMutation'); + + cy.waitForNetworkIdle('@idle', 500); + cy.get('.projects').click(); + + cy.waitForNetworkIdle('@projectsQuery', 1000); + + project.doDeleteProject(testData.organizations.project.projectName); + + cy.get('.notifications').click(); + + cy.waitForNetworkIdle('@idle', 500); + + const { + webhook: { name: webhooknName }, + email: { name: emailName }, + slack: { name: slackName }, + } = testData.organizations.notifications; + + notifications.doDeleteNotification(webhooknName); + cy.wait('@gqlremoveNotificationMutation'); // wait for a delete mutation instead + notifications.doDeleteNotification(emailName); + cy.wait('@gqlremoveNotificationMutation'); + notifications.doDeleteNotification(slackName); + }); + }); +}); diff --git a/cypress/support/actions/organizations/ManageAction.ts b/cypress/support/actions/organizations/ManageAction.ts index 6a8bb8c0..f22f11d3 100644 --- a/cypress/support/actions/organizations/ManageAction.ts +++ b/cypress/support/actions/organizations/ManageAction.ts @@ -16,10 +16,26 @@ export default class ManageAction { }); } - doEditOrgViewer(user: string) { + doEditOrgViewerToAdmin(user: string) { manageRepo.getUserRows().contains(user).parents('.tableRow').find('.link').click(); - manageRepo.getUserIsOwnerCheckbox().check(); + // admin + manageRepo.getUserRoleDropdown().click({ force: true }); + manageRepo.getUserAdminRoleOption().click(); + + manageRepo.getUpdateBtn().click(); + + cy.wait('@gqlAddUserToOrganizationMutation'); + + manageRepo.getUserRows().contains(user).parents('.tableRow').find(':contains("ORG ADMIN")').should('exist'); + } + + doEditOrgViewerToOwner(user: string) { + manageRepo.getUserRows().contains(user).parents('.tableRow').find('.link').click(); + + // owner + manageRepo.getUserRoleDropdown().click({ force: true }); + manageRepo.getUserOwnerRoleOption().click(); manageRepo.getUpdateBtn().click(); diff --git a/cypress/support/repositories/organizations/ManageRepository.ts b/cypress/support/repositories/organizations/ManageRepository.ts index 5d05690a..710eaa13 100644 --- a/cypress/support/repositories/organizations/ManageRepository.ts +++ b/cypress/support/repositories/organizations/ManageRepository.ts @@ -6,9 +6,17 @@ export default class ManageRepository { getUserEmailField() { return cy.getBySel('manageEmail'); } - getUserIsOwnerCheckbox() { - return cy.getBySel('userIsOwner'); + getUserRoleDropdown() { + return cy.get('.react-select__indicator'); } + + getUserAdminRoleOption() { + return cy.get('#react-select-2-option-1'); + } + getUserOwnerRoleOption() { + return cy.get('#react-select-2-option-2'); + } + getSubmitBtn() { return cy.getBySel('addUserConfirm'); } diff --git a/src/components/Organizations/AddUserToOrganization/index.js b/src/components/Organizations/AddUserToOrganization/index.js index 895415f8..600591ce 100644 --- a/src/components/Organizations/AddUserToOrganization/index.js +++ b/src/components/Organizations/AddUserToOrganization/index.js @@ -7,12 +7,15 @@ import Button from 'components/Button'; import withLogic from 'components/Organizations/AddUserToOrganization/logic'; import gql from 'graphql-tag'; +import { userTypeOptions } from '../Manage'; import { Footer } from '../SharedStyles'; import { NewUser } from './Styles'; export const ADD_USER_MUTATION = gql` - mutation AddUserToOrganization($email: String!, $organization: Int!, $owner: Boolean) { - addUserToOrganization(input: { user: { email: $email }, organization: $organization, owner: $owner }) { + mutation AddUserToOrganization($email: String!, $organization: Int!, $owner: Boolean, $admin: Boolean) { + addUserToOrganization( + input: { user: { email: $email }, organization: $organization, owner: $owner, admin: $admin } + ) { id } } @@ -21,17 +24,11 @@ export const ADD_USER_MUTATION = gql` /** * Adds/edits user to an organization */ -export const AddUserToOrganization = ({ - organization, - close, - inputValueEmail, - setInputValue, - checkboxValueOwner, - setCheckboxValueOwner, - onAddUser, - users, -}) => { +export const AddUserToOrganization = ({ organization, close, inputValueEmail, setInputValue, onAddUser, users }) => { const userAlreadyExists = users.find(u => u.email === inputValueEmail); + + const [newUserType, setNewUserType] = useState('viewer'); + return ( console.error(err)}> {(addUser, { called, error, data }) => { @@ -60,17 +57,31 @@ export const AddUserToOrganization = ({ /> +
-