From f767e712eb01f6b49f3eae7dc98e6ca58e0aa9bb Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 25 Jul 2018 15:52:23 +0200 Subject: [PATCH] SONAR-11037 SONAR-11038 Show list of personal public repositories and allow to provision project * SONAR-11043 List repositories with their types * SONAR-11046 Check and disable already linked repositories * SONAR-11044 Provision project and redirect to project dashboard * SONAR-11048 Reload repositories after create failure * SONAR-11038 Use new project create page everywhere on SonarCloud --- .../src/main/js/api/alm-integration.ts | 12 ++- .../main/js/app/components/StartupModal.tsx | 16 ++-- .../src/main/js/app/styles/init/links.css | 6 +- server/sonar-web/src/main/js/app/types.ts | 7 ++ .../components/NoFavoriteProjects.tsx | 16 ++-- .../NoFavoriteProjects-test.tsx.snap | 6 +- .../src/main/js/apps/tutorials/Onboarding.tsx | 23 +---- .../tutorials/__tests__/Onboarding-test.tsx | 5 +- .../__snapshots__/Onboarding-test.tsx.snap | 6 +- .../AlmRepositoryItem.tsx | 65 +++++++++++++ .../AutoProjectCreate.tsx | 93 +++++++++++++++++-- .../CreateProjectOnboarding.tsx | 5 +- .../__tests__/AlmRepositoryItem-test.tsx | 66 +++++++++++++ .../__tests__/AutoProjectCreate-test.tsx | 37 +++++++- .../AlmRepositoryItem-test.tsx.snap | 90 ++++++++++++++++++ .../AutoProjectCreate-test.tsx.snap | 74 +++++++++++++++ .../CreateProjectOnboarding-test.tsx.snap | 1 + .../src/main/js/apps/tutorials/routes.ts | 4 +- .../resources/org/sonar/l10n/core.properties | 1 + 19 files changed, 468 insertions(+), 65 deletions(-) create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/AlmRepositoryItem.tsx create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/AlmRepositoryItem-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/tutorials/createProjectOnboarding/__tests__/__snapshots__/AlmRepositoryItem-test.tsx.snap diff --git a/server/sonar-web/src/main/js/api/alm-integration.ts b/server/sonar-web/src/main/js/api/alm-integration.ts index 0232632ccc2..798f16e6d89 100644 --- a/server/sonar-web/src/main/js/api/alm-integration.ts +++ b/server/sonar-web/src/main/js/api/alm-integration.ts @@ -17,14 +17,20 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { getJSON } from '../helpers/request'; +import { getJSON, postJSON } from '../helpers/request'; +import { AlmRepository } from '../app/types'; import throwGlobalError from '../app/utils/throwGlobalError'; export function getRepositories(): Promise<{ - installation: { + almIntegration: { + installed: boolean; installationUrl: string; - enabled: boolean; }; + repositories: AlmRepository[]; }> { return getJSON('/api/alm_integration/list_repositories').catch(throwGlobalError); } + +export function provisionProject(data: { repositories: string[] }) { + return postJSON('api/alm_integration/provision_projects', data).catch(throwGlobalError); +} diff --git a/server/sonar-web/src/main/js/app/components/StartupModal.tsx b/server/sonar-web/src/main/js/app/components/StartupModal.tsx index c49a77d2052..8a7e7e2f444 100644 --- a/server/sonar-web/src/main/js/app/components/StartupModal.tsx +++ b/server/sonar-web/src/main/js/app/components/StartupModal.tsx @@ -99,13 +99,11 @@ export class StartupModal extends React.PureComponent { this.tryAutoOpenLicense().catch(this.tryAutoOpenOnboarding); } - closeOnboarding = (doSkipOnboarding = true) => { + closeOnboarding = () => { this.setState(state => { if (state.modal !== ModalKey.license) { - if (doSkipOnboarding) { - skipOnboarding(); - this.props.skipOnboardingAction(); - } + skipOnboarding(); + this.props.skipOnboardingAction(); return { automatic: false, modal: undefined }; } return undefined; @@ -135,7 +133,12 @@ export class StartupModal extends React.PureComponent { }; openProjectOnboarding = () => { - this.setState({ modal: ModalKey.projectOnboarding }); + if (isSonarCloud()) { + this.setState({ automatic: false, modal: undefined }); + this.context.router.push(`/onboarding`); + } else { + this.setState({ modal: ModalKey.projectOnboarding }); + } }; openTeamOnboarding = () => { @@ -189,6 +192,7 @@ export class StartupModal extends React.PureComponent { )} diff --git a/server/sonar-web/src/main/js/app/styles/init/links.css b/server/sonar-web/src/main/js/app/styles/init/links.css index bc4b5507a76..20edfe9dce6 100644 --- a/server/sonar-web/src/main/js/app/styles/init/links.css +++ b/server/sonar-web/src/main/js/app/styles/init/links.css @@ -73,10 +73,8 @@ a:focus { border-bottom: none; } -.link-checkbox.disabled { - cursor: not-allowed; -} - +.link-checkbox.disabled, +.link-checkbox.disabled:hover, .link-checkbox.disabled label { color: var(--secondFontColor); cursor: not-allowed; diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 4f1b00c3f76..ea3be59fc5c 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -21,6 +21,13 @@ export type Omit = Pick>; // Type ordered alphabetically to prevent merge conflicts +export interface AlmRepository { + label: string; + installationKey: string; + linkedProjectKey?: string; + linkedProjectName?: string; +} + export interface AppState { adminPages?: Extension[]; authenticationError?: boolean; diff --git a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx index c5727397933..271cf360e29 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/NoFavoriteProjects.tsx @@ -22,13 +22,14 @@ import { Link } from 'react-router'; import { connect } from 'react-redux'; import * as PropTypes from 'prop-types'; import { sortBy } from 'lodash'; -import { Organization } from '../../../app/types'; import DropdownIcon from '../../../components/icons-components/DropdownIcon'; import Dropdown from '../../../components/controls/Dropdown'; -import { getMyOrganizations } from '../../../store/rootReducer'; import OrganizationListItem from '../../../components/ui/OrganizationListItem'; -import { translate } from '../../../helpers/l10n'; +import { Button } from '../../../components/ui/buttons'; +import { getMyOrganizations } from '../../../store/rootReducer'; import { isSonarCloud } from '../../../helpers/system'; +import { Organization } from '../../../app/types'; +import { translate } from '../../../helpers/l10n'; interface StateProps { organizations: Organization[]; @@ -39,9 +40,7 @@ export class NoFavoriteProjects extends React.PureComponent { openProjectOnboarding: PropTypes.func }; - onAnalyzeProjectClick = (event: React.SyntheticEvent) => { - event.preventDefault(); - event.currentTarget.blur(); + onAnalyzeProjectClick = () => { this.context.openProjectOnboarding(); }; @@ -54,11 +53,12 @@ export class NoFavoriteProjects extends React.PureComponent {

{translate('projects.no_favorite_projects.how_to_add_projects')}

- + + - provisioning.create_new_project - + void; + onClose: () => void; onOpenOrganizationOnboarding: () => void; + onOpenProjectOnboarding: () => void; onOpenTeamOnboarding: () => void; } @@ -44,25 +44,12 @@ interface StateProps { type Props = OwnProps & StateProps; export class Onboarding extends React.PureComponent { - static contextTypes = { - router: PropTypes.object - }; - componentDidMount() { if (!isLoggedIn(this.props.currentUser)) { handleRequiredAuthentication(); } } - openProjectOnboarding = () => { - this.props.onClose(false); - this.context.router.push('/onboarding'); - }; - - onFinish = () => { - this.props.onClose(true); - }; - render() { if (!isLoggedIn(this.props.currentUser)) { return null; @@ -73,14 +60,14 @@ export class Onboarding extends React.PureComponent {

{translate('onboarding.header')}

{translate('onboarding.header.description')}

-
- + {translate('not_now')}

{translate('onboarding.footer')}

diff --git a/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx index d0350d43cc0..9bab36759a5 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx +++ b/server/sonar-web/src/main/js/apps/tutorials/__tests__/Onboarding-test.tsx @@ -29,6 +29,7 @@ it('renders correctly', () => { currentUser={{ isLoggedIn: true }} onClose={jest.fn()} onOpenOrganizationOnboarding={jest.fn()} + onOpenProjectOnboarding={jest.fn()} onOpenTeamOnboarding={jest.fn()} /> ) @@ -38,6 +39,7 @@ it('renders correctly', () => { it('should correctly open the different tutorials', () => { const onClose = jest.fn(); const onOpenOrganizationOnboarding = jest.fn(); + const onOpenProjectOnboarding = jest.fn(); const onOpenTeamOnboarding = jest.fn(); const push = jest.fn(); const wrapper = shallow( @@ -45,6 +47,7 @@ it('should correctly open the different tutorials', () => { currentUser={{ isLoggedIn: true }} onClose={onClose} onOpenOrganizationOnboarding={onOpenOrganizationOnboarding} + onOpenProjectOnboarding={onOpenProjectOnboarding} onOpenTeamOnboarding={onOpenTeamOnboarding} />, { context: { router: { push } } } @@ -55,6 +58,6 @@ it('should correctly open the different tutorials', () => { wrapper.find('Button').forEach(button => click(button)); expect(onOpenOrganizationOnboarding).toHaveBeenCalled(); + expect(onOpenProjectOnboarding).toHaveBeenCalled(); expect(onOpenTeamOnboarding).toHaveBeenCalled(); - expect(push).toHaveBeenCalledWith('/onboarding'); }); diff --git a/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap b/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap index e0a56e3fc79..18b2f16f846 100644 --- a/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/tutorials/__tests__/__snapshots__/Onboarding-test.tsx.snap @@ -4,7 +4,7 @@ exports[`renders correctly 1`] = `