aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2018-10-19 17:25:13 +0200
committerSonarTech <sonartech@sonarsource.com>2018-11-16 20:21:04 +0100
commit07546d5e1f4047a1030a91d0ffaa39fb96e66a41 (patch)
tree442327150154571ec1be84a47cf4c012f0ba50bc /server/sonar-web/src/main/js/apps
parent3ea9808248000c145f53a4f1cdb8711d63b97da4 (diff)
downloadsonarqube-07546d5e1f4047a1030a91d0ffaa39fb96e66a41.tar.gz
sonarqube-07546d5e1f4047a1030a91d0ffaa39fb96e66a41.zip
SONAR-11323 Ease workflow to bind personal organizations
* Create withUserOrganizations and use it in create Orgs/Projects page * Update ALM object format in api/navigation/component and api/organizations/search
Diffstat (limited to 'server/sonar-web/src/main/js/apps')
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx52
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx130
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsStep.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx24
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap59
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/CreateOrganization-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/components/OrganizationKeyInput.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/components/__tests__/OrganizationKeyInput-test.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/components/__tests__/__snapshots__/OrganizationKeyInput-test.tsx.snap21
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx38
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/OrganizationSelect.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/AutoProjectCreate-test.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/OrganizationSelect-test.tsx5
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap10
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap15
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/OrganizationSelect-test.tsx.snap5
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx3
-rw-r--r--server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap6
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx8
24 files changed, 333 insertions, 141 deletions
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 f7882de2ba9..14fc63bdbf8 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
@@ -30,45 +30,68 @@ import {
import { getBaseUrl } from '../../../helpers/urls';
import { translate } from '../../../helpers/l10n';
import { sanitizeAlmId } from '../../../helpers/almIntegrations';
+import OrganizationAvatar from '../../../components/common/OrganizationAvatar';
interface Props {
almApplication: AlmApplication;
almInstallId?: string;
almOrganization?: AlmOrganization;
createOrganization: (
- organization: OrganizationBase & { installId?: string }
+ organization: OrganizationBase & { installationId?: string }
) => Promise<Organization>;
+ importPersonalOrg?: Organization;
onOrgCreated: (organization: string) => void;
+ updateOrganization: (
+ organization: OrganizationBase & { installationId?: string }
+ ) => Promise<Organization>;
}
export default class AutoOrganizationCreate extends React.PureComponent<Props> {
handleCreateOrganization = (organization: Required<OrganizationBase>) => {
if (organization) {
- return this.props
- .createOrganization({
+ const { importPersonalOrg } = this.props;
+ let promise: Promise<Organization>;
+ if (importPersonalOrg) {
+ promise = this.props.updateOrganization({
+ avatar: organization.avatar,
+ description: organization.description,
+ installationId: this.props.almInstallId,
+ key: importPersonalOrg.key,
+ name: organization.name || organization.key,
+ url: organization.url
+ });
+ } else {
+ promise = this.props.createOrganization({
avatar: organization.avatar,
description: organization.description,
- installId: this.props.almInstallId,
+ installationId: this.props.almInstallId,
key: organization.key,
name: organization.name || organization.key,
url: organization.url
- })
- .then(({ key }) => this.props.onOrgCreated(key));
+ });
+ }
+ return promise.then(({ key }) => this.props.onOrgCreated(key));
} else {
return Promise.reject();
}
};
render() {
- const { almApplication, almInstallId, almOrganization } = this.props;
+ const { almApplication, almInstallId, almOrganization, importPersonalOrg } = this.props;
if (almInstallId && almOrganization) {
+ const description = importPersonalOrg
+ ? translate('onboarding.import_personal_organization_x')
+ : translate('onboarding.import_organization_x');
+ const submitText = importPersonalOrg
+ ? translate('onboarding.import_organization.bind')
+ : translate('my_account.create_organization');
return (
<OrganizationDetailsStep
description={
<p className="huge-spacer-bottom">
<FormattedMessage
- defaultMessage={translate('onboarding.create_organization.import_organization_x')}
- id="onboarding.create_organization.import_organization_x"
+ defaultMessage={description}
+ id={description}
values={{
avatar: (
<img
@@ -80,17 +103,22 @@ export default class AutoOrganizationCreate extends React.PureComponent<Props> {
width={16}
/>
),
- name: <strong>{almOrganization.name}</strong>
+ name: <strong>{almOrganization.name}</strong>,
+ personalAvatar: importPersonalOrg && (
+ <OrganizationAvatar organization={importPersonalOrg} small={true} />
+ ),
+ personalName: importPersonalOrg && <strong>{importPersonalOrg.name}</strong>
}}
/>
</p>
}
finished={false}
+ keyReadOnly={Boolean(importPersonalOrg)}
onContinue={this.handleCreateOrganization}
onOpen={() => {}}
open={true}
- organization={almOrganization}
- submitText={translate('my_account.create_organization')}
+ organization={importPersonalOrg || almOrganization}
+ submitText={submitText}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx b/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
index 56e401bd103..17c0e3a83ee 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
@@ -30,7 +30,12 @@ import ManualOrganizationCreate from './ManualOrganizationCreate';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import Tabs from '../../../components/controls/Tabs';
import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn';
-import { getAlmAppInfo, getAlmOrganization } from '../../../api/alm-integration';
+import { withUserOrganizations } from '../../../components/hoc/withUserOrganizations';
+import {
+ getAlmAppInfo,
+ getAlmOrganization,
+ bindAlmOrganization
+} from '../../../api/alm-integration';
import { getSubscriptionPlans } from '../../../api/billing';
import {
LoggedInUser,
@@ -40,7 +45,7 @@ import {
AlmOrganization,
OrganizationBase
} from '../../../app/types';
-import { hasAdvancedALMIntegration } from '../../../helpers/almIntegrations';
+import { hasAdvancedALMIntegration, isPersonal } from '../../../helpers/almIntegrations';
import { translate } from '../../../helpers/l10n';
import { getOrganizationUrl } from '../../../helpers/urls';
import * as api from '../../../api/organizations';
@@ -49,9 +54,15 @@ import '../../../app/styles/sonarcloud.css';
import '../../tutorials/styles.css'; // TODO remove me
interface Props {
- createOrganization: (organization: OrganizationBase) => Promise<Organization>;
+ createOrganization: (
+ organization: OrganizationBase & { installationId?: string }
+ ) => Promise<Organization>;
currentUser: LoggedInUser;
deleteOrganization: (key: string) => Promise<void>;
+ updateOrganization: (
+ organization: OrganizationBase & { installationId?: string }
+ ) => Promise<Organization>;
+ userOrganizations: Organization[];
}
interface State {
@@ -146,11 +157,19 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
};
render() {
- const { location } = this.props;
- const { almApplication, loading, subscriptionPlans } = this.state;
+ const { currentUser, location } = this.props;
+ const { almApplication, almOrganization, loading, subscriptionPlans } = this.state;
const state = (location.state || {}) as LocationState;
const query = parseQuery(location.query);
- const header = translate('onboarding.create_organization.page.header');
+ const importPersonalOrg = isPersonal(almOrganization)
+ ? this.props.userOrganizations.find(o => o.key === currentUser.personalOrganization)
+ : undefined;
+ const header = importPersonalOrg
+ ? translate('onboarding.import_organization.personal.page.header')
+ : translate('onboarding.create_organization.page.header');
+ const description = importPersonalOrg
+ ? translate('onboarding.import_organization.personal.page.description')
+ : translate('onboarding.create_organization.page.description');
const startedPrice = subscriptionPlans && subscriptionPlans[0] && subscriptionPlans[0].price;
const formattedPrice = formatPrice(startedPrice);
const showManualTab = state.tab === 'manual' && !query.almInstallId;
@@ -164,8 +183,8 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
{startedPrice !== undefined && (
<p className="page-description">
<FormattedMessage
- defaultMessage={translate('onboarding.create_organization.page.description')}
- id="onboarding.create_organization.page.description"
+ defaultMessage={description}
+ id={description}
values={{
break: <br />,
price: formattedPrice,
@@ -184,36 +203,34 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
<DeferredSpinner />
) : (
<>
- {almApplication && (
- <Tabs<TabKeys>
- onChange={this.onTabChange}
- selected={showManualTab ? 'manual' : 'auto'}
- tabs={[
- {
- key: 'auto',
- node: (
- <>
- {translate(
- 'onboarding.create_organization.import_organization',
- almApplication.key
- )}
- <span
- className={classNames('beta-badge spacer-left', {
- 'is-muted': showManualTab
- })}>
- {translate('beta')}
- </span>
- </>
- )
- },
- {
- disabled: Boolean(query.almInstallId),
- key: 'manual',
- node: translate('onboarding.create_organization.create_manually')
- }
- ]}
- />
- )}
+ {almApplication &&
+ !importPersonalOrg && (
+ <Tabs<TabKeys>
+ onChange={this.onTabChange}
+ selected={showManualTab ? 'manual' : 'auto'}
+ tabs={[
+ {
+ key: 'auto',
+ node: (
+ <>
+ {translate('onboarding.import_organization', almApplication.key)}
+ <span
+ className={classNames('beta-badge spacer-left', {
+ 'is-muted': showManualTab
+ })}>
+ {translate('beta')}
+ </span>
+ </>
+ )
+ },
+ {
+ disabled: Boolean(query.almInstallId),
+ key: 'manual',
+ node: translate('onboarding.create_organization.create_manually')
+ }
+ ]}
+ />
+ )}
{showManualTab || !almApplication ? (
<ManualOrganizationCreate
@@ -227,9 +244,11 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
<AutoOrganizationCreate
almApplication={almApplication}
almInstallId={query.almInstallId}
- almOrganization={this.state.almOrganization}
+ almOrganization={almOrganization}
createOrganization={this.props.createOrganization}
+ importPersonalOrg={importPersonalOrg}
onOrgCreated={this.handleOrgCreated}
+ updateOrganization={this.props.updateOrganization}
/>
)}
</>
@@ -240,7 +259,7 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
}
}
-function createOrganization(organization: OrganizationBase & { installId?: string }) {
+function createOrganization(organization: OrganizationBase & { installationId?: string }) {
return (dispatch: Dispatch) => {
return api.createOrganization(organization).then((organization: Organization) => {
dispatch(actions.createOrganization(organization));
@@ -249,6 +268,22 @@ function createOrganization(organization: OrganizationBase & { installId?: strin
};
}
+function updateOrganization(
+ organization: OrganizationBase & { key: string; installationId?: string }
+) {
+ return (dispatch: Dispatch) => {
+ const { key, installationId, ...changes } = organization;
+ const promises = [api.updateOrganization(key, changes)];
+ if (installationId) {
+ promises.push(bindAlmOrganization({ organization: key, installationId }));
+ }
+ return Promise.all(promises).then(() => {
+ dispatch(actions.updateOrganization(key, changes));
+ return organization;
+ });
+ };
+}
+
function deleteOrganization(key: string) {
return (dispatch: Dispatch) => {
return api.deleteOrganization(key).then(() => {
@@ -259,14 +294,17 @@ function deleteOrganization(key: string) {
const mapDispatchToProps = {
createOrganization: createOrganization as any,
- deleteOrganization: deleteOrganization as any
+ deleteOrganization: deleteOrganization as any,
+ updateOrganization: updateOrganization as any
};
export default whenLoggedIn(
- withRouter(
- connect(
- null,
- mapDispatchToProps
- )(CreateOrganization)
+ withUserOrganizations(
+ withRouter(
+ connect(
+ null,
+ mapDispatchToProps
+ )(CreateOrganization)
+ )
)
);
diff --git a/server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsStep.tsx b/server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsStep.tsx
index 2b31d8a2379..d6e5f0696a1 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsStep.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsStep.tsx
@@ -35,6 +35,7 @@ type RequiredOrganization = Required<OrganizationBase>;
interface Props {
description?: React.ReactNode;
finished: boolean;
+ keyReadOnly?: boolean;
onContinue: (organization: RequiredOrganization) => Promise<void>;
onOpen: () => void;
open: boolean;
@@ -141,7 +142,11 @@ export default class OrganizationDetailsStep extends React.PureComponent<Props,
<div className="boxed-group-inner">
<form id="organization-form" onSubmit={this.handleSubmit}>
{this.props.description}
- <OrganizationKeyInput initialValue={this.state.key} onChange={this.handleKeyUpdate} />
+ <OrganizationKeyInput
+ initialValue={this.state.key}
+ onChange={this.handleKeyUpdate}
+ readOnly={this.props.keyReadOnly}
+ />
<div className="big-spacer-top">
<ResetButtonLink onClick={this.handleAdditionalClick}>
{translate(
@@ -162,6 +167,7 @@ export default class OrganizationDetailsStep extends React.PureComponent<Props,
<div className="big-spacer-top">
<OrganizationAvatarInput
initialValue={this.state.avatar}
+ name={this.state.name}
onChange={this.handleDescriptionUpdate}
/>
</div>
diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx
index 8beb62e897c..17e57b70a94 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx
@@ -52,10 +52,31 @@ it('should render prefilled and create org', async () => {
wrapper.find('OrganizationDetailsStep').prop<Function>('onContinue')(organization);
await waitAndUpdate(wrapper);
- expect(createOrganization).toBeCalledWith({ ...organization, installId: 'id-foo' });
+ expect(createOrganization).toBeCalledWith({ ...organization, installationId: 'id-foo' });
expect(onOrgCreated).toBeCalledWith('foo');
});
+it('should render for personal organizations', async () => {
+ const personalOrg = { key: 'personal-org', name: 'personal-org' };
+ const updateOrganization = jest.fn().mockResolvedValue({ key: personalOrg.key });
+ const onOrgCreated = jest.fn();
+ const wrapper = shallowRender({
+ almInstallId: 'id-foo',
+ almOrganization: { ...organization, type: 'USER' },
+ importPersonalOrg: personalOrg,
+ onOrgCreated,
+ updateOrganization
+ });
+
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.find('OrganizationDetailsStep').prop<Function>('onContinue')(personalOrg);
+ await waitAndUpdate(wrapper);
+
+ expect(updateOrganization).toBeCalledWith({ ...personalOrg, installationId: 'id-foo' });
+ expect(onOrgCreated).toBeCalledWith(personalOrg.key);
+});
+
function shallowRender(props: Partial<AutoOrganizationCreate['props']> = {}) {
return shallow(
<AutoOrganizationCreate
@@ -68,6 +89,7 @@ function shallowRender(props: Partial<AutoOrganizationCreate['props']> = {}) {
}}
createOrganization={jest.fn()}
onOrgCreated={jest.fn()}
+ updateOrganization={jest.fn()}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx
index 9c1e58367fb..330f8062730 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx
@@ -116,6 +116,10 @@ function shallowRender(props: Partial<CreateOrganization['props']> = {}) {
// @ts-ignore avoid passing everything from WithRouterProps
location={{}}
router={mockRouter()}
+ userOrganizations={[
+ { key: 'foo', name: 'Foo' },
+ { alm: { key: 'github', url: '' }, key: 'bar', name: 'Bar' }
+ ]}
{...props}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap
index a57042c6f50..423b5f2181b 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap
@@ -1,5 +1,57 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`should render for personal organizations 1`] = `
+<OrganizationDetailsStep
+ description={
+ <p
+ className="huge-spacer-bottom"
+ >
+ <FormattedMessage
+ defaultMessage="onboarding.import_personal_organization_x"
+ id="onboarding.import_personal_organization_x"
+ values={
+ Object {
+ "avatar": <img
+ alt="BitBucket"
+ className="little-spacer-left"
+ src="/images/sonarcloud/bitbucket.svg"
+ width={16}
+ />,
+ "name": <strong>
+ name-foo
+ </strong>,
+ "personalAvatar": <OrganizationAvatar
+ organization={
+ Object {
+ "key": "personal-org",
+ "name": "personal-org",
+ }
+ }
+ small={true}
+ />,
+ "personalName": <strong>
+ personal-org
+ </strong>,
+ }
+ }
+ />
+ </p>
+ }
+ finished={false}
+ keyReadOnly={true}
+ onContinue={[Function]}
+ onOpen={[Function]}
+ open={true}
+ organization={
+ Object {
+ "key": "personal-org",
+ "name": "personal-org",
+ }
+ }
+ submitText="onboarding.import_organization.bind"
+/>
+`;
+
exports[`should render prefilled and create org 1`] = `
<OrganizationDetailsStep
description={
@@ -7,8 +59,8 @@ exports[`should render prefilled and create org 1`] = `
className="huge-spacer-bottom"
>
<FormattedMessage
- defaultMessage="onboarding.create_organization.import_organization_x"
- id="onboarding.create_organization.import_organization_x"
+ defaultMessage="onboarding.import_organization_x"
+ id="onboarding.import_organization_x"
values={
Object {
"avatar": <img
@@ -20,12 +72,15 @@ exports[`should render prefilled and create org 1`] = `
"name": <strong>
name-foo
</strong>,
+ "personalAvatar": undefined,
+ "personalName": undefined,
}
}
/>
</p>
}
finished={false}
+ keyReadOnly={false}
onContinue={[Function]}
onOpen={[Function]}
open={true}
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 c4b506bc1f3..c25c32f3e2c 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
@@ -50,7 +50,7 @@ exports[`should render with auto tab displayed 1`] = `
Object {
"key": "auto",
"node": <React.Fragment>
- onboarding.create_organization.import_organization.github
+ onboarding.import_organization.github
<span
className="beta-badge spacer-left"
>
@@ -132,7 +132,7 @@ exports[`should render with auto tab selected and manual disabled 1`] = `
Object {
"key": "auto",
"node": <React.Fragment>
- onboarding.create_organization.import_organization.github
+ onboarding.import_organization.github
<span
className="beta-badge spacer-left"
>
@@ -286,7 +286,7 @@ exports[`should switch tabs 1`] = `
Object {
"key": "auto",
"node": <React.Fragment>
- onboarding.create_organization.import_organization.github
+ onboarding.import_organization.github
<span
className="beta-badge spacer-left"
>
diff --git a/server/sonar-web/src/main/js/apps/create/organization/components/OrganizationKeyInput.tsx b/server/sonar-web/src/main/js/apps/create/organization/components/OrganizationKeyInput.tsx
index 0fd3c61b35a..a4fcc91d979 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/components/OrganizationKeyInput.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/components/OrganizationKeyInput.tsx
@@ -28,6 +28,7 @@ import { getHostUrl } from '../../../../helpers/urls';
interface Props {
initialValue?: string;
onChange: (value: string | undefined) => void;
+ readOnly?: boolean;
}
interface State {
@@ -50,7 +51,9 @@ export default class OrganizationKeyInput extends React.PureComponent<Props, Sta
this.mounted = true;
if (this.props.initialValue !== undefined) {
this.setState({ value: this.props.initialValue });
- this.validateKey(this.props.initialValue);
+ if (!this.props.readOnly) {
+ this.validateKey(this.props.initialValue);
+ }
}
}
@@ -118,25 +121,28 @@ export default class OrganizationKeyInput extends React.PureComponent<Props, Sta
isInvalid={isInvalid}
isValid={isValid}
label={translate('onboarding.create_organization.organization_name')}
- required={true}>
+ required={!this.props.readOnly}>
<div className="display-inline-flex-baseline">
<span className="little-spacer-right">
{getHostUrl().replace(/https*:\/\//, '') + '/organizations/'}
+ {this.props.readOnly && this.state.value}
</span>
- <input
- autoFocus={true}
- className={classNames('input-super-large', 'text-middle', {
- 'is-invalid': isInvalid,
- 'is-valid': isValid
- })}
- id="organization-key"
- maxLength={255}
- onBlur={this.handleBlur}
- onChange={this.handleChange}
- onFocus={this.handleFocus}
- type="text"
- value={this.state.value}
- />
+ {!this.props.readOnly && (
+ <input
+ autoFocus={true}
+ className={classNames('input-super-large', 'text-middle', {
+ 'is-invalid': isInvalid,
+ 'is-valid': isValid
+ })}
+ id="organization-key"
+ maxLength={255}
+ onBlur={this.handleBlur}
+ onChange={this.handleChange}
+ onFocus={this.handleFocus}
+ type="text"
+ value={this.state.value}
+ />
+ )}
</div>
</ValidationInput>
);
diff --git a/server/sonar-web/src/main/js/apps/create/organization/components/__tests__/OrganizationKeyInput-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/components/__tests__/OrganizationKeyInput-test.tsx
index a6bcde51a7e..d559b30e4d0 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/components/__tests__/OrganizationKeyInput-test.tsx
+++ b/server/sonar-web/src/main/js/apps/create/organization/components/__tests__/OrganizationKeyInput-test.tsx
@@ -38,6 +38,13 @@ it('should render correctly', () => {
expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot();
});
+it('should render correctly with readonly mode', () => {
+ const wrapper = shallow(
+ <OrganizationKeyInput initialValue="key" onChange={jest.fn()} readOnly={true} />
+ );
+ expect(wrapper).toMatchSnapshot();
+});
+
it('should not display any status when the key is not defined', async () => {
const wrapper = shallow(<OrganizationKeyInput onChange={jest.fn()} />);
await waitAndUpdate(wrapper);
diff --git a/server/sonar-web/src/main/js/apps/create/organization/components/__tests__/__snapshots__/OrganizationKeyInput-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/components/__tests__/__snapshots__/OrganizationKeyInput-test.tsx.snap
index 8cba7d969a3..05d2e74dd68 100644
--- a/server/sonar-web/src/main/js/apps/create/organization/components/__tests__/__snapshots__/OrganizationKeyInput-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/organization/components/__tests__/__snapshots__/OrganizationKeyInput-test.tsx.snap
@@ -32,3 +32,24 @@ exports[`should render correctly 1`] = `
`;
exports[`should render correctly 2`] = `true`;
+
+exports[`should render correctly with readonly mode 1`] = `
+<ValidationInput
+ id="organization-key"
+ isInvalid={false}
+ isValid={false}
+ label="onboarding.create_organization.organization_name"
+ required={false}
+>
+ <div
+ className="display-inline-flex-baseline"
+ >
+ <span
+ className="little-spacer-right"
+ >
+ localhost/organizations/
+ key
+ </span>
+ </div>
+</ValidationInput>
+`;
diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
index d46f7bbaa36..53382bfc34e 100644
--- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx
@@ -27,8 +27,7 @@ import ManualProjectCreate from './ManualProjectCreate';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import Tabs from '../../../components/controls/Tabs';
import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn';
-import { fetchMyOrganizations } from '../../account/organizations/actions';
-import { getMyOrganizations, Store } from '../../../store/rootReducer';
+import { withUserOrganizations } from '../../../components/hoc/withUserOrganizations';
import { skipOnboarding as skipOnboardingAction } from '../../../store/users';
import { LoggedInUser, AlmApplication, Organization } from '../../../app/types';
import { getAlmAppInfo } from '../../../api/alm-integration';
@@ -38,14 +37,10 @@ import { translate } from '../../../helpers/l10n';
import { getProjectUrl } from '../../../helpers/urls';
import '../../../app/styles/sonarcloud.css';
-interface StateProps {
- userOrganizations: Organization[];
-}
-
interface Props {
currentUser: LoggedInUser;
- fetchMyOrganizations: () => Promise<void>;
skipOnboardingAction: () => void;
+ userOrganizations: Organization[];
}
interface State {
@@ -60,16 +55,12 @@ interface LocationState {
tab?: TabKeys;
}
-export class CreateProjectPage extends React.PureComponent<
- Props & StateProps & WithRouterProps,
- State
-> {
+export class CreateProjectPage extends React.PureComponent<Props & WithRouterProps, State> {
mounted = false;
state: State = { loading: true };
componentDidMount() {
this.mounted = true;
- this.props.fetchMyOrganizations();
if (hasAdvancedALMIntegration(this.props.currentUser)) {
this.fetchAlmApplication();
} else {
@@ -178,7 +169,7 @@ export class CreateProjectPage extends React.PureComponent<
) : (
<AutoProjectCreate
almApplication={almApplication}
- boundOrganizations={userOrganizations.filter(o => o.almId)}
+ boundOrganizations={userOrganizations.filter(o => o.alm)}
onProjectCreate={this.handleProjectCreate}
organization={state.organization}
/>
@@ -191,20 +182,13 @@ export class CreateProjectPage extends React.PureComponent<
}
}
-const mapDispatchToProps = {
- fetchMyOrganizations,
- skipOnboardingAction
-};
-
-const mapStateToProps = (state: Store) => {
- return {
- userOrganizations: getMyOrganizations(state)
- };
-};
+const mapDispatchToProps = { skipOnboardingAction };
export default whenLoggedIn(
- connect<StateProps>(
- mapStateToProps,
- mapDispatchToProps
- )(CreateProjectPage)
+ withUserOrganizations(
+ connect(
+ null,
+ mapDispatchToProps
+ )(CreateProjectPage)
+ )
);
diff --git a/server/sonar-web/src/main/js/apps/create/project/OrganizationSelect.tsx b/server/sonar-web/src/main/js/apps/create/project/OrganizationSelect.tsx
index a1d52e0af64..ed7cf5d30ee 100644
--- a/server/sonar-web/src/main/js/apps/create/project/OrganizationSelect.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/OrganizationSelect.tsx
@@ -71,12 +71,12 @@ export default function OrganizationSelect({
export function optionRenderer(organization: Organization) {
return (
<span>
- {organization.almId && (
+ {organization.alm && (
<img
- alt={organization.almId}
+ alt={organization.alm.key}
className="spacer-right"
height={14}
- src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(organization.almId)}.svg`}
+ src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(organization.alm.key)}.svg`}
/>
)}
{organization.name}
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/AutoProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/AutoProjectCreate-test.tsx
index 3364a73a344..a7664c362be 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/AutoProjectCreate-test.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/AutoProjectCreate-test.tsx
@@ -42,8 +42,8 @@ function shallowRender(props: Partial<AutoProjectCreate['props']> = {}) {
<AutoProjectCreate
almApplication={almApplication}
boundOrganizations={[
- { almId: 'github', key: 'foo', name: 'Foo' },
- { almId: 'github', key: 'bar', name: 'Bar' }
+ { alm: { key: 'github', url: '' }, key: 'foo', name: 'Foo' },
+ { alm: { key: 'github', url: '' }, key: 'bar', name: 'Bar' }
]}
onProjectCreate={jest.fn()}
organization=""
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx
index 6c9acb9d77c..12af30eb2b8 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/CreateProjectPage-test.tsx
@@ -81,14 +81,13 @@ function getWrapper(props = {}) {
<CreateProjectPage
addGlobalErrorMessage={jest.fn()}
currentUser={user}
- fetchMyOrganizations={jest.fn()}
// @ts-ignore avoid passing everything from WithRouterProps
location={{}}
router={mockRouter()}
skipOnboardingAction={jest.fn()}
userOrganizations={[
{ key: 'foo', name: 'Foo' },
- { almId: 'github', key: 'bar', name: 'Bar' }
+ { alm: { key: 'github', url: '' }, key: 'bar', name: 'Bar' }
]}
{...props}
/>
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/OrganizationSelect-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/OrganizationSelect-test.tsx
index 4224b152a38..cc7e426bbc4 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/OrganizationSelect-test.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/OrganizationSelect-test.tsx
@@ -21,7 +21,10 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import OrganizationSelect, { optionRenderer } from '../OrganizationSelect';
-const organizations = [{ key: 'foo', name: 'Foo' }, { almId: 'github', key: 'bar', name: 'Bar' }];
+const organizations = [
+ { key: 'foo', name: 'Foo' },
+ { alm: { key: 'github', url: '' }, key: 'bar', name: 'Bar' }
+];
it('should render correctly', () => {
expect(
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap
index 147427d62a6..a96a37b53b5 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/AutoProjectCreate-test.tsx.snap
@@ -9,12 +9,18 @@ exports[`should display the bounded organizations dropdown with the list of repo
organizations={
Array [
Object {
- "almId": "github",
+ "alm": Object {
+ "key": "github",
+ "url": "",
+ },
"key": "foo",
"name": "Foo",
},
Object {
- "almId": "github",
+ "alm": Object {
+ "key": "github",
+ "url": "",
+ },
"key": "bar",
"name": "Bar",
},
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
index 6e1f9059e89..5e2c2e1a150 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/CreateProjectPage-test.tsx.snap
@@ -83,7 +83,10 @@ exports[`should render correctly 2`] = `
boundOrganizations={
Array [
Object {
- "almId": "github",
+ "alm": Object {
+ "key": "github",
+ "url": "",
+ },
"key": "bar",
"name": "Bar",
},
@@ -134,7 +137,10 @@ exports[`should render with Manual creation only 1`] = `
"name": "Foo",
},
Object {
- "almId": "github",
+ "alm": Object {
+ "key": "github",
+ "url": "",
+ },
"key": "bar",
"name": "Bar",
},
@@ -201,7 +207,10 @@ exports[`should switch tabs 1`] = `
boundOrganizations={
Array [
Object {
- "almId": "github",
+ "alm": Object {
+ "key": "github",
+ "url": "",
+ },
"key": "bar",
"name": "Bar",
},
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/OrganizationSelect-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/OrganizationSelect-test.tsx.snap
index 50cd939ec7e..367f0265e72 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/OrganizationSelect-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/OrganizationSelect-test.tsx.snap
@@ -25,7 +25,10 @@ exports[`should render correctly 1`] = `
options={
Array [
Object {
- "almId": "github",
+ "alm": Object {
+ "key": "github",
+ "url": "",
+ },
"key": "bar",
"name": "Bar",
},
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx
index d376085573a..4d7bb7f71e1 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx
@@ -58,17 +58,17 @@ export default function OrganizationNavigationHeader({ organization, organizatio
) : (
<span className="spacer-left">{organization.name}</span>
)}
- {organization.almRepoUrl && (
+ {organization.alm && (
<a
className="link-no-underline"
- href={organization.almRepoUrl}
+ href={organization.alm.url}
rel="noopener noreferrer"
target="_blank">
<img
- alt={sanitizeAlmId(organization.almId)}
+ alt={sanitizeAlmId(organization.alm.key)}
className="text-text-top spacer-left"
height={16}
- src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(organization.almId)}.svg`}
+ src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(organization.alm.key)}.svg`}
width={16}
/>
</a>
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx
index 021b80766e4..2d6853a617d 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx
@@ -38,8 +38,7 @@ it('renders with alm integration', () => {
shallow(
<OrganizationNavigationHeader
organization={{
- almId: 'github',
- almRepoUrl: 'https://github.com/foo',
+ alm: { key: 'github', url: 'https://github.com/foo' },
key: 'foo',
name: 'Foo',
projectVisibility: Visibility.Public
diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap
index cf3e383e573..ca3bd2d87cd 100644
--- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap
@@ -70,8 +70,10 @@ exports[`renders with alm integration 1`] = `
<OrganizationAvatar
organization={
Object {
- "almId": "github",
- "almRepoUrl": "https://github.com/foo",
+ "alm": Object {
+ "key": "github",
+ "url": "https://github.com/foo",
+ },
"key": "foo",
"name": "Foo",
"projectVisibility": "public",
diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx
index d930fb3d064..031af9848cd 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx
+++ b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorial.tsx
@@ -56,7 +56,7 @@ export default class AnalyzeTutorial extends React.PureComponent<Props, State> {
const { component, currentUser } = this.props;
const { step, token } = this.state;
- const almId = component.almId || currentUser.externalProvider;
+ const almKey = (component.alm && component.alm.key) || currentUser.externalProvider;
return (
<>
<div className="page-header big-spacer-bottom">
@@ -64,9 +64,9 @@ export default class AnalyzeTutorial extends React.PureComponent<Props, State> {
<p className="page-description">{translate('onboarding.project_analysis.description')}</p>
</div>
- <AnalyzeTutorialSuggestion almId={almId} />
+ <AnalyzeTutorialSuggestion almKey={almKey} />
- {!isVSTS(almId) && (
+ {!isVSTS(almKey) && (
<>
<TokenStep
currentUser={currentUser}
diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx
index d433da91112..65b4f31b510 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx
+++ b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/AnalyzeTutorialSuggestion.tsx
@@ -24,8 +24,8 @@ import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/urls';
import { Alert } from '../../../components/ui/Alert';
-export default function AnalyzeTutorialSuggestion({ almId }: { almId?: string }) {
- if (isBitbucket(almId)) {
+export default function AnalyzeTutorialSuggestion({ almKey }: { almKey?: string }) {
+ if (isBitbucket(almKey)) {
return (
<Alert className="big-spacer-bottom" variant="info">
<p>{translate('onboarding.project_analysis.commands_for_analysis')}</p>
@@ -49,7 +49,7 @@ export default function AnalyzeTutorialSuggestion({ almId }: { almId?: string })
/>
</Alert>
);
- } else if (isGithub(almId)) {
+ } else if (isGithub(almKey)) {
return (
<Alert className="big-spacer-bottom" variant="info">
<p>{translate('onboarding.project_analysis.commands_for_analysis')} </p>
@@ -70,7 +70,7 @@ export default function AnalyzeTutorialSuggestion({ almId }: { almId?: string })
/>
</Alert>
);
- } else if (isVSTS(almId)) {
+ } else if (isVSTS(almKey)) {
return (
<Alert className="big-spacer-bottom" variant="info">
<FormattedMessage
diff --git a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx
index bc4a6d7b55b..22c182361a1 100644
--- a/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx
+++ b/server/sonar-web/src/main/js/apps/tutorials/analyzeProject/__tests__/AnalyzeTutorialSuggestion-test.tsx
@@ -22,17 +22,17 @@ import { shallow } from 'enzyme';
import AnalyzeTutorialSuggestion from '../AnalyzeTutorialSuggestion';
it('should not render', () => {
- expect(shallow(<AnalyzeTutorialSuggestion almId={undefined} />).type()).toBeNull();
+ expect(shallow(<AnalyzeTutorialSuggestion almKey={undefined} />).type()).toBeNull();
});
it('renders bitbucket suggestions correctly', () => {
- expect(shallow(<AnalyzeTutorialSuggestion almId="bitbucket" />)).toMatchSnapshot();
+ expect(shallow(<AnalyzeTutorialSuggestion almKey="bitbucket" />)).toMatchSnapshot();
});
it('renders github suggestions correctly', () => {
- expect(shallow(<AnalyzeTutorialSuggestion almId="github" />)).toMatchSnapshot();
+ expect(shallow(<AnalyzeTutorialSuggestion almKey="github" />)).toMatchSnapshot();
});
it('renders vsts suggestions correctly', () => {
- expect(shallow(<AnalyzeTutorialSuggestion almId="microsoft" />)).toMatchSnapshot();
+ expect(shallow(<AnalyzeTutorialSuggestion almKey="microsoft" />)).toMatchSnapshot();
});