From 78f909be3875268ae1d56757da3cf7e4dd904666 Mon Sep 17 00:00:00 2001 From: Davit Date: Mon, 30 Oct 2023 09:20:53 +0400 Subject: [PATCH 01/24] first pass at adding cypress to the UI --- cypress.config.ts | 9 + cypress/e2e/environment.cy.ts | 46 ++ cypress/e2e/login.cy.ts | 41 ++ cypress/e2e/navigation.cy.ts | 55 ++ cypress/e2e/organizations/groups.cy.ts | 26 + cypress/e2e/organizations/manage.cy.ts | 20 + cypress/e2e/organizations/navigation.cy.ts | 52 ++ cypress/e2e/organizations/notifications.cy.ts | 48 ++ cypress/e2e/organizations/overview.cy.ts | 25 + cypress/e2e/organizations/projects.cy.ts | 18 + cypress/e2e/organizations/users.cy.ts | 18 + cypress/e2e/organizationsJourney.cy.ts | 70 ++ cypress/e2e/project.cy.ts | 36 + cypress/e2e/projects.cy.ts | 39 ++ cypress/e2e/settings.cy.ts | 26 + cypress/e2e/sidebarNav.cy.ts | 72 ++ cypress/fixtures/example.json | 5 + cypress/fixtures/variables.ts | 57 ++ cypress/global.d.ts | 13 + .../actions/environment/EnvironmentAction.ts | 43 ++ .../actions/organizations/GroupsAction.ts | 63 ++ .../actions/organizations/ManageAction.ts | 47 ++ .../organizations/NotificationsAction.ts | 59 ++ .../actions/organizations/OverviewAction.ts | 68 ++ .../actions/organizations/ProjectsActions.ts | 35 + .../actions/organizations/UsersAction.ts | 30 + .../support/actions/project/ProjectAction.ts | 53 ++ .../actions/projects/ProjectsAction.ts | 54 ++ .../actions/settings/SettingsAction.ts | 25 + cypress/support/commands.ts | 91 +++ cypress/support/e2e.ts | 2 + .../environment/EnvironmentRepository.ts | 21 + .../navigation/NavigationRepository.ts | 5 + .../organizations/GroupsRepository.ts | 29 + .../organizations/ManageRepository.ts | 26 + .../organizations/NotificationsRepository.ts | 9 + .../organizations/OverviewRepository.ts | 33 + .../organizations/ProjectsRepository.ts | 32 + .../organizations/UsersRepository.ts | 27 + .../repositories/project/ProjectRepository.ts | 35 + .../projects/ProjectsRepository.ts | 26 + .../settings/SettingsRepository.ts | 17 + cypress/tsconfig.json | 12 + package.json | 6 +- src/components/AddVariable/index.js | 2 + src/components/Button/index.tsx | 5 +- src/components/EnvironmentVariables/index.js | 1 + src/components/Header/ThemeToggler.tsx | 2 +- src/components/HeaderMenu/index.js | 14 +- src/components/NewEnvironment/index.js | 338 +++++----- .../Organizations/AddGroupToProject/index.js | 10 +- .../AddNotificationToProject/index.js | 5 +- .../Organizations/AddUserToGroup/index.js | 3 +- .../AddUserToGroupSelect/index.tsx | 4 + .../AddUserToOrganization/index.js | 3 + src/components/Organizations/Groups/index.js | 15 +- src/components/Organizations/Manage/index.js | 7 +- .../Organizations/NewGroup/index.js | 8 +- .../Organizations/NewProject/index.js | 16 +- .../Notifications/AddNotifications.tsx | 2 + .../Organizations/Notifications/index.js | 38 +- .../Organizations/Organization/index.js | 24 +- .../Organizations/Projects/index.js | 1 + .../RemoveNotificationConfirm/index.js | 2 +- src/components/Organizations/Users/index.js | 5 +- src/components/ProjectDetailsSidebar/index.js | 11 +- src/components/ProjectNavTabs/index.js | 2 +- src/components/ProjectVariables/index.js | 267 ++++---- src/components/Projects/index.js | 244 ++++--- src/components/SshKeys/AddSshKey.js | 6 +- src/components/SshKeys/index.js | 2 +- src/components/link/Project.js | 2 +- src/pages/projects.js | 2 +- yarn.lock | 614 +++++++++++++++++- 74 files changed, 2715 insertions(+), 464 deletions(-) create mode 100644 cypress.config.ts create mode 100644 cypress/e2e/environment.cy.ts create mode 100644 cypress/e2e/login.cy.ts create mode 100644 cypress/e2e/navigation.cy.ts create mode 100644 cypress/e2e/organizations/groups.cy.ts create mode 100644 cypress/e2e/organizations/manage.cy.ts create mode 100644 cypress/e2e/organizations/navigation.cy.ts create mode 100644 cypress/e2e/organizations/notifications.cy.ts create mode 100644 cypress/e2e/organizations/overview.cy.ts create mode 100644 cypress/e2e/organizations/projects.cy.ts create mode 100644 cypress/e2e/organizations/users.cy.ts create mode 100644 cypress/e2e/organizationsJourney.cy.ts create mode 100644 cypress/e2e/project.cy.ts create mode 100644 cypress/e2e/projects.cy.ts create mode 100644 cypress/e2e/settings.cy.ts create mode 100644 cypress/e2e/sidebarNav.cy.ts create mode 100644 cypress/fixtures/example.json create mode 100644 cypress/fixtures/variables.ts create mode 100644 cypress/global.d.ts create mode 100644 cypress/support/actions/environment/EnvironmentAction.ts create mode 100644 cypress/support/actions/organizations/GroupsAction.ts create mode 100644 cypress/support/actions/organizations/ManageAction.ts create mode 100644 cypress/support/actions/organizations/NotificationsAction.ts create mode 100644 cypress/support/actions/organizations/OverviewAction.ts create mode 100644 cypress/support/actions/organizations/ProjectsActions.ts create mode 100644 cypress/support/actions/organizations/UsersAction.ts create mode 100644 cypress/support/actions/project/ProjectAction.ts create mode 100644 cypress/support/actions/projects/ProjectsAction.ts create mode 100644 cypress/support/actions/settings/SettingsAction.ts create mode 100644 cypress/support/commands.ts create mode 100644 cypress/support/e2e.ts create mode 100644 cypress/support/repositories/environment/EnvironmentRepository.ts create mode 100644 cypress/support/repositories/navigation/NavigationRepository.ts create mode 100644 cypress/support/repositories/organizations/GroupsRepository.ts create mode 100644 cypress/support/repositories/organizations/ManageRepository.ts create mode 100644 cypress/support/repositories/organizations/NotificationsRepository.ts create mode 100644 cypress/support/repositories/organizations/OverviewRepository.ts create mode 100644 cypress/support/repositories/organizations/ProjectsRepository.ts create mode 100644 cypress/support/repositories/organizations/UsersRepository.ts create mode 100644 cypress/support/repositories/project/ProjectRepository.ts create mode 100644 cypress/support/repositories/projects/ProjectsRepository.ts create mode 100644 cypress/support/repositories/settings/SettingsRepository.ts create mode 100644 cypress/tsconfig.json diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 00000000..17161e32 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from "cypress"; + +export default defineConfig({ + e2e: { + setupNodeEvents(on, config) { + // implement node event listeners here + }, + }, +}); diff --git a/cypress/e2e/environment.cy.ts b/cypress/e2e/environment.cy.ts new file mode 100644 index 00000000..3a737b44 --- /dev/null +++ b/cypress/e2e/environment.cy.ts @@ -0,0 +1,46 @@ +import { testData } from 'cypress/fixtures/variables'; +import EnvironmentAction from 'cypress/support/actions/environment/EnvironmentAction'; +import ProjectAction from 'cypress/support/actions/project/ProjectAction'; + +const project = new ProjectAction(); + +const environment = new EnvironmentAction(); + +describe('Environment page', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + + cy.wait(500); + cy.log('Full user navigation from /projects page'); + + cy.visit(Cypress.env().CY_URL); + + project.doNavigateToFirst(); + + environment.doEnvNavigation(); + }); + + it('Hide/Show values', () => { + environment.doHideShowToggle(); + + cy.log('show all values'); + + environment.doValueToggle(); + + cy.log('disable show/edit buttons'); + + environment.doHideShowToggle(); + }); + + it('Add/update a variable', () => { + environment.doAddVariable(); + cy.wait(1000); + cy.log('check if variable was created'); + cy.get('.data-table > .data-row').should('contain', testData.variables.name); + }); + + it('Delete a variable', () => { + environment.doDeleteVariable(); + cy.get('.data-table > .data-row').should('not.contain', testData.variables.name); + }); +}); diff --git a/cypress/e2e/login.cy.ts b/cypress/e2e/login.cy.ts new file mode 100644 index 00000000..2e932e10 --- /dev/null +++ b/cypress/e2e/login.cy.ts @@ -0,0 +1,41 @@ +describe('Initial login and theme spec', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + }); + + it('Logins and gets redirected to /projects with correct user displayed', () => { + cy.visit(Cypress.env().CY_URL); + + cy.location('pathname').should('equal', '/projects'); + cy.getBySel('headerMenu').should('contain', Cypress.env().CY_EMAIL); + }); + + it('Switches themes', () => { + cy.visit(Cypress.env().CY_URL); + + cy.getBySel('themeToggler').then($toggler => { + if (localStorage.getItem('theme') === 'dark') { + cy.wrap($toggler).click(); + + cy.window().its('localStorage.theme').should('eq', 'light'); + // revert to dark + cy.getBySel('themeToggler').click(); + } else { + cy.wrap($toggler).click(); + + cy.window().its('localStorage.theme').should('eq', 'dark'); + } + }); + }); + + it('Logs out', () => { + cy.visit(Cypress.env().CY_URL); + cy.getBySel('headerMenu').click(); + cy.getBySel('logout').click(); + + cy.origin(Cypress.env().CY_KEYCLOAK_URL, () => { + cy.contains('Sign in to your account'); + }); + }); + }); + \ No newline at end of file diff --git a/cypress/e2e/navigation.cy.ts b/cypress/e2e/navigation.cy.ts new file mode 100644 index 00000000..bbe39d35 --- /dev/null +++ b/cypress/e2e/navigation.cy.ts @@ -0,0 +1,55 @@ +import NavigationRepository from 'cypress/support/repositories/navigation/NavigationRepository'; + +const navigation = new NavigationRepository(); + +describe('Navigation tests', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + }); + + it('Settings/Projects/Organizations', () => { + cy.visit(Cypress.env().CY_URL); + + context('From /projects to /settings', () => { + cy.getBySel('headerMenu').click(); + + navigation.getLinkElement('settings').click(); + + cy.location('pathname').should('equal', '/settings'); + }); + + cy.wait(500); + + context('From /settings to /organizations', () => { + cy.getBySel('headerMenu').click(); + + navigation.getLinkElement('organizations').click(); + + cy.location('pathname').should('equal', '/organizations'); + }); + + cy.wait(500); + + context('From /organizations to /projects', () => { + cy.getBySel('headerMenu').click(); + + navigation.getLinkElement('projects').click(); + + cy.location('pathname').should('equal', '/projects'); + }); + + cy.wait(500); + context('From /projects to /account', () => { + cy.getBySel('headerMenu').click(); + + navigation.getLinkElement('account').click(); + + const redirect = `${Cypress.env().CY_KEYCLOAK_URL}/auth/realms/lagoon/account/`; + cy.origin(redirect, { args: { redirect } }, ({ redirect }) => { + cy.location().should(loc => { + expect(loc.toString()).to.eq(redirect); + }); + }); + }); + }); +}); diff --git a/cypress/e2e/organizations/groups.cy.ts b/cypress/e2e/organizations/groups.cy.ts new file mode 100644 index 00000000..fec3c88e --- /dev/null +++ b/cypress/e2e/organizations/groups.cy.ts @@ -0,0 +1,26 @@ +import GroupAction from 'cypress/support/actions/organizations/GroupsAction'; + +const group = new GroupAction(); + +describe('Organization Groups page', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + cy.visit(`${Cypress.env().CY_URL}/organizations/84/groups`); + }); + + it('Add a group', () => { + group.doAddGroup(); + }); + + it('Search groups', () => { + group.doGroupSearch(); + }); + + it('Add member to group', () => { + group.doAddMemberToGroup(); + }); + + it('Delete group', () => { + group.doDeleteGroup(); + }); +}); diff --git a/cypress/e2e/organizations/manage.cy.ts b/cypress/e2e/organizations/manage.cy.ts new file mode 100644 index 00000000..8114cc4a --- /dev/null +++ b/cypress/e2e/organizations/manage.cy.ts @@ -0,0 +1,20 @@ +import ManageAction from 'cypress/support/actions/organizations/ManageAction'; + +const manage = new ManageAction(); + +describe('Org Manage page', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + cy.visit(`${Cypress.env().CY_URL}/organizations/84/manage`); + }); + + it('Add a org viewer', () => { + manage.doAddOrgViewer(); + }); + it('Upgrade/edit org viewer to owner', () => { + manage.doEditOrgViewer(); + }); + it('Delete user', () => { + manage.doDeleteUser(); + }); +}); diff --git a/cypress/e2e/organizations/navigation.cy.ts b/cypress/e2e/organizations/navigation.cy.ts new file mode 100644 index 00000000..4a3e98db --- /dev/null +++ b/cypress/e2e/organizations/navigation.cy.ts @@ -0,0 +1,52 @@ +describe('Org sidebar navigation', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + }); + + it('Groups/Users/Projects/Notifications/Manage', () => { + cy.visit(`${Cypress.env().CY_URL}/organizations/84`); + + context('From /org/id to /groups', () => { + cy.wait(3500); + + cy.get('.groups').click(); + + cy.location('pathname').should('equal', '/organizations/84/groups'); + }); + + cy.wait(3500); + + context('From /groups to /users', () => { + cy.get('.users').click(); + + cy.location('pathname').should('equal', '/organizations/84/users'); + }); + + cy.wait(3500); + + context('From /users to /projects', () => { + cy.get('.projects').click(); + + cy.location('pathname').should('equal', '/organizations/84/projects'); + }); + + cy.wait(3500); + context('From /projects to /notifications', () => { + cy.get('.notifications').click(); + cy.location('pathname').should('equal', '/organizations/84/notifications'); + }); + cy.wait(3500); + + context('From /notifications to /manage', () => { + cy.get('.manage').click(); + cy.location('pathname').should('equal', '/organizations/84/manage'); + }); + + cy.wait(3500); + + context('From /manage to /overview', () => { + cy.get('.overview').click(); + cy.location('pathname').should('equal', '/organizations/84'); + }); + }); +}); diff --git a/cypress/e2e/organizations/notifications.cy.ts b/cypress/e2e/organizations/notifications.cy.ts new file mode 100644 index 00000000..e505b5c7 --- /dev/null +++ b/cypress/e2e/organizations/notifications.cy.ts @@ -0,0 +1,48 @@ +import NotificationsAction from "cypress/support/actions/organizations/NotificationsAction"; + +const notifications = new NotificationsAction(); + +describe("Org Notifications page", ()=>{ + + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + cy.visit(`${Cypress.env().CY_URL}/organizations/84/notifications`); + }); + + it("Add Slack notification",()=>{ + + notifications.doAddNotification("slack"); + + }) + it("Add Rocketchat notification",()=>{ + + notifications.doAddNotification("rocketChat"); + + }) + it("Add Teams notification",()=>{ + + notifications.doAddNotification("teams"); + + }) + it("Add Email notification",()=>{ + + notifications.doAddNotification("email"); + + }) + it("Add Webhook notification",()=>{ + + notifications.doAddNotification("webhook"); + + }) + + it("Edit notification", ()=>{ + + notifications.doEditNotification(); + }); + + it("Delete notification", ()=>{ + + notifications.doDeleteNotification(); + }); + +}); \ No newline at end of file diff --git a/cypress/e2e/organizations/overview.cy.ts b/cypress/e2e/organizations/overview.cy.ts new file mode 100644 index 00000000..baa87269 --- /dev/null +++ b/cypress/e2e/organizations/overview.cy.ts @@ -0,0 +1,25 @@ +import OverviewAction from 'cypress/support/actions/organizations/OverviewAction'; + +const overview = new OverviewAction(); + +describe('Organization overview page', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + cy.visit(`${Cypress.env().CY_URL}/organizations/84`); + }); + + it('Check navigation links', () => { + overview.doNavLinkCheck(); + }); + + it('Check quota fields', () => { + // groups, projects, notifications, envs + overview.doQuotaFieldCheck(); + }); + + it('Change org friendlty name/description', () => { + overview.changeOrgFriendlyname(); + + overview.changeOrgDescription(); + }); +}); diff --git a/cypress/e2e/organizations/projects.cy.ts b/cypress/e2e/organizations/projects.cy.ts new file mode 100644 index 00000000..17b10d77 --- /dev/null +++ b/cypress/e2e/organizations/projects.cy.ts @@ -0,0 +1,18 @@ +import ProjectsActions from 'cypress/support/actions/organizations/ProjectsActions'; + +const project = new ProjectsActions(); + +describe('Org Projects page', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + cy.visit(`${Cypress.env().CY_URL}/organizations/84/projects`); + }); + + it('Add a project', () => { + project.doAddProject(); + }); + + it('Delete a project', () => { + project.doDeleteProject(); + }); +}); diff --git a/cypress/e2e/organizations/users.cy.ts b/cypress/e2e/organizations/users.cy.ts new file mode 100644 index 00000000..e89f67ee --- /dev/null +++ b/cypress/e2e/organizations/users.cy.ts @@ -0,0 +1,18 @@ +import UsersActions from 'cypress/support/actions/organizations/UsersAction'; + +const users = new UsersActions(); + +describe('Org Users page', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + cy.visit(`${Cypress.env().CY_URL}/organizations/84/users`); + }); + + it('Add user', () => { + users.doAddUser(); + }); + + it('Delete user', () => { + users.doDeleteUser(); + }); +}); diff --git a/cypress/e2e/organizationsJourney.cy.ts b/cypress/e2e/organizationsJourney.cy.ts new file mode 100644 index 00000000..43620319 --- /dev/null +++ b/cypress/e2e/organizationsJourney.cy.ts @@ -0,0 +1,70 @@ +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'; + +const overview = new OverviewAction(); +const group = new GroupAction(); +const project = new ProjectsActions(); +const notifications = new NotificationsAction(); + +describe('Organizations user journey', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + cy.visit(`${Cypress.env().CY_URL}/organizations/84`); + }); + + it('Change org name and desc', () => { + overview.changeOrgFriendlyname(); + overview.changeOrgDescription(); + }); + + it('Navigate to groups to create', () => { + cy.wait(3500); + + cy.get('.groups').click(); + + cy.location('pathname').should('equal', '/organizations/84/groups'); + + group.doAddGroup(true); + group.doAddMemberToGroup(); + }); + + it('Navigate to projects and add to group', () => { + cy.wait(3500); + cy.get('.projects').click(); + cy.location('pathname').should('equal', '/organizations/84/projects'); + + project.doAddProject(); + }); + + it('Navigate to notifications and create a couple', () => { + cy.wait(3500); + cy.get('.notifications').click(); + cy.location('pathname').should('equal', '/organizations/84/notifications'); + + notifications.doAddNotification('slack'); + notifications.doAddNotification('email'); + notifications.doAddNotification('webhook'); + }); + + it('Nav to a project, add group and notifications', () => { + cy.visit(`${Cypress.env().CY_URL}/organizations/84/projects/drupal-example-test`); + + 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('.react-select__indicator').click({ force: true }); + cy.get('#react-select-2-option-0').click(); + + cy.getBySel('addNotificationToProjectConfirm').click(); + }); +}); diff --git a/cypress/e2e/project.cy.ts b/cypress/e2e/project.cy.ts new file mode 100644 index 00000000..743a91ff --- /dev/null +++ b/cypress/e2e/project.cy.ts @@ -0,0 +1,36 @@ +import ProjectAction from 'cypress/support/actions/project/ProjectAction'; + +const project = new ProjectAction(); + +describe('Project page', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + }); + + it('Navigates from /projects to a project', () => { + cy.visit(Cypress.env().CY_URL); + + project.doNavigateToFirst(); + }); + + it('Checks sidebar values/actions', () => { + cy.visit(`${Cypress.env().CY_URL}/projects/drupal-example`); + + project.doClipboardCheck(); + + project.doSidebarPopulatedCheck(); + + project.doExternalLinkCheck(); + }); + + it("Check environment routes", () => { + cy.visit(`${Cypress.env().CY_URL}/projects/drupal-example`); + project.doEnvRouteCheck(); + }) + + it('Bad environment creation', () => { + cy.visit(`${Cypress.env().CY_URL}/projects/drupal-example`); + + project.doBadEnvCreation(); + }); +}); diff --git a/cypress/e2e/projects.cy.ts b/cypress/e2e/projects.cy.ts new file mode 100644 index 00000000..61b705f4 --- /dev/null +++ b/cypress/e2e/projects.cy.ts @@ -0,0 +1,39 @@ +import ProjectAction from '../support/actions/projects/ProjectsAction'; + +const projects = new ProjectAction(); + +describe('Projects page', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + }); + + it('Visits projects page', () => { + cy.visit(Cypress.env().CY_URL); + + projects.doPageCheck(); + }); + + it('Checks project length and counter value', () => { + cy.visit(Cypress.env().CY_URL); + + projects.doProjectLengthCheck(); + }); + + it('Does an empty search', () => { + cy.visit(Cypress.env().CY_URL); + + projects.doEmptySearch(); + }); + + it('Searches for projects', () => { + cy.visit(Cypress.env().CY_URL); + + projects.doSearch(); + }); + + it('Displays no projects (stubbed)', () => { + cy.visit(Cypress.env().CY_URL); + + projects.doEmptyProjectCheck(); + }); +}); diff --git a/cypress/e2e/settings.cy.ts b/cypress/e2e/settings.cy.ts new file mode 100644 index 00000000..0beea559 --- /dev/null +++ b/cypress/e2e/settings.cy.ts @@ -0,0 +1,26 @@ +import SettingAction from 'cypress/support/actions/settings/SettingsAction'; + +const settings = new SettingAction(); + +describe('Settings page', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + }); + + it('Initial SSH keys', () => { + cy.visit(`${Cypress.env().CY_URL}/settings`); + settings.doEmptySshCheck(); + }); + + it('Add SSH key', () => { + cy.visit(`${Cypress.env().CY_URL}/settings`); + + settings.addSshKey(); + }); + + it('Delete SSH key', () => { + cy.visit(`${Cypress.env().CY_URL}/settings`); + + settings.deleteSshKey(); + }); +}); diff --git a/cypress/e2e/sidebarNav.cy.ts b/cypress/e2e/sidebarNav.cy.ts new file mode 100644 index 00000000..e268be52 --- /dev/null +++ b/cypress/e2e/sidebarNav.cy.ts @@ -0,0 +1,72 @@ +describe('Environment sidebar navigation', () => { + beforeEach(() => { + cy.login(Cypress.env().CY_EMAIL, Cypress.env().CY_PASSWORD); + }); + + it('Overview/Deployments/Backups/Tasks/Vars/Problems/Facts/Insights', () => { + const suffix = '/projects/drupal-example/drupal-example-develop'; + cy.visit(`${Cypress.env().CY_URL}${suffix}`); + + context('From /Overview to /Deployments', () => { + cy.wait(3500); + + cy.get('.deployments').click(); + + cy.location('pathname').should('equal', `${suffix}/deployments`); + }); + + cy.wait(3500); + + context('From /deployments to /backups', () => { + cy.get('.backups').click(); + + cy.location('pathname').should('equal', `${suffix}/backups`); + }); + + cy.wait(3500); + + context('From /backups to /tasks', () => { + cy.get('.tasks').click(); + + cy.location('pathname').should('equal', `${suffix}/tasks`); + }); + + cy.wait(3500); + + context('From /tasks to /variables', () => { + cy.get('.environmentVariables').click(); + + cy.location('pathname').should('equal', `${suffix}/environment-variables`); + }); + + cy.wait(3500); + + context('From /variables to /problems', () => { + cy.get('.problems').click(); + + cy.location('pathname').should('equal', `${suffix}/problems`); + }); + + cy.wait(3500); + + context('From /problems to /facts', () => { + cy.get('.facts').click(); + + cy.location('pathname').should('equal', `${suffix}/facts`); + }); + + cy.wait(3500); + + context('From /facts to /insights', () => { + cy.get('.insights').click(); + + cy.location('pathname').should('equal', `${suffix}/insights`); + }); + + context('From /facts to /overview', () => { + cy.get('.overview').click(); + + cy.location('pathname').should('equal', `${suffix}`); + }); + }); +}); diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 00000000..02e42543 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/cypress/fixtures/variables.ts b/cypress/fixtures/variables.ts new file mode 100644 index 00000000..5e072ad6 --- /dev/null +++ b/cypress/fixtures/variables.ts @@ -0,0 +1,57 @@ +export const testData = { + variables: { + name: 'Test variable', + value: '123456789', + }, + ssh: { + name: 'Test SSH', + value: + 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqdSQ0y7tT+42qEdPlWniU5IGpBC8zKLq7DozcXSPAIzXVz853wFFOVCOcJSCGw/sF/7DQCgFWEV90uUBTdx0HPG6/i0n6DD92q4wK0tRBvYfBPernQ/iXXQxqO/Gg4b0O76z6PId+/35LoO5qdlfgbcAtn4b/ry9WF8hSar4az2qxgcpRVg4TpvFtvBX/ChxcmFzJRk0yWr4B+qEdLjaqJcobCgqcJoWYoIioUWEttX9Muz36Mst59ibqIDygI1kOGqQ7nf3AAVcMPoy7UdvkGD4lsi/Ibbi/8yRdlCzGoHBmTFV/R71XBg+tgN79ztmsxwap0uH1f/WKZRP4HzAd', + }, + organizations: { + overview: { + friendlyName: 'Friendly test org', + description: 'Test org description', + }, + groups: { + newGroupName: 'group-cy-test-1', + newGroupName2:"group-cy-test-2", + }, + users: { + email: 'davit.darsavelidze+100@amazee.io', + role: 'developer', + }, + projects: { + projectName: 'drupal-example-test-cy', + gitUrl: 'git@github.com:amazeeio/drupal-example.git', + prodEnv: 'main', + }, + notifications: { + slack: { + name: 'cy-slack-notification', + webhook: 'cy-slack-webhook', + channel: 'cy-slack-channel', + }, + rocketChat: { + name: 'cy-rocketChat-notification', + webhook: 'cy-rocketChat-webhook', + channel: 'cy-rocketChat-channel', + }, + email: { + name: 'cy-email-notification', + email: 'cy-email', + }, + teams: { + name: 'cy-msTeams-notification', + webhook: 'cy-msTeams-webhook', + }, + webhook: { + name: 'cy-webhook-notification', + webhook: 'cy-webhook', + }, + }, + manage:{ + user:"davit.darsavelidze+101@amazee.io" + } + }, +}; diff --git a/cypress/global.d.ts b/cypress/global.d.ts new file mode 100644 index 00000000..0d3138dc --- /dev/null +++ b/cypress/global.d.ts @@ -0,0 +1,13 @@ +declare namespace Cypress { + interface Chainable { + getBySel(dataTestAttribute: string, args?: any): Chainable>; + + login(username: string, password: string): void; + + gqlQuery(operationName: string, query: string, variables?: Record): void; + + gqlMutation(operationName: string, query: string, variables?: Record): void; + + cleanup(): void; + } +} diff --git a/cypress/support/actions/environment/EnvironmentAction.ts b/cypress/support/actions/environment/EnvironmentAction.ts new file mode 100644 index 00000000..1d4c8803 --- /dev/null +++ b/cypress/support/actions/environment/EnvironmentAction.ts @@ -0,0 +1,43 @@ +import { testData } from 'cypress/fixtures/variables'; +import EnvironmentRepository from 'cypress/support/repositories/environment/EnvironmentRepository'; + +const environment = new EnvironmentRepository(); + +export default class EnvironmentAction { + doEnvNavigation() { + environment.getVariablesLink().click(); + } + + doHideShowToggle() { + environment.getToggleShowButton().click(); + } + + doValueToggle() { + environment.getEnvDataRows().get('.showHide').click({ multiple: true }); + } + + doAddVariable() { + environment.getAddButton().click(); + + cy.get('.react-select__indicator').click({ force: true }); + cy.get('#react-select-2-option-1').click(); + + cy.getBySel('varName').focus().type(testData.variables.name); + cy.getBySel('varValue').focus().type(testData.variables.value); + + cy.get('.add-var-btn').click(); + } + + doDeleteVariable() { + environment + .getVariableToDelete() + .contains(testData.variables.name) + .parent() + .within(() => { + cy.getBySel('varDelete').click(); + }); + cy.log("enter the name and confirm"); + cy.get('.form-input > input').type(testData.variables.name); + cy.get(".btn-danger").click(); + } +} diff --git a/cypress/support/actions/organizations/GroupsAction.ts b/cypress/support/actions/organizations/GroupsAction.ts new file mode 100644 index 00000000..48608224 --- /dev/null +++ b/cypress/support/actions/organizations/GroupsAction.ts @@ -0,0 +1,63 @@ +import { testData } from 'cypress/fixtures/variables'; +import GroupsRepository from 'cypress/support/repositories/organizations/GroupsRepository'; + +const groupRepo = new GroupsRepository(); + +export default class GroupAction { + doAddGroup(returnEarly?: boolean) { + groupRepo.getAddGroupBtn('addNewGroup').click(); + groupRepo.getGroupNameInput().type(testData.organizations.groups.newGroupName); + groupRepo.getAddGroupSubmitBtn().click(); + + cy.get('.tableRow').first().should('contain', testData.organizations.groups.newGroupName); + + if(returnEarly) return; + + cy.log('Add another'); + + cy.wait(1000); + groupRepo.getAddGroupBtn('addNewGroup').click(); + groupRepo.getGroupNameInput().type(testData.organizations.groups.newGroupName2); + groupRepo.getAddGroupSubmitBtn().click(); + + cy.get('.tableRow').eq(1).should('contain', testData.organizations.groups.newGroupName2); + } + + doGroupSearch() { + cy.log('First group'); + groupRepo.getSearchBar().type(testData.organizations.groups.newGroupName); + cy.get('.tableRow').eq(0).should('contain', testData.organizations.groups.newGroupName); + + cy.log('No search results'); + groupRepo.getSearchBar().clear().type('does not exist'); + cy.contains('No groups found').should('be.visible'); + + cy.log('Second group'); + groupRepo.getSearchBar().clear().type(testData.organizations.groups.newGroupName); + cy.get('.tableRow').eq(0).should('contain', testData.organizations.groups.newGroupName); + } + doAddMemberToGroup() { + groupRepo.getAddUserBtn('adduser').first().click(); + cy.get('.inputEmail').type(testData.organizations.users.email); + + cy.get('.react-select__indicator').click({ force: true }); + cy.get('#react-select-2-option-2').click(); + cy.getBySel('addUserToGroup').click(); + + cy.wait(500); + cy.getBySel('memberCount') + .first() + .invoke('text') + .then(text => { + const trimmedText = text.trim(); + expect(trimmedText).to.equal('Members: 1'); + }); + } + + doDeleteGroup() { + groupRepo.getDeleteGroupBtn('deleteGroup').first().click(); + cy.getBySel('confirm').click(); + + cy.get('.tableRow').eq(0).should('not.contain', testData.organizations.groups.newGroupName); + } +} diff --git a/cypress/support/actions/organizations/ManageAction.ts b/cypress/support/actions/organizations/ManageAction.ts new file mode 100644 index 00000000..2c07c39d --- /dev/null +++ b/cypress/support/actions/organizations/ManageAction.ts @@ -0,0 +1,47 @@ +import { testData } from 'cypress/fixtures/variables'; +import ManageRepository from 'cypress/support/repositories/organizations/ManageRepository'; + +const manageRepo = new ManageRepository(); + +export default class ManageAction { + doAddOrgViewer() { + manageRepo.getAddUserBtn().click(); + manageRepo.getUserEmailField().type(testData.organizations.manage.user); + manageRepo.getSubmitBtn().click(); + + cy.wait(500); + + manageRepo.getUserRows().should($element => { + const elementText = $element.text(); + expect(elementText).to.include(testData.organizations.manage.user); + }); + } + + doEditOrgViewer() { + manageRepo.getUserRows().contains(testData.organizations.manage.user).parents('.tableRow').find('.link').click(); + + manageRepo.getUserIsOwnerCheckbox().check(); + + manageRepo.getUpdateBtn().click(); + + cy.wait(500); + + manageRepo + .getUserRows() + .contains(testData.organizations.manage.user) + .parents('.tableRow') + .find(':contains("ORG OWNER")') + .should('exist'); + } + + doDeleteUser() { + manageRepo + .getUserRows() + .contains(testData.organizations.manage.user) + .parents('.tableRow') + .find("[aria-label='delete']") + .click(); + + manageRepo.getDeleteConfirmBtn().click(); + } +} diff --git a/cypress/support/actions/organizations/NotificationsAction.ts b/cypress/support/actions/organizations/NotificationsAction.ts new file mode 100644 index 00000000..265846d4 --- /dev/null +++ b/cypress/support/actions/organizations/NotificationsAction.ts @@ -0,0 +1,59 @@ +import { testData } from 'cypress/fixtures/variables'; +import NotificationsRepository from 'cypress/support/repositories/organizations/NotificationsRepository'; + +const notificationRepo = new NotificationsRepository(); + +const notifMap = { + slack: ['name', 'webhook', 'channel'], + rocketChat: ['name', 'webhook', 'channel'], + email: ['name', 'email'], + teams: ['name', 'webhook'], + webhook: ['name', 'webhook'], +} as const; + +export default class NotificationsAction { + doAddNotification(notifType: keyof typeof notifMap) { + const fieldsToFill = notifMap[notifType]; + + const optionIndexMap = { + slack: 0, + rocketChat: 1, + email: 2, + teams: 3, + webhook: 4, + }; + + notificationRepo.getAddNotification().click(); + cy.get('.react-select__indicator').click({ force: true }); + + // Select the option based on the mapping + cy.get(`#react-select-2-option-${optionIndexMap[notifType]}`).click(); + + const data = testData.organizations.notifications[notifType]; + + fieldsToFill.forEach(field => { + cy.get(`.input${field.charAt(0).toUpperCase() + field.slice(1)}`).type(data[field]); + }); + + cy.getBySel('addNotifBtn').click(); + + // notification name + cy.get('div.data-table .data-row').should('include.text', data.name); + } + doEditNotification() { + notificationRepo.getLast('link').click(); + + cy.get('.inputName').type('-edited'); + cy.get('.inputWebhook').type('-edited'); + + cy.getBySel('continueEdit').click(); + + cy.get('div.data-table .data-row').should('include.text', '-edited'); + } + doDeleteNotification() { + notificationRepo.getLast('btn-red').click(); + cy.getBySel('confirmDelete').click(); + + cy.get('div.data-table .data-row').should('not.have.text', 'msTeams'); + } +} diff --git a/cypress/support/actions/organizations/OverviewAction.ts b/cypress/support/actions/organizations/OverviewAction.ts new file mode 100644 index 00000000..e855aa8d --- /dev/null +++ b/cypress/support/actions/organizations/OverviewAction.ts @@ -0,0 +1,68 @@ +import { testData } from 'cypress/fixtures/variables'; +import OverviewRepository from 'cypress/support/repositories/organizations/OverviewRepository'; + +const overviewRepo = new OverviewRepository(); + +export default class OverviewAction { + doNavLinkCheck() { + overviewRepo.getLinkElement('group-link').click(); + cy.location('pathname').should('equal', '/organizations/84/groups'); + + cy.visit(`${Cypress.env().CY_URL}/organizations/84`); + + overviewRepo.getLinkElement('project-link').click(); + cy.location('pathname').should('equal', '/organizations/84/projects'); + + cy.visit(`${Cypress.env().CY_URL}/organizations/84`); + + overviewRepo.getLinkElement('notification-link').click(); + cy.location('pathname').should('equal', '/organizations/84/notifications'); + + cy.visit(`${Cypress.env().CY_URL}/organizations/84`); + overviewRepo.getLinkElement('manage-link').click(); + cy.location('pathname').should('equal', '/organizations/84/manage'); + } + + doQuotaFieldCheck() { + overviewRepo.getFieldElement('group').should('exist').should('not.be.empty'); + + overviewRepo.getFieldElement('project').should('exist').should('not.be.empty'); + + overviewRepo.getFieldElement('notification').should('exist').should('not.be.empty'); + + overviewRepo.getFieldElement('environment').should('exist').should('not.be.empty'); + } + + changeOrgFriendlyname() { + overviewRepo.getNameEditButton('edit-name').click(); + overviewRepo.getEditField().type(testData.organizations.overview.friendlyName); + overviewRepo.getSubmitButton().click(); + + cy.wait(1000); + + overviewRepo + .getfriendlyName() + .invoke('text') + .then(text => { + const trimmedText = text.trim(); + + // Assert that the trimmed text is equal to the expected value + expect(trimmedText).to.equal(testData.organizations.overview.friendlyName); + }); + } + changeOrgDescription() { + overviewRepo.getDescEditButton('edit-description').click(); + overviewRepo.getEditField().type(testData.organizations.overview.description); + overviewRepo.getSubmitButton().click(); + + cy.wait(1000); + + overviewRepo + .getDescription() + .invoke('text') + .then(text => { + const trimmedText = text.trim(); + expect(trimmedText).to.equal(testData.organizations.overview.description); + }); + } +} diff --git a/cypress/support/actions/organizations/ProjectsActions.ts b/cypress/support/actions/organizations/ProjectsActions.ts new file mode 100644 index 00000000..62984e41 --- /dev/null +++ b/cypress/support/actions/organizations/ProjectsActions.ts @@ -0,0 +1,35 @@ +import { testData } from 'cypress/fixtures/variables'; +import ProjectsRepository from 'cypress/support/repositories/organizations/ProjectsRepository'; + +const projects = new ProjectsRepository(); + +export default class ProjectsActions { + doAddProject() { + // weird - it just closes the first time... + projects.getAddBtn().click({ force: true }); + projects.getName().type(testData.organizations.projects.projectName); + projects.getGit().type(testData.organizations.projects.gitUrl); + + projects.getAddBtn().click({ force: true }); + projects.getName().type(testData.organizations.projects.projectName); + projects.getGit().type(testData.organizations.projects.gitUrl); + projects.getEnv().type(testData.organizations.projects.prodEnv); + + projects.selectTarget(); + + projects.getAddConfirm().click(); + + cy.wait(1000); + + projects.getProjectRows().contains(testData.organizations.projects.projectName).should('exist'); + } + + doDeleteProject() { + projects.getDeleteBtn().click(); + + projects.getDeleteConfirm().click(); + + cy.wait(1000); + projects.getProjectRows().should('not.exist'); + } +} diff --git a/cypress/support/actions/organizations/UsersAction.ts b/cypress/support/actions/organizations/UsersAction.ts new file mode 100644 index 00000000..2dd22a90 --- /dev/null +++ b/cypress/support/actions/organizations/UsersAction.ts @@ -0,0 +1,30 @@ +import { testData } from 'cypress/fixtures/variables'; +import UsersRepository from 'cypress/support/repositories/organizations/UsersRepository'; + +const userRepo = new UsersRepository(); +export default class UsersActions { + doAddUser() { + userRepo.getAddUserBtn().click(); + userRepo.getAddUserEmail().type(testData.organizations.users.email); + userRepo.getAddUserGroup(); + userRepo.getAddUserRole(); + + userRepo.getAddUserConfirm().click(); + + userRepo.getRows().should($element => { + const elementText = $element.text(); + expect(elementText).to.include(testData.organizations.users.email); + }); + } + + doDeleteUser() { + userRepo + .getRows() + .contains(testData.organizations.users.email) + .parents('.tableRow') + .find("[aria-label='delete']") + .click(); + + userRepo.getConfirmDeleteBtn().click(); + } +} diff --git a/cypress/support/actions/project/ProjectAction.ts b/cypress/support/actions/project/ProjectAction.ts new file mode 100644 index 00000000..cb9e79ec --- /dev/null +++ b/cypress/support/actions/project/ProjectAction.ts @@ -0,0 +1,53 @@ +import { default as Project } from '../../repositories/project/ProjectRepository'; +import ProjectRepository from '../../repositories/projects/ProjectsRepository'; + +const projectRepo = new ProjectRepository(); + +const project = new Project(); + +export default class ProjectAction { + doNavigateToFirst() { + projectRepo.getProject().first().click(); + } + + doClipboardCheck() { + project.getCopyButton().realClick(); + + cy.window().then(win => { + win.navigator.clipboard.readText().then(text => { + project.getGitUrl().invoke('text').should('match', text); + }); + }); + } + + doSidebarPopulatedCheck() { + project.getGitUrl().should('not.be.empty'); + project.getBranchesField().should('not.be.empty'); + project.getCreatedField().should('not.be.empty'); + project.getDevEnvsField().should('not.be.empty'); + project.getPullRequestsField().should('not.be.empty'); + } + + doExternalLinkCheck() { + cy.getBySel('gitLink').contains('drupal-example'); + } + + doEnvRouteCheck() { + project.getEnvRoutes().each($element => { + cy.wrap($element).find('a').should('have.attr', 'href').and('not.be.empty'); + }); + } + + doBadEnvCreation() { + project.getEnvBtn().click(); + + project.getBranchNameInput().focus().type('does not exist'); + + project.getSubmitBtn().click(); + + project + .getErrorNotification() + .should('exist') + .should('include.text', 'There was a problem creating an Environment.'); + } +} diff --git a/cypress/support/actions/projects/ProjectsAction.ts b/cypress/support/actions/projects/ProjectsAction.ts new file mode 100644 index 00000000..9c2c2e83 --- /dev/null +++ b/cypress/support/actions/projects/ProjectsAction.ts @@ -0,0 +1,54 @@ +import ProjectRepository from '../../repositories/projects/ProjectsRepository'; + +const projectRepo = new ProjectRepository(); + +export default class ProjectAction { + doPageCheck() { + projectRepo.getPageTitle().should('have.text', 'Projects'); + } + + doSearch() { + projectRepo.getSearchBar().type('drupal-example'); + projectRepo.getProjects().its('length').should('equal', 3); + } + + doEmptySearch() { + projectRepo.getSearchBar().type('This does not exist'); + projectRepo + .getNotMatched() + .invoke('text') + .should('match', /No projects matching .*/); + } + + doEmptyProjectCheck() { + cy.intercept('POST', Cypress.env().CY_API, req => { + req.reply({ + statusCode: 200, + body: { + data: { + allProjects: [], + }, + }, + }); + }).as('allProjects'); + + cy.wait('@allProjects'); + + projectRepo.getNoProjectsLabel().should('exist'); + } + + doProjectLengthCheck() { + projectRepo + .getLengthCounter() + .invoke('text') + .then(text => { + const numberMatch = text.match(/\d+/); + if (numberMatch) { + let numberOfProjects = parseInt(numberMatch[0], 10); + projectRepo.getProjects().its('length').should('equal', numberOfProjects); + } else { + throw new Error('Failed to extract the number of projects from the element.'); + } + }); + } +} diff --git a/cypress/support/actions/settings/SettingsAction.ts b/cypress/support/actions/settings/SettingsAction.ts new file mode 100644 index 00000000..f3dfc8a8 --- /dev/null +++ b/cypress/support/actions/settings/SettingsAction.ts @@ -0,0 +1,25 @@ +import { testData } from 'cypress/fixtures/variables'; +import SettingsRepository from 'cypress/support/repositories/settings/SettingsRepository'; + +const settings = new SettingsRepository(); + +export default class SettingAction { + doEmptySshCheck() { + // no ssh keys at first + cy.contains('No SSH keys'); + } + + addSshKey() { + settings.getNameInput().type(testData.ssh.name); + settings.getValueInput().type(testData.ssh.value); + settings.getSubmitBtn().should('not.be.disabled').click(); + + cy.contains(testData.ssh.name); + } + + deleteSshKey(){ + settings.getDeleteBtn().click(); + + cy.contains(testData.ssh.name).should("not.exist"); + } +} diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts new file mode 100644 index 00000000..a1929112 --- /dev/null +++ b/cypress/support/commands.ts @@ -0,0 +1,91 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// + +Cypress.Commands.add('getBySel', (selector: string) => { + return cy.get(`[data-cy=${selector}]`); +}); + +Cypress.Commands.add('login', (username: string, password: string) => { + cy.session([username, password], () => { + cy.visit(Cypress.env().CY_URL); + cy.origin(Cypress.env().CY_KEYCLOAK_URL, { args: { username, password } }, ({ username, password }) => { + // do the stuff here + cy.get('#username').type(username); + cy.get('#password').type(password); + cy.get('#kc-login').click(); + }); + }); +}); + +Cypress.Commands.add('gqlQuery', (operationName, query, variables) => { + const gqlEndpoint = Cypress.env('graphqlEndpoint'); + + if (!gqlEndpoint) { + throw new Error('GraphQL endpoint is not defined'); + } + + const requestBody = { + operationName, + query, + ...(variables ? { variables } : {}), + }; + + // Send a POST request to the gql endpoint + return cy.request({ + method: 'POST', + url: gqlEndpoint, + body: requestBody, + headers: { + 'Content-Type': 'application/json', + }, + }); +}); + +Cypress.Commands.add('gqlMutation', (operationName, mutation, variables) => { + const gqlEndpoint = Cypress.env('graphqlEndpoint'); + + if (!gqlEndpoint) { + throw new Error('GraphQL endpoint is not defined'); + } + + const requestBody = { + operationName, + query: mutation, + ...(variables ? { variables } : {}), + }; + + // Send a POST request to the gql endpoint + return cy.request({ + method: 'POST', + url: gqlEndpoint, + body: requestBody, + headers: { + 'Content-Type': 'application/json', + }, + }); +}); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 00000000..12ceca31 --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1,2 @@ +import "./commands"; +import "cypress-real-events"; \ No newline at end of file diff --git a/cypress/support/repositories/environment/EnvironmentRepository.ts b/cypress/support/repositories/environment/EnvironmentRepository.ts new file mode 100644 index 00000000..0f0b3e76 --- /dev/null +++ b/cypress/support/repositories/environment/EnvironmentRepository.ts @@ -0,0 +1,21 @@ +import { testData } from 'cypress/fixtures/variables'; + +export default class EnvironmentRepository { + getVariablesLink() { + return cy.getBySel('variablesLink'); + } + + getToggleShowButton() { + return cy.getBySel('hideShowValues'); + } + getAddButton() { + return cy.getBySel('addVariable'); + } + + getEnvDataRows() { + return cy.get('.data-table > .data-row'); + } + getVariableToDelete() { + return cy.get('.data-table > .data-row'); + } +} diff --git a/cypress/support/repositories/navigation/NavigationRepository.ts b/cypress/support/repositories/navigation/NavigationRepository.ts new file mode 100644 index 00000000..5c815fcd --- /dev/null +++ b/cypress/support/repositories/navigation/NavigationRepository.ts @@ -0,0 +1,5 @@ +export default class NavigationRepository { + getLinkElement(selector: string) { + return cy.getBySel(selector); + } +} diff --git a/cypress/support/repositories/organizations/GroupsRepository.ts b/cypress/support/repositories/organizations/GroupsRepository.ts new file mode 100644 index 00000000..54f558d0 --- /dev/null +++ b/cypress/support/repositories/organizations/GroupsRepository.ts @@ -0,0 +1,29 @@ +export default class GroupsRepository { + getElement(selector: string) { + return cy.getBySel(selector); + } + getAddGroupBtn(selector: string) { + return this.getElement(selector); + } + getAddUserBtn(selector: string) { + return this.getElement(selector); + } + getDeleteGroupBtn(selector: string) { + return this.getElement(selector); + } + getSystemGroupCheckbox(selector: string) { + return this.getElement(selector); + } + getSorterDropdown(selector: string) { + return this.getElement(selector); + } + getSearchBar() { + return cy.get(".searchBar"); + } + getGroupNameInput(){ + return cy.get(".inputEmail") + } + getAddGroupSubmitBtn(){ + return this.getElement("createGroup"); + } +} diff --git a/cypress/support/repositories/organizations/ManageRepository.ts b/cypress/support/repositories/organizations/ManageRepository.ts new file mode 100644 index 00000000..243460c1 --- /dev/null +++ b/cypress/support/repositories/organizations/ManageRepository.ts @@ -0,0 +1,26 @@ + +export default class ManageRepository{ + + getAddUserBtn(){ + return cy.getBySel("addUserbtn") + } + + getUserEmailField(){ + return cy.getBySel("manageEmail") + } + getUserIsOwnerCheckbox(){ + return cy.get(".inputCheckbox") + } + getSubmitBtn(){ + return cy.getBySel("addUserConfirm"); + } + getUpdateBtn(){ + return cy.getBySel("updateUser"); + } + getUserRows(){ + return cy.get(".tableRow"); + } + getDeleteConfirmBtn(){ + return cy.getBySel("deleteConfirm"); + } +} \ No newline at end of file diff --git a/cypress/support/repositories/organizations/NotificationsRepository.ts b/cypress/support/repositories/organizations/NotificationsRepository.ts new file mode 100644 index 00000000..7bd68261 --- /dev/null +++ b/cypress/support/repositories/organizations/NotificationsRepository.ts @@ -0,0 +1,9 @@ +export default class NotificationsRepository { + getAddNotification() { + return cy.getBySel('addNotification'); + } + + getLast(identifier: string) { + return cy.get('div.data-table .data-row').eq(4).find(`.${identifier}`); + } +} diff --git a/cypress/support/repositories/organizations/OverviewRepository.ts b/cypress/support/repositories/organizations/OverviewRepository.ts new file mode 100644 index 00000000..23ef1d6a --- /dev/null +++ b/cypress/support/repositories/organizations/OverviewRepository.ts @@ -0,0 +1,33 @@ +export default class OverviewRepository { + getElement(selector: string) { + return cy.getBySel(selector); + } + getLinkElement(selector: string) { + return this.getElement(selector); + } + + getFieldElement(selector: string) { + return this.getElement(selector); + } + + getNameEditButton(selector: string) { + return this.getElement(selector); + } + + getfriendlyName() { + return this.getElement('friendlyName'); + } + getDescription() { + return this.getElement('description'); + } + getEditField() { + return cy.get('.inputName'); + } + getSubmitButton() { + return cy.get('button').first(); + } + + getDescEditButton(selector: string) { + return this.getElement(selector); + } +} diff --git a/cypress/support/repositories/organizations/ProjectsRepository.ts b/cypress/support/repositories/organizations/ProjectsRepository.ts new file mode 100644 index 00000000..343b958d --- /dev/null +++ b/cypress/support/repositories/organizations/ProjectsRepository.ts @@ -0,0 +1,32 @@ +export default class ProjectsRepository { + getAddBtn() { + return cy.getBySel('addNewProject'); + } + + getAddConfirm() { + return cy.getBySel('addProjectConfirm'); + } + + getName() { + return cy.get('.inputName'); + } + getGit() { + return cy.get('.inputGit'); + } + getEnv() { + return cy.get('.inputEnv'); + } + selectTarget() { + cy.get('.react-select__indicator').click({ force: true }); + cy.get('#react-select-3-option-0').click(); + } + getProjectRows() { + return cy.get('.tableRow'); + } + getDeleteBtn() { + return cy.get("[aria-label='delete']"); + } + getDeleteConfirm() { + return cy.getBySel('deleteConfirm'); + } +} diff --git a/cypress/support/repositories/organizations/UsersRepository.ts b/cypress/support/repositories/organizations/UsersRepository.ts new file mode 100644 index 00000000..b27c5e76 --- /dev/null +++ b/cypress/support/repositories/organizations/UsersRepository.ts @@ -0,0 +1,27 @@ +export default class UsersRepository { + getAddUserBtn() { + return cy.getBySel('addUser'); + } + getAddUserEmail() { + return cy.getBySel('addUserEmail'); + } + getAddUserGroup() { + cy.get('.react-select__indicator').first().click({ force: true }); + cy.get('#react-select-2-option-0').click(); + } + getAddUserRole() { + cy.get('.react-select__indicator').eq(1).click({ force: true }); + cy.get('#react-select-3-option-0').click(); + } + + getAddUserConfirm() { + return cy.getBySel('addUserConfirm'); + } + getRows() { + return cy.get('.tableRow'); + } + + getConfirmDeleteBtn() { + return cy.getBySel('confirmDeletion'); + } +} diff --git a/cypress/support/repositories/project/ProjectRepository.ts b/cypress/support/repositories/project/ProjectRepository.ts new file mode 100644 index 00000000..edd21fb4 --- /dev/null +++ b/cypress/support/repositories/project/ProjectRepository.ts @@ -0,0 +1,35 @@ +export default class ProjectRepository { + getGitUrl() { + return cy.getBySel('gitLink'); + } + getCopyButton() { + return cy.getBySel('copyButton'); + } + getCreatedField() { + return cy.getBySel('created'); + } + getBranchesField() { + return cy.getBySel('branches'); + } + getPullRequestsField() { + return cy.getBySel('pullRequests'); + } + getDevEnvsField() { + return cy.getBySel('devEnvs'); + } + getEnvBtn() { + return cy.getBySel('createEnvironment'); + } + getBranchNameInput() { + return cy.getBySel('branchName'); + } + getSubmitBtn() { + return cy.get('.btn-primary'); + } + getErrorNotification() { + return cy.get('.ant-notification-notice'); + } + getEnvRoutes() { + return cy.get('.routeLink > label'); + } +} diff --git a/cypress/support/repositories/projects/ProjectsRepository.ts b/cypress/support/repositories/projects/ProjectsRepository.ts new file mode 100644 index 00000000..7ce141e6 --- /dev/null +++ b/cypress/support/repositories/projects/ProjectsRepository.ts @@ -0,0 +1,26 @@ +export default class ProjectRepository { + getPageTitle() { + return cy.getBySel('projectsTitle'); + } + getSearchBar() { + return cy.getBySel('searchBar'); + } + + getLengthCounter() { + return cy.getBySel('projectsLength'); + } + + getProjects() { + return cy.getBySel('projects'); + } + + getNotMatched() { + return cy.getBySel('noMatch'); + } + getNoProjectsLabel() { + return cy.getBySel('noProjects'); + } + getProject() { + return cy.getBySel("project"); + } +} diff --git a/cypress/support/repositories/settings/SettingsRepository.ts b/cypress/support/repositories/settings/SettingsRepository.ts new file mode 100644 index 00000000..37513b3c --- /dev/null +++ b/cypress/support/repositories/settings/SettingsRepository.ts @@ -0,0 +1,17 @@ +export default class SettingsRepository { + getNameInput() { + return cy.getBySel('sshKeyName'); + } + + getValueInput() { + return cy.getBySel('sshKey'); + } + + getSubmitBtn() { + return cy.getBySel('sshKey').parent().next(); + } + getDeleteBtn() { + return cy.getBySel('deleteKey').first().get('button').first(); + } + +} diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json new file mode 100644 index 00000000..59fcb0c5 --- /dev/null +++ b/cypress/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "include": ["./**/*.ts", "../cypress.d.ts"], + "exclude": [], + "compilerOptions": { + "types": ["cypress", "node", "cypress-real-events"], + "lib": ["es2015", "dom"], + "isolatedModules": false, + "allowJs": true, + "noEmit": true + } +} \ No newline at end of file diff --git a/package.json b/package.json index e3466c56..baf97528 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "build-storybook": "storybook build", "serve-storybook": "storybook-server -s ./src", "format": "yarn prettier --write .", - "format-check": "yarn prettier --c ." + "format-check": "yarn prettier --c .", + "cypress:open": "cypress open", + "cypress:run": "cypress run" }, "dependencies": { "@ant-design/cssinjs": "^1.17.0", @@ -38,6 +40,7 @@ "colors": "^1.4.0", "core-js": "^3.30.1", "crypto-js": "^4.1.1", + "cypress-real-events": "^1.10.3", "d3": "^7.8.4", "date-fns": "^2.9.0", "dotenv-extended": "^2.2.0", @@ -107,6 +110,7 @@ "babel-loader": "^9.1.2", "babel-plugin-macros": "^3.1.0", "chromatic": "^6.19.5", + "cypress": "^13.3.2", "eslint-plugin-storybook": "^0.6.12", "faker": "^6.6.6", "msw": "^1.2.1", diff --git a/src/components/AddVariable/index.js b/src/components/AddVariable/index.js index 773ce9f7..9733300a 100644 --- a/src/components/AddVariable/index.js +++ b/src/components/AddVariable/index.js @@ -115,6 +115,7 @@ export const AddVariable = ({
= ({ @@ -22,6 +23,7 @@ const Button: FC = ({ children, variant, icon, + testId, }) => { const createClassName = () => { let className = `${variant ? `btn-${variant}` : 'btn'} ${disabled ? 'btn--disabled' : ''}`; @@ -42,7 +44,7 @@ const Button: FC = ({ const ButtonElement = href ? ( - {icon && } {children} + {icon && } {children} ) : ( = ({ className={createClassName()} onClick={onClick} disabled={loading || disabled} + data-cy={testId || ''} > {icon && (typeof icon === 'string' ? : icon)} diff --git a/src/components/EnvironmentVariables/index.js b/src/components/EnvironmentVariables/index.js index 256200fb..eb8c2861 100644 --- a/src/components/EnvironmentVariables/index.js +++ b/src/components/EnvironmentVariables/index.js @@ -215,6 +215,7 @@ const EnvironmentVariables = ({ environment, onVariableAdded }) => { - - + + + + + + + + +
- ) - } + ); + }; - const renderStep3 = (header) => { + const renderStep3 = header => { return (
- {header ?

Step 3: Add the webhook to your Git service

: null} + {header ? ( +

+ Step 3: Add the webhook to your Git service +

+ ) : null}
-
- { webhookURL } -
+
{webhookURL}
Copied @@ -168,121 +169,156 @@ const NewEnvironment = ({
-

Please ensure that all these steps have been completed before creating your new environment. Follow the Lagoon Docs for more details.

+

+ Please ensure that all these steps have been completed before creating your new environment. Follow the{' '} + + Lagoon Docs + {' '} + for more details. +

- ) - } + ); + }; const items = () => [ { key: '1', - label:

Step 2: Add this project's Deploy Key to your Git service.

, + label: ( +

+ Step 2: Add this project's Deploy Key to your Git service. +

+ ), children: renderStep2(), }, { key: '2', - label:

Step 3: Add the webhook to your Git service

, + label: ( +

+ Step 3: Add the webhook to your Git service +

+ ), children: renderStep3(), }, ]; return ( - -
- -
- - - console.error(e)}> - {(deployEnvironmentBranch, { loading, error, data }) => { - if (error) { - return
{error.message}
; - } - const errors = ["Skipped", "Error"] - const regex = new RegExp(errors.join("|"), "i"); - const err = regex.test(data && data.deployEnvironmentBranch); - if (data && !err) { - refresh().then(setClear).then(closeModal); - } + +
+ + + console.error(e)}> + {(deployEnvironmentBranch, { loading, error, data }) => { + if (error) { + return
{error.message}
; + } + const errors = ['Skipped', 'Error']; + const regex = new RegExp(errors.join('|'), 'i'); + const err = regex.test(data && data.deployEnvironmentBranch); + if (data && !err) { + refresh().then(setClear).then(closeModal); + } - return ( - <> - -
- Create an Environment -
-
- Step 1: Add the branch you wish to build this environment from. This branch must already exist in your git repository. -
- - toggleShowEnvType()} - /> + return ( + <> + +
+ Create an Environment +
+
+ + Step 1: Add the branch you wish to build this environment from. This branch must already + exist in your git repository. + +
+ + toggleShowEnvType()} + /> +
+
+ {environmentCount > 0 ? ( + + } + /> + + ) : ( + [renderStep2('header'), renderStep3('header')] + )} +
+
+ {showEnvType ? ( +
+ Creating{' '} + + {productionEnvironment === inputBranchName ? 'Production' : 'Development'} + {' '} + environment: {inputBranchName}
-
- { environmentCount > 0 ? - - } - /> - - : - [renderStep2("header"), renderStep3("header")] - } -
-
- { showEnvType ? -
Creating { - productionEnvironment === inputBranchName ? 'Production' : 'Development' - } environment: {inputBranchName}
- : null - } - + ) : null} + - -
-
-
- - ); - }} - - - - + + +
+ + + ); + }} + + + + ); }; -export default withLogic(NewEnvironment); +export default withLogic(NewEnvironment); \ No newline at end of file diff --git a/src/components/Organizations/AddGroupToProject/index.js b/src/components/Organizations/AddGroupToProject/index.js index 29bee614..dcba93ef 100644 --- a/src/components/Organizations/AddGroupToProject/index.js +++ b/src/components/Organizations/AddGroupToProject/index.js @@ -2,6 +2,7 @@ import React from 'react'; import { Mutation } from 'react-apollo'; import ReactSelect from 'react-select'; +import { Tooltip } from 'antd'; import Button from 'components/Button'; import Modal from 'components/Modal'; // @TODO: add this once the logic exists @@ -10,7 +11,6 @@ import gql from 'graphql-tag'; import { RoleSelect } from '../AddUserToGroup/Styles'; import { AddButtonContent, Footer, StyledNotification, StyledNotificationWrapper } from '../SharedStyles'; -import { Tooltip } from 'antd'; const ADD_GROUP_PROJECT_MUTATION = gql` mutation addProjectToGroup($groupName: String!, $projectName: String!) { @@ -44,7 +44,7 @@ export const AddGroupToProject = ({
<> - @@ -68,6 +68,7 @@ export const AddGroupToProject = ({ ({ ...base, zIndex: 9999, color: 'black', fontSize: '16px' }), @@ -89,6 +90,7 @@ export const AddGroupToProject = ({
-
@@ -117,4 +119,4 @@ export const AddGroupToProject = ({ ); }; -export default withLogic(AddGroupToProject); +export default withLogic(AddGroupToProject); \ No newline at end of file diff --git a/src/components/Organizations/AddNotificationToProject/index.js b/src/components/Organizations/AddNotificationToProject/index.js index d2a7348d..5684df65 100644 --- a/src/components/Organizations/AddNotificationToProject/index.js +++ b/src/components/Organizations/AddNotificationToProject/index.js @@ -50,7 +50,7 @@ export const AddNotificationToProject = ({
<> - @@ -107,6 +107,7 @@ export const AddNotificationToProject = ({
@@ -288,4 +291,4 @@ const Manage = ({ users = [], organization, organizationName, refetch }) => { ); }; -export default withLogic(Manage); +export default withLogic(Manage); \ No newline at end of file diff --git a/src/components/Organizations/NewGroup/index.js b/src/components/Organizations/NewGroup/index.js index 6fa44c6b..8b5d34bf 100644 --- a/src/components/Organizations/NewGroup/index.js +++ b/src/components/Organizations/NewGroup/index.js @@ -95,7 +95,7 @@ export const NewGroup = ({
<> - @@ -103,7 +103,7 @@ export const NewGroup = ({
- + console.error(e)}> {(addGroup, { called, error, data }) => { if (error) { return
{error.message}
; @@ -124,6 +124,7 @@ export const NewGroup = ({ Group Name: *{' '}

Please use (a to z) lower case, numbers and - only

@@ -110,7 +110,8 @@ const OrgNewProject = ({
@@ -275,7 +281,7 @@ const Organization = ({ organization, refetch }) => {
))} - + Manage @@ -287,4 +293,4 @@ const Organization = ({ organization, refetch }) => { ); }; -export default Organization; +export default Organization; \ No newline at end of file diff --git a/src/components/Organizations/Projects/index.js b/src/components/Organizations/Projects/index.js index 06508772..29aadf9c 100644 --- a/src/components/Organizations/Projects/index.js +++ b/src/components/Organizations/Projects/index.js @@ -120,6 +120,7 @@ const OrgProjects = ({ projects = [], organizationId, organizationName, refresh, return ( @@ -235,4 +236,4 @@ const Users = ({ users = [], organization, organizationId, organizationName, ref ); }; -export default withLogic(Users); +export default withLogic(Users); \ No newline at end of file diff --git a/src/components/ProjectDetailsSidebar/index.js b/src/components/ProjectDetailsSidebar/index.js index dc0de948..7efc24ed 100644 --- a/src/components/ProjectDetailsSidebar/index.js +++ b/src/components/ProjectDetailsSidebar/index.js @@ -28,7 +28,7 @@ const ProjectDetailsSidebar = ({ project }) => {
-
{moment.utc(project.created).local().format('DD MMM YYYY, HH:mm:ss (Z)')}
+
{moment.utc(project.created).local().format('DD MMM YYYY, HH:mm:ss (Z)')}
{gitLink ? ( @@ -36,7 +36,7 @@ const ProjectDetailsSidebar = ({ project }) => {
@@ -53,6 +53,7 @@ const ProjectDetailsSidebar = ({ project }) => { Copied { setCopied(true); @@ -70,7 +71,7 @@ const ProjectDetailsSidebar = ({ project }) => {
-
{project.branches}
+
{project.branches}
)} @@ -78,14 +79,14 @@ const ProjectDetailsSidebar = ({ project }) => {
-
{project.pullrequests}
+
{project.pullrequests}
)}
-
+
{developEnvironmentCount} of {R.defaultTo('unlimited', project.developmentEnvironmentsLimit)}
diff --git a/src/components/ProjectNavTabs/index.js b/src/components/ProjectNavTabs/index.js index a507890b..e6db197b 100644 --- a/src/components/ProjectNavTabs/index.js +++ b/src/components/ProjectNavTabs/index.js @@ -21,7 +21,7 @@ const ProjectNavTabs = ({ activeTab, project }) => { {publicRuntimeConfig.LAGOON_UI_VIEW_ENV_VARIABLES == null && ( -
  • +
  • Variables diff --git a/src/components/ProjectVariables/index.js b/src/components/ProjectVariables/index.js index edf025f7..6ce70f4e 100644 --- a/src/components/ProjectVariables/index.js +++ b/src/components/ProjectVariables/index.js @@ -1,35 +1,34 @@ -import React, { Fragment, useState } from "react"; -import "bootstrap/dist/css/bootstrap.min.css"; -import ProjectByNameWithEnvVarsValueQuery from "../../lib/query/ProjectByNameWithEnvVarsValue"; -import { useLazyQuery } from "@apollo/react-hooks"; -import AddVariable from "../AddVariable"; -import ViewVariableValue from "../ViewVariableValue"; -import Button from "react-bootstrap/Button"; -import Btn from 'components/Button' -import Collapse from "react-bootstrap/Collapse"; +import React, { Fragment, useState } from 'react'; +import Button from 'react-bootstrap/Button'; +import Collapse from 'react-bootstrap/Collapse'; + +import Image from 'next/image'; + +import { LoadingOutlined } from '@ant-design/icons'; +import { useLazyQuery } from '@apollo/react-hooks'; +import { Tag } from 'antd'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import Alert from 'components/Alert'; +import Btn from 'components/Button'; import withLogic from 'components/DeleteConfirm/logic'; -import Image from "next/image"; -import show from "../../static/images/show.svg"; -import hide from "../../static/images/hide.svg"; -import { - StyledProjectVariablesDetails, - StyledProjectVariableTable, - VariableActions, -} from "./StyledProjectVariables"; -import DeleteVariable from "components/DeleteVariable"; -import Alert from 'components/Alert' -import {Tag} from "antd"; -import {LoadingOutlined} from "@ant-design/icons"; -import {DeleteVariableButton} from "../DeleteVariable/StyledDeleteVariable"; +import DeleteVariable from 'components/DeleteVariable'; + +import ProjectByNameWithEnvVarsValueQuery from '../../lib/query/ProjectByNameWithEnvVarsValue'; +import hide from '../../static/images/hide.svg'; +import show from '../../static/images/show.svg'; +import AddVariable from '../AddVariable'; +import { DeleteVariableButton } from '../DeleteVariable/StyledDeleteVariable'; +import ViewVariableValue from '../ViewVariableValue'; +import { StyledProjectVariableTable, StyledProjectVariablesDetails, VariableActions } from './StyledProjectVariables'; /** * Displays the projects variable information. */ -const hashValue = (value) => { - let hashedVal = "●"; +const hashValue = value => { + let hashedVal = '●'; for (let l = 0; l < value.length; l++) { - hashedVal += "●"; + hashedVal += '●'; } return hashedVal; }; @@ -40,9 +39,9 @@ const ProjectVariables = ({ project, onVariableAdded, closeModal }) => { const [valueState, setValueState] = useState(initValueState); const [openPrjVars, setOpenPrjVars] = useState(false); - const [updateVarValue, setUpdateVarValue ] = useState(''); - const [updateVarName, setUpdateVarName ] = useState(''); - const [updateVarScope, setUpdateVarScope ] = useState(''); + const [updateVarValue, setUpdateVarValue] = useState(''); + const [updateVarName, setUpdateVarName] = useState(''); + const [updateVarScope, setUpdateVarScope] = useState(''); const [projectErrorAlert, setProjectErrorAlert] = useState(false); const [action, setAction] = useState(''); @@ -50,18 +49,17 @@ const ProjectVariables = ({ project, onVariableAdded, closeModal }) => { setProjectErrorAlert(false); }; - - const [ - getPrjEnvVarValues, - { loading: prjLoading, error: prjError, data: prjEnvValues }, - ] = useLazyQuery(ProjectByNameWithEnvVarsValueQuery, { - variables: { name: project.name }, - onError: () => { - setOpenPrjVars(false); - setValueState(initValueState); - setProjectErrorAlert(true); + const [getPrjEnvVarValues, { loading: prjLoading, error: prjError, data: prjEnvValues }] = useLazyQuery( + ProjectByNameWithEnvVarsValueQuery, + { + variables: { name: project.name }, + onError: () => { + setOpenPrjVars(false); + setValueState(initValueState); + setProjectErrorAlert(true); + }, } - }); + ); if (prjEnvValues) { displayVars = prjEnvValues.project.envVariables; @@ -69,58 +67,55 @@ const ProjectVariables = ({ project, onVariableAdded, closeModal }) => { if (prjError) console.error(prjError); - const valuesShow = (index) => { - setValueState((valueState) => - valueState.map((el, i) => (i === index ? true : el)) - ); + const valuesShow = index => { + setValueState(valueState => valueState.map((el, i) => (i === index ? true : el))); }; - const valuesHide = (index) => { - setValueState((valueState) => - valueState.map((el, i) => (i === index ? false : el)) - ); + const valuesHide = index => { + setValueState(valueState => valueState.map((el, i) => (i === index ? false : el))); }; const showVarValue = () => { getPrjEnvVarValues(); setOpenPrjVars(!openPrjVars); setValueState(initValueState); - setAction("view") + setAction('view'); }; const setUpdateValue = (rowValue, rowName, rowScope) => { setUpdateVarValue(rowValue); - setUpdateVarName(rowName) - setUpdateVarScope(rowScope) - } + setUpdateVarName(rowName); + setUpdateVarScope(rowScope); + }; const permissionCheck = (action, index = 0) => { setOpenPrjVars(false); setAction(action); valuesShow(index); getPrjEnvVarValues(); - } + }; return ( {displayVars.length === 0 ? ( <> - { - projectErrorAlert && ( - - ) - } + {projectErrorAlert && ( + + )}
    - : + + ) : ( { setProjectErrorAlert={setProjectErrorAlert} action="add" /> - } + )}
    -
    -
    +
    +
    -
    +
    ) : ( <> - { - projectErrorAlert && ( - - ) - } + {projectErrorAlert && ( + + )}
    - : + + ) : ( { refresh={onVariableAdded} action="add" /> - } + )}
    -
    +
    @@ -196,15 +189,11 @@ const ProjectVariables = ({ project, onVariableAdded, closeModal }) => {
    -
    +
    {displayVars.map((projEnvVar, index) => { return ( -
    +
    {projEnvVar.name}
    {projEnvVar.scope}
    {prjLoading ? ( @@ -216,84 +205,78 @@ const ProjectVariables = ({ project, onVariableAdded, closeModal }) => { ) : projEnvVar.value !== undefined ? (
    - {projEnvVar.value.length === 0 && - valueState[index] ? ( + {projEnvVar.value.length === 0 && valueState[index] ? (
    Empty valuesHide(index)}> - +
    - ) : projEnvVar.value.length <= 100 && - !valueState[index] ? ( + ) : projEnvVar.value.length <= 100 && !valueState[index] ? (
    {hashValue(projEnvVar.value).substring(0, 25)} valuesShow(index)}>
    - ) : projEnvVar.value.length <= 100 && - valueState[index] ? ( + ) : projEnvVar.value.length <= 100 && valueState[index] ? (
    {projEnvVar.value} valuesHide(index)}>
    - ) : projEnvVar.value.length >= 100 && - !valueState[index] ? ( + ) : projEnvVar.value.length >= 100 && !valueState[index] ? (
    {hashValue(projEnvVar.value).substring(0, 25)} valuesShow(index)}>
    - ) : projEnvVar.value.length >= 100 && - valueState[index] ? ( + ) : projEnvVar.value.length >= 100 && valueState[index] ? (
    ${projEnvVar.value.substring(0, 25)}.. valuesHide(index)}>
    ) : ( - `${hashValue(projEnvVar.value).substring( - 0, - 25 - )}...` + `${hashValue(projEnvVar.value).substring(0, 25)}...` )} {projEnvVar.value.length > 100 ? ( - + ) : ( - "" + '' )}
    @@ -305,8 +288,8 @@ const ProjectVariables = ({ project, onVariableAdded, closeModal }) => {
    -
    -
    @@ -359,4 +344,4 @@ const ProjectVariables = ({ project, onVariableAdded, closeModal }) => { ); }; -export default withLogic(ProjectVariables); +export default withLogic(ProjectVariables); \ No newline at end of file diff --git a/src/components/Projects/index.js b/src/components/Projects/index.js index 16668755..a754b410 100644 --- a/src/components/Projects/index.js +++ b/src/components/Projects/index.js @@ -1,98 +1,174 @@ -import React, { useEffect, useRef, useState } from 'react'; -import Highlighter from 'react-highlight-words'; +import React, { useState } from 'react'; +import { Mutation } from 'react-apollo'; -import Box from 'components/Box'; +import { DeleteOutlined, EditOutlined } from '@ant-design/icons'; +import { Tooltip } from 'antd'; +import Button from 'components/Button'; +import Modal from 'components/Modal'; +import ProjectGroupLink from 'components/link/Organizations/ProjectGroup'; import ProjectLink from 'components/link/Project'; +import gql from 'graphql-tag'; -import { - ProjectsHeader, - ProjectsPage, - SearchInput, - StyledCustomer, - StyledProject, - StyledRoute, -} from './StyledProjects'; +import { IconDashboard } from '../CustomIcons/OrganizationIcons'; +import { DeleteButton } from '../Groups/Styles'; +import NewProject from '../NewProject'; +import PaginatedTable from '../PaginatedTable/PaginatedTable'; +import { Footer, RemoveModalHeader, RemoveModalParagraph, TableActions, Tag } from '../SharedStyles'; +import { ProjectDashboard, StyledOrgProjects } from './Styles'; + +const DELETE_PROJECT = gql` + mutation deleteProject($project: String!) { + deleteProject(input: { project: $project }) + } +`; /** * The primary list of projects. */ -const Projects = ({ projects = [], initialSearch }) => { - const [searchInput, setSearchInput] = useState(initialSearch); +const OrgProjects = ({ projects = [], organizationId, organizationName, refresh, deployTargets }) => { + const [modalState, setModalState] = useState({ + open: false, + current: null, + }); - const searchInputRef = useRef(null); + const Columns = [ + { + width: '30%', + key: 'name', + render: project => { + return ( + <> + + {project.name} + + + ); + }, + }, + { + width: '30%', + key: 'groups', + render: project => { + return ( +
    + Groups: {project.groupCount} +
    + ); + }, + }, + { + width: '40%', + key: 'actions', + render: function (project) { + return ( + + + + + + + + - useEffect(() => { - if (initialSearch && searchInputRef.current) { - searchInputRef.current.focus(); - } - }, []); - const filteredProjects = projects.filter(key => { - const sortByName = key.name.toLowerCase().includes(searchInput.trim().toLowerCase()); - let sortByUrl = ''; - if (key.environments[0] !== void 0) { - if (key.environments[0].route !== null) { - sortByUrl = key.environments[0].route.toLowerCase().includes(searchInput.trim().toLowerCase()); - } - } - return ['name', 'environments', '__typename'].includes(key) ? false : (true && sortByName) || sortByUrl; - }); + + <> + + + + + + <> + + { + setModalState({ open: true, current: project.name }); + }} + /> + + + setModalState({ open: false, current: null })} + > + Are you sure? + + This action will delete project {project.name} from Lagoon and the organization. + + +
    + console.error(e)}> + {(deleteProject, { called, error, data }) => { + if (error) { + return
    {error.message}
    ; + } + if (data) { + refresh().then(() => setModalState({ open: false, current: null })); + return Continue; + } + + return ( + + ); + }} +
    + +
    +
    + +
    + ); + }, + }, + ]; return ( - - - - - setSearchInput(e.target.value)} - placeholder="Type to search" - disabled={projects.length === 0} - /> - - {!projects.length && ( - -
    -

    No projects

    -
    -
    - )} - {searchInput && !filteredProjects.length && ( - -
    -

    No projects matching "{searchInput}"

    -
    -
    - )} - {filteredProjects.map(project => ( - - - -

    - -

    - - {project.environments.map((environment, index) => ( - - ))} - -
    - -
    -
    - ))} -
    + + + { + return { label: deploytarget.name, value: deploytarget.id }; + })} + refresh={refresh} + /> + ); }; -export default Projects; +export default OrgProjects; \ No newline at end of file diff --git a/src/components/SshKeys/AddSshKey.js b/src/components/SshKeys/AddSshKey.js index 8dd1c548..52a48445 100644 --- a/src/components/SshKeys/AddSshKey.js +++ b/src/components/SshKeys/AddSshKey.js @@ -3,9 +3,7 @@ import { Mutation } from 'react-apollo'; import Button from 'components/Button'; import Me from 'lib/query/Me'; -import { bp, color, fontSize } from 'lib/variables'; -import { none } from 'ramda'; -import css from 'styled-jsx/css'; + import AddSshKeyMutation from '../../lib/mutation/AddSshKey'; @@ -57,6 +55,7 @@ const AddSshKey = ({ me: { id, email } }) => {
    {