diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2018-10-15 11:55:35 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-11-16 20:21:03 +0100 |
commit | f61d654f7d74036f50f2a1ca6b380a437ec911a2 (patch) | |
tree | 9a89718368daa1b60d5547ef606fbd00ebca59fc /server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx | |
parent | f8694e7d8b50651cba439fb5847ffde2d3bd013c (diff) | |
download | sonarqube-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.tsx | 222 |
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) + ) ); |