From 672a3f0b929200813feff94b8fd43c073e0424a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nuno=20G=C3=B3is?= Date: Thu, 4 Aug 2022 12:57:25 +0100 Subject: [PATCH] fix: group project access inconsistencies (#1178) * fix: group project access inconsistencies * fix relative path * wip * refactor: make project tabs work as routes * refactor: finish refactoring project assign forms * fix: update snaps * fix: update snaps * add some basic cypress e2e tests to groups * add remaining cypress e2e tests for group CRUD * add groups e2e to gh workflows * refactor: simplify useMemo usage * add GO_BACK navigate const * fix: remove trailing slash on user creation request Co-authored-by: olav Co-authored-by: Fredrik Strand Oseberg --- frontend/.github/workflows/e2e.groups.yml | 25 +++ .../cypress/integration/groups/groups.spec.ts | 160 ++++++++++++++++++ .../component/addons/AddonForm/AddonForm.tsx | 3 +- .../CreateApiToken/CreateApiToken.tsx | 3 +- .../admin/groups/CreateGroup/CreateGroup.tsx | 3 +- .../admin/groups/EditGroup/EditGroup.tsx | 13 +- .../Group/AddGroupUser/AddGroupUser.tsx | 2 + .../Group/EditGroupUser/EditGroupUser.tsx | 3 + .../component/admin/groups/Group/Group.tsx | 12 ++ .../GroupFormUsersSelect.tsx | 8 +- .../GroupUserRoleCell/GroupUserRoleCell.tsx | 2 + .../admin/groups/GroupsList/GroupsList.tsx | 2 + .../CreateProjectRole/CreateProjectRole.tsx | 3 +- .../EditProjectRole/EditProjectRole.tsx | 3 +- .../admin/users/CreateUser/CreateUser.tsx | 3 +- .../admin/users/EditUser/EditUser.tsx | 3 +- .../component/common/NotFound/NotFound.tsx | 3 +- .../common/ToastRenderer/Toast/Toast.tsx | 5 +- .../CreateUnleashContextPage.tsx | 3 +- .../context/EditContext/EditContext.tsx | 3 +- .../CreateEnvironment/CreateEnvironment.tsx | 3 +- .../EditEnvironment/EditEnvironment.tsx | 3 +- .../feature/CreateFeature/CreateFeature.tsx | 3 +- .../feature/EditFeature/EditFeature.tsx | 3 +- .../__snapshots__/routes.test.tsx.snap | 11 +- frontend/src/component/menu/routes.ts | 11 +- .../Project/CreateProject/CreateProject.tsx | 3 +- .../Project/EditProject/EditProject.tsx | 3 +- .../src/component/project/Project/Project.tsx | 66 ++------ .../ProjectFeaturesArchive.tsx | 14 +- .../Project/ProjectHealth/ProjectHealth.tsx | 11 +- .../project/Project/ProjectOverview.tsx | 18 +- .../project/ProjectAccess/ProjectAccess.tsx | 11 +- .../ProjectAccessAssign.tsx | 142 +++++++--------- .../ProjectAccessCreate.tsx | 24 +++ .../ProjectAccessEditGroup.tsx | 33 ++++ .../ProjectAccessEditUser.tsx | 32 ++++ .../ProjectAccessTable/ProjectAccessTable.tsx | 102 +++++------ .../ProjectEnvironment/ProjectEnvironment.tsx | 18 +- .../CreateStrategy/CreateStrategy.tsx | 3 +- .../strategies/EditStrategy/EditStrategy.tsx | 3 +- .../tags/CreateTagType/CreateTagType.tsx | 3 +- .../tags/EditTagType/EditTagType.tsx | 3 +- frontend/src/constants/navigate.ts | 1 + .../hooks/api/getters/useAccess/useAccess.ts | 30 ++-- .../api/getters/useProject/useProject.ts | 4 + .../useProjectAccess/useProjectAccess.ts | 48 ++++-- frontend/src/utils/testIds.ts | 12 ++ 48 files changed, 584 insertions(+), 296 deletions(-) create mode 100644 frontend/.github/workflows/e2e.groups.yml create mode 100644 frontend/cypress/integration/groups/groups.spec.ts create mode 100644 frontend/src/component/project/ProjectAccess/ProjectAccessCreate/ProjectAccessCreate.tsx create mode 100644 frontend/src/component/project/ProjectAccess/ProjectAccessEditGroup/ProjectAccessEditGroup.tsx create mode 100644 frontend/src/component/project/ProjectAccess/ProjectAccessEditUser/ProjectAccessEditUser.tsx create mode 100644 frontend/src/constants/navigate.ts diff --git a/frontend/.github/workflows/e2e.groups.yml b/frontend/.github/workflows/e2e.groups.yml new file mode 100644 index 0000000000..7eb5e056e6 --- /dev/null +++ b/frontend/.github/workflows/e2e.groups.yml @@ -0,0 +1,25 @@ +name: e2e:groups +# https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: [deployment_status] +jobs: + e2e: + # only runs this job on successful deploy + if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' + runs-on: ubuntu-latest + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: | + echo "$GITHUB_CONTEXT" + - name: Checkout + uses: actions/checkout@v3 + - name: Run Cypress + uses: cypress-io/github-action@v2 + with: + env: AUTH_USER=admin,AUTH_PASSWORD=unleash4all + config: baseUrl=${{ github.event.deployment_status.target_url }} + record: true + spec: cypress/integration/groups/groups.spec.ts + env: + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} diff --git a/frontend/cypress/integration/groups/groups.spec.ts b/frontend/cypress/integration/groups/groups.spec.ts new file mode 100644 index 0000000000..6977641946 --- /dev/null +++ b/frontend/cypress/integration/groups/groups.spec.ts @@ -0,0 +1,160 @@ +/// + +export {}; +const baseUrl = Cypress.config().baseUrl; +const randomId = String(Math.random()).split('.')[1]; +const groupName = `unleash-e2e-${randomId}`; +const userIds: any[] = []; + +// Disable all active splash pages by visiting them. +const disableActiveSplashScreens = () => { + cy.visit(`/splash/operators`); +}; + +describe('groups', () => { + before(() => { + disableActiveSplashScreens(); + cy.login(); + for (let i = 1; i <= 2; i++) { + cy.request('POST', `${baseUrl}/api/admin/user-admin`, { + name: `unleash-e2e-user${i}-${randomId}`, + email: `unleash-e2e-user${i}-${randomId}@test.com`, + sendEmail: false, + rootRole: 3, + }).then(response => userIds.push(response.body.id)); + } + }); + + after(() => { + userIds.forEach(id => + cy.request('DELETE', `${baseUrl}/api/admin/user-admin/${id}`) + ); + }); + + beforeEach(() => { + cy.login(); + cy.visit('/admin/groups'); + if (document.querySelector("[data-testid='CLOSE_SPLASH']")) { + cy.get("[data-testid='CLOSE_SPLASH']").click(); + } + }); + + it('gives an error if a group does not have an owner', () => { + cy.get("[data-testid='NAVIGATE_TO_CREATE_GROUP']").click(); + + cy.intercept('POST', '/api/admin/groups').as('createGroup'); + + cy.get("[data-testid='UG_NAME_ID']").type(groupName); + cy.get("[data-testid='UG_DESC_ID']").type('hello-world'); + cy.get("[data-testid='UG_USERS_ID']").click(); + cy.contains(`unleash-e2e-user1-${randomId}`).click(); + cy.get("[data-testid='UG_USERS_ADD_ID']").click(); + + cy.get("[data-testid='UG_CREATE_BTN_ID']").click(); + cy.get("[data-testid='TOAST_TEXT']").contains( + 'Group needs to have at least one Owner' + ); + }); + + it('can create a group', () => { + cy.get("[data-testid='NAVIGATE_TO_CREATE_GROUP']").click(); + + cy.intercept('POST', '/api/admin/groups').as('createGroup'); + + cy.get("[data-testid='UG_NAME_ID']").type(groupName); + cy.get("[data-testid='UG_DESC_ID']").type('hello-world'); + cy.get("[data-testid='UG_USERS_ID']").click(); + cy.contains(`unleash-e2e-user1-${randomId}`).click(); + cy.get("[data-testid='UG_USERS_ADD_ID']").click(); + cy.get("[data-testid='UG_USERS_TABLE_ROLE_ID']").click(); + cy.contains('Owner').click(); + + cy.get("[data-testid='UG_CREATE_BTN_ID']").click(); + cy.wait('@createGroup'); + cy.contains(groupName); + }); + + it('gives an error if a group exists with the same name', () => { + cy.get("[data-testid='NAVIGATE_TO_CREATE_GROUP']").click(); + + cy.intercept('POST', '/api/admin/groups').as('createGroup'); + + cy.get("[data-testid='UG_NAME_ID']").type(groupName); + cy.get("[data-testid='UG_DESC_ID']").type('hello-world'); + cy.get("[data-testid='UG_USERS_ID']").click(); + cy.contains(`unleash-e2e-user1-${randomId}`).click(); + cy.get("[data-testid='UG_USERS_ADD_ID']").click(); + cy.get("[data-testid='UG_USERS_TABLE_ROLE_ID']").click(); + cy.contains('Owner').click(); + + cy.get("[data-testid='UG_CREATE_BTN_ID']").click(); + cy.get("[data-testid='TOAST_TEXT']").contains( + 'Group name already exists' + ); + }); + + it('can edit a group', () => { + cy.contains(groupName).click(); + + cy.get("[data-testid='UG_EDIT_BTN_ID']").click(); + + cy.get("[data-testid='UG_DESC_ID']").type('-my edited description'); + + cy.get("[data-testid='UG_SAVE_BTN_ID']").click(); + + cy.contains('hello-world-my edited description'); + }); + + it('can add user to a group', () => { + cy.contains(groupName).click(); + + cy.get("[data-testid='UG_ADD_USER_BTN_ID']").click(); + + cy.get("[data-testid='UG_USERS_ID']").click(); + cy.contains(`unleash-e2e-user2-${randomId}`).click(); + cy.get("[data-testid='UG_USERS_ADD_ID']").click(); + + cy.get("[data-testid='UG_SAVE_BTN_ID']").click(); + + cy.contains(`unleash-e2e-user1-${randomId}`); + cy.contains(`unleash-e2e-user2-${randomId}`); + cy.get("td span:contains('Owner')").should('have.length', 1); + cy.get("td span:contains('Member')").should('have.length', 1); + }); + + it('can edit user role in a group', () => { + cy.contains(groupName).click(); + + cy.get(`[data-testid='UG_EDIT_USER_BTN_ID-${userIds[1]}']`).click(); + + cy.get("[data-testid='UG_USERS_ROLE_ID']").click(); + cy.get("li[data-value='Owner']").click(); + + cy.get("[data-testid='UG_SAVE_BTN_ID']").click(); + + cy.contains(`unleash-e2e-user1-${randomId}`); + cy.contains(`unleash-e2e-user2-${randomId}`); + cy.get("td span:contains('Owner')").should('have.length', 2); + cy.contains('Member').should('not.exist'); + }); + + it('can remove user from a group', () => { + cy.contains(groupName).click(); + + cy.get(`[data-testid='UG_REMOVE_USER_BTN_ID-${userIds[1]}']`).click(); + + cy.get("[data-testid='DIALOGUE_CONFIRM_ID'").click(); + + cy.contains(`unleash-e2e-user1-${randomId}`); + cy.contains(`unleash-e2e-user2-${randomId}`).should('not.exist'); + }); + + it('can delete a group', () => { + cy.contains(groupName).click(); + + cy.get("[data-testid='UG_DELETE_BTN_ID']").click(); + cy.get("[data-testid='DIALOGUE_CONFIRM_ID'").click(); + + cy.contains(groupName).should('not.exist'); + }); +}); diff --git a/frontend/src/component/addons/AddonForm/AddonForm.tsx b/frontend/src/component/addons/AddonForm/AddonForm.tsx index f7bf2a49e2..3a19477149 100644 --- a/frontend/src/component/addons/AddonForm/AddonForm.tsx +++ b/frontend/src/component/addons/AddonForm/AddonForm.tsx @@ -33,6 +33,7 @@ import { StyledButtonSection, } from './AddonForm.styles'; import { useTheme } from '@mui/system'; +import { GO_BACK } from 'constants/navigate'; interface IAddonFormProps { provider?: IAddonProvider; @@ -168,7 +169,7 @@ export const AddonForm: VFC = ({ }; const onCancel = () => { - navigate(-1); + navigate(GO_BACK); }; const onSubmit: FormEventHandler = async event => { diff --git a/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx b/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx index 07b9c0eb09..203f1ac1fb 100644 --- a/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx +++ b/frontend/src/component/admin/apiToken/CreateApiToken/CreateApiToken.tsx @@ -12,6 +12,7 @@ import { useState } from 'react'; import { scrollToTop } from 'component/common/util'; import { formatUnknownError } from 'utils/formatUnknownError'; import { usePageTitle } from 'hooks/usePageTitle'; +import { GO_BACK } from 'constants/navigate'; const pageTitle = 'Create API token'; @@ -75,7 +76,7 @@ export const CreateApiToken = () => { }; const handleCancel = () => { - navigate(-1); + navigate(GO_BACK); }; return ( diff --git a/frontend/src/component/admin/groups/CreateGroup/CreateGroup.tsx b/frontend/src/component/admin/groups/CreateGroup/CreateGroup.tsx index 8b6bcec4d7..8cb9dec9cb 100644 --- a/frontend/src/component/admin/groups/CreateGroup/CreateGroup.tsx +++ b/frontend/src/component/admin/groups/CreateGroup/CreateGroup.tsx @@ -9,6 +9,7 @@ import { formatUnknownError } from 'utils/formatUnknownError'; import { UG_CREATE_BTN_ID } from 'utils/testIds'; import { Button } from '@mui/material'; import { CREATE } from 'constants/misc'; +import { GO_BACK } from 'constants/navigate'; export const CreateGroup = () => { const { setToastData, setToastApiError } = useToast(); @@ -58,7 +59,7 @@ export const CreateGroup = () => { }; const handleCancel = () => { - navigate(-1); + navigate(GO_BACK); }; return ( diff --git a/frontend/src/component/admin/groups/EditGroup/EditGroup.tsx b/frontend/src/component/admin/groups/EditGroup/EditGroup.tsx index ab0f3a1972..2efac8018e 100644 --- a/frontend/src/component/admin/groups/EditGroup/EditGroup.tsx +++ b/frontend/src/component/admin/groups/EditGroup/EditGroup.tsx @@ -10,6 +10,8 @@ import { Button } from '@mui/material'; import { EDIT } from 'constants/misc'; import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; import { useGroup } from 'hooks/api/getters/useGroup/useGroup'; +import { UG_SAVE_BTN_ID } from 'utils/testIds'; +import { GO_BACK } from 'constants/navigate'; export const EditGroup = () => { const groupId = Number(useRequiredPathParam('groupId')); @@ -40,7 +42,7 @@ export const EditGroup = () => { try { await updateGroup(groupId, payload); refetchGroup(); - navigate(-1); + navigate(GO_BACK); setToastData({ title: 'Group updated successfully', type: 'success', @@ -60,7 +62,7 @@ export const EditGroup = () => { }; const handleCancel = () => { - navigate(-1); + navigate(GO_BACK); }; return ( @@ -85,7 +87,12 @@ export const EditGroup = () => { mode={EDIT} clearErrors={clearErrors} > - diff --git a/frontend/src/component/admin/groups/Group/AddGroupUser/AddGroupUser.tsx b/frontend/src/component/admin/groups/Group/AddGroupUser/AddGroupUser.tsx index 34e2729efc..a3b1577dcb 100644 --- a/frontend/src/component/admin/groups/Group/AddGroupUser/AddGroupUser.tsx +++ b/frontend/src/component/admin/groups/Group/AddGroupUser/AddGroupUser.tsx @@ -10,6 +10,7 @@ import { FC, FormEvent, useEffect, useMemo, useState } from 'react'; import { formatUnknownError } from 'utils/formatUnknownError'; import { GroupFormUsersSelect } from 'component/admin/groups/GroupForm/GroupFormUsersSelect/GroupFormUsersSelect'; import { GroupFormUsersTable } from 'component/admin/groups/GroupForm/GroupFormUsersTable/GroupFormUsersTable'; +import { UG_SAVE_BTN_ID } from 'utils/testIds'; const StyledForm = styled('form')(() => ({ display: 'flex', @@ -142,6 +143,7 @@ export const AddGroupUser: FC = ({ type="submit" variant="contained" color="primary" + data-testid={UG_SAVE_BTN_ID} > Save diff --git a/frontend/src/component/admin/groups/Group/EditGroupUser/EditGroupUser.tsx b/frontend/src/component/admin/groups/Group/EditGroupUser/EditGroupUser.tsx index bab86186d3..cdf1214340 100644 --- a/frontend/src/component/admin/groups/Group/EditGroupUser/EditGroupUser.tsx +++ b/frontend/src/component/admin/groups/Group/EditGroupUser/EditGroupUser.tsx @@ -8,6 +8,7 @@ import useToast from 'hooks/useToast'; import { IGroup, IGroupUser, Role } from 'interfaces/group'; import { FC, FormEvent, useEffect, useMemo, useState } from 'react'; import { formatUnknownError } from 'utils/formatUnknownError'; +import { UG_SAVE_BTN_ID, UG_USERS_ROLE_ID } from 'utils/testIds'; const StyledForm = styled('form')(() => ({ display: 'flex', @@ -143,6 +144,7 @@ export const EditGroupUser: FC = ({ Assign the role the user should have in this group @@ -159,6 +161,7 @@ export const EditGroupUser: FC = ({ diff --git a/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx b/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx index 6382e56dc7..64186f2cbe 100644 --- a/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx +++ b/frontend/src/component/admin/groups/GroupUserRoleCell/GroupUserRoleCell.tsx @@ -4,6 +4,7 @@ import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; import { Role } from 'interfaces/group'; import { Badge } from 'component/common/Badge/Badge'; import { StarRounded } from '@mui/icons-material'; +import { UG_USERS_TABLE_ROLE_ID } from 'utils/testIds'; const StyledPopupStar = styled(StarRounded)(({ theme }) => ({ color: theme.palette.warning.main, @@ -36,6 +37,7 @@ export const GroupUserRoleCell = ({ condition={Boolean(onChange)} show={