aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2018-10-15 11:55:35 +0200
committerSonarTech <sonartech@sonarsource.com>2018-11-16 20:21:03 +0100
commitf61d654f7d74036f50f2a1ca6b380a437ec911a2 (patch)
tree9a89718368daa1b60d5547ef606fbd00ebca59fc /server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx
parentf8694e7d8b50651cba439fb5847ffde2d3bd013c (diff)
downloadsonarqube-f61d654f7d74036f50f2a1ca6b380a437ec911a2.tar.gz
sonarqube-f61d654f7d74036f50f2a1ca6b380a437ec911a2.zip
SONAR-11321 Create organization from GitHub organization or BitBucket team
* Create api/alm_integration/show_organization and handle only GitHub installation * Add import from ALM tab in Create Org page * Do not show error while validating detail input * Add step to create organization from ALM * Display a warning if the installation id was not found * Add Alm link to remote organization in org context * Create GET api/alm_integration/show_app_info
Diffstat (limited to 'server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx')
-rw-r--r--server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx222
1 files changed, 130 insertions, 92 deletions
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 b6c0fa25b32..2767cdfa399 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
@@ -18,17 +18,29 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
+import * as classNames from 'classnames';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
import { Helmet } from 'react-helmet';
import { FormattedMessage } from 'react-intl';
import { Link, withRouter, WithRouterProps } from 'react-router';
-import { connect } from 'react-redux';
-import { Dispatch } from 'redux';
-import OrganizationDetailsStep from './OrganizationDetailsStep';
-import PlanStep from './PlanStep';
-import { formatPrice } from './utils';
+import { formatPrice, parseQuery } from './utils';
import { whenLoggedIn } from './whenLoggedIn';
+import AutoOrganizationCreate from './AutoOrganizationCreate';
+import ManualOrganizationCreate from './ManualOrganizationCreate';
+import DeferredSpinner from '../../../components/common/DeferredSpinner';
+import Tabs from '../../../components/controls/Tabs';
+import { getAlmAppInfo, getAlmOrganization } from '../../../api/alm-integration';
import { getSubscriptionPlans } from '../../../api/billing';
-import { OrganizationBase, Organization, SubscriptionPlan } from '../../../app/types';
+import {
+ LoggedInUser,
+ Organization,
+ SubscriptionPlan,
+ AlmApplication,
+ AlmOrganization,
+ OrganizationBase
+} from '../../../app/types';
+import { hasAdvancedALMIntegration } from '../../../helpers/almIntegrations';
import { translate } from '../../../helpers/l10n';
import { getOrganizationUrl } from '../../../helpers/urls';
import * as api from '../../../api/organizations';
@@ -38,27 +50,26 @@ import '../../tutorials/styles.css'; // TODO remove me
interface Props {
createOrganization: (organization: OrganizationBase) => Promise<Organization>;
+ currentUser: LoggedInUser;
deleteOrganization: (key: string) => Promise<void>;
}
-enum Step {
- OrganizationDetails,
- Plan
-}
-
interface State {
+ almApplication?: AlmApplication;
+ almOrganization?: AlmOrganization;
loading: boolean;
organization?: Organization;
- step: Step;
subscriptionPlans?: SubscriptionPlan[];
}
+interface LocationState {
+ paid?: boolean;
+ tab?: 'auto' | 'manual';
+}
+
export class CreateOrganization extends React.PureComponent<Props & WithRouterProps, State> {
mounted = false;
- state: State = {
- loading: true,
- step: Step.OrganizationDetails
- };
+ state: State = { loading: true };
componentDidMount() {
this.mounted = true;
@@ -66,7 +77,16 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
if (document.documentElement) {
document.documentElement.classList.add('white-page');
}
- this.fetchSubscriptionPlans();
+ const initRequests = [this.fetchSubscriptionPlans()];
+ if (hasAdvancedALMIntegration(this.props.currentUser)) {
+ initRequests.push(this.fetchAlmApplication());
+
+ const query = parseQuery(this.props.location.query);
+ if (query.almInstallId) {
+ initRequests.push(this.fetchAlmOrganization(query.almInstallId));
+ }
+ }
+ Promise.all(initRequests).then(this.stopLoading, this.stopLoading);
}
componentWillUnmount() {
@@ -74,79 +94,63 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
document.body.classList.remove('white-page');
}
- fetchSubscriptionPlans = () => {
- getSubscriptionPlans().then(
- subscriptionPlans => {
- if (this.mounted) {
- this.setState({ loading: false, subscriptionPlans });
- }
- },
- () => {
- if (this.mounted) {
- this.setState({ loading: false });
- }
+ fetchAlmApplication = () => {
+ return getAlmAppInfo().then(({ application }) => {
+ if (this.mounted) {
+ this.setState({ almApplication: application });
}
- );
- };
-
- finishCreation = (key: string) => {
- this.props.router.push({
- pathname: getOrganizationUrl(key),
- state: { justCreated: true }
});
};
- handleOrganizationDetailsStepOpen = () => {
- this.setState({ step: Step.OrganizationDetails });
+ fetchAlmOrganization = (installationId: string) => {
+ return getAlmOrganization({ installationId }).then(almOrganization => {
+ if (this.mounted) {
+ this.setState({ almOrganization });
+ }
+ });
};
- handleOrganizationDetailsFinish = (organization: Required<OrganizationBase>) => {
- this.setState({ organization, step: Step.Plan });
- return Promise.resolve();
+ fetchSubscriptionPlans = () => {
+ return getSubscriptionPlans().then(subscriptionPlans => {
+ if (this.mounted) {
+ this.setState({ subscriptionPlans });
+ }
+ });
};
- handlePaidPlanChoose = () => {
- if (this.state.organization) {
- this.finishCreation(this.state.organization.key);
- }
+ handleOrgCreated = (organization: string) => {
+ this.props.router.push({
+ pathname: getOrganizationUrl(organization),
+ state: { justCreated: true }
+ });
};
- handleFreePlanChoose = () => {
- return this.createOrganization().then(key => {
- this.finishCreation(key);
- });
+ onTabChange = (tab: 'auto' | 'manual') => {
+ this.updateUrl({ tab });
};
- createOrganization = () => {
- const { organization } = this.state;
- if (organization) {
- return this.props
- .createOrganization({
- avatar: organization.avatar,
- description: organization.description,
- key: organization.key,
- name: organization.name || organization.key,
- url: organization.url
- })
- .then(({ key }) => key);
- } else {
- return Promise.reject();
+ stopLoading = () => {
+ if (this.mounted) {
+ this.setState({ loading: false });
}
};
- deleteOrganization = () => {
- const { organization } = this.state;
- if (organization) {
- this.props.deleteOrganization(organization.key).catch(() => {});
- }
+ updateUrl = (state: Partial<LocationState> = {}) => {
+ this.props.router.replace({
+ pathname: this.props.location.pathname,
+ state: { ...(this.props.location.state || {}), ...state }
+ });
};
render() {
const { location } = this.props;
- const { loading, subscriptionPlans } = this.state;
+ const { almApplication, loading, subscriptionPlans } = this.state;
+ const state = (location.state || {}) as LocationState;
+ const query = parseQuery(location.query);
const header = translate('onboarding.create_organization.page.header');
const startedPrice = subscriptionPlans && subscriptionPlans[0] && subscriptionPlans[0].price;
const formattedPrice = formatPrice(startedPrice);
+ const showManualTab = state.tab === 'manual' && !query.almInstallId;
return (
<>
@@ -174,27 +178,59 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
</header>
{loading ? (
- <i className="spinner" />
+ <DeferredSpinner />
) : (
<>
- <OrganizationDetailsStep
- finished={this.state.organization !== undefined}
- onContinue={this.handleOrganizationDetailsFinish}
- onOpen={this.handleOrganizationDetailsStepOpen}
- open={this.state.step === Step.OrganizationDetails}
- organization={this.state.organization}
- />
-
- {subscriptionPlans !== undefined && (
- <PlanStep
- createOrganization={this.createOrganization}
- deleteOrganization={this.deleteOrganization}
- onFreePlanChoose={this.handleFreePlanChoose}
- onPaidPlanChoose={this.handlePaidPlanChoose}
- onlyPaid={location.state && location.state.paid === true}
- open={this.state.step === Step.Plan}
- startingPrice={formattedPrice}
- subscriptionPlans={subscriptionPlans}
+ {almApplication && (
+ <Tabs
+ onChange={this.onTabChange}
+ selected={showManualTab ? 'manual' : 'auto'}
+ tabs={[
+ {
+ key: 'auto',
+ node: (
+ <>
+ {translate(
+ 'onboarding.create_organization.import_organization',
+ almApplication.key
+ )}
+ <span
+ className={classNames(
+ 'rounded alert alert-small spacer-left display-inline-block',
+ {
+ 'alert-info': !showManualTab,
+ 'alert-muted': showManualTab
+ }
+ )}>
+ {translate('beta')}
+ </span>
+ </>
+ )
+ },
+ {
+ disabled: Boolean(query.almInstallId),
+ key: 'manual',
+ node: translate('onboarding.create_organization.create_manually')
+ }
+ ]}
+ />
+ )}
+
+ {showManualTab || !almApplication ? (
+ <ManualOrganizationCreate
+ createOrganization={this.props.createOrganization}
+ deleteOrganization={this.props.deleteOrganization}
+ onOrgCreated={this.handleOrgCreated}
+ onlyPaid={state.paid}
+ subscriptionPlans={this.state.subscriptionPlans}
+ />
+ ) : (
+ <AutoOrganizationCreate
+ almApplication={almApplication}
+ almInstallId={query.almInstallId}
+ almOrganization={this.state.almOrganization}
+ createOrganization={this.props.createOrganization}
+ onOrgCreated={this.handleOrgCreated}
/>
)}
</>
@@ -205,7 +241,7 @@ export class CreateOrganization extends React.PureComponent<Props & WithRouterPr
}
}
-function createOrganization(organization: OrganizationBase) {
+function createOrganization(organization: OrganizationBase & { installId?: string }) {
return (dispatch: Dispatch) => {
return api.createOrganization(organization).then((organization: Organization) => {
dispatch(actions.createOrganization(organization));
@@ -228,8 +264,10 @@ const mapDispatchToProps = {
};
export default whenLoggedIn(
- connect(
- null,
- mapDispatchToProps
- )(withRouter(CreateOrganization))
+ withRouter(
+ connect(
+ null,
+ mapDispatchToProps
+ )(CreateOrganization)
+ )
);