From: Grégoire Aubert Date: Wed, 24 Oct 2018 14:18:56 +0000 (+0200) Subject: SONAR-11325 Enable to continue an unfinished alm application installation X-Git-Tag: 7.5~142 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=83144d4988f1f5ecc1f07852e3d67fcd07a8024e;p=sonarqube.git SONAR-11325 Enable to continue an unfinished alm application installation --- 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 9b9df940713..05ba477a887 100644 --- a/server/sonar-web/src/main/js/api/alm-integration.ts +++ b/server/sonar-web/src/main/js/api/alm-integration.ts @@ -18,7 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { getJSON, postJSON, post } from '../helpers/request'; -import { AlmRepository, AlmApplication, AlmOrganization } from '../app/types'; +import { + AlmApplication, + AlmOrganization, + AlmRepository, + AlmUnboundApplication +} from '../app/types'; import throwGlobalError from '../app/utils/throwGlobalError'; export function bindAlmOrganization(data: { installationId: string; organization: string }) { @@ -59,6 +64,10 @@ export function getRepositories(data: { return getJSON('/api/alm_integration/list_repositories', data).catch(throwGlobalError); } +export function listUnboundApplications(): Promise<{ applications: AlmUnboundApplication[] }> { + return getJSON('/api/alm_integration/list_unbound_applications').catch(throwGlobalError); +} + export function provisionProject(data: { installationKeys: string[]; organization: string; diff --git a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx index 02d69091357..a3093d3e2fb 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/ComponentNavMeta.tsx @@ -115,7 +115,7 @@ export function ComponentNavMeta({ {branchMeasures && branchMeasures.length > 0 && ( <> - + .vertical-separator { + margin: 4px auto; } .capitalize { diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 01b41f6dbfd..e5b615e42d5 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -26,6 +26,7 @@ export type Omit = Pick>; export interface AlmApplication extends IdentityProvider { installationUrl: string; } + export interface AlmOrganization extends OrganizationBase { key: string; personal: boolean; @@ -38,6 +39,11 @@ export interface AlmRepository { linkedProjectName?: string; } +export interface AlmUnboundApplication { + installationId: string; + name: string; +} + export interface Analysis { date: string; events: AnalysisEvent[]; diff --git a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx index 92de554862d..f24b6c44f92 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx @@ -27,8 +27,9 @@ import RadioToggle from '../../../components/controls/RadioToggle'; import { AlmApplication, AlmOrganization, - OrganizationBase, - Organization + AlmUnboundApplication, + Organization, + OrganizationBase } from '../../../app/types'; import { bindAlmOrganization } from '../../../api/alm-integration'; import { sanitizeAlmId } from '../../../helpers/almIntegrations'; @@ -45,6 +46,7 @@ interface Props { almApplication: AlmApplication; almInstallId?: string; almOrganization?: AlmOrganization; + almUnboundApplications: AlmUnboundApplication[]; createOrganization: ( organization: OrganizationBase & { installationId?: string } ) => Promise; @@ -166,6 +168,7 @@ export default class AutoOrganizationCreate extends React.PureComponent ); } diff --git a/server/sonar-web/src/main/js/apps/create/organization/ChooseRemoteOrganizationStep.tsx b/server/sonar-web/src/main/js/apps/create/organization/ChooseRemoteOrganizationStep.tsx index 80e80a5f193..9a562150fec 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/ChooseRemoteOrganizationStep.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/ChooseRemoteOrganizationStep.tsx @@ -18,20 +18,72 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { WithRouterProps, withRouter } from 'react-router'; +import { sortBy } from 'lodash'; +import { serializeQuery } from './utils'; import IdentityProviderLink from '../../../components/ui/IdentityProviderLink'; +import Select from '../../../components/controls/Select'; import Step from '../../tutorials/components/Step'; -import { translate } from '../../../helpers/l10n'; -import { AlmApplication } from '../../../app/types'; import { Alert } from '../../../components/ui/Alert'; +import { SubmitButton } from '../../../components/ui/buttons'; +import { AlmApplication, AlmUnboundApplication } from '../../../app/types'; +import { getBaseUrl } from '../../../helpers/urls'; +import { sanitizeAlmId } from '../../../helpers/almIntegrations'; +import { translate } from '../../../helpers/l10n'; interface Props { almApplication: AlmApplication; almInstallId?: string; + almUnboundApplications: AlmUnboundApplication[]; +} + +interface State { + unboundInstallationId: string; } -export default class ChooseRemoteOrganizationStep extends React.PureComponent { +export class ChooseRemoteOrganizationStep extends React.PureComponent< + Props & WithRouterProps, + State +> { + state: State = { unboundInstallationId: '' }; + + handleSubmit = (event: React.FormEvent) => { + event.preventDefault(); + + const { unboundInstallationId } = this.state; + if (unboundInstallationId) { + this.props.router.push({ + pathname: '/create-organization', + query: serializeQuery({ + almInstallId: unboundInstallationId, + almKey: this.props.almApplication.key + }) + }); + } + }; + + handleInstallationChange = ({ installationId }: AlmUnboundApplication) => { + this.setState({ unboundInstallationId: installationId }); + }; + + renderOption = (organization: AlmUnboundApplication) => { + const { almApplication } = this.props; + return ( + + {almApplication.name} + {organization.name} + + ); + }; + renderForm = () => { - const { almApplication, almInstallId } = this.props; + const { almApplication, almInstallId, almUnboundApplications } = this.props; + const { unboundInstallationId } = this.state; return (
{almInstallId && ( @@ -43,16 +95,55 @@ export default class ChooseRemoteOrganizationStep extends React.PureComponent )} - - {translate( - 'onboarding.import_organization.choose_organization_button', - almApplication.key +
+
+ + {translate( + 'onboarding.import_organization.choose_organization_button', + almApplication.key + )} + +
+ {almUnboundApplications.length > 0 && ( +
+
+
+ {translate('or')} +
+
+
+
+ + +
+ + continue + +
+
+
+
+
+
+`; + exports[`should render 1`] = `
- - onboarding.import_organization.choose_organization_button.github - +
+ + onboarding.import_organization.choose_organization_button.github + +
+
diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/CreateOrganization-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/CreateOrganization-test.tsx.snap index 11acf9d7aba..741ed46784e 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/CreateOrganization-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/CreateOrganization-test.tsx.snap @@ -61,7 +61,7 @@ exports[`should render with auto personal organization bind page 2`] = ` almInstallId="foo" almOrganization={ Object { - "avatar": "https://avatars3.githubusercontent.com/u/37629810?v=4", + "avatar": "my-avatar", "key": "foo", "name": "Foo", "personal": true, @@ -148,6 +148,7 @@ exports[`should render with auto tab displayed 1`] = ` "name": "GitHub", } } + almUnboundApplications={Array []} onOrgCreated={[Function]} unboundOrganizations={ Array [ @@ -240,7 +241,7 @@ exports[`should render with auto tab selected and manual disabled 2`] = ` almInstallId="foo" almOrganization={ Object { - "avatar": "https://avatars3.githubusercontent.com/u/37629810?v=4", + "avatar": "my-avatar", "description": "Continuous Code Quality", "key": "sonarsource", "name": "SonarSource", @@ -248,6 +249,7 @@ exports[`should render with auto tab selected and manual disabled 2`] = ` "url": "https://www.sonarsource.com", } } + almUnboundApplications={Array []} onOrgCreated={[Function]} unboundOrganizations={ Array [ @@ -392,6 +394,7 @@ exports[`should switch tabs 1`] = ` "name": "GitHub", } } + almUnboundApplications={Array []} onOrgCreated={[Function]} unboundOrganizations={ Array [ diff --git a/server/sonar-web/src/main/js/apps/create/organization/utils.ts b/server/sonar-web/src/main/js/apps/create/organization/utils.ts index 71795b85c78..e5c92d28164 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/utils.ts +++ b/server/sonar-web/src/main/js/apps/create/organization/utils.ts @@ -20,7 +20,13 @@ import { memoize } from 'lodash'; import { translateWithParameters } from '../../../helpers/l10n'; import { formatMeasure } from '../../../helpers/measures'; -import { RawQuery, parseAsOptionalString } from '../../../helpers/query'; +import { + RawQuery, + parseAsOptionalString, + cleanQuery, + serializeString +} from '../../../helpers/query'; +import { isBitbucket, isGithub } from '../../../helpers/almIntegrations'; export function formatPrice(price?: number, noSign?: boolean) { const priceFormatted = formatMeasure(price, 'FLOAT') @@ -47,3 +53,10 @@ export const parseQuery = memoize( }; } ); + +export const serializeQuery = (query: Query): RawQuery => + cleanQuery({ + // eslint-disable-next-line camelcase + installation_id: isGithub(query.almKey) ? serializeString(query.almInstallId) : undefined, + clientKey: isBitbucket(query.almKey) ? serializeString(query.almInstallId) : undefined + }); diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 4b9175d39f8..b8f7aec17ba 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -112,6 +112,7 @@ no_tags=No tags not_now=Not now off=Off on=On +or=Or organization_key=Organization Key open=Open optional=Optional @@ -2754,6 +2755,8 @@ onboarding.create_organization.enter_your_coupon=Enter your coupon onboarding.create_organization.create_and_upgrade=Create Organization and Upgrade onboarding.create_organization.ready=All set! Your organization is now ready to go onboarding.import_organization.bind=Bind Organization +onboarding.import_organization.choose_unbound_installation.bitbucket=Choose one of your Bitbucket teams that already have the SonarCloud application installed: +onboarding.import_organization.choose_unbound_installation.github=Choose one of your GitHub organizations that already have the SonarCloud application installed: onboarding.import_organization.import=Import Organization onboarding.import_organization.import_org_details=Import organization details onboarding.import_organization.org_not_found=We were not able to find the requested organization, here are a few tips to help you troubleshoot the issue: