diff --git a/packages/desktop-gui/cypress/fixtures/organizations.json b/packages/desktop-gui/cypress/fixtures/organizations.json index 729350b967..44615afa13 100644 --- a/packages/desktop-gui/cypress/fixtures/organizations.json +++ b/packages/desktop-gui/cypress/fixtures/organizations.json @@ -1,14 +1,14 @@ [ - { - "id": "777", - "name": "Acme Developers", - "default": false - }, { "id": "000", "name": "Jane Lane", "default": true }, + { + "id": "777", + "name": "Acme Developers", + "default": false + }, { "id": "999", "name": "Osato Devs", diff --git a/packages/desktop-gui/cypress/integration/runs_list_spec.js b/packages/desktop-gui/cypress/integration/runs_list_spec.js index 6a9eca105c..45e6e31bfa 100644 --- a/packages/desktop-gui/cypress/integration/runs_list_spec.js +++ b/packages/desktop-gui/cypress/integration/runs_list_spec.js @@ -715,8 +715,10 @@ describe('Runs List', function () { it('clears message after setting up to record', function () { cy.contains('.btn', 'Set up project').click() - cy.get('.modal-body') - .contains('.btn', 'Me').click() + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Your personal organization').click() cy.get('.privacy-radio').find('input').last().check() cy.get('.modal-body') @@ -784,8 +786,10 @@ describe('Runs List', function () { it('clears message after setting up CI', function () { cy.contains('.btn', 'Set up a new project').click() - cy.get('.modal-body') - .contains('.btn', 'Me').click() + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Your personal organization').click() cy.get('.privacy-radio').find('input').last().check() cy.get('.modal-body') diff --git a/packages/desktop-gui/cypress/integration/setup_project_modal_spec.js b/packages/desktop-gui/cypress/integration/setup_project_modal_spec.js index 83f1f0a226..1489c7efa6 100644 --- a/packages/desktop-gui/cypress/integration/setup_project_modal_spec.js +++ b/packages/desktop-gui/cypress/integration/setup_project_modal_spec.js @@ -80,27 +80,43 @@ describe('Set Up Project', function () { .should('have.value', 'New Project Here') }) - describe('default owner', function () { - it('has no owner selected by default', function () { - cy.get('#me').should('not.be.selected') - - cy.get('#org').should('not.be.selected') - }) - - it('org docs are linked', () => { - cy.contains('label', 'Who should own this') - .find('a').click().then(function () { - expect(this.ipc.externalOpen).to.be.calledWith('https://on.cypress.io/what-are-organizations') - }) + it('org docs are linked', () => { + cy.contains('label', 'Who should own this') + .find('a').click().then(function () { + expect(this.ipc.externalOpen).to.be.calledWith('https://on.cypress.io/what-are-organizations') }) }) + }) - describe('selecting me as owner', function () { + describe('loading behavior', function () { + beforeEach(function () { + cy.get('.btn').contains('Set up project').click() + }) + + it('calls getOrgs', function () { + expect(this.ipc.getOrgs).to.be.calledOnce + }) + + it('displays loading view before orgs load', function () { + cy.get('.loader').then(function () { + this.getOrgs.resolve(this.orgs) + }) + + cy.get('.loader').should('not.exist') + }) + }) + + describe('selecting an org', function () { + describe('selecting Personal org', function () { beforeEach(function () { - cy.get('.privacy-radio').should('not.be.visible') + this.getOrgs.resolve(this.orgs) + cy.get('.btn').contains('Set up project').click() cy.get('.modal-content') - .contains('.btn', 'Me').click() + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Your personal organization').click() }) it('access docs are linked', () => { @@ -115,25 +131,29 @@ describe('Set Up Project', function () { .find('input').should('not.be.checked') }) }) - }) - describe('selecting an org', function () { context('with orgs', function () { beforeEach(function () { this.getOrgs.resolve(this.orgs) cy.get('.btn').contains('Set up project').click() cy.get('.modal-content') - .contains('.btn', 'An Organization').click() }) it('lists organizations to assign to project', function () { - cy.get('#organizations-select').find('option') + cy.get('.empty-select-orgs').should('not.be.visible') + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') .should('have.length', this.orgs.length) }) - it('selects none by default', () => { - cy.get('#organizations-select').should('have.value', '') + it('selects personal org by default', function () { + cy.get('.organizations-select').contains( + 'Your personal organization' + ) + + cy.get('.privacy-radio').should('be.visible') }) it('opens external link on click of manage', () => { @@ -143,23 +163,49 @@ describe('Set Up Project', function () { }) it('displays public & private radios on select', function () { - cy.get('.privacy-radio').should('not.be.visible') - cy.get('select').select('Acme Developers') + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Acme Developers').click() cy.get('.privacy-radio').should('be.visible') .find('input').should('not.be.checked') }) + }) - it('clears selections when switching back to Me', function () { - cy.get('select').select('Acme Developers') - cy.get('.privacy-radio') - .find('input').first().check() + context('orgs with no default org', function () { + beforeEach(function () { + this.getOrgs.resolve(Cypress._.filter(this.orgs, { 'default': false })) + cy.get('.btn').contains('Set up project').click() + }) - cy.get('.btn').contains('Me').click() - cy.get('.privacy-radio').find('input').should('not.be.checked') - cy.get('.btn').contains('An Organization').click() + it('lists organizations to assign to project', function () { + cy.get('.empty-select-orgs').should('not.be.visible') + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + // do not count the default org we removed + .should('have.length', this.orgs.length - 1) + }) - cy.get('#organizations-select').should('have.value', '') + it('selects first org by default', function () { + cy.get('.organizations-select').contains(this.orgs[1].name) + }) + + it('opens external link on click of manage', () => { + cy.get('.manage-orgs-btn').click().then(function () { + expect(this.ipc.externalOpen).to.be.calledWith('https://on.cypress.io/dashboard/organizations') + }) + }) + + it('displays public & private radios on select', function () { + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Acme Developers').click() + + cy.get('.privacy-radio').should('be.visible') + .find('input').should('not.be.checked') }) }) @@ -167,13 +213,12 @@ describe('Set Up Project', function () { beforeEach(function () { this.getOrgs.resolve([]) cy.get('.btn').contains('Set up project').click() - - cy.get('.modal-content') - .contains('.btn', 'An Organization').click() }) it('displays empty message', () => { cy.get('.empty-select-orgs').should('be.visible') + cy.get('.organizations-select').should('not.be.visible') + cy.get('.privacy-radio').should('not.be.visible') }) it('opens dashboard organizations when \'create org\' is clicked', () => { @@ -183,7 +228,7 @@ describe('Set Up Project', function () { }) }) - context('without only default org', function () { + context('with only default org', function () { beforeEach(function () { this.getOrgs.resolve([{ 'id': '000', @@ -192,19 +237,13 @@ describe('Set Up Project', function () { }]) cy.get('.btn').contains('Set up project').click() - cy.get('.modal-content') - .contains('.btn', 'An Organization').click() }) - it('displays empty message', () => { - cy.get('.empty-select-orgs').should('be.visible') - }) - - it('opens dashboard organizations when \'create org\' is clicked', () => { - cy.contains('Create organization').click().then(function () { - expect(this.ipc.externalOpen).to.be.calledWith('https://on.cypress.io/dashboard/organizations') - }) + it('displays in dropdown', () => { + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option').should('have.length', 1) }) }) @@ -213,9 +252,6 @@ describe('Set Up Project', function () { cy.clock() this.getOrgs.resolve(this.orgs) cy.get('.btn').contains('Set up project').click() - - cy.get('.modal-content') - .contains('.btn', 'An Organization').click() }) it('polls for orgs twice in 10+sec on click of org', function () { @@ -226,12 +262,14 @@ describe('Set Up Project', function () { it('updates org name on list on successful poll', function () { this.name = 'Foo Bar Devs' - this.orgs[0].name = this.name + this.orgs[1].name = this.name this.getOrgsAgain = this.ipc.getOrgs.onCall(2).resolves(this.orgs) cy.tick(11000) - cy.get('#organizations-select').find('option') + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') .contains(this.name) }) @@ -246,7 +284,9 @@ describe('Set Up Project', function () { cy.tick(11000) - cy.get('#organizations-select').find('option') + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') .should('have.length', this.orgs.length) }) }) @@ -256,8 +296,10 @@ describe('Set Up Project', function () { beforeEach(function () { this.getOrgs.resolve(this.orgs) cy.contains('.btn', 'Set up project').click() - cy.get('.modal-body') - .contains('.btn', 'Me').click() + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Your personal organization').click() cy.get('.privacy-radio').find('input').last().check() @@ -292,11 +334,12 @@ describe('Set Up Project', function () { }) it('sends project name, org id, and public flag to ipc event', function () { - cy.get('.modal-body') - .contains('.btn', 'An Organization').click() - cy.get('#projectName').clear().type('New Project') - cy.get('select').select('Acme Developers') + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Acme Developers').click() + cy.get('.privacy-radio').find('input').first().check() cy.get('.modal-body') @@ -312,10 +355,11 @@ describe('Set Up Project', function () { context('org/public', function () { beforeEach(function () { - cy.get('.modal-body') - .contains('.btn', 'An Organization').click() + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Acme Developers').click() - cy.get('select').select('Acme Developers') cy.get('.privacy-radio').find('input').first().check() cy.get('.modal-body') @@ -333,11 +377,12 @@ describe('Set Up Project', function () { context('me/private', function () { beforeEach(function () { - cy.get('.modal-body') - .contains('.btn', 'Me').click() + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Your personal organization').click() cy.get('.privacy-radio').find('input').last().check() - cy.get('.modal-body') .contains('.btn', 'Set up project').click() }) @@ -353,11 +398,12 @@ describe('Set Up Project', function () { context('me/public', function () { beforeEach(function () { - cy.get('.modal-body') - .contains('.btn', 'Me').click() + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Your personal organization').click() cy.get('.privacy-radio').find('input').first().check() - cy.get('.modal-body') .contains('.btn', 'Set up project').click() }) @@ -392,8 +438,10 @@ describe('Set Up Project', function () { beforeEach(function () { this.getOrgs.resolve(this.orgs) cy.contains('.btn', 'Set up project').click() - cy.get('.modal-body') - .contains('.btn', 'Me').click() + cy.get('.organizations-select__dropdown-indicator').click() + cy.get('.organizations-select__menu').should('be.visible') + cy.get('.organizations-select__option') + .contains('Your personal organization').click() cy.get('.privacy-radio').find('input').last().check() diff --git a/packages/desktop-gui/package.json b/packages/desktop-gui/package.json index b333abf7a4..67e998b54d 100644 --- a/packages/desktop-gui/package.json +++ b/packages/desktop-gui/package.json @@ -17,7 +17,7 @@ "watch": "npm run build -- --watch --progress" }, "devDependencies": { - "@babel/polyfill": "^7.7.0", + "@babel/polyfill": "7.7.0", "@cypress/icons": "0.7.0", "@cypress/json-schemas": "5.33.0", "@cypress/react-tooltip": "0.5.3", @@ -41,8 +41,9 @@ "react": "16.8.6", "react-bootstrap-modal": "4.2.0", "react-dom": "16.8.6", - "react-inspector": "^4.0.0", + "react-inspector": "4.0.0", "react-loader": "2.4.5", + "react-select": "3.0.8", "webpack": "4.35.3", "webpack-cli": "3.3.2" }, diff --git a/packages/desktop-gui/src/runs/runs.scss b/packages/desktop-gui/src/runs/runs.scss index 3a3b941970..883372e4e0 100644 --- a/packages/desktop-gui/src/runs/runs.scss +++ b/packages/desktop-gui/src/runs/runs.scss @@ -322,6 +322,15 @@ } .setup-project-modal { + form { + overflow: visible; + } + + .loader { + position: relative; + min-height: 181px; + } + a>i { color: #999; cursor: pointer; @@ -345,34 +354,14 @@ } .owner-parts { - overflow: auto; clear: both; } - .owner-part-one { - float: left; - width: 42%; - margin-right: 1%; - } - - .owner-part-two { - float: left; - width: 57%; - } - - .radio + .radio { - margin-bottom: 0; - } - .form-horizontal { background: none; margin: 0; padding: 0; border: 0; - - .form-group { - overflow: auto; - } } .help-block { @@ -390,22 +379,10 @@ border-color: #dadada !important; } - .btn-group .btn { - padding: 4px 12px; - - i { - font-size: 12px; - position: relative; - top: -1px; - margin-right: 2px; - } - } - .manage-orgs-btn { padding: 0; font-size: 12px; - margin-top: 20px; - line-height: 30px; + line-height: 20px; margin-left: 10px; } @@ -415,7 +392,7 @@ } .well.empty-select-orgs { - margin-top: 20px; + margin-top: 10px; a.btn.btn-link { padding: 0; @@ -429,6 +406,7 @@ .user-avatar { position: relative; top: -2px; + margin-right: 5px; } .control-label { @@ -443,7 +421,7 @@ input[type='text'].form-control { padding: 3px 6px; - height: 28px; + height: 38px; } .privacy-radio { @@ -469,6 +447,8 @@ .actions.form-group { margin-bottom: 0; + display: flex; + flex-direction: row-reverse; button { position: relative; @@ -478,12 +458,13 @@ .text-danger { margin-bottom: 0; } - .alert-danger { - margin: 0 0 1em; + pre.alert-danger { + margin: 1em 0; padding: 5px; max-height: 200px; overflow: auto; text-align: left; + border-radius: 5px; } } @@ -531,11 +512,9 @@ } .select-orgs { - overflow: auto; - select { float: left; width: 205px; - margin-top: 20px; + margin-top: 10px; } } diff --git a/packages/desktop-gui/src/runs/setup-project-modal.jsx b/packages/desktop-gui/src/runs/setup-project-modal.jsx index e5793d9c4f..e8c612ac54 100644 --- a/packages/desktop-gui/src/runs/setup-project-modal.jsx +++ b/packages/desktop-gui/src/runs/setup-project-modal.jsx @@ -5,10 +5,11 @@ import PropTypes from 'prop-types' import { observer } from 'mobx-react' import BootstrapModal from 'react-bootstrap-modal' import Loader from 'react-loader' +import Select from 'react-select' +import { gravatarUrl } from '../lib/utils' import authStore from '../auth/auth-store' import ipc from '../lib/ipc' -import { gravatarUrl } from '../lib/utils' import orgsStore from '../organizations/organizations-store' import orgsApi from '../organizations/organizations-api' @@ -26,8 +27,7 @@ class SetupProject extends Component { error: null, projectName: this.props.project.displayName, public: null, - owner: null, - orgId: null, + selectedOrg: {}, showNameMissingError: false, isSubmitting: false, } @@ -75,10 +75,6 @@ class SetupProject extends Component { return null } - if (!orgsStore.isLoaded) { - this._loading() - } - return (
You don't have any organizations yet.
-Organizations can help you manage projects, including billing.
- -You don't have any organizations yet.
+Organizations can help you manage projects, including billing.
+ +