diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2020-09-22 14:20:28 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2020-09-28 20:07:23 +0000 |
commit | 71cc0592365fc7ff36694c726c1a8ca4e1fcc6cd (patch) | |
tree | 79f66251286871397c898aca6ccb050c5f102f23 /server/sonar-web | |
parent | 7d546a463731e1410ba34225e2e1aa5d9586a3b2 (diff) | |
download | sonarqube-71cc0592365fc7ff36694c726c1a8ca4e1fcc6cd.tar.gz sonarqube-71cc0592365fc7ff36694c726c1a8ca4e1fcc6cd.zip |
SONAR-13880 internationalize tooltips
Diffstat (limited to 'server/sonar-web')
179 files changed, 556 insertions, 13488 deletions
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx index adfb0b37550..120b5d76e9a 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx @@ -23,7 +23,7 @@ import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon'; import PlusCircleIcon from 'sonar-ui-common/components/icons/PlusCircleIcon'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../../../../components/docs/DocTooltip'; +import DocumentationTooltip from '../../../../../components/common/DocumentationTooltip'; import BranchLikeIcon from '../../../../../components/icons/BranchLikeIcon'; import { getBranchLikeDisplayName } from '../../../../../helpers/branch-like'; import { getPortfolioAdminUrl } from '../../../../../helpers/urls'; @@ -78,21 +78,39 @@ export function CurrentBranchLike(props: CurrentBranchLikeProps) { } else { if (!branchesEnabled) { return ( - <DocTooltip + <DocumentationTooltip + content={translate('branch_like_navigation.no_branch_support.content')} data-test="branches-support-disabled" - doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/branches/no-branch-support.md')}> + links={[ + { + href: 'https://redirect.sonarsource.com/editions/developer.html', + label: translate('learn_more') + } + ]} + title={translate('branch_like_navigation.no_branch_support.title')}> {plusIcon} - </DocTooltip> + </DocumentationTooltip> ); } if (!hasManyBranches) { return ( - <DocTooltip + <DocumentationTooltip + content={translate('branch_like_navigation.only_one_branch.content')} data-test="only-one-branch-like" - doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/branches/single-branch.md')}> + links={[ + { + href: '/documentation/branches/overview/', + label: translate('branch_like_navigation.only_one_branch.documentation') + }, + { + href: '/documentation/analysis/pull-request/', + label: translate('branch_like_navigation.only_one_branch.pr_analysis') + } + ]} + title={translate('branch_like_navigation.only_one_branch.title')}> {plusIcon} - </DocTooltip> + </DocumentationTooltip> ); } } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap index 40ee3ab13ab..a76cb8e9184 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap @@ -122,15 +122,24 @@ exports[`CurrentBranchLikeRenderer should render correctly for project when bran > master </span> - <DocTooltip + <DocumentationTooltip + content="branch_like_navigation.no_branch_support.content" data-test="branches-support-disabled" - doc={Promise {}} + links={ + Array [ + Object { + "href": "https://redirect.sonarsource.com/editions/developer.html", + "label": "learn_more", + }, + ] + } + title="branch_like_navigation.no_branch_support.title" > <PlusCircleIcon fill="#4b9fd5" size={12} /> - </DocTooltip> + </DocumentationTooltip> </span> `; @@ -178,14 +187,27 @@ exports[`CurrentBranchLikeRenderer should render correctly for project when ther > master </span> - <DocTooltip + <DocumentationTooltip + content="branch_like_navigation.only_one_branch.content" data-test="only-one-branch-like" - doc={Promise {}} + links={ + Array [ + Object { + "href": "/documentation/branches/overview/", + "label": "branch_like_navigation.only_one_branch.documentation", + }, + Object { + "href": "/documentation/analysis/pull-request/", + "label": "branch_like_navigation.only_one_branch.pr_analysis", + }, + ] + } + title="branch_like_navigation.only_one_branch.title" > <PlusCircleIcon fill="#4b9fd5" size={12} /> - </DocTooltip> + </DocumentationTooltip> </span> `; diff --git a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx index 44a7a8a386f..8fd903ea286 100644 --- a/server/sonar-web/src/main/js/app/utils/startReactApp.tsx +++ b/server/sonar-web/src/main/js/app/utils/startReactApp.tsx @@ -42,7 +42,6 @@ import groupsRoutes from '../../apps/groups/routes'; import Issues from '../../apps/issues/components/AppContainer'; import { maintenanceRoutes, setupRoutes } from '../../apps/maintenance/routes'; import marketplaceRoutes from '../../apps/marketplace/routes'; -import organizationsRoutes from '../../apps/organizations/routes'; import overviewRoutes from '../../apps/overview/routes'; import permissionTemplatesRoutes from '../../apps/permission-templates/routes'; import { globalPermissionsRoutes, projectPermissionsRoutes } from '../../apps/permissions/routes'; @@ -299,7 +298,6 @@ export default function startReactApp( path="issues" component={withIndexationGuard(Issues, PageContext.Issues)} /> - <RouteWithChildRoutes path="organizations" childRoutes={organizationsRoutes} /> <RouteWithChildRoutes path="projects" childRoutes={projectsRoutes} /> <RouteWithChildRoutes path="quality_gates" childRoutes={qualityGatesRoutes} /> <Route diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx index cff73265b60..ed15f030dea 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/ProfileFacet.tsx @@ -22,7 +22,7 @@ import { sortBy } from 'lodash'; import * as React from 'react'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { Profile } from '../../../api/quality-profiles'; -import DocTooltip from '../../../components/docs/DocTooltip'; +import DocumentationTooltip from '../../../components/common/DocumentationTooltip'; import FacetBox from '../../../components/facet/FacetBox'; import FacetHeader from '../../../components/facet/FacetHeader'; import FacetItem from '../../../components/facet/FacetItem'; @@ -169,9 +169,15 @@ export default class ProfileFacet extends React.PureComponent<Props> { onClick={this.handleHeaderClick} open={this.props.open} values={this.getTextValue()}> - <DocTooltip + <DocumentationTooltip className="spacer-left" - doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/rules/rules-quality-profiles.md')} + content={translate('coding_rules.facet.qprofile.help')} + links={[ + { + href: '/documentation/instance-administration/quality-profiles/', + label: translate('coding_rules.facet.qprofile.link') + } + ]} /> </FacetHeader> diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx index 10e5f8729d2..fecab52d5ee 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetails.tsx @@ -20,11 +20,11 @@ import * as React from 'react'; import { Button } from 'sonar-ui-common/components/controls/buttons'; import ConfirmButton from 'sonar-ui-common/components/controls/ConfirmButton'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; import { Profile } from '../../../api/quality-profiles'; import { deleteRule, getRuleDetails, updateRule } from '../../../api/rules'; -import DocTooltip from '../../../components/docs/DocTooltip'; import { Activation, Query } from '../query'; import CustomRuleButton from './CustomRuleButton'; import RuleDetailsCustomRules from './RuleDetailsCustomRules'; @@ -226,11 +226,13 @@ export default class RuleDetails extends React.PureComponent<Props, State> { onClick={onClick}> {translate('delete')} </Button> - <DocTooltip + <HelpTooltip className="spacer-left" - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/rules/custom-rule-removal.md' - )} + overlay={ + <div className="big-padded-top big-padded-bottom"> + {translate('coding_rules.custom_rule.removal')} + </div> + } /> </> )} diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx index 88c76616153..c0819178da4 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/RuleDetailsMeta.tsx @@ -21,13 +21,13 @@ import * as React from 'react'; import { Link } from 'react-router'; import { ButtonLink } from 'sonar-ui-common/components/controls/buttons'; import Dropdown from 'sonar-ui-common/components/controls/Dropdown'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; import IssueTypeIcon from 'sonar-ui-common/components/icons/IssueTypeIcon'; import LinkIcon from 'sonar-ui-common/components/icons/LinkIcon'; import DateFormatter from 'sonar-ui-common/components/intl/DateFormatter'; import { PopupPlacement } from 'sonar-ui-common/components/ui/popups'; import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../../components/docs/DocTooltip'; import SeverityHelper from '../../../components/shared/SeverityHelper'; import TagsList from '../../../components/tags/TagsList'; import { getRuleUrl } from '../../../helpers/urls'; @@ -169,9 +169,9 @@ export default class RuleDetailsMeta extends React.PureComponent<Props> { {translate('coding_rules.show_template')} </Link> {')'} - <DocTooltip + <HelpTooltip className="little-spacer-left" - doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/rules/custom-rules.md')} + overlay={translate('coding_rules.custom_rule.help')} /> </li> ); diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx index af73da1278f..c90892f594c 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/TemplateFacet.tsx @@ -18,8 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../../components/docs/DocTooltip'; import Facet, { BasicProps } from './Facet'; interface Props extends T.Omit<BasicProps, 'onChange' | 'values'> { @@ -56,9 +56,13 @@ export default class TemplateFacet extends React.PureComponent<Props> { renderTextName={this.renderName} singleSelection={true} values={value !== undefined ? [String(value)] : []}> - <DocTooltip + <HelpTooltip className="spacer-left" - doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/rules/rule-templates.md')} + overlay={ + <div className="big-padded-top big-padded-bottom"> + {translate('coding_rules.rule_template.help')} + </div> + } /> </Facet> ); diff --git a/server/sonar-web/src/main/js/apps/create/components/BillingFormShim.tsx b/server/sonar-web/src/main/js/apps/create/components/BillingFormShim.tsx deleted file mode 100644 index bc3cf8b9874..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/BillingFormShim.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; - -interface ChildrenProps { - onSubmit: React.FormEventHandler; - processingUpgrade: boolean; - renderFormFields: () => React.ReactNode; - renderNextCharge: (className?: string) => React.ReactNode; - renderRecap: () => React.ReactNode; - renderSubmitButton: (submitText?: string) => React.ReactNode; - renderSubmitGroup: (submitText?: string) => React.ReactNode; -} - -interface Props { - children: (props: ChildrenProps) => React.ReactNode; - initialCountry?: string; - currentUser: T.CurrentUser; - onCommit: () => void | Promise<void>; - onFailToUpgrade?: () => void; - organizationKey: string | (() => Promise<string>); - subscriptionPlans: T.SubscriptionPlan[]; -} - -export default class BillingFormShim extends React.Component<Props> { - render() { - const { BillingForm } = (window as any).SonarBilling; - return <BillingForm {...this.props} />; - } -} diff --git a/server/sonar-web/src/main/js/apps/create/components/FreeCardPlan.tsx b/server/sonar-web/src/main/js/apps/create/components/FreeCardPlan.tsx deleted file mode 100644 index 6d7f0aaab3e..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/FreeCardPlan.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import RadioCard, { RadioCardProps } from 'sonar-ui-common/components/controls/RadioCard'; -import { Alert } from 'sonar-ui-common/components/ui/Alert'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { formatPrice } from '../organization/utils'; - -interface Props extends RadioCardProps { - almName?: string; - hasWarning: boolean; -} - -export default function FreeCardPlan({ almName, hasWarning, ...props }: Props) { - const showInfo = almName && props.disabled; - const showWarning = almName && hasWarning && !props.disabled; - - return ( - <RadioCard title={translate('billing.free_plan.title')} titleInfo={formatPrice(0)} {...props}> - <div className="spacer-left"> - <ul className="big-spacer-left note"> - <li className="little-spacer-bottom"> - {translate('billing.free_plan.all_projects_analyzed_public')} - </li> - <li>{translate('billing.free_plan.anyone_can_browse_source_code')}</li> - </ul> - </div> - {showWarning && ( - <Alert variant="warning"> - <FormattedMessage - defaultMessage={translate('billing.free_plan.private_repo_warning')} - id="billing.free_plan.private_repo_warning" - values={{ alm: almName }} - /> - </Alert> - )} - {showInfo && ( - <Alert variant="info"> - <FormattedMessage - defaultMessage={translate('billing.free_plan.not_available_info')} - id="billing.free_plan.not_available_info" - values={{ alm: almName }} - /> - </Alert> - )} - </RadioCard> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/components/OrganizationAvatarInput.tsx b/server/sonar-web/src/main/js/apps/create/components/OrganizationAvatarInput.tsx deleted file mode 100644 index 78495b31f6a..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/OrganizationAvatarInput.tsx +++ /dev/null @@ -1,112 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as classNames from 'classnames'; -import * as React from 'react'; -import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { isWebUri } from 'valid-url'; -import OrganizationAvatar from '../../../components/common/OrganizationAvatar'; - -interface Props { - initialValue?: string; - name?: string; - onChange: (value: string | undefined) => void; -} - -interface State { - editing: boolean; - error?: string; - touched: boolean; - value: string; -} - -export default class OrganizationAvatarInput extends React.PureComponent<Props, State> { - state: State = { error: undefined, editing: false, touched: false, value: '' }; - - componentDidMount() { - if (this.props.initialValue) { - const value = this.props.initialValue; - const error = this.validateUrl(value); - this.setState({ error, touched: Boolean(error), value }); - } - } - - handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const value = event.currentTarget.value.trim(); - const error = this.validateUrl(value); - this.setState({ error, touched: true, value }); - this.props.onChange(error === undefined ? value : undefined); - }; - - handleBlur = () => { - this.setState({ editing: false }); - }; - - handleFocus = () => { - this.setState({ editing: true }); - }; - - validateUrl(url: string) { - if (url.length > 0 && !isWebUri(url)) { - return translate('onboarding.create_organization.url.error'); - } - return undefined; - } - - render() { - const isInvalid = this.state.touched && !this.state.editing && this.state.error !== undefined; - const isValidUrl = this.state.error === undefined && this.state.value !== ''; - const isValid = this.state.touched && isValidUrl; - return ( - <ValidationInput - description={translate('onboarding.create_organization.avatar.description')} - error={this.state.error} - id="organization-avatar" - isInvalid={isInvalid} - isValid={isValid} - label={translate('onboarding.create_organization.avatar')}> - <> - {(isValidUrl || this.props.name) && ( - <OrganizationAvatar - className="display-block spacer-bottom" - organization={{ - avatar: isValidUrl ? this.state.value : undefined, - name: this.props.name || '' - }} - /> - )} - <input - className={classNames('input-super-large', 'text-middle', { - 'is-invalid': isInvalid, - 'is-valid': isValid - })} - id="organization-avatar" - onBlur={this.handleBlur} - onChange={this.handleChange} - onFocus={this.handleFocus} - placeholder={translate('onboarding.create_organization.avatar.placeholder')} - type="text" - value={this.state.value} - /> - </> - </ValidationInput> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/components/OrganizationKeyInput.tsx b/server/sonar-web/src/main/js/apps/create/components/OrganizationKeyInput.tsx deleted file mode 100644 index f1f88be187b..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/OrganizationKeyInput.tsx +++ /dev/null @@ -1,135 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as classNames from 'classnames'; -import { debounce } from 'lodash'; -import * as React from 'react'; -import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { getHostUrl } from 'sonar-ui-common/helpers/urls'; -import { getOrganization } from '../../../api/organizations'; - -interface Props { - initialValue?: string; - onChange: (value: string | undefined) => void; -} - -interface State { - error?: string; - touched: boolean; - validating: boolean; - value: string; -} - -export default class OrganizationKeyInput extends React.PureComponent<Props, State> { - mounted = false; - constructor(props: Props) { - super(props); - this.state = { error: undefined, touched: false, validating: false, value: '' }; - this.checkFreeKey = debounce(this.checkFreeKey, 250); - } - - componentDidMount() { - this.mounted = true; - if (this.props.initialValue !== undefined) { - this.setState({ value: this.props.initialValue }); - this.validateKey(this.props.initialValue); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - checkFreeKey = (key: string) => { - this.setState({ validating: true }); - return getOrganization(key) - .then(organization => { - if (this.mounted) { - if (organization === undefined) { - this.setState({ error: undefined, validating: false }); - this.props.onChange(key); - } else { - this.setState({ - error: translate('onboarding.create_organization.organization_name.taken'), - touched: true, - validating: false - }); - this.props.onChange(undefined); - } - } - }) - .catch(() => { - if (this.mounted) { - this.setState({ error: undefined, validating: false }); - this.props.onChange(key); - } - }); - }; - - handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const { value } = event.currentTarget; - this.setState({ touched: true, value }); - this.validateKey(value); - }; - - validateKey(key: string) { - if (key.length > 255 || !/^[a-z0-9][a-z0-9-]*[a-z0-9]?$/.test(key)) { - this.setState({ - error: translate('onboarding.create_organization.organization_name.error'), - touched: true - }); - this.props.onChange(undefined); - } else { - this.checkFreeKey(key); - } - } - - render() { - const isInvalid = this.state.touched && this.state.error !== undefined; - const isValid = this.state.touched && !this.state.validating && this.state.error === undefined; - return ( - <ValidationInput - error={this.state.error} - id="organization-key" - isInvalid={isInvalid} - isValid={isValid} - label={translate('onboarding.create_organization.organization_name')} - required={true}> - <div className="display-inline-flex-baseline"> - <span className="little-spacer-right"> - {getHostUrl().replace(/https*:\/\//, '') + '/organizations/'} - </span> - <input - autoFocus={true} - className={classNames('input-super-large', { - 'is-invalid': isInvalid, - 'is-valid': isValid - })} - id="organization-key" - maxLength={255} - onChange={this.handleChange} - type="text" - value={this.state.value} - /> - </div> - </ValidationInput> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/components/OrganizationSelect.tsx b/server/sonar-web/src/main/js/apps/create/components/OrganizationSelect.tsx deleted file mode 100644 index 34baa319fc5..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/OrganizationSelect.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { sortBy } from 'lodash'; -import * as React from 'react'; -import Select from 'sonar-ui-common/components/controls/Select'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; -import { sanitizeAlmId } from '../../../helpers/almIntegrations'; - -interface Props { - hideIcons?: boolean; - onChange: (organization: T.Organization) => void; - organization: string; - organizations: T.Organization[]; -} - -export default function OrganizationSelect({ - hideIcons, - onChange, - organization, - organizations -}: Props) { - const optionRenderer = getOptionRenderer(hideIcons); - return ( - <Select - autoFocus={!organization} - className="input-super-large" - clearable={false} - id="select-organization" - labelKey="name" - onChange={onChange} - optionRenderer={optionRenderer} - options={sortBy(organizations, o => o.name.toLowerCase())} - placeholder={translate('onboarding.import_organization.choose_organization')} - required={true} - value={organization} - valueKey="key" - valueRenderer={optionRenderer} - /> - ); -} - -export function getOptionRenderer(hideIcons?: boolean) { - return function optionRenderer(organization: T.Organization) { - const icon = organization.alm - ? `sonarcloud/${sanitizeAlmId(organization.alm.key)}` - : 'sonarcloud-square-logo'; - const isPaidOrg = organization.subscription === 'PAID'; - return ( - <div className="display-flex-space-between"> - <span className="text-ellipsis flex-1"> - {!hideIcons && ( - <img - alt={organization.alm ? organization.alm.key : 'SonarCloud'} - className="little-spacer-right" - height={14} - src={`${getBaseUrl()}/images/${icon}.svg`} - /> - )} - {organization.name} - <span className="note little-spacer-left">{organization.key}</span> - </span> - {isPaidOrg && <div className="badge">{translate('organization.paid_plan.badge')}</div>} - </div> - ); - }; -} diff --git a/server/sonar-web/src/main/js/apps/create/components/OrganizationUrlInput.tsx b/server/sonar-web/src/main/js/apps/create/components/OrganizationUrlInput.tsx deleted file mode 100644 index 5f716947f02..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/OrganizationUrlInput.tsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as classNames from 'classnames'; -import * as React from 'react'; -import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { isWebUri } from 'valid-url'; - -interface Props { - initialValue?: string; - onChange: (value: string | undefined) => void; -} - -interface State { - editing: boolean; - error?: string; - touched: boolean; - value: string; -} - -export default class OrganizationUrlInput extends React.PureComponent<Props, State> { - state: State = { error: undefined, editing: false, touched: false, value: '' }; - - componentDidMount() { - if (this.props.initialValue) { - const value = this.props.initialValue; - const error = this.validateUrl(value); - this.setState({ error, touched: Boolean(error), value }); - } - } - - handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const value = event.currentTarget.value.trim(); - const error = this.validateUrl(value); - this.setState({ error, touched: true, value }); - this.props.onChange(error === undefined ? value : undefined); - }; - - handleBlur = () => { - this.setState({ editing: false }); - }; - - handleFocus = () => { - this.setState({ editing: true }); - }; - - validateUrl(url: string) { - if (url.length > 0 && !isWebUri(url)) { - return translate('onboarding.create_organization.url.error'); - } - return undefined; - } - - render() { - const isInvalid = this.state.touched && !this.state.editing && this.state.error !== undefined; - const isValid = this.state.touched && this.state.error === undefined && this.state.value !== ''; - return ( - <ValidationInput - error={this.state.error} - id="organization-url" - isInvalid={isInvalid} - isValid={isValid} - label={translate('onboarding.create_organization.url')}> - <input - className={classNames('input-super-large', 'text-middle', { - 'is-invalid': isInvalid, - 'is-valid': isValid - })} - id="organization-url" - onBlur={this.handleBlur} - onChange={this.handleChange} - onFocus={this.handleFocus} - type="text" - value={this.state.value} - /> - </ValidationInput> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/components/PaidCardPlan.tsx b/server/sonar-web/src/main/js/apps/create/components/PaidCardPlan.tsx deleted file mode 100644 index a8150a08a85..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/PaidCardPlan.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router'; -import RadioCard, { RadioCardProps } from 'sonar-ui-common/components/controls/RadioCard'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { formatPrice } from '../organization/utils'; -import UpgradeOrganizationAdvantages from './UpgradeOrganizationAdvantages'; - -interface Props extends RadioCardProps { - isRecommended: boolean; - startingPrice?: number; -} - -export default function PaidCardPlan({ isRecommended, startingPrice, ...props }: Props) { - return ( - <RadioCard - recommended={isRecommended ? translate('billing.paid_plan.recommended') : undefined} - title={translate('billing.paid_plan.title')} - titleInfo={ - startingPrice !== undefined && ( - <FormattedMessage - defaultMessage={translate('billing.price_from_x')} - id="billing.price_from_x" - values={{ - price: <span className="big">{formatPrice(startingPrice)}</span> - }} - /> - ) - } - {...props}> - <UpgradeOrganizationAdvantages /> - <div className="big-spacer-left"> - <Link className="spacer-left" target="_blank" to="/about/pricing"> - {translate('billing.pricing.learn_more')} - </Link> - </div> - </RadioCard> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationAdvantages.tsx b/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationAdvantages.tsx deleted file mode 100644 index 6cfe7cf1526..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationAdvantages.tsx +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import CheckIcon from 'sonar-ui-common/components/icons/CheckIcon'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { colors } from '../../../app/theme'; - -const TRIAL_DURATION_DAYS = 14; - -export default function UpgradeOrganizationAdvantages() { - return ( - <ul className="note"> - <Advantage>{translate('billing.upgrade_box.unlimited_private_projects')}</Advantage> - <Advantage>{translate('billing.upgrade_box.strict_control_private_data')}</Advantage> - <Advantage>{translate('billing.upgrade_box.cancel_anytime')}</Advantage> - <Advantage> - <strong> - {translateWithParameters('billing.upgrade_box.free_trial_x', TRIAL_DURATION_DAYS)} - </strong> - </Advantage> - </ul> - ); -} - -export function Advantage({ children }: { children: React.ReactNode }) { - return ( - <li className="display-flex-center little-spacer-bottom"> - <CheckIcon className="spacer-right" fill={colors.lightGreen} /> - {children} - </li> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationBox.tsx b/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationBox.tsx deleted file mode 100644 index c5466166692..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationBox.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router'; -import { Button } from 'sonar-ui-common/components/controls/buttons'; -import RadioCard from 'sonar-ui-common/components/controls/RadioCard'; -import { hasMessage, translate } from 'sonar-ui-common/helpers/l10n'; -import { getSubscriptionPlans } from '../../../api/billing'; -import { formatPrice } from '../organization/utils'; -import UpgradeOrganizationAdvantages from './UpgradeOrganizationAdvantages'; -import UpgradeOrganizationModal from './UpgradeOrganizationModal'; - -interface Props { - className?: string; - insideModal?: boolean; - onOrganizationUpgrade: () => void; - organization: T.Organization; -} - -interface State { - subscriptionPlans: T.SubscriptionPlan[]; - upgradeOrganizationModal: boolean; -} - -export default class UpgradeOrganizationBox extends React.PureComponent<Props, State> { - mounted = false; - state: State = { subscriptionPlans: [], upgradeOrganizationModal: false }; - - componentDidMount() { - this.mounted = true; - this.fetchSubscriptionPlans(); - } - - componentWillUnmount() { - this.mounted = false; - } - - fetchSubscriptionPlans = () => { - return getSubscriptionPlans().then(subscriptionPlans => { - if (this.mounted) { - this.setState({ subscriptionPlans }); - } - }); - }; - - handleUpgradeClick = () => { - this.setState({ upgradeOrganizationModal: true }); - }; - - handleUpgradeOrganizationModalClose = () => { - if (this.mounted) { - this.setState({ upgradeOrganizationModal: false }); - } - }; - - handleOrganizationUpgrade = () => { - this.props.onOrganizationUpgrade(); - this.handleUpgradeOrganizationModalClose(); - }; - - render() { - if (!hasMessage('billing.upgrade_box.header')) { - return null; - } - - const { subscriptionPlans, upgradeOrganizationModal } = this.state; - const startingPrice = subscriptionPlans[0] && subscriptionPlans[0].price; - - return ( - <> - <RadioCard - className={this.props.className} - title={translate('billing.upgrade_box.header')} - titleInfo={ - startingPrice !== undefined && ( - <FormattedMessage - defaultMessage={translate('billing.price_from_x')} - id="billing.price_from_x" - values={{ - price: <span className="big">{formatPrice(startingPrice)}</span> - }} - /> - ) - }> - <> - <UpgradeOrganizationAdvantages /> - <div className="big-spacer-left"> - <Button className="js-upgrade-organization" onClick={this.handleUpgradeClick}> - {translate('billing.paid_plan.upgrade')} - </Button> - <Link className="spacer-left" target="_blank" to="/about/pricing"> - {translate('billing.pricing.learn_more')} - </Link> - </div> - </> - </RadioCard> - {upgradeOrganizationModal && ( - <UpgradeOrganizationModal - insideModal={this.props.insideModal} - onClose={this.handleUpgradeOrganizationModalClose} - onUpgradeDone={this.handleOrganizationUpgrade} - organization={this.props.organization} - subscriptionPlans={subscriptionPlans} - /> - )} - </> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationModal.tsx b/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationModal.tsx deleted file mode 100644 index 7e1b63ae176..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/UpgradeOrganizationModal.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { ResetButtonLink } from 'sonar-ui-common/components/controls/buttons'; -import Modal from 'sonar-ui-common/components/controls/Modal'; -import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { withCurrentUser } from '../../../components/hoc/withCurrentUser'; -import { getExtensionStart } from '../../../helpers/extensions'; -import BillingFormShim from './BillingFormShim'; -import UpgradeOrganizationAdvantages from './UpgradeOrganizationAdvantages'; - -const BillingForm = withCurrentUser(BillingFormShim); - -interface Props { - insideModal?: boolean; - onUpgradeDone: () => void; - onClose: () => void; - organization: T.Organization; - subscriptionPlans: T.SubscriptionPlan[]; -} - -interface State { - ready: boolean; -} - -export default class UpgradeOrganizationModal extends React.PureComponent<Props, State> { - mounted = false; - state: State = { ready: false }; - - componentDidMount() { - this.mounted = true; - getExtensionStart('billing/billing').then( - () => { - if (this.mounted) { - this.setState({ ready: true }); - } - }, - () => {} - ); - } - - componentWillUnmount() { - this.mounted = false; - } - - render() { - const header = translate('billing.upgrade_box.upgrade_to_paid_plan'); - - if (!this.state.ready) { - return null; - } - - return ( - <Modal - contentLabel={header} - noBackdrop={this.props.insideModal} - onRequestClose={this.props.onClose} - shouldCloseOnOverlayClick={false} - size="medium"> - <div className="modal-head"> - <h2>{header}</h2> - </div> - <BillingForm - onCommit={this.props.onUpgradeDone} - organizationKey={this.props.organization.key} - subscriptionPlans={this.props.subscriptionPlans}> - {({ - onSubmit, - processingUpgrade, - renderFormFields, - renderNextCharge, - renderRecap, - renderSubmitButton - }) => ( - <form id="organization-paid-plan-form" onSubmit={onSubmit}> - <div className="modal-body modal-container"> - <div className="huge-spacer-bottom"> - <p className="spacer-bottom"> - <FormattedMessage - defaultMessage={translate('billing.upgrade.org_x_advantages')} - id="billing.coupon.description" - values={{ - org: <strong>{this.props.organization.name}</strong> - }} - /> - </p> - <UpgradeOrganizationAdvantages /> - </div> - {renderFormFields()} - <div className="big-spacer-top">{renderRecap()}</div> - </div> - <footer className="modal-foot display-flex-center display-flex-space-between"> - {renderNextCharge() || <span />} - <div> - <DeferredSpinner loading={processingUpgrade} /> - {renderSubmitButton()} - <ResetButtonLink onClick={this.props.onClose}> - {translate('cancel')} - </ResetButtonLink> - </div> - </footer> - </form> - )} - </BillingForm> - </Modal> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/components/__mocks__/BillingFormShim.tsx b/server/sonar-web/src/main/js/apps/create/components/__mocks__/BillingFormShim.tsx deleted file mode 100644 index ce16888ffaf..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__mocks__/BillingFormShim.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; - -export default class BillingFormShim extends React.Component<{ children: any }> { - render() { - return ( - <div id="BillingFormShim"> - {this.props.children({ - onSubmit: jest.fn(), - processingUpgrade: true, - renderFormFields: () => <div id="form-fields" />, - renderNextCharge: () => <div id="form-next-charge" />, - renderRecap: () => <div id="form-recap" />, - renderSubmitButton: () => <div id="form-submit" />, - renderSubmitGroup: () => <div id="submit-group" /> - })} - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/BillingFormShim-test.tsx b/server/sonar-web/src/main/js/apps/create/components/__tests__/BillingFormShim-test.tsx deleted file mode 100644 index 1fb03480e3f..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/BillingFormShim-test.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import BillingFormShim from '../BillingFormShim'; - -beforeAll(() => { - function BillingForm() { - return <div id="billing-form" />; - } - - (window as any).SonarBilling = { BillingForm }; -}); - -afterAll(() => { - delete (window as any).SonarBilling; -}); - -it('should render', () => { - expect( - shallow( - <BillingFormShim - currentUser={{ isLoggedIn: false }} - onCommit={jest.fn()} - organizationKey="org" - subscriptionPlans={[]}> - {() => <div id="inner-billing-form" />} - </BillingFormShim> - ) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationAvatarInput-test.tsx b/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationAvatarInput-test.tsx deleted file mode 100644 index 55a2ca81896..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationAvatarInput-test.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import OrganizationAvatarInput from '../OrganizationAvatarInput'; - -it('should render correctly', () => { - const wrapper = shallow( - <OrganizationAvatarInput initialValue="https://my.avatar" onChange={jest.fn()} /> - ); - expect(wrapper).toMatchSnapshot(); - wrapper.setState({ touched: true }); - expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot(); -}); - -it('should have an error when the avatar url is not valid', () => { - expect( - shallow(<OrganizationAvatarInput initialValue="whatever" onChange={jest.fn()} />) - .find('ValidationInput') - .prop('isInvalid') - ).toBe(true); -}); - -it('should display the fallback avatar when there is no url', () => { - expect( - shallow(<OrganizationAvatarInput initialValue="" name="Luke Skywalker" onChange={jest.fn()} />) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationKeyInput-test.tsx b/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationKeyInput-test.tsx deleted file mode 100644 index 667114a0da0..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationKeyInput-test.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { getOrganization } from '../../../../api/organizations'; -import OrganizationKeyInput from '../OrganizationKeyInput'; - -jest.mock('../../../../api/organizations', () => ({ - getOrganization: jest.fn().mockResolvedValue(undefined) -})); - -beforeEach(() => { - (getOrganization as jest.Mock<any>).mockClear(); -}); - -it('should render correctly', async () => { - const wrapper = shallow(<OrganizationKeyInput initialValue="key" onChange={jest.fn()} />); - expect(wrapper).toMatchSnapshot(); - wrapper.setState({ touched: true }); - await waitAndUpdate(wrapper); - expect(wrapper.find('ValidationInput').prop('isValid')).toBe(true); -}); - -it('should not display any status when the key is not defined', async () => { - const wrapper = shallow(<OrganizationKeyInput onChange={jest.fn()} />); - await waitAndUpdate(wrapper); - expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(false); - expect(wrapper.find('ValidationInput').prop('isValid')).toBe(false); -}); - -it('should have an error when the key is invalid', async () => { - const wrapper = shallow( - <OrganizationKeyInput initialValue="KEy-with#speci@l_char" onChange={jest.fn()} /> - ); - await waitAndUpdate(wrapper); - expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(true); -}); - -it('should have an error when the key already exists', async () => { - (getOrganization as jest.Mock<any>).mockResolvedValue({}); - const wrapper = shallow(<OrganizationKeyInput initialValue="" onChange={jest.fn()} />); - await waitAndUpdate(wrapper); - expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(true); -}); diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationSelect-test.tsx b/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationSelect-test.tsx deleted file mode 100644 index f248defd48b..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationSelect-test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockOrganization, mockOrganizationWithAlm } from '../../../../helpers/testMocks'; -import OrganizationSelect, { getOptionRenderer } from '../OrganizationSelect'; - -const organizations = [mockOrganization(), mockOrganizationWithAlm({ key: 'bar', name: 'Bar' })]; - -it('should render correctly', () => { - expect( - shallow( - <OrganizationSelect onChange={jest.fn()} organization="bar" organizations={organizations} /> - ) - ).toMatchSnapshot(); -}); - -it('should render options correctly', () => { - expect(shallow(getOptionRenderer()(organizations[0]))).toMatchSnapshot(); - expect(shallow(getOptionRenderer()(organizations[1]))).toMatchSnapshot(); - expect(shallow(getOptionRenderer(true)(organizations[0]))).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationUrlInput-test.tsx b/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationUrlInput-test.tsx deleted file mode 100644 index bda1bd73d4a..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationUrlInput-test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import OrganizationUrlInput from '../OrganizationUrlInput'; - -it('should render correctly', () => { - const wrapper = shallow( - <OrganizationUrlInput initialValue="http://my.website" onChange={jest.fn()} /> - ); - expect(wrapper).toMatchSnapshot(); - wrapper.setState({ touched: true }); - expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot(); -}); - -it('should have an error when the url is invalid', () => { - expect( - shallow(<OrganizationUrlInput initialValue="whatever" onChange={jest.fn()} />) - .find('ValidationInput') - .prop('isInvalid') - ).toBe(true); -}); diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/PaidCardPlan-test.tsx b/server/sonar-web/src/main/js/apps/create/components/__tests__/PaidCardPlan-test.tsx deleted file mode 100644 index 5e6e7f55256..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/PaidCardPlan-test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import PaidCardPlan from '../PaidCardPlan'; - -it('should render correctly', () => { - expect(shallow(<PaidCardPlan isRecommended={true} startingPrice={10} />)).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/UpgradeOrganizationAdvantages-test.tsx b/server/sonar-web/src/main/js/apps/create/components/__tests__/UpgradeOrganizationAdvantages-test.tsx deleted file mode 100644 index 388b00f408b..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/UpgradeOrganizationAdvantages-test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import UpgradeOrganizationAdvantages from '../UpgradeOrganizationAdvantages'; - -it('should render correctly', () => { - expect(shallow(<UpgradeOrganizationAdvantages />)).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/UpgradeOrganizationBox-test.tsx b/server/sonar-web/src/main/js/apps/create/components/__tests__/UpgradeOrganizationBox-test.tsx deleted file mode 100644 index ad1bab841e8..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/UpgradeOrganizationBox-test.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { hasMessage } from 'sonar-ui-common/helpers/l10n'; -import { click, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { getSubscriptionPlans } from '../../../../api/billing'; -import UpgradeOrganizationBox from '../UpgradeOrganizationBox'; - -jest.mock('sonar-ui-common/helpers/l10n', () => ({ - ...jest.requireActual('sonar-ui-common/helpers/l10n'), - hasMessage: jest.fn().mockReturnValue(true) -})); - -jest.mock('../../../../api/billing', () => ({ - getSubscriptionPlans: jest.fn().mockResolvedValue([{ maxNcloc: 100000, price: 10 }]) -})); - -const organization = { key: 'foo', name: 'Foo' }; - -beforeEach(() => { - (hasMessage as jest.Mock<any>).mockClear(); - (getSubscriptionPlans as jest.Mock<any>).mockClear(); -}); - -it('should not render', () => { - (hasMessage as jest.Mock<any>).mockReturnValueOnce(false); - expect( - shallow( - <UpgradeOrganizationBox onOrganizationUpgrade={jest.fn()} organization={organization} /> - ).type() - ).toBeNull(); -}); - -it('should render correctly', async () => { - const wrapper = shallow( - <UpgradeOrganizationBox onOrganizationUpgrade={jest.fn()} organization={organization} /> - ); - await waitAndUpdate(wrapper); - expect(getSubscriptionPlans).toHaveBeenCalled(); - expect(wrapper).toMatchSnapshot(); - click(wrapper.find('Button')); - expect(wrapper.find('UpgradeOrganizationModal').exists()).toBe(true); -}); diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/UpgradeOrganizationModal-test.tsx b/server/sonar-web/src/main/js/apps/create/components/__tests__/UpgradeOrganizationModal-test.tsx deleted file mode 100644 index b68be4d410d..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/UpgradeOrganizationModal-test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { getExtensionStart } from '../../../../helpers/extensions'; -import UpgradeOrganizationModal from '../UpgradeOrganizationModal'; - -jest.mock('../../../../helpers/extensions', () => ({ - getExtensionStart: jest.fn().mockResolvedValue(undefined) -})); - -const organization = { key: 'foo', name: 'Foo' }; - -it('should render correctly', async () => { - const wrapper = shallow( - <UpgradeOrganizationModal - onClose={jest.fn()} - onUpgradeDone={jest.fn()} - organization={organization} - subscriptionPlans={[]} - /> - ); - await waitAndUpdate(wrapper); - expect(getExtensionStart).toHaveBeenCalled(); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/BillingFormShim-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/BillingFormShim-test.tsx.snap deleted file mode 100644 index fc6fb48982e..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/BillingFormShim-test.tsx.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render 1`] = ` -<BillingForm - currentUser={ - Object { - "isLoggedIn": false, - } - } - onCommit={[MockFunction]} - organizationKey="org" - subscriptionPlans={Array []} -> - <Component /> -</BillingForm> -`; diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/FreeCardPlan-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/FreeCardPlan-test.tsx.snap deleted file mode 100644 index 096ad74b503..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/FreeCardPlan-test.tsx.snap +++ /dev/null @@ -1,101 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render 1`] = ` -<RadioCard - title="billing.free_plan.title" - titleInfo="billing.price_format.0" -> - <div - className="spacer-left" - > - <ul - className="big-spacer-left note" - > - <li - className="little-spacer-bottom" - > - billing.free_plan.all_projects_analyzed_public - </li> - <li> - billing.free_plan.anyone_can_browse_source_code - </li> - </ul> - </div> -</RadioCard> -`; - -exports[`should render disabled with info 1`] = ` -<RadioCard - disabled={true} - title="billing.free_plan.title" - titleInfo="billing.price_format.0" -> - <div - className="spacer-left" - > - <ul - className="big-spacer-left note" - > - <li - className="little-spacer-bottom" - > - billing.free_plan.all_projects_analyzed_public - </li> - <li> - billing.free_plan.anyone_can_browse_source_code - </li> - </ul> - </div> - <Alert - variant="info" - > - <FormattedMessage - defaultMessage="billing.free_plan.not_available_info" - id="billing.free_plan.not_available_info" - values={ - Object { - "alm": "GitHub", - } - } - /> - </Alert> -</RadioCard> -`; - -exports[`should render with warning 1`] = ` -<RadioCard - selected={true} - title="billing.free_plan.title" - titleInfo="billing.price_format.0" -> - <div - className="spacer-left" - > - <ul - className="big-spacer-left note" - > - <li - className="little-spacer-bottom" - > - billing.free_plan.all_projects_analyzed_public - </li> - <li> - billing.free_plan.anyone_can_browse_source_code - </li> - </ul> - </div> - <Alert - variant="warning" - > - <FormattedMessage - defaultMessage="billing.free_plan.private_repo_warning" - id="billing.free_plan.private_repo_warning" - values={ - Object { - "alm": "GitHub", - } - } - /> - </Alert> -</RadioCard> -`; diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationAvatarInput-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationAvatarInput-test.tsx.snap deleted file mode 100644 index a2f801bc5d4..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationAvatarInput-test.tsx.snap +++ /dev/null @@ -1,63 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display the fallback avatar when there is no url 1`] = ` -<ValidationInput - description="onboarding.create_organization.avatar.description" - id="organization-avatar" - isInvalid={false} - isValid={false} - label="onboarding.create_organization.avatar" -> - <OrganizationAvatar - className="display-block spacer-bottom" - organization={ - Object { - "avatar": undefined, - "name": "Luke Skywalker", - } - } - /> - <input - className="input-super-large text-middle" - id="organization-avatar" - onBlur={[Function]} - onChange={[Function]} - onFocus={[Function]} - placeholder="onboarding.create_organization.avatar.placeholder" - type="text" - value="" - /> -</ValidationInput> -`; - -exports[`should render correctly 1`] = ` -<ValidationInput - description="onboarding.create_organization.avatar.description" - id="organization-avatar" - isInvalid={false} - isValid={false} - label="onboarding.create_organization.avatar" -> - <OrganizationAvatar - className="display-block spacer-bottom" - organization={ - Object { - "avatar": "https://my.avatar", - "name": "", - } - } - /> - <input - className="input-super-large text-middle" - id="organization-avatar" - onBlur={[Function]} - onChange={[Function]} - onFocus={[Function]} - placeholder="onboarding.create_organization.avatar.placeholder" - type="text" - value="https://my.avatar" - /> -</ValidationInput> -`; - -exports[`should render correctly 2`] = `true`; diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationKeyInput-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationKeyInput-test.tsx.snap deleted file mode 100644 index 5a4f352f826..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationKeyInput-test.tsx.snap +++ /dev/null @@ -1,30 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<ValidationInput - id="organization-key" - isInvalid={false} - isValid={false} - label="onboarding.create_organization.organization_name" - required={true} -> - <div - className="display-inline-flex-baseline" - > - <span - className="little-spacer-right" - > - localhost/organizations/ - </span> - <input - autoFocus={true} - className="input-super-large" - id="organization-key" - maxLength={255} - onChange={[Function]} - type="text" - value="key" - /> - </div> -</ValidationInput> -`; diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationSelect-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationSelect-test.tsx.snap deleted file mode 100644 index 46e9dbfcba1..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationSelect-test.tsx.snap +++ /dev/null @@ -1,99 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<Select - autoFocus={false} - className="input-super-large" - clearable={false} - id="select-organization" - labelKey="name" - onChange={[MockFunction]} - optionRenderer={[Function]} - options={ - Array [ - Object { - "alm": Object { - "key": "github", - "membersSync": false, - "personal": false, - "url": "https://github.com/foo", - }, - "key": "bar", - "name": "Bar", - }, - Object { - "key": "foo", - "name": "Foo", - }, - ] - } - placeholder="onboarding.import_organization.choose_organization" - required={true} - value="bar" - valueKey="key" - valueRenderer={[Function]} -/> -`; - -exports[`should render options correctly 1`] = ` -<div - className="display-flex-space-between" -> - <span - className="text-ellipsis flex-1" - > - <img - alt="SonarCloud" - className="little-spacer-right" - height={14} - src="/images/sonarcloud-square-logo.svg" - /> - Foo - <span - className="note little-spacer-left" - > - foo - </span> - </span> -</div> -`; - -exports[`should render options correctly 2`] = ` -<div - className="display-flex-space-between" -> - <span - className="text-ellipsis flex-1" - > - <img - alt="github" - className="little-spacer-right" - height={14} - src="/images/sonarcloud/github.svg" - /> - Bar - <span - className="note little-spacer-left" - > - bar - </span> - </span> -</div> -`; - -exports[`should render options correctly 3`] = ` -<div - className="display-flex-space-between" -> - <span - className="text-ellipsis flex-1" - > - Foo - <span - className="note little-spacer-left" - > - foo - </span> - </span> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationUrlInput-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationUrlInput-test.tsx.snap deleted file mode 100644 index d3f571b4db8..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationUrlInput-test.tsx.snap +++ /dev/null @@ -1,22 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<ValidationInput - id="organization-url" - isInvalid={false} - isValid={false} - label="onboarding.create_organization.url" -> - <input - className="input-super-large text-middle" - id="organization-url" - onBlur={[Function]} - onChange={[Function]} - onFocus={[Function]} - type="text" - value="http://my.website" - /> -</ValidationInput> -`; - -exports[`should render correctly 2`] = `true`; diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/PaidCardPlan-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/PaidCardPlan-test.tsx.snap deleted file mode 100644 index 94ac5a89244..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/PaidCardPlan-test.tsx.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<RadioCard - recommended="billing.paid_plan.recommended" - title="billing.paid_plan.title" - titleInfo={ - <FormattedMessage - defaultMessage="billing.price_from_x" - id="billing.price_from_x" - values={ - Object { - "price": <span - className="big" - > - billing.price_format.10 - </span>, - } - } - /> - } -> - <UpgradeOrganizationAdvantages /> - <div - className="big-spacer-left" - > - <Link - className="spacer-left" - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to="/about/pricing" - > - billing.pricing.learn_more - </Link> - </div> -</RadioCard> -`; diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationAdvantages-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationAdvantages-test.tsx.snap deleted file mode 100644 index dce3b0b46e6..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationAdvantages-test.tsx.snap +++ /dev/null @@ -1,22 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<ul - className="note" -> - <Advantage> - billing.upgrade_box.unlimited_private_projects - </Advantage> - <Advantage> - billing.upgrade_box.strict_control_private_data - </Advantage> - <Advantage> - billing.upgrade_box.cancel_anytime - </Advantage> - <Advantage> - <strong> - billing.upgrade_box.free_trial_x.14 - </strong> - </Advantage> -</ul> -`; diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationBox-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationBox-test.tsx.snap deleted file mode 100644 index e389049612a..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationBox-test.tsx.snap +++ /dev/null @@ -1,45 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<Fragment> - <RadioCard - title="billing.upgrade_box.header" - titleInfo={ - <FormattedMessage - defaultMessage="billing.price_from_x" - id="billing.price_from_x" - values={ - Object { - "price": <span - className="big" - > - billing.price_format.10 - </span>, - } - } - /> - } - > - <UpgradeOrganizationAdvantages /> - <div - className="big-spacer-left" - > - <Button - className="js-upgrade-organization" - onClick={[Function]} - > - billing.paid_plan.upgrade - </Button> - <Link - className="spacer-left" - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to="/about/pricing" - > - billing.pricing.learn_more - </Link> - </div> - </RadioCard> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationModal-test.tsx.snap deleted file mode 100644 index 4d294fc1497..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/UpgradeOrganizationModal-test.tsx.snap +++ /dev/null @@ -1,25 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<Modal - contentLabel="billing.upgrade_box.upgrade_to_paid_plan" - onRequestClose={[MockFunction]} - shouldCloseOnOverlayClick={false} - size="medium" -> - <div - className="modal-head" - > - <h2> - billing.upgrade_box.upgrade_to_paid_plan - </h2> - </div> - <Connect(withCurrentUser(BillingFormShim)) - onCommit={[MockFunction]} - organizationKey="foo" - subscriptionPlans={Array []} - > - <Component /> - </Connect(withCurrentUser(BillingFormShim))> -</Modal> -`; diff --git a/server/sonar-web/src/main/js/apps/create/organization/AlmApplicationInstalling.tsx b/server/sonar-web/src/main/js/apps/create/organization/AlmApplicationInstalling.tsx deleted file mode 100644 index d6b9dcebf01..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/AlmApplicationInstalling.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { sanitizeAlmId } from '../../../helpers/almIntegrations'; - -export default function AlmApplicationInstalling({ almKey }: { almKey?: string }) { - return ( - <DeferredSpinner - customSpinner={ - <div className="sonarcloud page page-limited"> - <div className="huge-spacer-top text-center"> - <i className="spinner" /> - <p className="big-spacer-top"> - {translateWithParameters( - 'onboarding.import_organization.installing', - almKey ? translate(sanitizeAlmId(almKey)) : 'ALM' - )} - </p> - </div> - </div> - } - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationBind.tsx b/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationBind.tsx deleted file mode 100644 index 38d3862aa7d..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationBind.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { Link } from 'react-router'; -import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import { Alert } from 'sonar-ui-common/components/ui/Alert'; -import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { isGithub } from '../../../helpers/almIntegrations'; -import OrganizationSelect from '../components/OrganizationSelect'; - -interface Props { - almKey: string; - onBindOrganization: (organization: string) => Promise<void>; - unboundOrganizations: T.Organization[]; -} - -interface State { - organization: string; - submitting: boolean; -} - -export default class AutoOrganizationBind extends React.PureComponent<Props, State> { - mounted = false; - - constructor(props: Props) { - super(props); - this.state = { organization: this.getInitialSelectedOrganization(props), submitting: false }; - } - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - getInitialSelectedOrganization(props: Props) { - if (props.unboundOrganizations.length === 1) { - return props.unboundOrganizations[0].key; - } - return ''; - } - - handleChange = ({ key }: T.Organization) => { - this.setState({ organization: key }); - }; - - handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { - event.preventDefault(); - const { organization } = this.state; - if (organization) { - this.setState({ submitting: true }); - this.props.onBindOrganization(organization).then(this.stopSubmitting, this.stopSubmitting); - } - }; - - stopSubmitting = () => { - if (this.mounted) { - this.setState({ submitting: false }); - } - }; - - render() { - const { almKey } = this.props; - const { organization, submitting } = this.state; - return ( - <form id="bind-organization-form" onSubmit={this.handleSubmit}> - <OrganizationSelect - onChange={this.handleChange} - organization={organization} - organizations={this.props.unboundOrganizations} - /> - {isGithub(almKey) && ( - <Alert className="abs-width-400 big-spacer-top" display="block" variant="info"> - {translateWithParameters( - 'onboarding.import_organization.bind_members_not_sync_info_x', - translate('organization', almKey) - )} - <Link - className="spacer-left" - target="_blank" - to={{ pathname: '/documentation/organizations/manage-team/' }}> - {translate('learn_more')} - </Link> - </Alert> - )} - <div className="display-flex-center big-spacer-top"> - <SubmitButton disabled={submitting || !organization}> - {translate('onboarding.import_organization.bind')} - </SubmitButton> - {submitting && <DeferredSpinner className="spacer-left" />} - </div> - </form> - ); - } -} 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 deleted file mode 100644 index 1f0542b429c..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/AutoOrganizationCreate.tsx +++ /dev/null @@ -1,215 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { ClearButton } from 'sonar-ui-common/components/controls/buttons'; -import RadioToggle from 'sonar-ui-common/components/controls/RadioToggle'; -import { Alert } from 'sonar-ui-common/components/ui/Alert'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; -import { bindAlmOrganization } from '../../../api/alm-integration'; -import { getAlmMembersUrl, isGithub, sanitizeAlmId } from '../../../helpers/almIntegrations'; -import AutoOrganizationBind from './AutoOrganizationBind'; -import OrganizationDetailsForm from './OrganizationDetailsForm'; -import OrganizationDetailsStep from './OrganizationDetailsStep'; -import PlanStep from './PlanStep'; -import { Step } from './utils'; - -enum Filters { - Bind = 'bind', - Create = 'create' -} - -interface Props { - almApplication: T.AlmApplication; - almInstallId: string; - almOrganization: T.AlmOrganization; - className?: string; - createOrganization: ( - organization: T.Organization & { installationId?: string } - ) => Promise<string>; - handleCancelImport: () => void; - handleOrgDetailsFinish: (organization: T.Organization) => Promise<void>; - handleOrgDetailsStepOpen: () => void; - onDone: () => void; - onOrgCreated: (organization: string) => void; - onUpgradeFail: () => void; - organization?: T.Organization; - step: Step; - subscriptionPlans?: T.SubscriptionPlan[]; - unboundOrganizations: T.Organization[]; -} - -interface State { - filter?: Filters; -} - -export default class AutoOrganizationCreate extends React.PureComponent<Props, State> { - constructor(props: Props) { - super(props); - this.state = { - filter: props.unboundOrganizations.length === 0 ? Filters.Create : undefined - }; - } - - handleBindOrganization = (organization: string) => { - return bindAlmOrganization({ - organization, - installationId: this.props.almInstallId - }).then(() => this.props.onOrgCreated(organization)); - }; - - handleCreateOrganization = () => { - const { almApplication, almOrganization, organization } = this.props; - if (!organization) { - return Promise.reject(); - } - return this.props.createOrganization({ - ...organization, - alm: { - key: almApplication.key, - membersSync: true, - personal: almOrganization.personal, - url: almOrganization.almUrl - }, - installationId: this.props.almInstallId - }); - }; - - handleOptionChange = (filter: Filters) => { - this.setState({ filter }); - }; - - render() { - const { - almApplication, - almOrganization, - className, - organization, - step, - subscriptionPlans, - unboundOrganizations - } = this.props; - const { filter } = this.state; - const hasUnboundOrgs = unboundOrganizations.length > 0; - const almKey = sanitizeAlmId(almApplication.key); - return ( - <div className={className}> - <OrganizationDetailsStep - finished={organization !== undefined} - onOpen={this.props.handleOrgDetailsStepOpen} - open={step === Step.OrganizationDetails} - organization={organization} - stepTitle={translate('onboarding.import_organization.import_org_details')}> - <div className="huge-spacer-bottom"> - <p className="display-flex-center big-spacer-bottom"> - <FormattedMessage - defaultMessage={translate('onboarding.import_organization_x')} - id="onboarding.import_organization_x" - values={{ - avatar: ( - <img - alt={almApplication.name} - className="little-spacer-left" - src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId( - almApplication.key - )}.svg`} - width={16} - /> - ), - name: <strong>{almOrganization.name}</strong> - }} - /> - <ClearButton className="little-spacer-left" onClick={this.props.handleCancelImport} /> - </p> - - {hasUnboundOrgs && ( - <RadioToggle - name="filter" - onCheck={this.handleOptionChange} - options={[ - { - label: translate('onboarding.import_organization.create_new'), - value: Filters.Create - }, - { - label: translate('onboarding.import_organization.bind_existing'), - value: Filters.Bind - } - ]} - value={filter} - /> - )} - </div> - - {filter === Filters.Create && ( - <OrganizationDetailsForm - infoBlock={ - isGithub(almKey) && ( - <Alert className="abs-width-600 big-spacer-top" display="block" variant="info"> - <p> - {translateWithParameters( - 'onboarding.import_organization.members_sync_info_x', - translate('organization', almKey), - almOrganization.name, - translate(almKey) - )} - </p> - <a - href={getAlmMembersUrl(almApplication.key, almOrganization.almUrl)} - rel="noopener noreferrer" - target="_blank"> - {translateWithParameters( - 'organization.members.see_all_members_on_x', - translate(almKey) - )} - </a> - </Alert> - ) - } - onContinue={this.props.handleOrgDetailsFinish} - organization={almOrganization} - submitText={translate('continue')} - /> - )} - {filter === Filters.Bind && ( - <AutoOrganizationBind - almKey={almKey} - onBindOrganization={this.handleBindOrganization} - unboundOrganizations={unboundOrganizations} - /> - )} - </OrganizationDetailsStep> - - {subscriptionPlans !== undefined && filter !== Filters.Bind && ( - <PlanStep - almApplication={this.props.almApplication} - almOrganization={this.props.almOrganization} - createOrganization={this.handleCreateOrganization} - onDone={this.props.onDone} - onUpgradeFail={this.props.onUpgradeFail} - open={step === Step.Plan} - subscriptionPlans={subscriptionPlans} - /> - )} - </div> - ); - } -} 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 deleted file mode 100644 index ac6a60056f8..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/CreateOrganization.tsx +++ /dev/null @@ -1,456 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as classNames from 'classnames'; -import * as differenceInMinutes from 'date-fns/difference_in_minutes'; -import { times } from 'lodash'; -import * as React from 'react'; -import { Helmet } from 'react-helmet-async'; -import { connect } from 'react-redux'; -import { withRouter, WithRouterProps } from 'react-router'; -import Tabs from 'sonar-ui-common/components/controls/Tabs'; -import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { addWhitePageClass, removeWhitePageClass } from 'sonar-ui-common/helpers/pages'; -import { get, remove } from 'sonar-ui-common/helpers/storage'; -import { slugify } from 'sonar-ui-common/helpers/strings'; -import { - bindAlmOrganization, - getAlmAppInfo, - getAlmOrganization, - GetAlmOrganizationResponse, - listUnboundApplications -} from '../../../api/alm-integration'; -import { getSubscriptionPlans } from '../../../api/billing'; -import * as api from '../../../api/organizations'; -import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; -import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage'; -import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn'; -import { withUserOrganizations } from '../../../components/hoc/withUserOrganizations'; -import { hasAdvancedALMIntegration, sanitizeAlmId } from '../../../helpers/almIntegrations'; -import { getOrganizationUrl } from '../../../helpers/urls'; -import { skipOnboarding } from '../../../store/users'; -import { deleteOrganization } from '../../organizations/actions'; -import { createOrganization } from './actions'; -import AlmApplicationInstalling from './AlmApplicationInstalling'; -import AutoOrganizationCreate from './AutoOrganizationCreate'; -import ManualOrganizationCreate from './ManualOrganizationCreate'; -import RemoteOrganizationChoose from './RemoteOrganizationChoose'; -import { - BIND_ORGANIZATION_KEY, - BIND_ORGANIZATION_REDIRECT_TO_ORG_TIMESTAMP, - ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP, - ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP, - parseQuery, - Query, - serializeQuery, - Step -} from './utils'; - -interface Props { - createOrganization: ( - organization: T.Organization & { installationId?: string } - ) => Promise<string>; - currentUser: T.LoggedInUser; - deleteOrganization: (key: string) => Promise<void>; - userOrganizations: T.Organization[]; - skipOnboarding: () => void; -} - -interface State { - almApplication?: T.AlmApplication; - almOrganization?: T.AlmOrganization; - almOrgLoading: boolean; - almUnboundApplications: T.AlmUnboundApplication[]; - bindingExistingOrg: boolean; - boundOrganization?: T.OrganizationBase; - loading: boolean; - organization?: T.Organization; - step: Step; - subscriptionPlans?: T.SubscriptionPlan[]; -} - -type StateWithAutoImport = State & Required<Pick<State, 'almApplication'>>; - -type TabKeys = 'auto' | 'manual'; - -interface LocationState { - tab?: TabKeys; -} - -export class CreateOrganization extends React.PureComponent<Props & WithRouterProps, State> { - mounted = false; - state: State = { - almOrgLoading: false, - almUnboundApplications: [], - bindingExistingOrg: false, - loading: true, - step: Step.OrganizationDetails - }; - - componentDidMount() { - this.mounted = true; - addWhitePageClass(); - - const query = parseQuery(this.props.location.query); - - //highjack the process for the organization settings - if ( - hasAdvancedALMIntegration(this.props.currentUser) && - query.almInstallId && - this.isStoredTimestampValid(BIND_ORGANIZATION_REDIRECT_TO_ORG_TIMESTAMP) - ) { - this.bindAndRedirectToOrganizationSettings(query.almInstallId); - } else { - const initRequests = [this.fetchSubscriptionPlans()]; - if (hasAdvancedALMIntegration(this.props.currentUser)) { - initRequests.push(this.fetchAlmApplication()); - - if (query.almInstallId) { - this.fetchAlmOrganization(query.almInstallId); - } else { - initRequests.push(this.fetchAlmUnboundApplications()); - } - } - Promise.all(initRequests).then(this.stopLoading, this.stopLoading); - } - } - - componentDidUpdate(prevProps: WithRouterProps) { - const prevQuery = parseQuery(prevProps.location.query); - const query = parseQuery(this.props.location.query); - if (this.state.almApplication && prevQuery.almInstallId !== query.almInstallId) { - if (query.almInstallId) { - this.fetchAlmOrganization(query.almInstallId); - } else { - this.setState({ almOrganization: undefined, boundOrganization: undefined, loading: true }); - this.fetchAlmUnboundApplications().then(this.stopLoading, this.stopLoading); - } - } - } - - componentWillUnmount() { - this.mounted = false; - removeWhitePageClass(); - } - - deleteOrganization = () => { - if (this.state.organization) { - this.props.deleteOrganization(this.state.organization.key); - } - }; - - fetchAlmApplication = () => { - return getAlmAppInfo().then(({ application }) => { - if (this.mounted) { - this.setState({ almApplication: application }); - } - }); - }; - - fetchAlmOrganization = (installationId: string) => { - this.setState({ almOrgLoading: true }); - return getAlmOrganization({ installationId }) - .then(({ almOrganization, boundOrganization }) => { - if (boundOrganization) { - return { almOrganization, boundOrganization }; - } - return this.setValidOrgKey(almOrganization); - }) - .then( - ({ almOrganization, boundOrganization }: GetAlmOrganizationResponse) => { - if (this.mounted) { - if ( - boundOrganization && - boundOrganization.key && - !this.isStoredTimestampValid(ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP) - ) { - this.props.router.push({ - pathname: getOrganizationUrl(boundOrganization.key) - }); - } else { - this.setState({ almOrganization, almOrgLoading: false, boundOrganization }); - } - } - }, - () => { - if (this.mounted) { - this.setState({ almOrgLoading: false }); - } - } - ); - }; - - fetchAlmUnboundApplications = () => { - return listUnboundApplications().then(almUnboundApplications => { - if (this.mounted) { - this.setState({ almUnboundApplications }); - } - }); - }; - - fetchSubscriptionPlans = () => { - return getSubscriptionPlans().then(subscriptionPlans => { - if (this.mounted) { - this.setState({ subscriptionPlans }); - } - }); - }; - - handleCancelImport = () => { - this.updateUrlQuery({ almInstallId: undefined, almKey: undefined }); - }; - - handleOrgCreated = (organization: string) => { - this.props.skipOnboarding(); - if (this.isStoredTimestampValid(ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP)) { - this.props.router.push({ - pathname: '/projects/create', - state: { organization, tab: this.state.almOrganization ? 'auto' : 'manual' } - }); - } else { - this.props.router.push({ pathname: getOrganizationUrl(organization) }); - } - }; - - handleOrgDetailsFinish = (organization: T.Organization) => { - this.setState({ organization, step: Step.Plan }); - return Promise.resolve(); - }; - - handleOrgDetailsStepOpen = () => { - this.setState({ step: Step.OrganizationDetails }); - }; - - handlePlanDone = () => { - if (this.state.organization) { - this.handleOrgCreated(this.state.organization.key); - } - }; - - hasAutoImport(state: State): state is StateWithAutoImport { - return Boolean(state.almApplication); - } - - isStoredTimestampValid = (timestampKey: string) => { - const storedTimestamp = get(timestampKey); - remove(timestampKey); - return storedTimestamp && differenceInMinutes(Date.now(), Number(storedTimestamp)) < 10; - }; - - onTabChange = (tab: TabKeys) => { - this.updateUrlState({ tab }); - }; - - bindAndRedirectToOrganizationSettings(installationId: string) { - const organizationKey = get(BIND_ORGANIZATION_KEY) || ''; - remove(BIND_ORGANIZATION_KEY); - - this.setState({ bindingExistingOrg: true }); - - bindAlmOrganization({ - installationId, - organization: organizationKey - }).then( - () => { - this.props.router.push({ - pathname: `/organizations/${organizationKey}` - }); - addGlobalSuccessMessage(translate('organization.bind.success')); - }, - () => {} - ); - } - - getHeader = (bindingExistingOrg: boolean) => { - if (bindingExistingOrg) { - return translate('onboarding.binding_organization'); - } else { - return translate('onboarding.create_organization.page.header'); - } - }; - - setValidOrgKey = (almOrganization: T.AlmOrganization) => { - const key = slugify(almOrganization.key); - const keys = [key, ...times(9, i => `${key}-${i + 1}`)]; - return api - .getOrganizations({ organizations: keys.join(',') }) - .then( - ({ organizations }) => { - const availableKey = keys.find(key => !organizations.find(o => o.key === key)); - return availableKey || `${key}-${Math.ceil(Math.random() * 1000) + 10}`; - }, - () => key - ) - .then(key => { - return { almOrganization: { ...almOrganization, key } }; - }); - }; - - stopLoading = () => { - if (this.mounted) { - this.setState({ loading: false }); - } - }; - - updateUrlQuery = (query: Partial<Query> = {}) => { - this.props.router.push({ - pathname: this.props.location.pathname, - query: serializeQuery({ ...parseQuery(this.props.location.query), ...query }), - state: this.props.location.state - }); - }; - - updateUrlState = (state: Partial<LocationState> = {}) => { - this.props.router.replace({ - pathname: this.props.location.pathname, - query: this.props.location.query, - state: { ...(this.props.location.state || {}), ...state } - }); - }; - - renderContent = (almInstallId?: string) => { - const { location } = this.props; - const { state } = this; - const { organization, step, subscriptionPlans } = state; - const { tab = 'auto' } = (location.state || {}) as LocationState; - - const commonProps = { - handleOrgDetailsFinish: this.handleOrgDetailsFinish, - handleOrgDetailsStepOpen: this.handleOrgDetailsStepOpen, - onDone: this.handlePlanDone, - organization, - step, - subscriptionPlans - }; - - if (!this.hasAutoImport(state)) { - return ( - <ManualOrganizationCreate - {...commonProps} - createOrganization={this.props.createOrganization} - onUpgradeFail={this.deleteOrganization} - organization={this.state.organization} - step={this.state.step} - /> - ); - } - - const { almApplication, almOrganization, boundOrganization } = state; - - return ( - <> - <Tabs<TabKeys> - onChange={this.onTabChange} - selected={tab || 'auto'} - tabs={[ - { - key: 'auto', - node: translateWithParameters( - 'onboarding.import_organization.import_from_x', - translate(sanitizeAlmId(almApplication.key)) - ) - }, - { - key: 'manual', - node: translate('onboarding.create_organization.create_manually') - } - ]} - /> - - <ManualOrganizationCreate - {...commonProps} - className={classNames({ hidden: tab !== 'manual' && this.hasAutoImport(state) })} - createOrganization={this.props.createOrganization} - onUpgradeFail={this.deleteOrganization} - /> - - {almInstallId && almOrganization && !boundOrganization ? ( - <AutoOrganizationCreate - {...commonProps} - almApplication={almApplication} - almInstallId={almInstallId} - almOrganization={almOrganization} - className={classNames({ hidden: tab !== 'auto' })} - createOrganization={this.props.createOrganization} - handleCancelImport={this.handleCancelImport} - onOrgCreated={this.handleOrgCreated} - onUpgradeFail={this.deleteOrganization} - unboundOrganizations={this.props.userOrganizations.filter( - ({ actions = {}, alm }) => !alm && actions.admin - )} - /> - ) : ( - <RemoteOrganizationChoose - almApplication={almApplication} - almInstallId={almInstallId} - almOrganization={almOrganization} - almUnboundApplications={state.almUnboundApplications} - boundOrganization={boundOrganization} - className={classNames({ hidden: tab !== 'auto' })} - /> - )} - </> - ); - }; - - render() { - const { location } = this.props; - const query = parseQuery(location.query); - - if (this.state.almOrgLoading) { - return <AlmApplicationInstalling almKey={query.almKey} />; - } - - const { bindingExistingOrg, subscriptionPlans } = this.state; - const header = this.getHeader(bindingExistingOrg); - const startedPrice = subscriptionPlans && subscriptionPlans[0] && subscriptionPlans[0].price; - - return ( - <> - <Helmet defer={false} title={header} titleTemplate="%s" /> - <div className="page page-limited huge-spacer-top huge-spacer-bottom"> - <A11ySkipTarget anchor="create_org_main" /> - - <header className="page-header huge-spacer-bottom"> - <h1 className="page-title huge big-spacer-bottom"> - <strong>{header}</strong> - </h1> - {startedPrice !== undefined && ( - <p className="page-description"> - {translate('onboarding.create_organization.page.description')} - </p> - )} - </header> - {this.state.loading ? <DeferredSpinner /> : this.renderContent(query.almInstallId)} - </div> - </> - ); - } -} - -const mapDispatchToProps = { - createOrganization: createOrganization as any, - deleteOrganization: deleteOrganization as any, - skipOnboarding: skipOnboarding as any -}; - -export default whenLoggedIn( - withUserOrganizations(withRouter(connect(null, mapDispatchToProps)(CreateOrganization))) -); diff --git a/server/sonar-web/src/main/js/apps/create/organization/ManualOrganizationCreate.tsx b/server/sonar-web/src/main/js/apps/create/organization/ManualOrganizationCreate.tsx deleted file mode 100644 index 157acec75a5..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/ManualOrganizationCreate.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import OrganizationDetailsForm from './OrganizationDetailsForm'; -import OrganizationDetailsStep from './OrganizationDetailsStep'; -import PlanStep from './PlanStep'; -import { Step } from './utils'; - -interface Props { - createOrganization: (organization: T.Organization) => Promise<string>; - className?: string; - onUpgradeFail: () => void; - handleOrgDetailsFinish: (organization: T.Organization) => Promise<void>; - handleOrgDetailsStepOpen: () => void; - onDone: () => void; - organization?: T.Organization; - step: Step; - subscriptionPlans?: T.SubscriptionPlan[]; -} - -export default class ManualOrganizationCreate extends React.PureComponent<Props> { - handleCreateOrganization = () => { - const { organization } = this.props; - if (!organization) { - return Promise.reject(); - } - return this.props.createOrganization(organization); - }; - - render() { - const { className, organization, subscriptionPlans } = this.props; - return ( - <div className={className}> - <OrganizationDetailsStep - finished={organization !== undefined} - onOpen={this.props.handleOrgDetailsStepOpen} - open={this.props.step === Step.OrganizationDetails} - organization={organization}> - <OrganizationDetailsForm - onContinue={this.props.handleOrgDetailsFinish} - organization={organization} - submitText={translate('continue')} - /> - </OrganizationDetailsStep> - - {subscriptionPlans !== undefined && ( - <PlanStep - createOrganization={this.handleCreateOrganization} - onDone={this.props.onDone} - onUpgradeFail={this.props.onUpgradeFail} - open={this.props.step === Step.Plan} - subscriptionPlans={subscriptionPlans} - /> - )} - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsForm.tsx b/server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsForm.tsx deleted file mode 100644 index 928efd8f0cc..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsForm.tsx +++ /dev/null @@ -1,205 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon'; -import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import OrganizationAvatarInput from '../components/OrganizationAvatarInput'; -import OrganizationKeyInput from '../components/OrganizationKeyInput'; -import OrganizationUrlInput from '../components/OrganizationUrlInput'; - -type RequiredOrganization = Required<T.OrganizationBase>; - -interface Props { - infoBlock?: React.ReactNode; - onContinue: (organization: T.Organization) => Promise<void>; - organization?: T.Organization; - submitText: string; -} - -interface State { - additional: boolean; - avatar?: string; - description?: string; - key?: string; - name?: string; - submitting: boolean; - url?: string; -} - -type ValidState = Pick<State, Exclude<keyof State, RequiredOrganization>> & RequiredOrganization; - -export default class OrganizationDetailsForm extends React.PureComponent<Props, State> { - mounted = false; - - constructor(props: Props) { - super(props); - const { organization } = props; - this.state = { - additional: false, - avatar: (organization && organization.avatar) || '', - description: (organization && organization.description) || '', - key: (organization && organization.key) || undefined, - name: (organization && organization.name) || '', - submitting: false, - url: (organization && organization.url) || '' - }; - } - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - canSubmit(state: State): state is ValidState { - return Boolean( - state.key !== undefined && - state.name !== undefined && - state.description !== undefined && - state.avatar !== undefined && - state.url !== undefined - ); - } - - handleAdditionalClick = () => { - this.setState(state => ({ additional: !state.additional })); - }; - - handleAvatarUpdate = (avatar: string | undefined) => { - this.setState({ avatar }); - }; - - handleDescriptionUpdate = (event: React.ChangeEvent<HTMLTextAreaElement>) => { - this.setState({ description: event.currentTarget.value }); - }; - - handleKeyUpdate = (key: string | undefined) => { - this.setState({ key }); - }; - - handleNameUpdate = (event: React.ChangeEvent<HTMLInputElement>) => { - this.setState({ name: event.currentTarget.value }); - }; - - handleUrlUpdate = (url: string | undefined) => { - this.setState({ url }); - }; - - handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - const { state } = this; - if (this.canSubmit(state)) { - this.setState({ submitting: true }); - this.props - .onContinue({ - avatar: state.avatar, - description: state.description, - key: state.key, - name: state.name, - url: state.url - }) - .then(this.stopSubmitting, this.stopSubmitting); - } - }; - - stopSubmitting = () => { - if (this.mounted) { - this.setState({ submitting: false }); - } - }; - - render() { - const { submitting } = this.state; - const { infoBlock } = this.props; - return ( - <form id="organization-form" onSubmit={this.handleSubmit}> - <OrganizationKeyInput initialValue={this.state.key} onChange={this.handleKeyUpdate} /> - <div className="big-spacer-top"> - <ResetButtonLink onClick={this.handleAdditionalClick}> - {translate( - this.state.additional - ? 'onboarding.create_organization.hide_additional_info' - : 'onboarding.create_organization.add_additional_info' - )} - <DropdownIcon className="little-spacer-left" turned={this.state.additional} /> - </ResetButtonLink> - </div> - <div className="js-additional-info" hidden={!this.state.additional}> - <div className="big-spacer-top"> - <label htmlFor="organization-display-name"> - <strong>{translate('onboarding.create_organization.display_name')}</strong> - </label> - <div className="little-spacer-top"> - <input - className="input-super-large text-middle" - id="organization-display-name" - maxLength={255} - onChange={this.handleNameUpdate} - type="text" - value={this.state.name} - /> - </div> - <div className="note abs-width-400"> - {translate('onboarding.create_organization.display_name.description')} - </div> - </div> - <div className="big-spacer-top"> - <OrganizationAvatarInput - initialValue={this.state.avatar} - name={this.state.name} - onChange={this.handleAvatarUpdate} - /> - </div> - <div className="big-spacer-top"> - <label htmlFor="organization-description"> - <strong>{translate('onboarding.create_organization.description')}</strong> - </label> - <div className="little-spacer-top"> - <textarea - className="input-super-large text-middle" - id="organization-description" - maxLength={256} - onChange={this.handleDescriptionUpdate} - rows={3} - value={this.state.description} - /> - </div> - </div> - <div className="big-spacer-top"> - <OrganizationUrlInput initialValue={this.state.url} onChange={this.handleUrlUpdate} /> - </div> - </div> - - {infoBlock} - - <div className="display-flex-center big-spacer-top"> - <SubmitButton disabled={submitting || !this.canSubmit(this.state)}> - {this.props.submitText} - </SubmitButton> - {submitting && <DeferredSpinner className="spacer-left" />} - </div> - </form> - ); - } -} 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 deleted file mode 100644 index 91b45c1e44f..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsStep.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import AlertSuccessIcon from 'sonar-ui-common/components/icons/AlertSuccessIcon'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import Step from '../../../components/tutorials/components/Step'; - -interface Props { - children: React.ReactNode; - finished: boolean; - onOpen: () => void; - open: boolean; - organization?: T.Organization; - stepTitle?: string; -} -export default class OrganizationDetailsStep extends React.PureComponent<Props> { - renderForm = () => { - return <div className="boxed-group-inner">{this.props.children}</div>; - }; - - renderResult = () => { - const { organization } = this.props; - return organization ? ( - <div className="boxed-group-actions display-flex-center"> - <AlertSuccessIcon className="spacer-right" /> - <strong className="text-limited">{organization.key}</strong> - </div> - ) : null; - }; - - render() { - return ( - <Step - finished={this.props.finished} - onOpen={this.props.onOpen} - open={this.props.open} - renderForm={this.renderForm} - renderResult={this.renderResult} - stepNumber={1} - stepTitle={ - this.props.stepTitle || translate('onboarding.create_organization.enter_org_details') - } - /> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/organization/PlanSelect.tsx b/server/sonar-web/src/main/js/apps/create/organization/PlanSelect.tsx deleted file mode 100644 index 0c2460fd7d9..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/PlanSelect.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import FreeCardPlan from '../components/FreeCardPlan'; -import PaidCardPlan from '../components/PaidCardPlan'; - -export enum Plan { - Free = 'free', - Paid = 'paid' -} - -interface Props { - almApplication?: T.AlmApplication; - almOrganization?: T.AlmOrganization; - onChange: (plan: Plan) => void; - plan: Plan; - startingPrice: number; -} - -export default class PlanSelect extends React.PureComponent<Props> { - handleFreePlanClick = () => { - this.props.onChange(Plan.Free); - }; - - handlePaidPlanClick = () => { - this.props.onChange(Plan.Paid); - }; - - render() { - const { almApplication, almOrganization, plan } = this.props; - const hasPrivateRepo = Boolean(almOrganization && almOrganization.privateRepos > 0); - const onlyPrivateRepo = Boolean( - hasPrivateRepo && almOrganization && almOrganization.publicRepos === 0 - ); - - const cards = [ - <PaidCardPlan - isRecommended={hasPrivateRepo} - key="paid" - onClick={this.handlePaidPlanClick} - selected={plan === Plan.Paid} - startingPrice={this.props.startingPrice} - />, - <FreeCardPlan - almName={almApplication && almApplication.name} - disabled={onlyPrivateRepo} - hasWarning={hasPrivateRepo && plan === Plan.Free} - key="free" - onClick={this.handleFreePlanClick} - selected={plan === Plan.Free} - /> - ]; - - return ( - <div - aria-label={translate('onboarding.create_organization.choose_plan')} - className="display-flex-row huge-spacer-bottom" - role="radiogroup"> - {hasPrivateRepo ? cards : cards.reverse()} - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/organization/PlanStep.tsx b/server/sonar-web/src/main/js/apps/create/organization/PlanStep.tsx deleted file mode 100644 index 042210cee1e..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/PlanStep.tsx +++ /dev/null @@ -1,165 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { withCurrentUser } from '../../../components/hoc/withCurrentUser'; -import Step from '../../../components/tutorials/components/Step'; -import { getExtensionStart } from '../../../helpers/extensions'; -import BillingFormShim from '../components/BillingFormShim'; -import PlanSelect, { Plan } from './PlanSelect'; - -const BillingForm = withCurrentUser(BillingFormShim); - -interface Props { - almApplication?: T.AlmApplication; - almOrganization?: T.AlmOrganization; - createOrganization: () => Promise<string>; - onDone: () => void; - onUpgradeFail?: () => void; - open: boolean; - subscriptionPlans: T.SubscriptionPlan[]; -} - -interface State { - plan: Plan; - ready: boolean; - submitting: boolean; -} - -export default class PlanStep extends React.PureComponent<Props, State> { - mounted = false; - - constructor(props: Props) { - super(props); - this.state = { - plan: props.almOrganization && props.almOrganization.privateRepos > 0 ? Plan.Paid : Plan.Free, - ready: false, - submitting: false - }; - } - - componentDidMount() { - this.mounted = true; - getExtensionStart('billing/billing').then( - () => { - if (this.mounted) { - this.setState({ ready: true }); - } - }, - () => {} - ); - } - - componentWillUnmount() { - this.mounted = false; - } - - handlePlanChange = (plan: Plan) => { - this.setState({ plan }); - }; - - stopSubmitting = () => { - if (this.mounted) { - this.setState({ submitting: false }); - } - }; - - handleFreePlanSubmit = (event: React.FormEvent) => { - event.preventDefault(); - this.setState({ submitting: true }); - return this.props.createOrganization().then(() => { - this.props.onDone(); - this.stopSubmitting(); - }, this.stopSubmitting); - }; - - renderForm = () => { - const { submitting } = this.state; - const { subscriptionPlans } = this.props; - const startingPrice = subscriptionPlans && subscriptionPlans[0] && subscriptionPlans[0].price; - return ( - <div className="boxed-group-inner"> - {this.state.ready && ( - <> - <PlanSelect - almApplication={this.props.almApplication} - almOrganization={this.props.almOrganization} - onChange={this.handlePlanChange} - plan={this.state.plan} - startingPrice={startingPrice} - /> - - {this.state.plan === Plan.Paid ? ( - <BillingForm - onCommit={this.props.onDone} - onFailToUpgrade={this.props.onUpgradeFail} - organizationKey={this.props.createOrganization} - subscriptionPlans={this.props.subscriptionPlans}> - {({ onSubmit, renderFormFields, renderSubmitGroup }) => ( - <form id="organization-paid-plan-form" onSubmit={onSubmit}> - {renderFormFields()} - <div className="billing-input-large big-spacer-top"> - {renderSubmitGroup( - translate('onboarding.create_organization.create_and_upgrade') - )} - </div> - </form> - )} - </BillingForm> - ) : ( - <form - className="display-flex-center big-spacer-top" - id="organization-free-plan-form" - onSubmit={this.handleFreePlanSubmit}> - <SubmitButton disabled={submitting}> - {translate('my_account.create_organization')} - </SubmitButton> - {submitting && <DeferredSpinner className="spacer-left" />} - </form> - )} - </> - )} - </div> - ); - }; - - render() { - const { almOrganization } = this.props; - const stepTitle = translate( - almOrganization && almOrganization.privateRepos > 0 && almOrganization.publicRepos === 0 - ? 'onboarding.create_organization.enter_payment_details' - : 'onboarding.create_organization.choose_plan' - ); - - return ( - <Step - finished={false} - onOpen={() => {}} - open={this.props.open} - renderForm={this.renderForm} - renderResult={() => null} - stepNumber={2} - stepTitle={stepTitle} - /> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx b/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx deleted file mode 100644 index 00b9744b7cb..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/RemoteOrganizationChoose.tsx +++ /dev/null @@ -1,204 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as classNames from 'classnames'; -import { sortBy } from 'lodash'; -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { withRouter, WithRouterProps } from 'react-router'; -import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import IdentityProviderLink from 'sonar-ui-common/components/controls/IdentityProviderLink'; -import Select from 'sonar-ui-common/components/controls/Select'; -import { Alert } from 'sonar-ui-common/components/ui/Alert'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { save } from 'sonar-ui-common/helpers/storage'; -import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; -import OrganizationAvatar from '../../../components/common/OrganizationAvatar'; -import { sanitizeAlmId } from '../../../helpers/almIntegrations'; -import { ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP, serializeQuery } from './utils'; - -interface Props { - almApplication: T.AlmApplication; - almInstallId?: string; - almOrganization?: T.AlmOrganization; - almUnboundApplications: T.AlmUnboundApplication[]; - boundOrganization?: T.OrganizationBase; - className?: string; -} - -interface State { - unboundInstallationId: string; -} - -export class RemoteOrganizationChoose extends React.PureComponent<Props & WithRouterProps, State> { - state: State = { unboundInstallationId: '' }; - - handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { - event.preventDefault(); - - const { unboundInstallationId } = this.state; - if (unboundInstallationId) { - this.props.router.push({ - pathname: '/create-organization', - query: serializeQuery({ - almInstallId: unboundInstallationId, - almKey: this.props.almApplication.key - }) - }); - } - }; - - handleInstallAppClick = () => { - save(ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP, Date.now().toString(10)); - }; - - handleInstallationChange = ({ installationId }: T.AlmUnboundApplication) => { - this.setState({ unboundInstallationId: installationId }); - }; - - renderOption = (organization: T.AlmUnboundApplication) => { - const { almApplication } = this.props; - return ( - <span> - <img - alt={almApplication.name} - className="spacer-right" - height={14} - src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId(almApplication.key)}.svg`} - /> - {organization.name} - </span> - ); - }; - - render() { - const { - almApplication, - almInstallId, - almOrganization, - almUnboundApplications, - boundOrganization, - className - } = this.props; - const { unboundInstallationId } = this.state; - return ( - <div className={classNames('boxed-group', className)}> - <div className="boxed-group-header"> - <h2>{translate('onboarding.import_organization.import_org_details')}</h2> - </div> - <div className="boxed-group-inner"> - {almInstallId && !almOrganization && ( - <Alert className="big-spacer-bottom width-60" variant="error"> - <div className="markdown"> - {translate('onboarding.import_organization.org_not_found')} - <ul> - <li>{translate('onboarding.import_organization.org_not_found.tips_1')}</li> - <li>{translate('onboarding.import_organization.org_not_found.tips_2')}</li> - </ul> - </div> - </Alert> - )} - {almOrganization && boundOrganization && ( - <Alert className="big-spacer-bottom width-60" variant="error"> - <FormattedMessage - defaultMessage={translate('onboarding.import_organization.already_bound_x')} - id="onboarding.import_organization.already_bound_x" - values={{ - avatar: ( - <img - alt={almApplication.name} - className="little-spacer-left" - src={`${getBaseUrl()}/images/sonarcloud/${sanitizeAlmId( - almApplication.key - )}.svg`} - width={16} - /> - ), - name: <strong>{almOrganization.name}</strong>, - boundAvatar: ( - <OrganizationAvatar - className="little-spacer-left" - organization={boundOrganization} - small={true} - /> - ), - boundName: <strong>{boundOrganization.name}</strong> - }} - /> - </Alert> - )} - <div className="display-flex-center"> - <div className="display-inline-block"> - <IdentityProviderLink - backgroundColor={almApplication.backgroundColor} - className="display-inline-block" - iconPath={almApplication.iconPath} - name={almApplication.name} - onClick={this.handleInstallAppClick} - small={true} - url={almApplication.installationUrl}> - {translate( - 'onboarding.import_organization.choose_organization_button', - almApplication.key - )} - </IdentityProviderLink> - </div> - {almUnboundApplications.length > 0 && ( - <div className="display-flex-stretch"> - <div className="vertical-pipe-separator"> - <div className="vertical-separator " /> - <span className="note">{translate('or')}</span> - <div className="vertical-separator" /> - </div> - <form className="big-spacer-top big-spacer-bottom" onSubmit={this.handleSubmit}> - <div className="form-field abs-width-400"> - <label className="text-normal" htmlFor="select-unbound-installation"> - {translateWithParameters( - 'onboarding.import_organization.choose_unbound_installation_x', - translate(sanitizeAlmId(almApplication.key)) - )} - </label> - <Select - className="input-super-large" - clearable={false} - id="select-unbound-installation" - labelKey="name" - onChange={this.handleInstallationChange} - optionRenderer={this.renderOption} - options={sortBy(almUnboundApplications, o => o.name.toLowerCase())} - placeholder={translate('onboarding.import_organization.choose_organization')} - value={unboundInstallationId} - valueKey="installationId" - valueRenderer={this.renderOption} - /> - </div> - <SubmitButton disabled={!unboundInstallationId}> - {translate('continue')} - </SubmitButton> - </form> - </div> - )} - </div> - </div> - </div> - ); - } -} - -export default withRouter(RemoteOrganizationChoose); diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/AlmApplicationInstalling-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/AlmApplicationInstalling-test.tsx deleted file mode 100644 index 352e72f3781..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/AlmApplicationInstalling-test.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import AlmApplicationInstalling from '../AlmApplicationInstalling'; - -it('should render correctly', () => { - expect(shallow(<AlmApplicationInstalling />)).toMatchSnapshot(); - expect(shallow(<AlmApplicationInstalling almKey="github" />)).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationBind-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationBind-test.tsx deleted file mode 100644 index ecc8107689c..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationBind-test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { submit } from 'sonar-ui-common/helpers/testUtils'; -import { mockOrganization } from '../../../../helpers/testMocks'; -import AutoOrganizationBind from '../AutoOrganizationBind'; - -it('should render correctly', () => { - const onBindOrganization = jest.fn().mockResolvedValue({}); - const wrapper = shallowRender({ onBindOrganization }); - expect(wrapper).toMatchSnapshot(); - - submit(wrapper.find('form')); - expect(onBindOrganization).toHaveBeenCalled(); -}); - -it('should not show member sync info box for Bitbucket', () => { - expect( - shallowRender({ almKey: 'bitbucket' }) - .find('Alert') - .exists() - ).toBe(false); -}); - -function shallowRender(props: Partial<AutoOrganizationBind['props']> = {}) { - return shallow( - <AutoOrganizationBind - almKey="github" - onBindOrganization={jest.fn()} - unboundOrganizations={[mockOrganization()]} - {...props} - /> - ); -} 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 deleted file mode 100644 index c358824365d..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/AutoOrganizationCreate-test.tsx +++ /dev/null @@ -1,128 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { click, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { bindAlmOrganization } from '../../../../api/alm-integration'; -import { mockAlmApplication, mockAlmOrganization } from '../../../../helpers/testMocks'; -import AutoOrganizationCreate from '../AutoOrganizationCreate'; -import { Step } from '../utils'; - -jest.mock('../../../../api/alm-integration', () => ({ - bindAlmOrganization: jest.fn().mockResolvedValue({}) -})); - -const organization = mockAlmOrganization(); - -it('should render prefilled and create org', async () => { - const createOrganization = jest.fn().mockResolvedValue({ key: 'foo' }); - const handleOrgDetailsFinish = jest.fn(); - const almOrganization = mockAlmOrganization({ almUrl: 'http://github.com/thing' }); - const wrapper = shallowRender({ - almOrganization, - createOrganization, - handleOrgDetailsFinish - }); - - expect(wrapper).toMatchSnapshot(); - - wrapper.find('OrganizationDetailsForm').prop<Function>('onContinue')(organization); - await waitAndUpdate(wrapper); - expect(handleOrgDetailsFinish).toBeCalled(); - - wrapper.setProps({ organization }); - wrapper.find('PlanStep').prop<Function>('createOrganization')(); - - const alm: T.Organization['alm'] = { - key: 'github', - membersSync: true, - personal: false, - url: 'http://github.com/thing' - }; - expect(createOrganization).toBeCalledWith({ ...organization, alm, installationId: 'id-foo' }); -}); - -it('should allow to cancel org import', () => { - const handleCancelImport = jest.fn().mockResolvedValue({ key: 'foo' }); - const wrapper = shallowRender({ handleCancelImport }); - - click(wrapper.find('ClearButton')); - expect(handleCancelImport).toBeCalled(); -}); - -it('should display choice between import or creation', () => { - const wrapper = shallowRender({ unboundOrganizations: [organization] }); - expect(wrapper).toMatchSnapshot(); - - wrapper.find('RadioToggle').prop<Function>('onCheck')('create'); - wrapper.update(); - expect(wrapper.find('OrganizationDetailsForm').exists()).toBe(true); - - wrapper.find('RadioToggle').prop<Function>('onCheck')('bind'); - wrapper.update(); - expect(wrapper.find('AutoOrganizationBind').exists()).toBe(true); -}); - -it('should bind existing organization', async () => { - const onOrgCreated = jest.fn(); - const wrapper = shallowRender({ onOrgCreated, unboundOrganizations: [organization] }); - - wrapper.find('RadioToggle').prop<Function>('onCheck')('bind'); - wrapper.update(); - wrapper.find('AutoOrganizationBind').prop<Function>('onBindOrganization')('foo'); - expect(bindAlmOrganization as jest.Mock<any>).toHaveBeenCalledWith({ - installationId: 'id-foo', - organization: 'foo' - }); - await waitAndUpdate(wrapper); - expect(onOrgCreated).toHaveBeenCalledWith('foo'); -}); - -it('should not show member sync info box for Bitbucket', () => { - expect( - shallowRender({ almApplication: mockAlmApplication({ key: 'bitbucket-cloud' }) }) - .find('Alert') - .exists() - ).toBe(false); -}); - -function shallowRender(props: Partial<AutoOrganizationCreate['props']> = {}) { - return shallow( - <AutoOrganizationCreate - almApplication={mockAlmApplication()} - almInstallId="id-foo" - almOrganization={organization} - createOrganization={jest.fn()} - handleCancelImport={jest.fn()} - handleOrgDetailsFinish={jest.fn()} - handleOrgDetailsStepOpen={jest.fn()} - onDone={jest.fn()} - onOrgCreated={jest.fn()} - onUpgradeFail={jest.fn()} - step={Step.OrganizationDetails} - subscriptionPlans={[ - { maxNcloc: 100000, price: 10 }, - { maxNcloc: 250000, price: 75 } - ]} - unboundOrganizations={[]} - {...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 deleted file mode 100644 index 1f1e0a03dad..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx +++ /dev/null @@ -1,342 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { mount, shallow } from 'enzyme'; -import { Location } from 'history'; -import { times } from 'lodash'; -import * as React from 'react'; -import { HelmetProvider } from 'react-helmet-async'; -import { get, remove } from 'sonar-ui-common/helpers/storage'; -import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { - bindAlmOrganization, - getAlmAppInfo, - getAlmOrganization, - listUnboundApplications -} from '../../../../api/alm-integration'; -import { getSubscriptionPlans } from '../../../../api/billing'; -import { getOrganizations } from '../../../../api/organizations'; -import { - mockAlmOrganization, - mockLocation, - mockLoggedInUser, - mockOrganizationWithAdminActions, - mockOrganizationWithAlm, - mockRouter -} from '../../../../helpers/testMocks'; -import { CreateOrganization } from '../CreateOrganization'; - -jest.mock('../../../../api/billing', () => ({ - getSubscriptionPlans: jest.fn().mockResolvedValue([ - { maxNcloc: 100000, price: 10 }, - { maxNcloc: 250000, price: 75 } - ]) -})); - -jest.mock('../../../../api/alm-integration', () => ({ - getAlmAppInfo: jest.fn().mockResolvedValue({ - application: { - backgroundColor: 'blue', - iconPath: 'icon/path', - installationUrl: 'https://alm.installation.url', - key: 'github', - name: 'GitHub' - } - }), - getAlmOrganization: jest.fn().mockResolvedValue({ - almOrganization: { - avatar: 'my-avatar', - description: 'Continuous Code Quality', - key: 'sonarsource', - name: 'SonarSource', - privateRepos: 0, - publicRepos: 3, - url: 'https://www.sonarsource.com' - } - }), - listUnboundApplications: jest.fn().mockResolvedValue([]), - bindAlmOrganization: jest.fn().mockResolvedValue({}) -})); - -jest.mock('../../../../api/organizations', () => ({ - getOrganizations: jest.fn().mockResolvedValue({ organizations: [] }) -})); - -jest.mock('sonar-ui-common/helpers/storage', () => ({ - get: jest.fn().mockReturnValue(undefined), - remove: jest.fn() -})); - -const user = mockLoggedInUser(); -const fooAlmOrganization = mockAlmOrganization(); -const fooBarAlmOrganization = mockAlmOrganization({ - avatar: 'https://avatars3.githubusercontent.com/u/37629810?v=4', - key: 'Foo&Bar', - name: 'Foo & Bar' -}); - -const boundOrganization = { key: 'foobar', name: 'Foo & Bar' }; - -beforeEach(() => { - (getAlmAppInfo as jest.Mock<any>).mockClear(); - (getAlmOrganization as jest.Mock<any>).mockClear(); - (listUnboundApplications as jest.Mock<any>).mockClear(); - (getSubscriptionPlans as jest.Mock<any>).mockClear(); - (getOrganizations as jest.Mock<any>).mockClear(); - (get as jest.Mock<any>).mockClear(); - (remove as jest.Mock<any>).mockClear(); -}); - -it('should render with manual tab displayed', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - expect(getSubscriptionPlans).toHaveBeenCalled(); - expect(getAlmAppInfo).not.toHaveBeenCalled(); -}); - -it('should render with auto tab displayed', async () => { - const wrapper = shallowRender({ currentUser: { ...user, externalProvider: 'github' } }); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - expect(getAlmAppInfo).toHaveBeenCalled(); - expect(listUnboundApplications).toHaveBeenCalled(); -}); - -it('should render with auto tab selected and manual disabled', async () => { - const wrapper = shallowRender({ - currentUser: { ...user, externalProvider: 'github' }, - location: { query: { installation_id: 'foo' } } as Location - }); - expect(wrapper).toMatchSnapshot(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - expect(getAlmAppInfo).toHaveBeenCalled(); - expect(getAlmOrganization).toHaveBeenCalled(); - expect(getOrganizations).toHaveBeenCalled(); -}); - -it('should render with organization bind page', async () => { - (getAlmOrganization as jest.Mock<any>).mockResolvedValueOnce({ - almOrganization: fooAlmOrganization - }); - const wrapper = shallowRender({ - currentUser: { ...user, externalProvider: 'github' }, - location: { query: { installation_id: 'foo' } } as Location - }); - expect(wrapper).toMatchSnapshot(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); - -it('should slugify and find a uniq organization key', async () => { - (getAlmOrganization as jest.Mock<any>).mockResolvedValueOnce({ - almOrganization: fooBarAlmOrganization - }); - (getOrganizations as jest.Mock<any>).mockResolvedValueOnce({ - organizations: [{ key: 'foo-and-bar' }, { key: 'foo-and-bar-1' }] - }); - const wrapper = shallowRender({ - currentUser: { ...user, externalProvider: 'github' }, - location: { query: { installation_id: 'foo' } } as Location - }); - await waitAndUpdate(wrapper); - expect(getOrganizations).toHaveBeenCalledWith({ - organizations: ['foo-and-bar', ...times(9, i => `foo-and-bar-${i + 1}`)].join(',') - }); - expect(wrapper.find('AutoOrganizationCreate').prop('almOrganization')).toMatchObject({ - key: 'foo-and-bar-2' - }); -}); - -it('should switch tabs', async () => { - const replace = jest.fn(); - const wrapper = shallowRender({ - currentUser: { ...user, externalProvider: 'github' }, - router: mockRouter({ replace }) - }); - - replace.mockImplementation(location => { - wrapper.setProps({ location }).update(); - }); - - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - - (wrapper.find('Tabs').prop('onChange') as Function)('manual'); - expect(wrapper.find('ManualOrganizationCreate').hasClass('hidden')).toBe(false); - expect(wrapper.find('withRouter(RemoteOrganizationChoose)').hasClass('hidden')).toBe(true); - (wrapper.find('Tabs').prop('onChange') as Function)('auto'); - expect(wrapper.find('withRouter(RemoteOrganizationChoose)').hasClass('hidden')).toBe(false); - expect(wrapper.find('ManualOrganizationCreate').hasClass('hidden')).toBe(true); -}); - -it('should reload the alm organization when the url query changes', async () => { - const wrapper = shallowRender({ currentUser: { ...user, externalProvider: 'github' } }); - await waitAndUpdate(wrapper); - expect(getAlmOrganization).not.toHaveBeenCalled(); - wrapper.setProps({ location: { query: { installation_id: 'foo' } } as Location }); - expect(getAlmOrganization).toHaveBeenCalledWith({ installationId: 'foo' }); - wrapper.setProps({ location: { query: {} } as Location }); - expect(wrapper.state('almOrganization')).toBeUndefined(); - expect(listUnboundApplications).toHaveBeenCalledTimes(2); -}); - -it('should redirect to organization page after creation', async () => { - const push = jest.fn(); - const wrapper = shallowRender({ router: mockRouter({ push }) }); - await waitAndUpdate(wrapper); - - wrapper.setState({ organization: boundOrganization }); - wrapper.instance().handleOrgCreated('foo'); - expect(push).toHaveBeenCalledWith({ pathname: '/organizations/foo' }); -}); - -it('should redirect to projects creation page after creation', async () => { - const push = jest.fn(); - const wrapper = shallowRender({ router: mockRouter({ push }) }); - await waitAndUpdate(wrapper); - - (get as jest.Mock<any>).mockReturnValueOnce(Date.now().toString()); - wrapper.instance().handleOrgCreated('foo'); - expect(get).toHaveBeenCalled(); - expect(remove).toHaveBeenCalled(); - expect(push).toHaveBeenCalledWith({ - pathname: '/projects/create', - state: { organization: 'foo', tab: 'manual' } - }); - - wrapper.setState({ almOrganization: fooAlmOrganization }); - (get as jest.Mock<any>).mockReturnValueOnce(Date.now().toString()); - wrapper.instance().handleOrgCreated('foo'); - expect(push).toHaveBeenCalledWith({ - pathname: '/projects/create', - state: { organization: 'foo', tab: 'auto' } - }); -}); - -it('should display AutoOrganizationCreate with already bound organization', async () => { - (getAlmOrganization as jest.Mock<any>).mockResolvedValueOnce({ - almOrganization: fooBarAlmOrganization, - boundOrganization - }); - (get as jest.Mock<any>) - .mockReturnValueOnce(undefined) // For BIND_ORGANIZATION_REDIRECT_TO_ORG_TIMESTAMP - .mockReturnValueOnce(Date.now().toString()); // For ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP - const push = jest.fn(); - const wrapper = shallowRender({ - currentUser: { ...user, externalProvider: 'github' }, - location: { query: { installation_id: 'foo' } } as Location, - router: mockRouter({ push }) - }); - await waitAndUpdate(wrapper); - expect(get).toHaveBeenCalled(); - expect(remove).toHaveBeenCalled(); - expect(getAlmOrganization).toHaveBeenCalledWith({ installationId: 'foo' }); - expect(push).not.toHaveBeenCalled(); - expect(wrapper.find('withRouter(RemoteOrganizationChoose)').prop('boundOrganization')).toEqual({ - key: 'foobar', - name: 'Foo & Bar' - }); -}); - -it('should redirect to org page when already bound and no binding in progress', async () => { - (getAlmOrganization as jest.Mock<any>).mockResolvedValueOnce({ - almOrganization: fooBarAlmOrganization, - boundOrganization - }); - const push = jest.fn(); - const wrapper = shallowRender({ - currentUser: { ...user, externalProvider: 'github' }, - location: { query: { installation_id: 'foo' } } as Location, - router: mockRouter({ push }) - }); - await waitAndUpdate(wrapper); - expect(getAlmOrganization).toHaveBeenCalledWith({ installationId: 'foo' }); - expect(push).toHaveBeenCalledWith({ pathname: '/organizations/foobar' }); -}); - -it('should roll back after upgrade failure', async () => { - const deleteOrganization = jest.fn(); - const wrapper = shallowRender({ deleteOrganization }); - await waitAndUpdate(wrapper); - wrapper.setState({ organization: boundOrganization }); - wrapper.find('ManualOrganizationCreate').prop<Function>('onUpgradeFail')(); - expect(deleteOrganization).toBeCalled(); -}); - -it('should cancel imports', async () => { - const push = jest.fn(); - const wrapper = shallowRender({ router: mockRouter({ push }) }); - await waitAndUpdate(wrapper); - wrapper.instance().handleCancelImport(); - expect(push).toBeCalledWith({ pathname: '/path', query: {}, state: {} }); -}); - -it('should bind org and redirect to org home when coming from org binding', async () => { - const installation_id = '5328'; - const orgKey = 'org4test'; - const push = jest.fn(); - - (get as jest.Mock<any>) - .mockReturnValueOnce(Date.now().toString()) // For BIND_ORGANIZATION_REDIRECT_TO_ORG_TIMESTAMP - .mockReturnValueOnce(orgKey); // For BIND_ORGANIZATION_KEY - - const wrapper = mountRender({ - currentUser: mockLoggedInUser({ ...user, externalProvider: 'github' }), - location: mockLocation({ query: { installation_id } }), - router: mockRouter({ push }) - }); - await waitAndUpdate(wrapper); - - expect(bindAlmOrganization).toBeCalled(); - expect(getAlmOrganization).not.toBeCalled(); - expect(push).toBeCalledWith({ - pathname: `/organizations/${orgKey}` - }); -}); - -function mountRender(props: Partial<CreateOrganization['props']> = {}) { - return mount<CreateOrganization>(<HelmetProvider>{createComponent(props)}</HelmetProvider>); -} - -function shallowRender(props: Partial<CreateOrganization['props']> = {}) { - return shallow<CreateOrganization>(createComponent(props)); -} - -function createComponent(props: Partial<CreateOrganization['props']> = {}) { - return ( - <CreateOrganization - createOrganization={jest.fn()} - currentUser={user} - deleteOrganization={jest.fn()} - location={mockLocation()} - params={{}} - router={mockRouter()} - routes={[]} - skipOnboarding={jest.fn()} - userOrganizations={[ - mockOrganizationWithAdminActions(), - mockOrganizationWithAdminActions(mockOrganizationWithAlm({ key: 'bar', name: 'Bar' })), - mockOrganizationWithAdminActions({ key: 'baz', name: 'Baz' }, { admin: false }) - ]} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/ManualOrganizationCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/ManualOrganizationCreate-test.tsx deleted file mode 100644 index 46c1fab37f4..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/ManualOrganizationCreate-test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { mockOrganization } from '../../../../helpers/testMocks'; -import ManualOrganizationCreate from '../ManualOrganizationCreate'; -import { Step } from '../utils'; - -it('should render and create organization', async () => { - const createOrganization = jest.fn().mockResolvedValue({ key: 'foo' }); - const onDone = jest.fn(); - const handleOrgDetailsFinish = jest.fn(); - const wrapper = shallowRender({ createOrganization, handleOrgDetailsFinish, onDone }); - - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - - wrapper.find('OrganizationDetailsForm').prop<Function>('onContinue')(mockOrganization()); - await waitAndUpdate(wrapper); - expect(handleOrgDetailsFinish).toHaveBeenCalled(); - wrapper.setProps({ step: Step.Plan }); - expect(wrapper).toMatchSnapshot(); -}); - -function shallowRender(props: Partial<ManualOrganizationCreate['props']> = {}) { - return shallow( - <ManualOrganizationCreate - createOrganization={jest.fn()} - handleOrgDetailsFinish={jest.fn()} - handleOrgDetailsStepOpen={jest.fn()} - onDone={jest.fn()} - onUpgradeFail={jest.fn()} - step={Step.OrganizationDetails} - subscriptionPlans={[ - { maxNcloc: 100000, price: 10 }, - { maxNcloc: 250000, price: 75 } - ]} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/OrganizationDetailsForm-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/OrganizationDetailsForm-test.tsx deleted file mode 100644 index 548d59e4cec..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/OrganizationDetailsForm-test.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { click, submit } from 'sonar-ui-common/helpers/testUtils'; -import OrganizationDetailsForm from '../OrganizationDetailsForm'; - -it('should render form', () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.find('.js-additional-info').prop('hidden')).toBe(true); - - click(wrapper.find('ResetButtonLink')); - wrapper.update(); - expect(wrapper.find('.js-additional-info').prop('hidden')).toBe(false); -}); - -it('should validate before submit', () => { - const wrapper = shallowRender(); - const instance = wrapper.instance() as OrganizationDetailsForm; - - expect( - instance.canSubmit({ - additional: false, - avatar: '', - description: '', - name: '', - key: 'foo', - submitting: false, - url: '' - }) - ).toBe(true); - - expect( - instance.canSubmit({ - additional: false, - avatar: '', - description: '', - name: '', - key: undefined, - submitting: false, - url: '' - }) - ).toBe(false); - - expect( - instance.canSubmit({ - additional: false, - avatar: undefined, - description: '', - name: '', - key: 'foo', - submitting: false, - url: '' - }) - ).toBe(false); - - instance.canSubmit = jest.fn() as any; - submit(wrapper.find('form')); - expect(instance.canSubmit).toHaveBeenCalled(); -}); - -function shallowRender(props: Partial<OrganizationDetailsForm['props']> = {}) { - return shallow( - <OrganizationDetailsForm onContinue={jest.fn()} submitText="continue" {...props} /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/OrganizationDetailsStep-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/OrganizationDetailsStep-test.tsx deleted file mode 100644 index c88443b685d..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/OrganizationDetailsStep-test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import OrganizationDetailsStep from '../OrganizationDetailsStep'; - -it('should render form', () => { - const wrapper = shallow( - <OrganizationDetailsStep finished={false} onOpen={jest.fn()} open={true}> - <form /> - </OrganizationDetailsStep> - ); - expect(wrapper).toMatchSnapshot(); - expect( - wrapper - .dive() - .find('form') - .exists() - ).toBe(true); -}); - -it('should render result', () => { - const wrapper = shallow( - <OrganizationDetailsStep - finished={true} - onOpen={jest.fn()} - open={false} - organization={{ avatar: '', description: '', key: 'org', name: 'Organization', url: '' }}> - <div /> - </OrganizationDetailsStep> - ); - expect(wrapper.dive().find('.boxed-group-actions')).toMatchSnapshot(); - expect( - wrapper - .dive() - .find('.hidden') - .exists() - ).toBe(true); -}); diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/PlanSelect-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/PlanSelect-test.tsx deleted file mode 100644 index 55fa395aead..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/PlanSelect-test.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { click } from 'sonar-ui-common/helpers/testUtils'; -import { mockAlmOrganization } from '../../../../helpers/testMocks'; -import PlanSelect, { Plan } from '../PlanSelect'; - -it('should render and select', () => { - const onChange = jest.fn(); - const wrapper = shallowRender({ onChange }); - expect(wrapper).toMatchSnapshot(); - - click(wrapper.find('PaidCardPlan')); - expect(onChange).toBeCalledWith(Plan.Paid); - wrapper.setProps({ plan: Plan.Paid }); - expect(wrapper).toMatchSnapshot(); -}); - -it('should recommend paid plan', () => { - const wrapper = shallowRender({ - almOrganization: mockAlmOrganization({ privateRepos: 1, publicRepos: 5 }), - plan: Plan.Paid - }); - expect(wrapper.find('PaidCardPlan').prop('isRecommended')).toBe(true); - expect(wrapper.find('FreeCardPlan').prop('disabled')).toBe(false); - expect(wrapper.find('FreeCardPlan').prop('hasWarning')).toBe(false); - - wrapper.setProps({ plan: Plan.Free }); - expect(wrapper.find('FreeCardPlan').prop('hasWarning')).toBe(true); -}); - -it('should recommend paid plan and disable free plan', () => { - const wrapper = shallowRender({ - almOrganization: mockAlmOrganization({ privateRepos: 1, publicRepos: 0 }) - }); - expect(wrapper.find('PaidCardPlan').prop('isRecommended')).toBe(true); - expect(wrapper.find('FreeCardPlan').prop('disabled')).toBe(true); -}); - -function shallowRender(props: Partial<PlanSelect['props']> = {}) { - return shallow( - <PlanSelect onChange={jest.fn()} plan={Plan.Free} startingPrice={10} {...props} /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/PlanStep-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/PlanStep-test.tsx deleted file mode 100644 index a306d1ccf0a..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/PlanStep-test.tsx +++ /dev/null @@ -1,93 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { mockAlmOrganization } from '../../../../helpers/testMocks'; -import { Plan } from '../PlanSelect'; -import PlanStep from '../PlanStep'; - -jest.mock('../../../../helpers/extensions', () => ({ - getExtensionStart: jest.fn().mockResolvedValue(undefined) -})); -const subscriptionPlans = [{ maxNcloc: 1000, price: 100 }]; - -it('should render and use free plan', async () => { - const onDone = jest.fn(); - const createOrganization = jest.fn().mockResolvedValue('org'); - const wrapper = shallow( - <PlanStep - createOrganization={createOrganization} - onDone={onDone} - onUpgradeFail={jest.fn()} - open={true} - subscriptionPlans={subscriptionPlans} - /> - ); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.dive()).toMatchSnapshot(); - - submit(wrapper.dive().find('form')); - await waitAndUpdate(wrapper); - expect(createOrganization).toBeCalled(); - expect(onDone).toBeCalled(); -}); - -it('should upgrade', async () => { - const onDone = jest.fn(); - const wrapper = shallow( - <PlanStep - createOrganization={jest.fn().mockResolvedValue('org')} - onDone={onDone} - onUpgradeFail={jest.fn()} - open={true} - subscriptionPlans={subscriptionPlans} - /> - ); - await waitAndUpdate(wrapper); - - wrapper - .dive() - .find('PlanSelect') - .prop<Function>('onChange')(Plan.Paid); - expect(wrapper.dive()).toMatchSnapshot(); - - wrapper - .dive() - .find('Connect(withCurrentUser(BillingFormShim))') - .prop<Function>('onCommit')(); - expect(onDone).toBeCalled(); -}); - -it('should preselect paid plan', async () => { - const wrapper = shallow( - <PlanStep - almOrganization={mockAlmOrganization({ privateRepos: 5, publicRepos: 0 })} - createOrganization={jest.fn()} - onDone={jest.fn()} - onUpgradeFail={jest.fn()} - open={true} - subscriptionPlans={subscriptionPlans} - /> - ); - await waitAndUpdate(wrapper); - expect(wrapper.dive()).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/RemoteOrganizationChoose-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/RemoteOrganizationChoose-test.tsx deleted file mode 100644 index b19e2bfa6c3..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/RemoteOrganizationChoose-test.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { submit } from 'sonar-ui-common/helpers/testUtils'; -import { mockAlmOrganization, mockRouter } from '../../../../helpers/testMocks'; -import { RemoteOrganizationChoose } from '../RemoteOrganizationChoose'; - -it('should render', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should display an alert message', () => { - expect(shallowRender({ almInstallId: 'foo' }).find('Alert')).toMatchSnapshot(); -}); - -it('should display unbound installations', () => { - const installation = { installationId: '12345', key: 'foo', name: 'Foo' }; - const push = jest.fn(); - const wrapper = shallowRender({ - almUnboundApplications: [installation], - router: mockRouter({ push }) - }); - expect(wrapper).toMatchSnapshot(); - - wrapper.find('Select').prop<Function>('onChange')(installation); - submit(wrapper.find('form')); - expect(push).toHaveBeenCalledWith({ - pathname: '/create-organization', - query: { installation_id: installation.installationId } - }); -}); - -it('should display already bound alert message', () => { - expect( - shallowRender({ - almInstallId: 'foo', - almOrganization: mockAlmOrganization(), - boundOrganization: { avatar: 'bound-avatar', key: 'bound', name: 'Bound' } - }).find('Alert') - ).toMatchSnapshot(); -}); - -function shallowRender(props: Partial<RemoteOrganizationChoose['props']> = {}) { - return shallow( - // @ts-ignore avoid passing everything from WithRouterProps - <RemoteOrganizationChoose - almApplication={{ - backgroundColor: 'blue', - iconPath: 'icon/path', - installationUrl: 'https://alm.application.url', - key: 'github', - name: 'GitHub' - }} - almUnboundApplications={[]} - router={mockRouter()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AlmApplicationInstalling-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AlmApplicationInstalling-test.tsx.snap deleted file mode 100644 index 135829ce072..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AlmApplicationInstalling-test.tsx.snap +++ /dev/null @@ -1,47 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<DeferredSpinner - customSpinner={ - <div - className="sonarcloud page page-limited" - > - <div - className="huge-spacer-top text-center" - > - <i - className="spinner" - /> - <p - className="big-spacer-top" - > - onboarding.import_organization.installing.ALM - </p> - </div> - </div> - } -/> -`; - -exports[`should render correctly 2`] = ` -<DeferredSpinner - customSpinner={ - <div - className="sonarcloud page page-limited" - > - <div - className="huge-spacer-top text-center" - > - <i - className="spinner" - /> - <p - className="big-spacer-top" - > - onboarding.import_organization.installing.github - </p> - </div> - </div> - } -/> -`; diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationBind-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationBind-test.tsx.snap deleted file mode 100644 index 7c32193e58c..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationBind-test.tsx.snap +++ /dev/null @@ -1,50 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<form - id="bind-organization-form" - onSubmit={[Function]} -> - <OrganizationSelect - onChange={[Function]} - organization="foo" - organizations={ - Array [ - Object { - "key": "foo", - "name": "Foo", - }, - ] - } - /> - <Alert - className="abs-width-400 big-spacer-top" - display="block" - variant="info" - > - onboarding.import_organization.bind_members_not_sync_info_x.organization.github - <Link - className="spacer-left" - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to={ - Object { - "pathname": "/documentation/organizations/manage-team/", - } - } - > - learn_more - </Link> - </Alert> - <div - className="display-flex-center big-spacer-top" - > - <SubmitButton - disabled={false} - > - onboarding.import_organization.bind - </SubmitButton> - </div> -</form> -`; 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 deleted file mode 100644 index 9b74fb68d47..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/AutoOrganizationCreate-test.tsx.snap +++ /dev/null @@ -1,216 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display choice between import or creation 1`] = ` -<div> - <OrganizationDetailsStep - finished={false} - onOpen={[MockFunction]} - open={true} - stepTitle="onboarding.import_organization.import_org_details" - > - <div - className="huge-spacer-bottom" - > - <p - className="display-flex-center big-spacer-bottom" - > - <FormattedMessage - defaultMessage="onboarding.import_organization_x" - id="onboarding.import_organization_x" - values={ - Object { - "avatar": <img - alt="GitHub" - className="little-spacer-left" - src="/images/sonarcloud/github.svg" - width={16} - />, - "name": <strong> - foo - </strong>, - } - } - /> - <ClearButton - className="little-spacer-left" - onClick={[MockFunction]} - /> - </p> - <RadioToggle - disabled={false} - name="filter" - onCheck={[Function]} - options={ - Array [ - Object { - "label": "onboarding.import_organization.create_new", - "value": "create", - }, - Object { - "label": "onboarding.import_organization.bind_existing", - "value": "bind", - }, - ] - } - value={null} - /> - </div> - </OrganizationDetailsStep> - <PlanStep - almApplication={ - Object { - "backgroundColor": "#444444", - "iconPath": "/images/sonarcloud/github-white.svg", - "installationUrl": "https://github.com/apps/greg-sonarcloud/installations/new", - "key": "github", - "name": "GitHub", - } - } - almOrganization={ - Object { - "almUrl": "https://github.com/foo", - "avatar": "http://example.com/avatar", - "description": "description-foo", - "key": "foo", - "name": "foo", - "personal": false, - "privateRepos": 0, - "publicRepos": 3, - "url": "http://example.com/foo", - } - } - createOrganization={[Function]} - onDone={[MockFunction]} - onUpgradeFail={[MockFunction]} - open={false} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - /> -</div> -`; - -exports[`should render prefilled and create org 1`] = ` -<div> - <OrganizationDetailsStep - finished={false} - onOpen={[MockFunction]} - open={true} - stepTitle="onboarding.import_organization.import_org_details" - > - <div - className="huge-spacer-bottom" - > - <p - className="display-flex-center big-spacer-bottom" - > - <FormattedMessage - defaultMessage="onboarding.import_organization_x" - id="onboarding.import_organization_x" - values={ - Object { - "avatar": <img - alt="GitHub" - className="little-spacer-left" - src="/images/sonarcloud/github.svg" - width={16} - />, - "name": <strong> - foo - </strong>, - } - } - /> - <ClearButton - className="little-spacer-left" - onClick={[MockFunction]} - /> - </p> - </div> - <OrganizationDetailsForm - infoBlock={ - <Alert - className="abs-width-600 big-spacer-top" - display="block" - variant="info" - > - <p> - onboarding.import_organization.members_sync_info_x.organization.github.foo.github - </p> - <a - href="http://github.com/orgs/thing/people" - rel="noopener noreferrer" - target="_blank" - > - organization.members.see_all_members_on_x.github - </a> - </Alert> - } - onContinue={[MockFunction]} - organization={ - Object { - "almUrl": "http://github.com/thing", - "avatar": "http://example.com/avatar", - "description": "description-foo", - "key": "foo", - "name": "foo", - "personal": false, - "privateRepos": 0, - "publicRepos": 3, - "url": "http://example.com/foo", - } - } - submitText="continue" - /> - </OrganizationDetailsStep> - <PlanStep - almApplication={ - Object { - "backgroundColor": "#444444", - "iconPath": "/images/sonarcloud/github-white.svg", - "installationUrl": "https://github.com/apps/greg-sonarcloud/installations/new", - "key": "github", - "name": "GitHub", - } - } - almOrganization={ - Object { - "almUrl": "http://github.com/thing", - "avatar": "http://example.com/avatar", - "description": "description-foo", - "key": "foo", - "name": "foo", - "personal": false, - "privateRepos": 0, - "publicRepos": 3, - "url": "http://example.com/foo", - } - } - createOrganization={[Function]} - onDone={[MockFunction]} - onUpgradeFail={[MockFunction]} - open={false} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - /> -</div> -`; 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 deleted file mode 100644 index 927b7d368f9..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/CreateOrganization-test.tsx.snap +++ /dev/null @@ -1,489 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render with auto tab displayed 1`] = ` -<Fragment> - <Helmet - defer={false} - encodeSpecialCharacters={true} - title="onboarding.create_organization.page.header" - titleTemplate="%s" - /> - <div - className="page page-limited huge-spacer-top huge-spacer-bottom" - > - <A11ySkipTarget - anchor="create_org_main" - /> - <header - className="page-header huge-spacer-bottom" - > - <h1 - className="page-title huge big-spacer-bottom" - > - <strong> - onboarding.create_organization.page.header - </strong> - </h1> - <p - className="page-description" - > - onboarding.create_organization.page.description - </p> - </header> - <Tabs - onChange={[Function]} - selected="auto" - tabs={ - Array [ - Object { - "key": "auto", - "node": "onboarding.import_organization.import_from_x.github", - }, - Object { - "key": "manual", - "node": "onboarding.create_organization.create_manually", - }, - ] - } - /> - <ManualOrganizationCreate - className="hidden" - createOrganization={[MockFunction]} - handleOrgDetailsFinish={[Function]} - handleOrgDetailsStepOpen={[Function]} - onDone={[Function]} - onUpgradeFail={[Function]} - step={0} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - /> - <withRouter(RemoteOrganizationChoose) - almApplication={ - Object { - "backgroundColor": "blue", - "iconPath": "icon/path", - "installationUrl": "https://alm.installation.url", - "key": "github", - "name": "GitHub", - } - } - almUnboundApplications={Array []} - className="" - /> - </div> -</Fragment> -`; - -exports[`should render with auto tab selected and manual disabled 1`] = ` -<AlmApplicationInstalling - almKey="github" -/> -`; - -exports[`should render with auto tab selected and manual disabled 2`] = ` -<Fragment> - <Helmet - defer={false} - encodeSpecialCharacters={true} - title="onboarding.create_organization.page.header" - titleTemplate="%s" - /> - <div - className="page page-limited huge-spacer-top huge-spacer-bottom" - > - <A11ySkipTarget - anchor="create_org_main" - /> - <header - className="page-header huge-spacer-bottom" - > - <h1 - className="page-title huge big-spacer-bottom" - > - <strong> - onboarding.create_organization.page.header - </strong> - </h1> - <p - className="page-description" - > - onboarding.create_organization.page.description - </p> - </header> - <Tabs - onChange={[Function]} - selected="auto" - tabs={ - Array [ - Object { - "key": "auto", - "node": "onboarding.import_organization.import_from_x.github", - }, - Object { - "key": "manual", - "node": "onboarding.create_organization.create_manually", - }, - ] - } - /> - <ManualOrganizationCreate - className="hidden" - createOrganization={[MockFunction]} - handleOrgDetailsFinish={[Function]} - handleOrgDetailsStepOpen={[Function]} - onDone={[Function]} - onUpgradeFail={[Function]} - step={0} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - /> - <AutoOrganizationCreate - almApplication={ - Object { - "backgroundColor": "blue", - "iconPath": "icon/path", - "installationUrl": "https://alm.installation.url", - "key": "github", - "name": "GitHub", - } - } - almInstallId="foo" - almOrganization={ - Object { - "avatar": "my-avatar", - "description": "Continuous Code Quality", - "key": "sonarsource", - "name": "SonarSource", - "privateRepos": 0, - "publicRepos": 3, - "url": "https://www.sonarsource.com", - } - } - className="" - createOrganization={[MockFunction]} - handleCancelImport={[Function]} - handleOrgDetailsFinish={[Function]} - handleOrgDetailsStepOpen={[Function]} - onDone={[Function]} - onOrgCreated={[Function]} - onUpgradeFail={[Function]} - step={0} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - unboundOrganizations={ - Array [ - Object { - "actions": Object { - "admin": true, - }, - "key": "foo", - "name": "Foo", - }, - ] - } - /> - </div> -</Fragment> -`; - -exports[`should render with manual tab displayed 1`] = ` -<Fragment> - <Helmet - defer={false} - encodeSpecialCharacters={true} - title="onboarding.create_organization.page.header" - titleTemplate="%s" - /> - <div - className="page page-limited huge-spacer-top huge-spacer-bottom" - > - <A11ySkipTarget - anchor="create_org_main" - /> - <header - className="page-header huge-spacer-bottom" - > - <h1 - className="page-title huge big-spacer-bottom" - > - <strong> - onboarding.create_organization.page.header - </strong> - </h1> - <p - className="page-description" - > - onboarding.create_organization.page.description - </p> - </header> - <ManualOrganizationCreate - createOrganization={[MockFunction]} - handleOrgDetailsFinish={[Function]} - handleOrgDetailsStepOpen={[Function]} - onDone={[Function]} - onUpgradeFail={[Function]} - step={0} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - /> - </div> -</Fragment> -`; - -exports[`should render with organization bind page 1`] = ` -<AlmApplicationInstalling - almKey="github" -/> -`; - -exports[`should render with organization bind page 2`] = ` -<Fragment> - <Helmet - defer={false} - encodeSpecialCharacters={true} - title="onboarding.create_organization.page.header" - titleTemplate="%s" - /> - <div - className="page page-limited huge-spacer-top huge-spacer-bottom" - > - <A11ySkipTarget - anchor="create_org_main" - /> - <header - className="page-header huge-spacer-bottom" - > - <h1 - className="page-title huge big-spacer-bottom" - > - <strong> - onboarding.create_organization.page.header - </strong> - </h1> - <p - className="page-description" - > - onboarding.create_organization.page.description - </p> - </header> - <Tabs - onChange={[Function]} - selected="auto" - tabs={ - Array [ - Object { - "key": "auto", - "node": "onboarding.import_organization.import_from_x.github", - }, - Object { - "key": "manual", - "node": "onboarding.create_organization.create_manually", - }, - ] - } - /> - <ManualOrganizationCreate - className="hidden" - createOrganization={[MockFunction]} - handleOrgDetailsFinish={[Function]} - handleOrgDetailsStepOpen={[Function]} - onDone={[Function]} - onUpgradeFail={[Function]} - step={0} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - /> - <AutoOrganizationCreate - almApplication={ - Object { - "backgroundColor": "blue", - "iconPath": "icon/path", - "installationUrl": "https://alm.installation.url", - "key": "github", - "name": "GitHub", - } - } - almInstallId="foo" - almOrganization={ - Object { - "almUrl": "https://github.com/foo", - "avatar": "http://example.com/avatar", - "description": "description-foo", - "key": "foo", - "name": "foo", - "personal": false, - "privateRepos": 0, - "publicRepos": 3, - "url": "http://example.com/foo", - } - } - className="" - createOrganization={[MockFunction]} - handleCancelImport={[Function]} - handleOrgDetailsFinish={[Function]} - handleOrgDetailsStepOpen={[Function]} - onDone={[Function]} - onOrgCreated={[Function]} - onUpgradeFail={[Function]} - step={0} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - unboundOrganizations={ - Array [ - Object { - "actions": Object { - "admin": true, - }, - "key": "foo", - "name": "Foo", - }, - ] - } - /> - </div> -</Fragment> -`; - -exports[`should switch tabs 1`] = ` -<Fragment> - <Helmet - defer={false} - encodeSpecialCharacters={true} - title="onboarding.create_organization.page.header" - titleTemplate="%s" - /> - <div - className="page page-limited huge-spacer-top huge-spacer-bottom" - > - <A11ySkipTarget - anchor="create_org_main" - /> - <header - className="page-header huge-spacer-bottom" - > - <h1 - className="page-title huge big-spacer-bottom" - > - <strong> - onboarding.create_organization.page.header - </strong> - </h1> - <p - className="page-description" - > - onboarding.create_organization.page.description - </p> - </header> - <Tabs - onChange={[Function]} - selected="auto" - tabs={ - Array [ - Object { - "key": "auto", - "node": "onboarding.import_organization.import_from_x.github", - }, - Object { - "key": "manual", - "node": "onboarding.create_organization.create_manually", - }, - ] - } - /> - <ManualOrganizationCreate - className="hidden" - createOrganization={[MockFunction]} - handleOrgDetailsFinish={[Function]} - handleOrgDetailsStepOpen={[Function]} - onDone={[Function]} - onUpgradeFail={[Function]} - step={0} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - /> - <withRouter(RemoteOrganizationChoose) - almApplication={ - Object { - "backgroundColor": "blue", - "iconPath": "icon/path", - "installationUrl": "https://alm.installation.url", - "key": "github", - "name": "GitHub", - } - } - almUnboundApplications={Array []} - className="" - /> - </div> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/ManualOrganizationCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/ManualOrganizationCreate-test.tsx.snap deleted file mode 100644 index 9ede472a8d9..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/ManualOrganizationCreate-test.tsx.snap +++ /dev/null @@ -1,84 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render and create organization 1`] = ` -<div> - <OrganizationDetailsStep - finished={false} - onOpen={[MockFunction]} - open={true} - > - <OrganizationDetailsForm - onContinue={[MockFunction]} - submitText="continue" - /> - </OrganizationDetailsStep> - <PlanStep - createOrganization={[Function]} - onDone={[MockFunction]} - onUpgradeFail={[MockFunction]} - open={false} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - /> -</div> -`; - -exports[`should render and create organization 2`] = ` -<div> - <OrganizationDetailsStep - finished={false} - onOpen={[MockFunction]} - open={false} - > - <OrganizationDetailsForm - onContinue={ - [MockFunction] { - "calls": Array [ - Array [ - Object { - "key": "foo", - "name": "Foo", - }, - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], - } - } - submitText="continue" - /> - </OrganizationDetailsStep> - <PlanStep - createOrganization={[Function]} - onDone={[MockFunction]} - onUpgradeFail={[MockFunction]} - open={true} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 100000, - "price": 10, - }, - Object { - "maxNcloc": 250000, - "price": 75, - }, - ] - } - /> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/OrganizationDetailsForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/OrganizationDetailsForm-test.tsx.snap deleted file mode 100644 index b29f23245fe..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/OrganizationDetailsForm-test.tsx.snap +++ /dev/null @@ -1,107 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render form 1`] = ` -<form - id="organization-form" - onSubmit={[Function]} -> - <OrganizationKeyInput - onChange={[Function]} - /> - <div - className="big-spacer-top" - > - <ResetButtonLink - onClick={[Function]} - > - onboarding.create_organization.add_additional_info - <DropdownIcon - className="little-spacer-left" - turned={false} - /> - </ResetButtonLink> - </div> - <div - className="js-additional-info" - hidden={true} - > - <div - className="big-spacer-top" - > - <label - htmlFor="organization-display-name" - > - <strong> - onboarding.create_organization.display_name - </strong> - </label> - <div - className="little-spacer-top" - > - <input - className="input-super-large text-middle" - id="organization-display-name" - maxLength={255} - onChange={[Function]} - type="text" - value="" - /> - </div> - <div - className="note abs-width-400" - > - onboarding.create_organization.display_name.description - </div> - </div> - <div - className="big-spacer-top" - > - <OrganizationAvatarInput - initialValue="" - name="" - onChange={[Function]} - /> - </div> - <div - className="big-spacer-top" - > - <label - htmlFor="organization-description" - > - <strong> - onboarding.create_organization.description - </strong> - </label> - <div - className="little-spacer-top" - > - <textarea - className="input-super-large text-middle" - id="organization-description" - maxLength={256} - onChange={[Function]} - rows={3} - value="" - /> - </div> - </div> - <div - className="big-spacer-top" - > - <OrganizationUrlInput - initialValue="" - onChange={[Function]} - /> - </div> - </div> - <div - className="display-flex-center big-spacer-top" - > - <SubmitButton - disabled={true} - > - continue - </SubmitButton> - </div> -</form> -`; diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/OrganizationDetailsStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/OrganizationDetailsStep-test.tsx.snap deleted file mode 100644 index 3b15b770b40..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/OrganizationDetailsStep-test.tsx.snap +++ /dev/null @@ -1,28 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render form 1`] = ` -<Step - finished={false} - onOpen={[MockFunction]} - open={true} - renderForm={[Function]} - renderResult={[Function]} - stepNumber={1} - stepTitle="onboarding.create_organization.enter_org_details" -/> -`; - -exports[`should render result 1`] = ` -<div - className="boxed-group-actions display-flex-center" -> - <AlertSuccessIcon - className="spacer-right" - /> - <strong - className="text-limited" - > - org - </strong> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/PlanSelect-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/PlanSelect-test.tsx.snap deleted file mode 100644 index cb99c558c4a..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/PlanSelect-test.tsx.snap +++ /dev/null @@ -1,47 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render and select 1`] = ` -<div - aria-label="onboarding.create_organization.choose_plan" - className="display-flex-row huge-spacer-bottom" - role="radiogroup" -> - <FreeCardPlan - disabled={false} - hasWarning={false} - key="free" - onClick={[Function]} - selected={true} - /> - <PaidCardPlan - isRecommended={false} - key="paid" - onClick={[Function]} - selected={false} - startingPrice={10} - /> -</div> -`; - -exports[`should render and select 2`] = ` -<div - aria-label="onboarding.create_organization.choose_plan" - className="display-flex-row huge-spacer-bottom" - role="radiogroup" -> - <FreeCardPlan - disabled={false} - hasWarning={false} - key="free" - onClick={[Function]} - selected={false} - /> - <PaidCardPlan - isRecommended={false} - key="paid" - onClick={[Function]} - selected={true} - startingPrice={10} - /> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/PlanStep-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/PlanStep-test.tsx.snap deleted file mode 100644 index ca7cb711940..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/PlanStep-test.tsx.snap +++ /dev/null @@ -1,163 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should preselect paid plan 1`] = ` -<div - className="boxed-group onboarding-step is-open" -> - <div - className="onboarding-step-number" - > - 2 - </div> - <div - className="boxed-group-header" - > - <h2> - onboarding.create_organization.enter_payment_details - </h2> - </div> - <div - className="" - > - <div - className="boxed-group-inner" - > - <PlanSelect - almOrganization={ - Object { - "almUrl": "https://github.com/foo", - "avatar": "http://example.com/avatar", - "description": "description-foo", - "key": "foo", - "name": "foo", - "personal": false, - "privateRepos": 5, - "publicRepos": 0, - "url": "http://example.com/foo", - } - } - onChange={[Function]} - plan="paid" - startingPrice={100} - /> - <Connect(withCurrentUser(BillingFormShim)) - onCommit={[MockFunction]} - onFailToUpgrade={[MockFunction]} - organizationKey={[MockFunction]} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 1000, - "price": 100, - }, - ] - } - > - <Component /> - </Connect(withCurrentUser(BillingFormShim))> - </div> - </div> -</div> -`; - -exports[`should render and use free plan 1`] = ` -<Step - finished={false} - onOpen={[Function]} - open={true} - renderForm={[Function]} - renderResult={[Function]} - stepNumber={2} - stepTitle="onboarding.create_organization.choose_plan" -/> -`; - -exports[`should render and use free plan 2`] = ` -<div - className="boxed-group onboarding-step is-open" -> - <div - className="onboarding-step-number" - > - 2 - </div> - <div - className="boxed-group-header" - > - <h2> - onboarding.create_organization.choose_plan - </h2> - </div> - <div - className="" - > - <div - className="boxed-group-inner" - > - <PlanSelect - onChange={[Function]} - plan="free" - startingPrice={100} - /> - <form - className="display-flex-center big-spacer-top" - id="organization-free-plan-form" - onSubmit={[Function]} - > - <SubmitButton - disabled={false} - > - my_account.create_organization - </SubmitButton> - </form> - </div> - </div> -</div> -`; - -exports[`should upgrade 1`] = ` -<div - className="boxed-group onboarding-step is-open" -> - <div - className="onboarding-step-number" - > - 2 - </div> - <div - className="boxed-group-header" - > - <h2> - onboarding.create_organization.choose_plan - </h2> - </div> - <div - className="" - > - <div - className="boxed-group-inner" - > - <PlanSelect - onChange={[Function]} - plan="paid" - startingPrice={100} - /> - <Connect(withCurrentUser(BillingFormShim)) - onCommit={[MockFunction]} - onFailToUpgrade={[MockFunction]} - organizationKey={[MockFunction]} - subscriptionPlans={ - Array [ - Object { - "maxNcloc": 1000, - "price": 100, - }, - ] - } - > - <Component /> - </Connect(withCurrentUser(BillingFormShim))> - </div> - </div> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/RemoteOrganizationChoose-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/RemoteOrganizationChoose-test.tsx.snap deleted file mode 100644 index 946d551efa3..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/RemoteOrganizationChoose-test.tsx.snap +++ /dev/null @@ -1,195 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should display already bound alert message 1`] = ` -<Alert - className="big-spacer-bottom width-60" - variant="error" -> - <FormattedMessage - defaultMessage="onboarding.import_organization.already_bound_x" - id="onboarding.import_organization.already_bound_x" - values={ - Object { - "avatar": <img - alt="GitHub" - className="little-spacer-left" - src="/images/sonarcloud/github.svg" - width={16} - />, - "boundAvatar": <OrganizationAvatar - className="little-spacer-left" - organization={ - Object { - "avatar": "bound-avatar", - "key": "bound", - "name": "Bound", - } - } - small={true} - />, - "boundName": <strong> - Bound - </strong>, - "name": <strong> - foo - </strong>, - } - } - /> -</Alert> -`; - -exports[`should display an alert message 1`] = ` -<Alert - className="big-spacer-bottom width-60" - variant="error" -> - <div - className="markdown" - > - onboarding.import_organization.org_not_found - <ul> - <li> - onboarding.import_organization.org_not_found.tips_1 - </li> - <li> - onboarding.import_organization.org_not_found.tips_2 - </li> - </ul> - </div> -</Alert> -`; - -exports[`should display unbound installations 1`] = ` -<div - className="boxed-group" -> - <div - className="boxed-group-header" - > - <h2> - onboarding.import_organization.import_org_details - </h2> - </div> - <div - className="boxed-group-inner" - > - <div - className="display-flex-center" - > - <div - className="display-inline-block" - > - <IdentityProviderLink - backgroundColor="blue" - className="display-inline-block" - iconPath="icon/path" - name="GitHub" - onClick={[Function]} - small={true} - url="https://alm.application.url" - > - onboarding.import_organization.choose_organization_button.github - </IdentityProviderLink> - </div> - <div - className="display-flex-stretch" - > - <div - className="vertical-pipe-separator" - > - <div - className="vertical-separator " - /> - <span - className="note" - > - or - </span> - <div - className="vertical-separator" - /> - </div> - <form - className="big-spacer-top big-spacer-bottom" - onSubmit={[Function]} - > - <div - className="form-field abs-width-400" - > - <label - className="text-normal" - htmlFor="select-unbound-installation" - > - onboarding.import_organization.choose_unbound_installation_x.github - </label> - <Select - className="input-super-large" - clearable={false} - id="select-unbound-installation" - labelKey="name" - onChange={[Function]} - optionRenderer={[Function]} - options={ - Array [ - Object { - "installationId": "12345", - "key": "foo", - "name": "Foo", - }, - ] - } - placeholder="onboarding.import_organization.choose_organization" - value="" - valueKey="installationId" - valueRenderer={[Function]} - /> - </div> - <SubmitButton - disabled={true} - > - continue - </SubmitButton> - </form> - </div> - </div> - </div> -</div> -`; - -exports[`should render 1`] = ` -<div - className="boxed-group" -> - <div - className="boxed-group-header" - > - <h2> - onboarding.import_organization.import_org_details - </h2> - </div> - <div - className="boxed-group-inner" - > - <div - className="display-flex-center" - > - <div - className="display-inline-block" - > - <IdentityProviderLink - backgroundColor="blue" - className="display-inline-block" - iconPath="icon/path" - name="GitHub" - onClick={[Function]} - small={true} - url="https://alm.application.url" - > - onboarding.import_organization.choose_organization_button.github - </IdentityProviderLink> - </div> - </div> - </div> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/actions-test.ts b/server/sonar-web/src/main/js/apps/create/organization/__tests__/actions-test.ts deleted file mode 100644 index 9248d38b46b..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/actions-test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { createOrganization, syncMembers } from '../../../../api/organizations'; -import { mockOrganization, mockOrganizationWithAlm } from '../../../../helpers/testMocks'; -import * as actions from '../actions'; - -jest.mock('../../../../api/alm-integration', () => ({ - bindAlmOrganization: jest.fn().mockResolvedValue({}) -})); - -jest.mock('../../../../api/organizations', () => ({ - createOrganization: jest.fn().mockResolvedValue({ key: 'foo', name: 'Foo' }), - updateOrganization: jest.fn().mockResolvedValue({}), - syncMembers: jest.fn() -})); - -const dispatch = jest.fn(); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -describe('#createOrganization', () => { - it('should create and return an org key', async () => { - const org = mockOrganization(); - const promise = actions.createOrganization(org)(dispatch); - - expect(createOrganization).toHaveBeenCalledWith(org); - const returnValue = await promise; - expect(dispatch).toHaveBeenCalledWith({ organization: org, type: 'CREATE_ORGANIZATION' }); - expect(syncMembers).not.toBeCalled(); - expect(returnValue).toBe(org.key); - }); - - it('should create and sync members', async () => { - const { alm, ...org } = mockOrganizationWithAlm( - {}, - { key: 'github', membersSync: true, url: 'https://github.com/foo' } - ); - - (createOrganization as jest.Mock).mockResolvedValueOnce(org); - const promise = actions.createOrganization({ alm, ...org })(dispatch); - - expect(createOrganization).toHaveBeenCalledWith(org); - await promise; - expect(syncMembers).toHaveBeenCalledWith(org.key); - }); - - it('should not sync members for personal Github orgs', async () => { - const { alm, ...org } = mockOrganizationWithAlm( - {}, - { key: 'github', membersSync: true, personal: true, url: 'https://github.com/foo' } - ); - - (createOrganization as jest.Mock).mockResolvedValueOnce(org); - const promise = actions.createOrganization({ alm, ...org })(dispatch); - - expect(createOrganization).toHaveBeenCalledWith(org); - await promise; - expect(syncMembers).not.toBeCalled(); - }); -}); diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/utils-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/utils-test.tsx deleted file mode 100644 index 8ba2a9f1824..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/utils-test.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { formatPrice } from '../utils'; - -jest.mock('sonar-ui-common/helpers/urls', () => ({ - getHostUrl: () => 'http://host.url' -})); - -describe('#formatPrice', () => { - it('formats correctly', () => { - expect(formatPrice(10)).toBe('billing.price_format.10'); - expect(formatPrice(10000)).toBe('billing.price_format.10,000'); - expect(formatPrice(10000, true)).toBe('10,000'); - }); -}); diff --git a/server/sonar-web/src/main/js/apps/create/organization/actions.ts b/server/sonar-web/src/main/js/apps/create/organization/actions.ts deleted file mode 100644 index 4abaa682d0f..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/actions.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { Dispatch } from 'redux'; -import * as api from '../../../api/organizations'; -import { isGithub } from '../../../helpers/almIntegrations'; -import * as actions from '../../../store/organizations'; - -export function createOrganization({ - alm, - ...organization -}: T.Organization & { installationId?: string }) { - return (dispatch: Dispatch) => { - return api - .createOrganization({ ...organization, name: organization.name || organization.key }) - .then((newOrganization: T.Organization) => { - dispatch(actions.createOrganization({ ...newOrganization, alm })); - if (alm && alm.membersSync && !alm.personal && isGithub(alm.key)) { - api.syncMembers(newOrganization.key); - } - return newOrganization.key; - }); - }; -} 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 deleted file mode 100644 index e0d5ca58124..00000000000 --- a/server/sonar-web/src/main/js/apps/create/organization/utils.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { memoize } from 'lodash'; -import { translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { formatMeasure } from 'sonar-ui-common/helpers/measures'; -import { cleanQuery, parseAsOptionalString, serializeString } from 'sonar-ui-common/helpers/query'; -import { decodeJwt } from 'sonar-ui-common/helpers/strings'; -import { isBitbucket, isGithub } from '../../../helpers/almIntegrations'; - -export const ORGANIZATION_IMPORT_BINDING_IN_PROGRESS_TIMESTAMP = - 'sonarcloud.import_org.binding_in_progress'; - -export const ORGANIZATION_IMPORT_REDIRECT_TO_PROJECT_TIMESTAMP = - 'sonarcloud.import_org.redirect_to_projects'; - -export const BIND_ORGANIZATION_KEY = 'sonarcloud.bind_org.key'; - -export const BIND_ORGANIZATION_REDIRECT_TO_ORG_TIMESTAMP = 'sonarcloud.bind_org.redirect_to_org'; - -export enum Step { - OrganizationDetails, - Plan -} - -export function formatPrice(price?: number, noSign?: boolean) { - const priceFormatted = formatMeasure(price, 'FLOAT') - .replace(/[.|,]0$/, '') - .replace(/([.|,]\d)$/, '$10'); - return noSign ? priceFormatted : translateWithParameters('billing.price_format', priceFormatted); -} - -export interface Query { - almInstallId?: string; - almKey?: string; -} - -export const parseQuery = memoize( - (urlQuery: T.RawQuery = {}): Query => { - let almInstallId = undefined; - let almKey = undefined; - - if (urlQuery['installation_id']) { - almKey = 'github'; - almInstallId = parseAsOptionalString(urlQuery['installation_id']); - } else if (urlQuery['clientKey']) { - almKey = 'bitbucket'; - almInstallId = parseAsOptionalString(urlQuery['clientKey']); - } else if (urlQuery['jwt']) { - const jwt = decodeJwt(urlQuery['jwt']); - if (jwt && jwt.iss) { - almKey = 'bitbucket'; - almInstallId = jwt.iss; - } - } - return { almInstallId, almKey }; - } -); - -export const serializeQuery = (query: Query): T.RawQuery => - cleanQuery({ - installation_id: isGithub(query.almKey) ? serializeString(query.almInstallId) : undefined, - clientKey: isBitbucket(query.almKey) ? serializeString(query.almInstallId) : undefined - }); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx deleted file mode 100644 index 40bd2b774c8..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/AddMemberForm.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { Button, ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import Modal from 'sonar-ui-common/components/controls/Modal'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { searchMembers } from '../../api/organizations'; -import UsersSelectSearch from '../users/components/UsersSelectSearch'; - -interface Props { - addMember: (member: T.OrganizationMember) => void; - organization: T.Organization; - memberLogins: string[]; -} - -interface State { - open: boolean; - selectedMember?: T.OrganizationMember; -} - -export default class AddMemberForm extends React.PureComponent<Props, State> { - state: State = { - open: false - }; - - openForm = () => { - this.setState({ open: true }); - }; - - closeForm = () => { - this.setState({ open: false, selectedMember: undefined }); - }; - - handleSearch = (query: string | undefined, ps: number) => { - const data = { organization: this.props.organization.key, ps, selected: 'deselected' }; - if (!query) { - return searchMembers(data); - } - return searchMembers({ ...data, q: query }); - }; - - handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { - event.preventDefault(); - if (this.state.selectedMember) { - this.props.addMember(this.state.selectedMember); - this.closeForm(); - } - }; - - selectedMemberChange = (member: T.OrganizationMember) => { - this.setState({ selectedMember: member }); - }; - - renderModal() { - const header = translate('users.add'); - return ( - <Modal contentLabel={header} key="add-member-modal" onRequestClose={this.closeForm}> - <header className="modal-head"> - <h2>{header}</h2> - </header> - <form onSubmit={this.handleSubmit}> - <div className="modal-body"> - <div className="modal-field"> - <label>{translate('users.search_description')}</label> - <UsersSelectSearch - autoFocus={true} - excludedUsers={this.props.memberLogins} - handleValueChange={this.selectedMemberChange} - searchUsers={this.handleSearch} - selectedUser={this.state.selectedMember} - /> - </div> - </div> - <footer className="modal-foot"> - <div> - <SubmitButton disabled={!this.state.selectedMember}> - {translate('organization.members.add_to_members')} - </SubmitButton> - <ResetButtonLink onClick={this.closeForm}>{translate('cancel')}</ResetButtonLink> - </div> - </footer> - </form> - </Modal> - ); - } - - render() { - return ( - <> - <Button key="add-member-button" onClick={this.openForm}> - {translate('organization.members.add')} - </Button> - {this.state.open && this.renderModal()} - </> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx deleted file mode 100644 index c1198762ffb..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/ManageMemberGroupsForm.tsx +++ /dev/null @@ -1,162 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { keyBy, pickBy, some } from 'lodash'; -import * as React from 'react'; -import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import SimpleModal from 'sonar-ui-common/components/controls/SimpleModal'; -import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { getUserGroups, UserGroup } from '../../api/users'; -import OrganizationGroupCheckbox from '../organizations/components/OrganizationGroupCheckbox'; - -interface Props { - onClose: () => void; - member: T.OrganizationMember; - organization: T.Organization; - organizationGroups: T.Group[]; - updateMemberGroups: ( - member: T.OrganizationMember, - add: string[], - remove: string[] - ) => Promise<void>; -} - -interface State { - userGroups?: T.Dict<UserGroup & { status?: string }>; - loading?: boolean; -} - -export default class ManageMemberGroupsForm extends React.PureComponent<Props, State> { - mounted = false; - state: State = {}; - - componentDidMount() { - this.mounted = true; - this.loadUserGroups(); - } - - componentWillUnmount() { - this.mounted = false; - } - - loadUserGroups = () => { - this.setState({ loading: true }); - getUserGroups({ - login: this.props.member.login, - organization: this.props.organization.key - }).then( - response => { - if (this.mounted) { - this.setState({ loading: false, userGroups: keyBy(response.groups, 'name') }); - } - }, - () => { - if (this.mounted) { - this.setState({ loading: false }); - } - } - ); - }; - - isGroupSelected = (groupName: string) => { - if (this.state.userGroups) { - const group = this.state.userGroups[groupName] || {}; - if (group.status) { - return group.status === 'add'; - } else { - return group.selected === true; - } - } - return false; - }; - - onCheck = (groupName: string, checked: boolean) => { - this.setState((prevState: State) => { - const { userGroups = {} } = prevState; - const group = userGroups[groupName] || {}; - let status = ''; - if (group.selected && !checked) { - status = 'remove'; - } else if (!group.selected && checked) { - status = 'add'; - } - return { userGroups: { ...userGroups, [groupName]: { ...group, status } } }; - }); - }; - - handleSubmit = () => { - return this.props - .updateMemberGroups( - this.props.member, - Object.keys(pickBy(this.state.userGroups, group => group.status === 'add')), - Object.keys(pickBy(this.state.userGroups, group => group.status === 'remove')) - ) - .then(this.props.onClose); - }; - - render() { - const { loading, userGroups = {} } = this.state; - const header = translate('organization.members.manage_groups'); - const hasChanges = some(userGroups, group => group.status !== undefined); - return ( - <SimpleModal header={header} onClose={this.props.onClose} onSubmit={this.handleSubmit}> - {({ onCloseClick, onFormSubmit, submitting }) => ( - <form onSubmit={onFormSubmit}> - <header className="modal-head"> - <h2>{header}</h2> - </header> - <div className="modal-body modal-container"> - <p> - <strong> - {translateWithParameters( - 'organization.members.members_groups', - this.props.member.name - )} - </strong> - </p> - {loading ? ( - <DeferredSpinner className="spacer-top" /> - ) : ( - <ul className="list-spaced"> - {this.props.organizationGroups.map(group => ( - <OrganizationGroupCheckbox - checked={this.isGroupSelected(group.name)} - group={group} - key={group.name} - onCheck={this.onCheck} - /> - ))} - </ul> - )} - </div> - - <footer className="modal-foot"> - <DeferredSpinner className="spacer-right" loading={submitting} /> - <SubmitButton disabled={submitting || !hasChanges}>{translate('save')}</SubmitButton> - <ResetButtonLink disabled={submitting} onClick={onCloseClick}> - {translate('cancel')} - </ResetButtonLink> - </footer> - </form> - )} - </SimpleModal> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx deleted file mode 100644 index 551ca6b51c9..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersList.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { sortBy } from 'lodash'; -import * as React from 'react'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import MembersListItem from './MembersListItem'; - -interface Props { - currentUser: T.LoggedInUser; - members: T.OrganizationMember[]; - organizationGroups: T.Group[]; - organization: T.Organization; - removeMember?: (member: T.OrganizationMember) => void; - updateMemberGroups: ( - member: T.OrganizationMember, - add: Array<string>, - remove: Array<string> - ) => Promise<void>; -} - -export default class MembersList extends React.PureComponent<Props> { - render() { - const { currentUser, members } = this.props; - - if (!members.length) { - return <div className="note">{translate('no_results')}</div>; - } - - const sortedMembers = sortBy( - members, - member => member.name, - member => member.login - ); - return ( - <div className="boxed-group boxed-group-inner"> - <table className="data zebra"> - <tbody> - {sortedMembers.map(member => ( - <MembersListItem - key={member.login} - member={member} - organization={this.props.organization} - organizationGroups={this.props.organizationGroups} - removeMember={ - currentUser.login !== member.login ? this.props.removeMember : undefined - } - updateMemberGroups={this.props.updateMemberGroups} - /> - ))} - </tbody> - </table> - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx deleted file mode 100644 index a4d39692988..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListHeader.tsx +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; -import SearchBox from 'sonar-ui-common/components/controls/SearchBox'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { formatMeasure } from 'sonar-ui-common/helpers/measures'; -import { getAlmMembersUrl, sanitizeAlmId } from '../../helpers/almIntegrations'; - -export interface Props { - handleSearch: (query?: string) => void; - organization: T.Organization; - total?: number; -} - -export default function MembersListHeader({ handleSearch, organization, total }: Props) { - return ( - <div className="panel panel-vertical bordered-bottom spacer-bottom"> - <SearchBox - minLength={2} - onChange={handleSearch} - placeholder={translate('search.search_for_members')} - /> - {total !== undefined && ( - <span className="pull-right little-spacer-top"> - <strong>{formatMeasure(total, 'INT')}</strong> {translate('organization.members.members')} - {organization.alm && organization.alm.membersSync && ( - <HelpTooltip - className="spacer-left" - overlay={ - <div className="abs-width-300 markdown cut-margins"> - <p> - {translate( - 'organization.members.auto_sync_total_help', - sanitizeAlmId(organization.alm.key) - )} - </p> - <hr /> - <p> - <a - href={getAlmMembersUrl(organization.alm.key, organization.alm.url)} - rel="noopener noreferrer" - target="_blank"> - {translateWithParameters( - 'organization.members.see_all_members_on_x', - translate(sanitizeAlmId(organization.alm.key)) - )} - </a> - </p> - </div> - } - /> - )} - </span> - )} - </div> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx deleted file mode 100644 index c059d8daf39..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersListItem.tsx +++ /dev/null @@ -1,143 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import ActionsDropdown, { - ActionsDropdownDivider, - ActionsDropdownItem -} from 'sonar-ui-common/components/controls/ActionsDropdown'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { formatMeasure } from 'sonar-ui-common/helpers/measures'; -import Avatar from '../../components/ui/Avatar'; -import ManageMemberGroupsForm from './ManageMemberGroupsForm'; -import RemoveMemberForm from './RemoveMemberForm'; - -interface Props { - member: T.OrganizationMember; - organization: T.Organization; - organizationGroups: T.Group[]; - removeMember?: (member: T.OrganizationMember) => void; - updateMemberGroups: ( - member: T.OrganizationMember, - add: string[], - remove: string[] - ) => Promise<void>; -} - -interface State { - removeMemberForm: boolean; - manageGroupsForm: boolean; -} - -const AVATAR_SIZE = 36; - -export default class MembersListItem extends React.PureComponent<Props, State> { - mounted = false; - state: State = { removeMemberForm: false, manageGroupsForm: false }; - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - handleManageGroupsClick = () => { - this.setState({ manageGroupsForm: true }); - }; - - closeManageGroupsForm = () => { - if (this.mounted) { - this.setState({ manageGroupsForm: false }); - } - }; - - handleRemoveMemberClick = () => { - this.setState({ removeMemberForm: true }); - }; - - closeRemoveMemberForm = () => { - if (this.mounted) { - this.setState({ removeMemberForm: false }); - } - }; - - render() { - const { member, organization, removeMember } = this.props; - const { actions = {} } = organization; - return ( - <tr> - <td className="thin nowrap"> - <Avatar hash={member.avatar} name={member.name} size={AVATAR_SIZE} /> - </td> - <td className="nowrap text-middle"> - <strong>{member.name}</strong> - <span className="note little-spacer-left">{member.login}</span> - </td> - {actions.admin && ( - <td className="text-right text-middle"> - {translateWithParameters( - 'organization.members.x_groups', - formatMeasure(member.groupCount || 0, 'INT') - )} - </td> - )} - {actions.admin && ( - <> - <td className="nowrap text-middle text-right"> - <ActionsDropdown> - <ActionsDropdownItem onClick={this.handleManageGroupsClick}> - {translate('organization.members.manage_groups')} - </ActionsDropdownItem> - {removeMember && ( - <> - <ActionsDropdownDivider /> - <ActionsDropdownItem destructive={true} onClick={this.handleRemoveMemberClick}> - {translate('organization.members.remove')} - </ActionsDropdownItem> - </> - )} - </ActionsDropdown> - </td> - - {this.state.manageGroupsForm && ( - <ManageMemberGroupsForm - member={this.props.member} - onClose={this.closeManageGroupsForm} - organization={this.props.organization} - organizationGroups={this.props.organizationGroups} - updateMemberGroups={this.props.updateMemberGroups} - /> - )} - - {removeMember && this.state.removeMemberForm && ( - <RemoveMemberForm - member={this.props.member} - onClose={this.closeRemoveMemberForm} - organization={this.props.organization} - removeMember={removeMember} - /> - )} - </> - )} - </tr> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx deleted file mode 100644 index 06bc49340fc..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/MembersPageHeader.tsx +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router'; -import { Alert } from 'sonar-ui-common/components/ui/Alert'; -import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../components/docs/DocTooltip'; -import { isGithub, sanitizeAlmId } from '../../helpers/almIntegrations'; -import AddMemberForm from './AddMemberForm'; -import SyncMemberForm from './SyncMemberForm'; - -export interface Props { - handleAddMember: (member: T.OrganizationMember) => void; - loading: boolean; - members?: T.OrganizationMember[]; - organization: T.Organization; - refreshMembers: () => Promise<void>; -} - -export default function MembersPageHeader(props: Props) { - const { members, organization, refreshMembers } = props; - const memberLogins = members ? members.map(member => member.login) : []; - const isAdmin = organization.actions && organization.actions.admin; - const almKey = organization.alm && sanitizeAlmId(organization.alm.key); - const hasMemberSync = organization.alm && organization.alm.membersSync; - const showSyncNotif = isAdmin && organization.alm && !hasMemberSync; - const isSyncEligible = - almKey && isGithub(almKey) && organization.alm && !organization.alm.personal; - - return ( - <header className="page-header"> - <h1 className="page-title"> - {translate('organization.members.page')} - <DeferredSpinner className="little-spacer-left" loading={props.loading} /> - </h1> - - {isAdmin && ( - <div className="page-actions text-right"> - {isSyncEligible && !showSyncNotif && ( - <SyncMemberForm - buttonText={translate('organization.members.config_synchro')} - hasOtherMembers={members && members.length > 1} - organization={organization} - refreshMembers={refreshMembers} - /> - )} - {!hasMemberSync && ( - <div className="display-inline-block spacer-left spacer-bottom"> - <AddMemberForm - addMember={props.handleAddMember} - memberLogins={memberLogins} - organization={organization} - /> - <DocTooltip - className="spacer-left" - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/organizations/add-organization-member.md' - )} - /> - </div> - )} - </div> - )} - <div className="page-description"> - <FormattedMessage - defaultMessage={translate('organization.members.page.description')} - id="organization.members.page.description" - values={{ - link: ( - <Link target="_blank" to="/documentation/organizations/manage-team/"> - {translate('organization.members.manage_a_team')} - </Link> - ) - }} - /> - {almKey && isSyncEligible && showSyncNotif && ( - <Alert className="spacer-top" display="inline" variant="info"> - {translateWithParameters( - 'organization.members.auto_sync_members_from_org_x', - translate('organization', almKey) - )} - <span className="spacer-left"> - <SyncMemberForm - buttonText={translate('configure')} - hasOtherMembers={members && members.length > 1} - organization={organization} - refreshMembers={refreshMembers} - /> - </span> - </Alert> - )} - </div> - </header> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx deleted file mode 100644 index 766921c28cf..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx +++ /dev/null @@ -1,240 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { Helmet } from 'react-helmet-async'; -import ListFooter from 'sonar-ui-common/components/controls/ListFooter'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { addMember, removeMember, searchMembers } from '../../api/organizations'; -import { addUserToGroup, removeUserFromGroup, searchUsersGroups } from '../../api/user_groups'; -import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget'; -import Suggestions from '../../app/components/embed-docs-modal/Suggestions'; -import MembersList from './MembersList'; -import MembersListHeader from './MembersListHeader'; -import MembersPageHeader from './MembersPageHeader'; - -interface Props { - currentUser: T.LoggedInUser; - organization: T.Organization; -} - -interface State { - groups: T.Group[]; - loading: boolean; - members?: T.OrganizationMember[]; - paging?: T.Paging; - query: string; -} - -const PAGE_SIZE = 50; - -export default class OrganizationMembers extends React.PureComponent<Props, State> { - mounted = false; - state: State = { - groups: [], - loading: true, - query: '' - }; - - componentDidMount() { - this.mounted = true; - this.fetchMembers(); - if (this.props.organization.actions && this.props.organization.actions.admin) { - this.fetchGroups(); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - stopLoading = () => { - if (this.mounted) { - this.setState({ loading: false }); - } - }; - - fetchMembers = (query?: string) => { - this.setState({ loading: true }); - searchMembers({ - organization: this.props.organization.key, - ps: PAGE_SIZE, - q: query - }).then(({ paging, users }) => { - if (this.mounted) { - this.setState({ loading: false, members: users, paging }); - } - }, this.stopLoading); - }; - - fetchGroups = () => { - searchUsersGroups({ organization: this.props.organization.key }).then( - ({ groups }) => { - if (this.mounted) { - this.setState({ groups }); - } - }, - () => {} - ); - }; - - handleSearchMembers = (query: string) => { - this.setState({ query }); - this.fetchMembers(query || undefined); // empty string -> undefined - }; - - handleLoadMoreMembers = () => { - const { paging, query } = this.state; - if (!paging) { - return; - } - - this.setState({ loading: true }); - searchMembers({ - organization: this.props.organization.key, - p: paging.pageIndex + 1, - ps: PAGE_SIZE, - q: query || undefined // empty string -> undefined - }).then(({ paging, users }) => { - if (this.mounted) { - this.setState(({ members = [] }) => ({ - loading: false, - members: [...members, ...users], - paging - })); - } - }, this.stopLoading); - }; - - handleAddMember = ({ login }: T.OrganizationMember) => { - // TODO optimistic update - addMember({ login, organization: this.props.organization.key }).then( - member => { - if (this.mounted) { - this.setState(({ members, paging }) => ({ - members: members && [...members, member], - paging: paging && { ...paging, total: paging.total + 1 } - })); - } - }, - () => {} - ); - }; - - handleRemoveMember = ({ login }: T.OrganizationMember) => { - // TODO optimistic update - removeMember({ login, organization: this.props.organization.key }).then( - () => { - if (this.mounted) { - this.setState(({ members, paging }) => ({ - members: members && members.filter(member => member.login !== login), - paging: paging && { ...paging, total: paging.total - 1 } - })); - } - }, - () => {} - ); - }; - - refreshMembers = () => { - return searchMembers({ - organization: this.props.organization.key, - ps: PAGE_SIZE, - q: this.state.query || undefined - }).then(({ paging, users }) => { - if (this.mounted) { - this.setState({ members: users, paging }); - } - }); - }; - - updateGroup = ( - login: string, - updater: (member: T.OrganizationMember) => T.OrganizationMember - ) => { - this.setState(({ members }) => ({ - members: members && members.map(member => (member.login === login ? updater(member) : member)) - })); - }; - - updateMemberGroups = ({ login }: T.OrganizationMember, add: string[], remove: string[]) => { - // TODO optimistic update - const promises = [ - ...add.map(name => - addUserToGroup({ name, login, organization: this.props.organization.key }) - ), - ...remove.map(name => - removeUserFromGroup({ name, login, organization: this.props.organization.key }) - ) - ]; - return Promise.all(promises).then(() => { - if (this.mounted) { - this.updateGroup(login, member => ({ - ...member, - groupCount: (member.groupCount || 0) + add.length - remove.length - })); - } - }); - }; - - render() { - const { currentUser, organization } = this.props; - const { groups, loading, members, paging } = this.state; - const hasMemberSync = organization.alm && organization.alm.membersSync; - return ( - <div className="page page-limited"> - <Helmet defer={false} title={translate('organization.members.page')} /> - <Suggestions suggestions="organization_members" /> - <A11ySkipTarget anchor="members_main" /> - <MembersPageHeader - handleAddMember={this.handleAddMember} - loading={loading} - members={members} - organization={organization} - refreshMembers={this.refreshMembers} - /> - {members !== undefined && paging !== undefined && ( - <> - <MembersListHeader - handleSearch={this.handleSearchMembers} - organization={organization} - total={paging.total} - /> - <MembersList - currentUser={currentUser} - members={members} - organization={organization} - organizationGroups={groups} - removeMember={hasMemberSync ? undefined : this.handleRemoveMember} - updateMemberGroups={this.updateMemberGroups} - /> - {paging.total !== 0 && ( - <ListFooter - count={members.length} - loadMore={this.handleLoadMoreMembers} - ready={!loading} - total={paging.total} - /> - )} - </> - )} - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembersContainer.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembersContainer.tsx deleted file mode 100644 index 7dfebf0b397..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembersContainer.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { connect } from 'react-redux'; -import { withCurrentUser } from '../../components/hoc/withCurrentUser'; -import { getOrganizationByKey, Store } from '../../store/rootReducer'; -import OrganizationMembers from './OrganizationMembers'; - -interface OwnProps { - params: { organizationKey: string }; -} - -interface StateProps { - organization: T.Organization; -} - -const mapStateToProps = (state: Store, ownProps: OwnProps): StateProps => { - return { organization: getOrganizationByKey(state, ownProps.params.organizationKey) }; -}; - -export default withCurrentUser(connect(mapStateToProps)(OrganizationMembers)); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/RemoveMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/RemoveMemberForm.tsx deleted file mode 100644 index ce93a356758..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/RemoveMemberForm.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import Modal from 'sonar-ui-common/components/controls/Modal'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; - -interface Props { - onClose: () => void; - member: T.OrganizationMember; - organization: T.Organization; - removeMember: (member: T.OrganizationMember) => void; -} - -export default class RemoveMemberForm extends React.PureComponent<Props> { - handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { - event.preventDefault(); - this.props.removeMember(this.props.member); - this.props.onClose(); - }; - - render() { - const header = translate('users.remove'); - return ( - <Modal contentLabel={header} key="remove-member-modal" onRequestClose={this.props.onClose}> - <header className="modal-head"> - <h2>{header}</h2> - </header> - <form onSubmit={this.handleSubmit}> - <div className="modal-body"> - {translateWithParameters( - 'organization.members.remove_x', - this.props.member.name, - this.props.organization.name - )} - </div> - <footer className="modal-foot"> - <div> - <SubmitButton autoFocus={true} className="button-red"> - {translate('remove')} - </SubmitButton> - <ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink> - </div> - </footer> - </form> - </Modal> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx deleted file mode 100644 index 6e1f3a0e802..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/SyncMemberForm.tsx +++ /dev/null @@ -1,175 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Link } from 'react-router'; -import { Button } from 'sonar-ui-common/components/controls/buttons'; -import ConfirmButton from 'sonar-ui-common/components/controls/ConfirmButton'; -import RadioCard from 'sonar-ui-common/components/controls/RadioCard'; -import { Alert } from 'sonar-ui-common/components/ui/Alert'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { setOrganizationMemberSync, syncMembers } from '../../api/organizations'; -import { sanitizeAlmId } from '../../helpers/almIntegrations'; -import { fetchOrganization } from '../../store/rootActions'; - -interface Props { - buttonText: string; - fetchOrganization: (key: string) => void; - hasOtherMembers?: boolean; - organization: T.Organization; - refreshMembers: () => Promise<void>; -} - -interface State { - membersSync: boolean; -} - -export class SyncMemberForm extends React.PureComponent<Props, State> { - constructor(props: Props) { - super(props); - this.state = { - membersSync: Boolean(props.organization.alm && props.organization.alm.membersSync) - }; - } - - handleConfirm = () => { - const { organization } = this.props; - const { membersSync } = this.state; - - return setOrganizationMemberSync({ - organization: organization.key, - enabled: membersSync - }).then(() => { - this.props.fetchOrganization(organization.key); - if (membersSync) { - return this.handleMemberSync(); - } - }); - }; - - handleManualClick = () => { - this.setState({ membersSync: false }); - }; - - handleAutoClick = () => { - this.setState({ membersSync: true }); - }; - - handleMemberSync = () => { - return syncMembers(this.props.organization.key).then(this.props.refreshMembers); - }; - - renderModalDescription = () => { - return ( - <p className="spacer-top"> - {translate('organization.members.management.description')} - <Link - className="spacer-left" - target="_blank" - to={{ pathname: '/documentation/organizations/manage-team/' }}> - {translate('learn_more')} - </Link> - </p> - ); - }; - - renderModalBody = () => { - const { membersSync } = this.state; - const { hasOtherMembers, organization } = this.props; - const almKey = organization.alm && sanitizeAlmId(organization.alm.key); - const showWarning = hasOtherMembers && organization.alm && !organization.alm.membersSync; - return ( - <div className="display-flex-stretch big-spacer-top"> - <RadioCard - onClick={this.handleManualClick} - selected={!membersSync} - title={translate('organization.members.management.manual')}> - <div className="spacer-left"> - <ul className="big-spacer-left note"> - <li className="spacer-bottom"> - {translate('organization.members.management.manual.add_members_manually')} - </li> - <li>{translate('organization.members.management.choose_members_permissions')}</li> - </ul> - </div> - </RadioCard> - <RadioCard - onClick={this.handleAutoClick} - selected={membersSync} - title={translateWithParameters( - 'organization.members.management.automatic', - translate(almKey || '') - )}> - <div className="spacer-left"> - <ul className="big-spacer-left note"> - {almKey && ( - <> - <li className="spacer-bottom"> - {translateWithParameters( - 'organization.members.management.automatic.synchronized_from_x', - translate('organization', almKey) - )} - </li> - <li className="spacer-bottom"> - {translate( - 'organization.members.management.automatic.members_changes_reflected', - almKey - )} - </li> - </> - )} - <li>{translate('organization.members.management.choose_members_permissions')}</li> - </ul> - </div> - {almKey && showWarning && ( - <Alert className="big-spacer-top" variant="warning"> - {translateWithParameters( - 'organization.members.management.automatic.warning_x', - translate('organization', almKey) - )} - </Alert> - )} - </RadioCard> - </div> - ); - }; - - render() { - const { organization } = this.props; - const orgMemberSync = Boolean(organization.alm && organization.alm.membersSync); - return ( - <ConfirmButton - cancelButtonText={translate('close')} - confirmButtonText={translate('save')} - confirmDisable={this.state.membersSync === orgMemberSync} - modalBody={this.renderModalBody()} - modalHeader={translate('organization.members.management.title')} - modalHeaderDescription={this.renderModalDescription()} - onConfirm={this.handleConfirm} - size="medium"> - {({ onClick }) => <Button onClick={onClick}>{this.props.buttonText}</Button>} - </ConfirmButton> - ); - } -} - -const mapDispatchToProps = { fetchOrganization }; - -export default connect(null, mapDispatchToProps)(SyncMemberForm); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/AddMemberForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/AddMemberForm-test.tsx deleted file mode 100644 index 1be6c70cd74..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/AddMemberForm-test.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { click, submit } from 'sonar-ui-common/helpers/testUtils'; -import { searchMembers } from '../../../api/organizations'; -import AddMemberForm from '../AddMemberForm'; - -jest.mock('../../../api/organizations', () => ({ - searchMembers: jest.fn().mockResolvedValue({ paging: {}, users: [] }) -})); - -const memberLogins = ['admin']; - -it('should render and open the modal', () => { - const wrapper = shallow( - <AddMemberForm - addMember={jest.fn()} - memberLogins={memberLogins} - organization={{ key: 'foo', name: 'Foo' }} - /> - ); - expect(wrapper).toMatchSnapshot(); - wrapper.setState({ open: true }); - expect(wrapper).toMatchSnapshot(); -}); - -it('should correctly handle user interactions', () => { - const wrapper = shallow( - <AddMemberForm - addMember={jest.fn()} - memberLogins={memberLogins} - organization={{ key: 'foo', name: 'Foo' }} - /> - ); - click(wrapper.find('Button')); - expect(wrapper.state('open')).toBe(true); - (wrapper.instance() as AddMemberForm).closeForm(); - expect(wrapper.state('open')).toBe(false); -}); - -it('should search users', () => { - const wrapper = shallow( - <AddMemberForm - addMember={jest.fn()} - memberLogins={memberLogins} - organization={{ key: 'foo', name: 'Foo' }} - /> - ); - click(wrapper.find('Button')); - - wrapper.find('UsersSelectSearch').prop<Function>('searchUsers')('foo', 100); - expect(searchMembers).lastCalledWith({ - organization: 'foo', - ps: 100, - q: 'foo', - selected: 'deselected' - }); - - wrapper.find('UsersSelectSearch').prop<Function>('searchUsers')('', 100); - expect(searchMembers).lastCalledWith({ - organization: 'foo', - ps: 100, - selected: 'deselected' - }); -}); - -it('should select user', () => { - const addMember = jest.fn(); - const user = { login: 'luke', name: 'Luke' }; - const wrapper = shallow( - <AddMemberForm - addMember={addMember} - memberLogins={memberLogins} - organization={{ key: 'foo', name: 'Foo' }} - /> - ); - click(wrapper.find('Button')); - - wrapper.find('UsersSelectSearch').prop<Function>('handleValueChange')(user); - submit(wrapper.find('form')); - expect(addMember).toBeCalledWith(user); -}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx deleted file mode 100644 index 7ce1733550c..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/ManageMemberGroupsForm-test.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import ManageMemberGroupsForm from '../ManageMemberGroupsForm'; - -const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; -const organization = { name: 'MyOrg', key: 'myorg' }; -const organizationGroups = [ - { - id: 7, - name: 'professionals', - description: '', - membersCount: 12 - }, - { - id: 11, - name: 'pull-request-analysers', - description: 'Technical accounts', - membersCount: 3 - }, - { - id: 1, - name: 'sonar-administrators', - description: 'System administrators', - membersCount: 17 - } -]; -const userGroups = { - '11': { - default: false, - id: 11, - name: 'pull-request-analysers', - description: 'Technical accounts', - selected: true - } -}; - -function getMountedForm(updateFunc = jest.fn().mockResolvedValue({})) { - const wrapper = shallow<ManageMemberGroupsForm>( - <ManageMemberGroupsForm - member={member} - onClose={jest.fn()} - organization={organization} - organizationGroups={organizationGroups} - updateMemberGroups={updateFunc} - />, - { disableLifecycleMethods: true } - ); - const instance = wrapper.instance(); - wrapper.setState({ loading: false, userGroups }); - return { wrapper, instance }; -} - -it('should render', () => { - const wrapper = shallow( - <ManageMemberGroupsForm - member={member} - onClose={jest.fn()} - organization={organization} - organizationGroups={organizationGroups} - updateMemberGroups={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); - expect(wrapper.dive()).toMatchSnapshot(); -}); - -it('should correctly select the groups', () => { - const form = getMountedForm(); - expect(form.instance.isGroupSelected('11')).toBe(true); - expect(form.instance.isGroupSelected('7')).toBe(false); - form.instance.onCheck('11', false); - form.instance.onCheck('7', true); - expect(form.wrapper.state('userGroups')).toMatchSnapshot(); - expect(form.instance.isGroupSelected('11')).toBe(false); - expect(form.instance.isGroupSelected('7')).toBe(true); -}); - -it('should correctly handle the submit event and close the modal', () => { - const updateMemberGroups = jest.fn().mockResolvedValue({}); - const form = getMountedForm(updateMemberGroups); - form.instance.onCheck('11', false); - form.instance.onCheck('7', true); - form.instance.handleSubmit(); - expect(updateMemberGroups.mock.calls).toMatchSnapshot(); - expect(form.wrapper.state()).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx deleted file mode 100644 index 2522eb75984..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersList-test.tsx +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockLoggedInUser, mockOrganization } from '../../../helpers/testMocks'; -import MembersList from '../MembersList'; - -const members = [ - { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }, - { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af', groupCount: 1 } -]; - -it('should render a list of members of an organization', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should render "no results"', () => { - expect(shallowRender({ members: [] })).toMatchSnapshot(); -}); - -function shallowRender(props: Partial<MembersList['props']> = {}) { - return shallow( - <MembersList - currentUser={mockLoggedInUser({ login: 'admin' })} - members={members} - organization={mockOrganization()} - organizationGroups={[]} - removeMember={jest.fn()} - updateMemberGroups={jest.fn()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListHeader-test.tsx deleted file mode 100644 index 0acad9c271e..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListHeader-test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockOrganization, mockOrganizationWithAlm } from '../../../helpers/testMocks'; -import MembersListHeader, { Props } from '../MembersListHeader'; - -it('should render without the total', () => { - expect(shallowRender({ total: undefined })).toMatchSnapshot(); -}); - -it('should render with the total', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should render a help tooltip', () => { - expect( - shallowRender({ organization: mockOrganizationWithAlm({}, { membersSync: true }) }).find( - 'HelpTooltip' - ) - ).toMatchSnapshot(); - expect( - shallowRender({ - organization: mockOrganizationWithAlm( - {}, - { key: 'bitbucket', membersSync: true, url: 'https://bitbucket.com/foo' } - ) - }).find('HelpTooltip') - ).toMatchSnapshot(); -}); - -function shallowRender(props: Partial<Props> = {}) { - return shallow( - <MembersListHeader - handleSearch={jest.fn()} - organization={mockOrganization()} - total={8} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx deleted file mode 100644 index bfa51e2c95b..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersListItem-test.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { click } from 'sonar-ui-common/helpers/testUtils'; -import { mockOrganization, mockOrganizationWithAdminActions } from '../../../helpers/testMocks'; -import MembersListItem from '../MembersListItem'; - -it('should not render actions and groups for non admin', () => { - expect(shallowRender({ organization: mockOrganization() })).toMatchSnapshot(); -}); - -it('should render actions and groups for admin', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should show groups at 0 if the groupCount field is not defined (just added user)', () => { - expect( - shallowRender({ - member: { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af' } - }) - ).toMatchSnapshot(); -}); - -it('should not display the remove member action', () => { - expect(shallowRender({ removeMember: undefined }).find('ActionsDropdown')).toMatchSnapshot(); -}); - -it('should open groups form', () => { - const wrapper = shallowRender(); - - click(wrapper.find('ActionsDropdownItem').first()); - expect(wrapper.find('ManageMemberGroupsForm').exists()).toBe(true); - - wrapper.find('ManageMemberGroupsForm').prop<Function>('onClose')(); - wrapper.update(); - expect(wrapper.find('ManageMemberGroupsForm').exists()).toBe(false); -}); - -it('should open remove member form', () => { - const wrapper = shallowRender(); - - click(wrapper.find('ActionsDropdownItem').last()); - expect(wrapper.find('RemoveMemberForm').exists()).toBe(true); - - wrapper.find('RemoveMemberForm').prop<Function>('onClose')(); - wrapper.update(); - expect(wrapper.find('RemoveMemberForm').exists()).toBe(false); -}); - -function shallowRender(props: Partial<MembersListItem['props']> = {}) { - return shallow( - <MembersListItem - member={{ login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }} - organization={mockOrganizationWithAdminActions()} - organizationGroups={[]} - removeMember={jest.fn()} - updateMemberGroups={jest.fn()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx deleted file mode 100644 index 4b72e23ee82..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/MembersPageHeader-test.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { - mockOrganization, - mockOrganizationWithAdminActions, - mockOrganizationWithAlm -} from '../../../helpers/testMocks'; -import MembersPageHeader, { Props } from '../MembersPageHeader'; - -it('should render correctly', () => { - expect(shallowRender({ loading: true })).toMatchSnapshot(); -}); - -it('should render for admin', () => { - expect( - shallowRender({ organization: mockOrganization({ actions: { admin: true } }) }) - ).toMatchSnapshot(); -}); - -it('should render for Bitbucket bound organization', () => { - const organization = mockOrganizationWithAlm(mockOrganizationWithAdminActions(), { - key: 'bitbucket' - }); - expect(shallowRender({ organization })).toMatchSnapshot(); -}); - -it('should render for GitHub bound organization without sync', () => { - const organization = mockOrganizationWithAlm(mockOrganizationWithAdminActions()); - expect(shallowRender({ organization })).toMatchSnapshot(); -}); - -it('should render for personal GitHub bound organization without sync', () => { - const organization = mockOrganizationWithAlm(mockOrganizationWithAdminActions(), { - personal: true - }); - expect(shallowRender({ organization })).toMatchSnapshot(); -}); - -it('should render for bound organization with sync', () => { - const organization = mockOrganizationWithAlm(mockOrganizationWithAdminActions(), { - membersSync: true - }); - const wrapper = shallowRender({ organization }); - expect(wrapper.find('Connect(SyncMemberForm)').exists()).toBe(true); - expect(wrapper.find('AddMemberForm').exists()).toBe(false); - expect(wrapper.find('Alert').exists()).toBe(false); -}); - -function shallowRender(props: Partial<Props> = {}) { - return shallow( - <MembersPageHeader - handleAddMember={jest.fn()} - loading={false} - members={[]} - organization={mockOrganization()} - refreshMembers={jest.fn()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx deleted file mode 100644 index 3865e9727be..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/OrganizationMembers-test.tsx +++ /dev/null @@ -1,180 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { addMember, removeMember, searchMembers } from '../../../api/organizations'; -import { addUserToGroup, removeUserFromGroup, searchUsersGroups } from '../../../api/user_groups'; -import { - mockLoggedInUser, - mockOrganization, - mockOrganizationWithAdminActions, - mockOrganizationWithAlm -} from '../../../helpers/testMocks'; -import OrganizationMembers from '../OrganizationMembers'; - -jest.mock('../../../api/organizations', () => ({ - addMember: jest.fn().mockResolvedValue({ login: 'bar', name: 'Bar', groupCount: 1 }), - removeMember: jest.fn().mockResolvedValue(undefined), - searchMembers: jest.fn().mockResolvedValue({ - paging: { pageIndex: 1, pageSize: 2, total: 3 }, - users: [ - { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }, - { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af', groupCount: 1 } - ] - }) -})); - -jest.mock('../../../api/user_groups', () => ({ - addUserToGroup: jest.fn().mockResolvedValue(undefined), - removeUserFromGroup: jest.fn().mockResolvedValue(undefined), - searchUsersGroups: jest.fn().mockResolvedValue({ - paging: { pageIndex: 1, pageSize: 100, total: 2 }, - groups: [ - { id: 1, name: 'Members', description: '', membersCount: 2, default: true }, - { id: 2, name: 'Watchers', description: '', membersCount: 0, default: false } - ] - }) -})); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -it('should fetch members and render for non-admin', async () => { - const wrapper = shallowRender({ organization: mockOrganization() }); - expect(wrapper).toMatchSnapshot(); - - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); - expect(searchMembers).toBeCalledWith({ organization: 'foo', ps: 50, q: undefined }); -}); - -it('should fetch members and groups for admin', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - expect(searchMembers).toBeCalledWith({ organization: 'foo', ps: 50, q: undefined }); - expect(searchUsersGroups).toBeCalledWith({ organization: 'foo' }); -}); - -it('should search users', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.find('MembersListHeader').prop<Function>('handleSearch')('user'); - expect(searchMembers).lastCalledWith({ organization: 'foo', ps: 50, q: 'user' }); -}); - -it('should load more members', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.find('ListFooter').prop<Function>('loadMore')(); - expect(searchMembers).lastCalledWith({ organization: 'foo', p: 2, ps: 50, q: undefined }); -}); - -it('should refresh members', async () => { - const paging = { pageIndex: 1, pageSize: 5, total: 3 }; - const users = [ - { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }, - { login: 'john', name: 'John Doe', avatar: '7daf6c79d4802916d83f6266e24850af', groupCount: 1 }, - { login: 'stan', name: 'Stan Marsh', avatar: '', groupCount: 7 } - ]; - - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - - (searchMembers as jest.Mock).mockResolvedValueOnce({ - paging, - users - }); - - await wrapper.instance().refreshMembers(); - expect(searchMembers).toBeCalled(); - expect(wrapper.state('members')).toEqual(users); - expect(wrapper.state('paging')).toEqual(paging); -}); - -it('should add new member', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.find('MembersPageHeader').prop<Function>('handleAddMember')({ login: 'bar' }); - await waitAndUpdate(wrapper); - expect( - wrapper - .find('MembersList') - .prop<T.OrganizationMember[]>('members') - .find(m => m.login === 'bar') - ).toBeDefined(); - expect(wrapper.find('ListFooter').prop('total')).toEqual(4); - expect(addMember).toBeCalledWith({ login: 'bar', organization: 'foo' }); -}); - -it('should remove member', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.find('MembersList').prop<Function>('removeMember')({ login: 'john' }); - await waitAndUpdate(wrapper); - expect( - wrapper - .find('MembersList') - .prop<T.OrganizationMember[]>('members') - .find(m => m.login === 'john') - ).toBeUndefined(); - expect(wrapper.find('ListFooter').prop('total')).toEqual(2); - expect(removeMember).toBeCalledWith({ login: 'john', organization: 'foo' }); -}); - -it('should update groups', async () => { - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.find('MembersList').prop<Function>('updateMemberGroups')( - { login: 'john' }, - ['cats', 'dogs'], // add to - ['birds'] // remove from - ); - await waitAndUpdate(wrapper); - const john = wrapper - .find('MembersList') - .prop<T.OrganizationMember[]>('members') - .find(m => m.login === 'john'); - expect(john && john.groupCount).toBe(2); - expect(addUserToGroup).toHaveBeenCalledTimes(2); - expect(addUserToGroup).toBeCalledWith({ login: 'john', name: 'cats', organization: 'foo' }); - expect(addUserToGroup).toBeCalledWith({ login: 'john', name: 'dogs', organization: 'foo' }); - expect(removeUserFromGroup).toHaveBeenCalledTimes(1); - expect(removeUserFromGroup).toBeCalledWith({ login: 'john', name: 'birds', organization: 'foo' }); -}); - -it('should not allow to remove members when in sync mode', async () => { - const wrapper = shallowRender({ - organization: mockOrganizationWithAlm(mockOrganizationWithAdminActions(), { membersSync: true }) - }); - await waitAndUpdate(wrapper); - expect(wrapper.find('MembersList').prop('removeMember')).toBeUndefined(); -}); - -function shallowRender(props: Partial<OrganizationMembers['props']> = {}) { - return shallow<OrganizationMembers>( - <OrganizationMembers - currentUser={mockLoggedInUser()} - organization={mockOrganizationWithAdminActions()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/RemoveMemberForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/RemoveMemberForm-test.tsx deleted file mode 100644 index 657b6797190..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/RemoveMemberForm-test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockEvent } from '../../../helpers/testMocks'; -import RemoveMemberForm from '../RemoveMemberForm'; - -const member = { login: 'admin', name: 'Admin Istrator', avatar: '', groupCount: 3 }; -const organization = { key: 'myorg', name: 'MyOrg' }; - -it('should render', () => { - const wrapper = shallow( - <RemoveMemberForm - member={member} - onClose={jest.fn()} - organization={organization} - removeMember={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); -}); - -it('should correctly handle user interactions', () => { - const removeMember = jest.fn(); - const wrapper = shallow<RemoveMemberForm>( - <RemoveMemberForm - member={member} - onClose={jest.fn()} - organization={organization} - removeMember={removeMember} - /> - ); - wrapper.instance().handleSubmit(mockEvent()); - expect(removeMember).toBeCalledWith({ - avatar: '', - groupCount: 3, - login: 'admin', - name: 'Admin Istrator' - }); -}); diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/SyncMemberForm-test.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/SyncMemberForm-test.tsx deleted file mode 100644 index e7e8f814b1b..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/SyncMemberForm-test.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { setOrganizationMemberSync, syncMembers } from '../../../api/organizations'; -import { mockOrganizationWithAlm } from '../../../helpers/testMocks'; -import { SyncMemberForm } from '../SyncMemberForm'; - -jest.mock('../../../api/organizations', () => ({ - setOrganizationMemberSync: jest.fn().mockResolvedValue(undefined), - syncMembers: jest.fn().mockResolvedValue(undefined) -})); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -it('should allow to switch to automatic mode', async () => { - const fetchOrganization = jest.fn(); - const refreshMembers = jest.fn().mockResolvedValue({}); - const wrapper = shallowRender({ fetchOrganization, refreshMembers }); - expect(wrapper).toMatchSnapshot(); - - wrapper.setState({ membersSync: true }); - wrapper.find('ConfirmButton').prop<Function>('onConfirm')(); - expect(setOrganizationMemberSync).toHaveBeenCalledWith({ organization: 'foo', enabled: true }); - - await waitAndUpdate(wrapper); - expect(fetchOrganization).toHaveBeenCalledWith('foo'); - expect(syncMembers).toHaveBeenCalledWith('foo'); - expect(refreshMembers).toBeCalled(); -}); - -it('should allow to switch to manual mode', async () => { - const fetchOrganization = jest.fn(); - const wrapper = shallowRender({ - fetchOrganization, - organization: mockOrganizationWithAlm({}, { membersSync: true }) - }); - expect(wrapper).toMatchSnapshot(); - - wrapper.setState({ membersSync: false }); - wrapper.find('ConfirmButton').prop<Function>('onConfirm')(); - expect(setOrganizationMemberSync).toHaveBeenCalledWith({ organization: 'foo', enabled: false }); - - await waitAndUpdate(wrapper); - expect(fetchOrganization).toHaveBeenCalledWith('foo'); - expect(syncMembers).not.toHaveBeenCalled(); -}); - -function shallowRender(props: Partial<SyncMemberForm['props']> = {}) { - return shallow<SyncMemberForm>( - <SyncMemberForm - buttonText="configure" - fetchOrganization={jest.fn()} - hasOtherMembers={true} - organization={mockOrganizationWithAlm()} - refreshMembers={jest.fn().mockResolvedValue({})} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap deleted file mode 100644 index 5eafb110cf2..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/AddMemberForm-test.tsx.snap +++ /dev/null @@ -1,77 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render and open the modal 1`] = ` -<Fragment> - <Button - key="add-member-button" - onClick={[Function]} - > - organization.members.add - </Button> -</Fragment> -`; - -exports[`should render and open the modal 2`] = ` -<Fragment> - <Button - key="add-member-button" - onClick={[Function]} - > - organization.members.add - </Button> - <Modal - contentLabel="users.add" - key="add-member-modal" - onRequestClose={[Function]} - > - <header - className="modal-head" - > - <h2> - users.add - </h2> - </header> - <form - onSubmit={[Function]} - > - <div - className="modal-body" - > - <div - className="modal-field" - > - <label> - users.search_description - </label> - <UsersSelectSearch - autoFocus={true} - excludedUsers={ - Array [ - "admin", - ] - } - handleValueChange={[Function]} - searchUsers={[Function]} - /> - </div> - </div> - <footer - className="modal-foot" - > - <div> - <SubmitButton - disabled={true} - > - organization.members.add_to_members - </SubmitButton> - <ResetButtonLink - onClick={[Function]} - > - cancel - </ResetButtonLink> - </div> - </footer> - </form> - </Modal> -</Fragment> -`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap deleted file mode 100644 index d83c2bbd6f9..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/ManageMemberGroupsForm-test.tsx.snap +++ /dev/null @@ -1,115 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should correctly handle the submit event and close the modal 1`] = ` -Array [ - Array [ - Object { - "avatar": "", - "groupCount": 3, - "login": "admin", - "name": "Admin Istrator", - }, - Array [ - "7", - ], - Array [ - "11", - ], - ], -] -`; - -exports[`should correctly handle the submit event and close the modal 2`] = ` -Object { - "loading": false, - "userGroups": Object { - "11": Object { - "default": false, - "description": "Technical accounts", - "id": 11, - "name": "pull-request-analysers", - "selected": true, - "status": "remove", - }, - "7": Object { - "status": "add", - }, - }, -} -`; - -exports[`should correctly select the groups 1`] = ` -Object { - "11": Object { - "default": false, - "description": "Technical accounts", - "id": 11, - "name": "pull-request-analysers", - "selected": true, - "status": "remove", - }, - "7": Object { - "status": "add", - }, -} -`; - -exports[`should render 1`] = ` -<SimpleModal - header="organization.members.manage_groups" - onClose={[MockFunction]} - onSubmit={[Function]} -> - <Component /> -</SimpleModal> -`; - -exports[`should render 2`] = ` -<Modal - contentLabel="organization.members.manage_groups" - onRequestClose={[MockFunction]} -> - <form - onSubmit={[Function]} - > - <header - className="modal-head" - > - <h2> - organization.members.manage_groups - </h2> - </header> - <div - className="modal-body modal-container" - > - <p> - <strong> - organization.members.members_groups.Admin Istrator - </strong> - </p> - <DeferredSpinner - className="spacer-top" - /> - </div> - <footer - className="modal-foot" - > - <DeferredSpinner - className="spacer-right" - loading={false} - /> - <SubmitButton - disabled={true} - > - save - </SubmitButton> - <ResetButtonLink - disabled={false} - onClick={[Function]} - > - cancel - </ResetButtonLink> - </footer> - </form> -</Modal> -`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap deleted file mode 100644 index a99cabd208d..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersList-test.tsx.snap +++ /dev/null @@ -1,61 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render "no results" 1`] = ` -<div - className="note" -> - no_results -</div> -`; - -exports[`should render a list of members of an organization 1`] = ` -<div - className="boxed-group boxed-group-inner" -> - <table - className="data zebra" - > - <tbody> - <MembersListItem - key="admin" - member={ - Object { - "avatar": "", - "groupCount": 3, - "login": "admin", - "name": "Admin Istrator", - } - } - organization={ - Object { - "key": "foo", - "name": "Foo", - } - } - organizationGroups={Array []} - updateMemberGroups={[MockFunction]} - /> - <MembersListItem - key="john" - member={ - Object { - "avatar": "7daf6c79d4802916d83f6266e24850af", - "groupCount": 1, - "login": "john", - "name": "John Doe", - } - } - organization={ - Object { - "key": "foo", - "name": "Foo", - } - } - organizationGroups={Array []} - removeMember={[MockFunction]} - updateMemberGroups={[MockFunction]} - /> - </tbody> - </table> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap deleted file mode 100644 index 713c2e01c14..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListHeader-test.tsx.snap +++ /dev/null @@ -1,84 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render a help tooltip 1`] = ` -<HelpTooltip - className="spacer-left" - overlay={ - <div - className="abs-width-300 markdown cut-margins" - > - <p> - organization.members.auto_sync_total_help.github - </p> - <hr /> - <p> - <a - href="https://github.com/orgs/foo/people" - rel="noopener noreferrer" - target="_blank" - > - organization.members.see_all_members_on_x.github - </a> - </p> - </div> - } -/> -`; - -exports[`should render a help tooltip 2`] = ` -<HelpTooltip - className="spacer-left" - overlay={ - <div - className="abs-width-300 markdown cut-margins" - > - <p> - organization.members.auto_sync_total_help.bitbucket - </p> - <hr /> - <p> - <a - href="https://bitbucket.com/foo/profile/members" - rel="noopener noreferrer" - target="_blank" - > - organization.members.see_all_members_on_x.bitbucket - </a> - </p> - </div> - } -/> -`; - -exports[`should render with the total 1`] = ` -<div - className="panel panel-vertical bordered-bottom spacer-bottom" -> - <SearchBox - minLength={2} - onChange={[MockFunction]} - placeholder="search.search_for_members" - /> - <span - className="pull-right little-spacer-top" - > - <strong> - 8 - </strong> - - organization.members.members - </span> -</div> -`; - -exports[`should render without the total 1`] = ` -<div - className="panel panel-vertical bordered-bottom spacer-bottom" -> - <SearchBox - minLength={2} - onChange={[MockFunction]} - placeholder="search.search_for_members" - /> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap deleted file mode 100644 index 414c9f4f30e..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersListItem-test.tsx.snap +++ /dev/null @@ -1,135 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should not display the remove member action 1`] = ` -<ActionsDropdown> - <ActionsDropdownItem - onClick={[Function]} - > - organization.members.manage_groups - </ActionsDropdownItem> -</ActionsDropdown> -`; - -exports[`should not render actions and groups for non admin 1`] = ` -<tr> - <td - className="thin nowrap" - > - <Connect(Avatar) - hash="" - name="Admin Istrator" - size={36} - /> - </td> - <td - className="nowrap text-middle" - > - <strong> - Admin Istrator - </strong> - <span - className="note little-spacer-left" - > - admin - </span> - </td> -</tr> -`; - -exports[`should render actions and groups for admin 1`] = ` -<tr> - <td - className="thin nowrap" - > - <Connect(Avatar) - hash="" - name="Admin Istrator" - size={36} - /> - </td> - <td - className="nowrap text-middle" - > - <strong> - Admin Istrator - </strong> - <span - className="note little-spacer-left" - > - admin - </span> - </td> - <td - className="text-right text-middle" - > - organization.members.x_groups.3 - </td> - <td - className="nowrap text-middle text-right" - > - <ActionsDropdown> - <ActionsDropdownItem - onClick={[Function]} - > - organization.members.manage_groups - </ActionsDropdownItem> - <ActionsDropdownDivider /> - <ActionsDropdownItem - destructive={true} - onClick={[Function]} - > - organization.members.remove - </ActionsDropdownItem> - </ActionsDropdown> - </td> -</tr> -`; - -exports[`should show groups at 0 if the groupCount field is not defined (just added user) 1`] = ` -<tr> - <td - className="thin nowrap" - > - <Connect(Avatar) - hash="7daf6c79d4802916d83f6266e24850af" - name="John Doe" - size={36} - /> - </td> - <td - className="nowrap text-middle" - > - <strong> - John Doe - </strong> - <span - className="note little-spacer-left" - > - john - </span> - </td> - <td - className="text-right text-middle" - > - organization.members.x_groups.0 - </td> - <td - className="nowrap text-middle text-right" - > - <ActionsDropdown> - <ActionsDropdownItem - onClick={[Function]} - > - organization.members.manage_groups - </ActionsDropdownItem> - <ActionsDropdownDivider /> - <ActionsDropdownItem - destructive={true} - onClick={[Function]} - > - organization.members.remove - </ActionsDropdownItem> - </ActionsDropdown> - </td> -</tr> -`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap deleted file mode 100644 index 1430d03089e..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/MembersPageHeader-test.tsx.snap +++ /dev/null @@ -1,330 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<header - className="page-header" -> - <h1 - className="page-title" - > - organization.members.page - <DeferredSpinner - className="little-spacer-left" - loading={true} - /> - </h1> - <div - className="page-description" - > - <FormattedMessage - defaultMessage="organization.members.page.description" - id="organization.members.page.description" - values={ - Object { - "link": <Link - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to="/documentation/organizations/manage-team/" - > - organization.members.manage_a_team - </Link>, - } - } - /> - </div> -</header> -`; - -exports[`should render for Bitbucket bound organization 1`] = ` -<header - className="page-header" -> - <h1 - className="page-title" - > - organization.members.page - <DeferredSpinner - className="little-spacer-left" - loading={false} - /> - </h1> - <div - className="page-actions text-right" - > - <div - className="display-inline-block spacer-left spacer-bottom" - > - <AddMemberForm - addMember={[MockFunction]} - memberLogins={Array []} - organization={ - Object { - "actions": Object { - "admin": true, - }, - "alm": Object { - "key": "bitbucket", - "membersSync": false, - "personal": false, - "url": "https://github.com/foo", - }, - "key": "foo", - "name": "Foo", - } - } - /> - <DocTooltip - className="spacer-left" - doc={Promise {}} - /> - </div> - </div> - <div - className="page-description" - > - <FormattedMessage - defaultMessage="organization.members.page.description" - id="organization.members.page.description" - values={ - Object { - "link": <Link - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to="/documentation/organizations/manage-team/" - > - organization.members.manage_a_team - </Link>, - } - } - /> - </div> -</header> -`; - -exports[`should render for GitHub bound organization without sync 1`] = ` -<header - className="page-header" -> - <h1 - className="page-title" - > - organization.members.page - <DeferredSpinner - className="little-spacer-left" - loading={false} - /> - </h1> - <div - className="page-actions text-right" - > - <div - className="display-inline-block spacer-left spacer-bottom" - > - <AddMemberForm - addMember={[MockFunction]} - memberLogins={Array []} - organization={ - Object { - "actions": Object { - "admin": true, - }, - "alm": Object { - "key": "github", - "membersSync": false, - "personal": false, - "url": "https://github.com/foo", - }, - "key": "foo", - "name": "Foo", - } - } - /> - <DocTooltip - className="spacer-left" - doc={Promise {}} - /> - </div> - </div> - <div - className="page-description" - > - <FormattedMessage - defaultMessage="organization.members.page.description" - id="organization.members.page.description" - values={ - Object { - "link": <Link - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to="/documentation/organizations/manage-team/" - > - organization.members.manage_a_team - </Link>, - } - } - /> - <Alert - className="spacer-top" - display="inline" - variant="info" - > - organization.members.auto_sync_members_from_org_x.organization.github - <span - className="spacer-left" - > - <Connect(SyncMemberForm) - buttonText="configure" - hasOtherMembers={false} - organization={ - Object { - "actions": Object { - "admin": true, - }, - "alm": Object { - "key": "github", - "membersSync": false, - "personal": false, - "url": "https://github.com/foo", - }, - "key": "foo", - "name": "Foo", - } - } - refreshMembers={[MockFunction]} - /> - </span> - </Alert> - </div> -</header> -`; - -exports[`should render for admin 1`] = ` -<header - className="page-header" -> - <h1 - className="page-title" - > - organization.members.page - <DeferredSpinner - className="little-spacer-left" - loading={false} - /> - </h1> - <div - className="page-actions text-right" - > - <div - className="display-inline-block spacer-left spacer-bottom" - > - <AddMemberForm - addMember={[MockFunction]} - memberLogins={Array []} - organization={ - Object { - "actions": Object { - "admin": true, - }, - "key": "foo", - "name": "Foo", - } - } - /> - <DocTooltip - className="spacer-left" - doc={Promise {}} - /> - </div> - </div> - <div - className="page-description" - > - <FormattedMessage - defaultMessage="organization.members.page.description" - id="organization.members.page.description" - values={ - Object { - "link": <Link - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to="/documentation/organizations/manage-team/" - > - organization.members.manage_a_team - </Link>, - } - } - /> - </div> -</header> -`; - -exports[`should render for personal GitHub bound organization without sync 1`] = ` -<header - className="page-header" -> - <h1 - className="page-title" - > - organization.members.page - <DeferredSpinner - className="little-spacer-left" - loading={false} - /> - </h1> - <div - className="page-actions text-right" - > - <div - className="display-inline-block spacer-left spacer-bottom" - > - <AddMemberForm - addMember={[MockFunction]} - memberLogins={Array []} - organization={ - Object { - "actions": Object { - "admin": true, - }, - "alm": Object { - "key": "github", - "membersSync": false, - "personal": true, - "url": "https://github.com/foo", - }, - "key": "foo", - "name": "Foo", - } - } - /> - <DocTooltip - className="spacer-left" - doc={Promise {}} - /> - </div> - </div> - <div - className="page-description" - > - <FormattedMessage - defaultMessage="organization.members.page.description" - id="organization.members.page.description" - values={ - Object { - "link": <Link - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to="/documentation/organizations/manage-team/" - > - organization.members.manage_a_team - </Link>, - } - } - /> - </div> -</header> -`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap deleted file mode 100644 index 23fbb81a067..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/OrganizationMembers-test.tsx.snap +++ /dev/null @@ -1,127 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should fetch members and render for non-admin 1`] = ` -<div - className="page page-limited" -> - <Helmet - defer={false} - encodeSpecialCharacters={true} - title="organization.members.page" - /> - <Suggestions - suggestions="organization_members" - /> - <A11ySkipTarget - anchor="members_main" - /> - <MembersPageHeader - handleAddMember={[Function]} - loading={true} - organization={ - Object { - "key": "foo", - "name": "Foo", - } - } - refreshMembers={[Function]} - /> -</div> -`; - -exports[`should fetch members and render for non-admin 2`] = ` -<div - className="page page-limited" -> - <Helmet - defer={false} - encodeSpecialCharacters={true} - title="organization.members.page" - /> - <Suggestions - suggestions="organization_members" - /> - <A11ySkipTarget - anchor="members_main" - /> - <MembersPageHeader - handleAddMember={[Function]} - loading={false} - members={ - Array [ - Object { - "avatar": "", - "groupCount": 3, - "login": "admin", - "name": "Admin Istrator", - }, - Object { - "avatar": "7daf6c79d4802916d83f6266e24850af", - "groupCount": 1, - "login": "john", - "name": "John Doe", - }, - ] - } - organization={ - Object { - "key": "foo", - "name": "Foo", - } - } - refreshMembers={[Function]} - /> - <MembersListHeader - handleSearch={[Function]} - organization={ - Object { - "key": "foo", - "name": "Foo", - } - } - total={3} - /> - <MembersList - currentUser={ - Object { - "groups": Array [], - "isLoggedIn": true, - "login": "luke", - "name": "Skywalker", - "scmAccounts": Array [], - } - } - members={ - Array [ - Object { - "avatar": "", - "groupCount": 3, - "login": "admin", - "name": "Admin Istrator", - }, - Object { - "avatar": "7daf6c79d4802916d83f6266e24850af", - "groupCount": 1, - "login": "john", - "name": "John Doe", - }, - ] - } - organization={ - Object { - "key": "foo", - "name": "Foo", - } - } - organizationGroups={Array []} - removeMember={[Function]} - updateMemberGroups={[Function]} - /> - <ListFooter - count={2} - loadMore={[Function]} - ready={true} - total={3} - /> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap deleted file mode 100644 index 44022d1568c..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/RemoveMemberForm-test.tsx.snap +++ /dev/null @@ -1,43 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render 1`] = ` -<Modal - contentLabel="users.remove" - key="remove-member-modal" - onRequestClose={[MockFunction]} -> - <header - className="modal-head" - > - <h2> - users.remove - </h2> - </header> - <form - onSubmit={[Function]} - > - <div - className="modal-body" - > - organization.members.remove_x.Admin Istrator.MyOrg - </div> - <footer - className="modal-foot" - > - <div> - <SubmitButton - autoFocus={true} - className="button-red" - > - remove - </SubmitButton> - <ResetButtonLink - onClick={[MockFunction]} - > - cancel - </ResetButtonLink> - </div> - </footer> - </form> -</Modal> -`; diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/SyncMemberForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/SyncMemberForm-test.tsx.snap deleted file mode 100644 index 66187c5588d..00000000000 --- a/server/sonar-web/src/main/js/apps/organizationMembers/__tests__/__snapshots__/SyncMemberForm-test.tsx.snap +++ /dev/null @@ -1,187 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should allow to switch to automatic mode 1`] = ` -<ConfirmButton - cancelButtonText="close" - confirmButtonText="save" - confirmDisable={true} - modalBody={ - <div - className="display-flex-stretch big-spacer-top" - > - <RadioCard - onClick={[Function]} - selected={true} - title="organization.members.management.manual" - > - <div - className="spacer-left" - > - <ul - className="big-spacer-left note" - > - <li - className="spacer-bottom" - > - organization.members.management.manual.add_members_manually - </li> - <li> - organization.members.management.choose_members_permissions - </li> - </ul> - </div> - </RadioCard> - <RadioCard - onClick={[Function]} - selected={false} - title="organization.members.management.automatic.github" - > - <div - className="spacer-left" - > - <ul - className="big-spacer-left note" - > - <React.Fragment> - <li - className="spacer-bottom" - > - organization.members.management.automatic.synchronized_from_x.organization.github - </li> - <li - className="spacer-bottom" - > - organization.members.management.automatic.members_changes_reflected.github - </li> - </React.Fragment> - <li> - organization.members.management.choose_members_permissions - </li> - </ul> - </div> - <Alert - className="big-spacer-top" - variant="warning" - > - organization.members.management.automatic.warning_x.organization.github - </Alert> - </RadioCard> - </div> - } - modalHeader="organization.members.management.title" - modalHeaderDescription={ - <p - className="spacer-top" - > - organization.members.management.description - <Link - className="spacer-left" - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to={ - Object { - "pathname": "/documentation/organizations/manage-team/", - } - } - > - learn_more - </Link> - </p> - } - onConfirm={[Function]} - size="medium" -> - <Component /> -</ConfirmButton> -`; - -exports[`should allow to switch to manual mode 1`] = ` -<ConfirmButton - cancelButtonText="close" - confirmButtonText="save" - confirmDisable={true} - modalBody={ - <div - className="display-flex-stretch big-spacer-top" - > - <RadioCard - onClick={[Function]} - selected={false} - title="organization.members.management.manual" - > - <div - className="spacer-left" - > - <ul - className="big-spacer-left note" - > - <li - className="spacer-bottom" - > - organization.members.management.manual.add_members_manually - </li> - <li> - organization.members.management.choose_members_permissions - </li> - </ul> - </div> - </RadioCard> - <RadioCard - onClick={[Function]} - selected={true} - title="organization.members.management.automatic.github" - > - <div - className="spacer-left" - > - <ul - className="big-spacer-left note" - > - <React.Fragment> - <li - className="spacer-bottom" - > - organization.members.management.automatic.synchronized_from_x.organization.github - </li> - <li - className="spacer-bottom" - > - organization.members.management.automatic.members_changes_reflected.github - </li> - </React.Fragment> - <li> - organization.members.management.choose_members_permissions - </li> - </ul> - </div> - </RadioCard> - </div> - } - modalHeader="organization.members.management.title" - modalHeaderDescription={ - <p - className="spacer-top" - > - organization.members.management.description - <Link - className="spacer-left" - onlyActiveOnIndex={false} - style={Object {}} - target="_blank" - to={ - Object { - "pathname": "/documentation/organizations/manage-team/", - } - } - > - learn_more - </Link> - </p> - } - onConfirm={[Function]} - size="medium" -> - <Component /> -</ConfirmButton> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/__tests__/actions-test.ts b/server/sonar-web/src/main/js/apps/organizations/__tests__/actions-test.ts deleted file mode 100644 index e12ef944753..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/__tests__/actions-test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { deleteOrganization, updateOrganization } from '../../../api/organizations'; -import { mockOrganization } from '../../../helpers/testMocks'; -import * as actions from '../actions'; - -jest.mock('../../../api/organizations', () => ({ - deleteOrganization: jest.fn().mockResolvedValue({}), - updateOrganization: jest.fn().mockResolvedValue({}) -})); - -const dispatch = jest.fn(); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -describe('#updateOrganization', () => { - it('should update and dispatch', async () => { - const org = mockOrganization(); - const { key, ...changes } = org; - const promise = actions.updateOrganization(key, changes)(dispatch); - - expect(updateOrganization).toHaveBeenCalledWith(key, changes); - await promise; - expect(dispatch).toHaveBeenCalledWith({ changes, key, type: 'UPDATE_ORGANIZATION' }); - }); -}); - -describe('#deleteOrganization', () => { - it('should delete and dispatch', async () => { - const key = 'foo'; - const promise = actions.deleteOrganization(key)(dispatch); - - expect(deleteOrganization).toHaveBeenCalledWith(key); - await promise; - expect(dispatch).toHaveBeenCalledWith({ key, type: 'DELETE_ORGANIZATION' }); - }); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/actions.ts b/server/sonar-web/src/main/js/apps/organizations/actions.ts deleted file mode 100644 index 5ce6c84a8d0..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/actions.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { Dispatch } from 'redux'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import * as api from '../../api/organizations'; -import { addGlobalSuccessMessage } from '../../store/globalMessages'; -import * as actions from '../../store/organizations'; - -export function updateOrganization(key: string, changes: T.OrganizationBase) { - return (dispatch: Dispatch<any>) => { - return api.updateOrganization(key, changes).then(() => { - dispatch(actions.updateOrganization(key, changes)); - dispatch(addGlobalSuccessMessage(translate('organization.updated'))); - }); - }; -} - -export function deleteOrganization(key: string) { - return (dispatch: Dispatch<any>) => { - return api.deleteOrganization(key).then(() => { - dispatch(actions.deleteOrganization(key)); - }); - }; -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx deleted file mode 100644 index 378b3c06357..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationAccessContainer.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { connect } from 'react-redux'; -import { RouterState } from 'react-router'; -import handleRequiredAuthorization from '../../../app/utils/handleRequiredAuthorization'; -import { isLoggedIn } from '../../../helpers/users'; -import { getCurrentUser, getOrganizationByKey, Store } from '../../../store/rootReducer'; - -interface StateToProps { - currentUser: T.CurrentUser; - organization?: T.Organization; -} - -interface OwnProps extends RouterState { - children: JSX.Element; -} - -interface Props extends StateToProps, Pick<OwnProps, 'children' | 'location'> { - hasAccess: (props: Props) => boolean; -} - -export class OrganizationAccess extends React.PureComponent<Props> { - componentDidMount() { - this.checkPermissions(); - } - - componentDidUpdate() { - this.checkPermissions(); - } - - checkPermissions = () => { - if (!this.props.hasAccess(this.props)) { - handleRequiredAuthorization(); - } - }; - - render() { - if (!this.props.hasAccess(this.props)) { - return null; - } - return React.cloneElement(this.props.children, { - location: this.props.location, - organization: this.props.organization - }); - } -} - -const mapStateToProps = (state: Store, ownProps: OwnProps) => ({ - currentUser: getCurrentUser(state), - organization: getOrganizationByKey(state, ownProps.params.organizationKey) -}); - -const OrganizationAccessContainer = connect(mapStateToProps)(OrganizationAccess); - -export function hasAdminAccess({ - currentUser, - organization -}: Pick<StateToProps, 'currentUser' | 'organization'>) { - return Boolean( - isLoggedIn(currentUser) && organization && organization.actions && organization.actions.admin - ); -} - -export default function OrganizationAdminAccess(props: OwnProps) { - return <OrganizationAccessContainer hasAccess={hasAdminAccess} {...props} />; -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationBind.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationBind.tsx deleted file mode 100644 index 60f9b64d8a9..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationBind.tsx +++ /dev/null @@ -1,128 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import IdentityProviderLink from 'sonar-ui-common/components/controls/IdentityProviderLink'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { save } from 'sonar-ui-common/helpers/storage'; -import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; -import { getAlmAppInfo } from '../../../api/alm-integration'; -import { sanitizeAlmId } from '../../../helpers/almIntegrations'; -import { - BIND_ORGANIZATION_KEY, - BIND_ORGANIZATION_REDIRECT_TO_ORG_TIMESTAMP -} from '../../create/organization/utils'; - -interface Props { - currentUser: T.LoggedInUser; - organization: T.Organization; -} - -interface State { - almApplication?: T.AlmApplication; -} - -export default class OrganizationBind extends React.PureComponent<Props, State> { - mounted = false; - state: State = {}; - - componentDidMount() { - this.mounted = true; - this.fetchAlmApplication(); - } - - componentWillUnmount() { - this.mounted = false; - } - - fetchAlmApplication = () => { - return getAlmAppInfo().then(({ application }) => { - if (this.mounted) { - this.setState({ almApplication: application }); - } - }); - }; - - handleInstallAppClick = () => { - save(BIND_ORGANIZATION_KEY, this.props.organization.key); - save(BIND_ORGANIZATION_REDIRECT_TO_ORG_TIMESTAMP, Date.now().toString()); - }; - - render() { - const { currentUser, organization } = this.props; - - const { almApplication } = this.state; - - const almKey = sanitizeAlmId(currentUser.externalProvider || ''); - const orgAlmKey = organization.alm ? sanitizeAlmId(organization.alm.key) : ''; - return ( - <div className="boxed-group boxed-group-inner"> - <h2 className="boxed-title"> - {translateWithParameters('organization.bind_to_x', translate(almKey))} - </h2> - {organization.alm ? ( - <> - <span>{translate('organization.bound')}</span> - <a - className="link-no-underline big-spacer-left" - href={organization.alm.url} - rel="noopener noreferrer" - target="_blank"> - <img - alt={translate(orgAlmKey)} - className="text-text-top little-spacer-right" - height={16} - src={`${getBaseUrl()}/images/sonarcloud/${orgAlmKey}.svg`} - width={16} - /> - {translateWithParameters('organization.see_on_x', translate(orgAlmKey))} - </a> - </> - ) : ( - <> - <p className="spacer-bottom"> - {translateWithParameters('organization.binding_with_x_easy_sync', translate(almKey))} - </p> - <p className="big-spacer-bottom"> - {translateWithParameters( - 'organization.app_will_be_installed_on_x', - translate(almKey) - )} - </p> - {almApplication && ( - <IdentityProviderLink - backgroundColor={almApplication.backgroundColor} - className="display-inline-block" - iconPath={almApplication.iconPath} - name={almApplication.name} - onClick={this.handleInstallAppClick} - small={true} - url={almApplication.installationUrl}> - {translate( - 'onboarding.import_organization.choose_the_organization_button', - almApplication.key - )} - </IdentityProviderLink> - )} - </> - )} - </div> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationContainer.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationContainer.tsx deleted file mode 100644 index eb33a4efc5d..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationContainer.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { connect } from 'react-redux'; -import { RouterState } from 'react-router'; -import { getCurrentUser, getOrganizationByKey, Store } from '../../../store/rootReducer'; - -interface StateToProps { - organization?: T.Organization; - currentUser: T.CurrentUser; -} - -interface OwnProps extends RouterState { - children: JSX.Element; -} - -interface Props extends StateToProps, Pick<OwnProps, 'children' | 'location'> {} - -class OrganizationContainer extends React.PureComponent<Props> { - render() { - return React.cloneElement(this.props.children, { - location: this.props.location, - currentUser: this.props.currentUser, - organization: this.props.organization - }); - } -} - -const mapStateToProps = (state: Store, ownProps: OwnProps) => ({ - organization: getOrganizationByKey(state, ownProps.params.organizationKey), - currentUser: getCurrentUser(state) -}); - -export default connect(mapStateToProps)(OrganizationContainer); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx deleted file mode 100644 index 89abf5f7e17..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationDelete.tsx +++ /dev/null @@ -1,166 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Button } from 'sonar-ui-common/components/controls/buttons'; -import ConfirmButton from 'sonar-ui-common/components/controls/ConfirmButton'; -import { Alert } from 'sonar-ui-common/components/ui/Alert'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { getOrganizationBilling } from '../../../api/organizations'; -import addGlobalSuccessMessage from '../../../app/utils/addGlobalSuccessMessage'; -import InstanceMessage from '../../../components/common/InstanceMessage'; -import { Router, withRouter } from '../../../components/hoc/withRouter'; -import { isSonarCloud } from '../../../helpers/system'; -import { deleteOrganization } from '../actions'; - -interface DispatchToProps { - deleteOrganization: (key: string) => Promise<void>; -} - -interface OwnProps { - organization: Pick<T.Organization, 'key' | 'name'>; - router: Pick<Router, 'replace'>; -} - -type Props = OwnProps & DispatchToProps; - -interface State { - hasPaidPlan?: boolean; - verify: string; -} - -export class OrganizationDelete extends React.PureComponent<Props, State> { - mounted = false; - state: State = { verify: '' }; - - componentDidMount() { - this.mounted = true; - this.fetchOrganizationPlanInfo(); - } - - componentWillUnmount() { - this.mounted = false; - } - - fetchOrganizationPlanInfo = () => { - if (isSonarCloud()) { - getOrganizationBilling(this.props.organization.key).then( - billingInfo => { - if (this.mounted) { - this.setState({ - hasPaidPlan: billingInfo.subscription.status !== 'inactive' - }); - } - }, - () => { - if (this.mounted) { - this.setState({ hasPaidPlan: false }); - } - } - ); - } - }; - - handleInput = (event: React.ChangeEvent<HTMLInputElement>) => { - this.setState({ verify: event.currentTarget.value }); - }; - - isVerified = () => { - return this.state.verify.toLowerCase() === this.props.organization.name.toLowerCase(); - }; - - onDelete = () => { - const { organization } = this.props; - return this.props.deleteOrganization(organization.key).then(() => { - if (this.state.hasPaidPlan) { - this.props.router.replace({ - pathname: '/feedback/downgrade', - state: { - confirmationMessage: translateWithParameters( - 'organization.deleted_x', - organization.name - ), - organization, - title: translate('billing.downgrade.reason.title_deleted') - } - }); - } else { - addGlobalSuccessMessage(translate('organization.deleted')); - this.props.router.replace('/'); - } - }); - }; - - render() { - const { hasPaidPlan } = this.state; - return ( - <div className="boxed-group boxed-group-inner"> - <h2 className="boxed-title">{translate('organization.delete')}</h2> - <p className="big-spacer-bottom width-50"> - <InstanceMessage message={translate('organization.delete.description')} /> - </p> - <ConfirmButton - confirmButtonText={translate('delete')} - confirmDisable={!this.isVerified()} - isDestructive={true} - modalBody={ - <div> - {hasPaidPlan && ( - <Alert variant="warning"> - {translate('organization.delete.sonarcloud.paid_plan_info')} - </Alert> - )} - <p>{translate('organization.delete.question')}</p> - <div className="spacer-top"> - <label htmlFor="downgrade-organization-name"> - {translate('billing.downgrade.modal.type_to_proceed')} - </label> - <div className="little-spacer-top"> - <input - autoFocus={true} - className="input-super-large" - id="downgrade-organization-name" - onChange={this.handleInput} - type="text" - value={this.state.verify} - /> - </div> - </div> - </div> - } - modalHeader={translateWithParameters( - 'organization.delete_x', - this.props.organization.name - )} - onConfirm={this.onDelete}> - {({ onClick }) => ( - <Button className="js-custom-measure-delete button-red" onClick={onClick}> - {translate('delete')} - </Button> - )} - </ConfirmButton> - </div> - ); - } -} - -const mapDispatchToProps: DispatchToProps = { deleteOrganization: deleteOrganization as any }; - -export default withRouter(connect(null, mapDispatchToProps)(OrganizationDelete)); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx deleted file mode 100644 index da74e304893..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEdit.tsx +++ /dev/null @@ -1,223 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { debounce } from 'lodash'; -import * as React from 'react'; -import { Helmet } from 'react-helmet-async'; -import { connect } from 'react-redux'; -import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import OrganizationAvatar from '../../../components/common/OrganizationAvatar'; -import { whenLoggedIn } from '../../../components/hoc/whenLoggedIn'; -import { hasAdvancedALMIntegration } from '../../../helpers/almIntegrations'; -import { updateOrganization } from '../actions'; -import OrganizationBind from './OrganizationBind'; -import OrganizationDelete from './OrganizationDelete'; - -interface DispatchProps { - updateOrganization: (organization: string, changes: T.OrganizationBase) => Promise<any>; -} - -interface OwnProps { - currentUser: T.LoggedInUser; - organization: T.Organization; -} - -type Props = OwnProps & DispatchProps; - -interface State { - loading: boolean; - avatar: string; - avatarImage: string; - description: string; - name: string; - url: string; -} - -export class OrganizationEdit extends React.PureComponent<Props, State> { - mounted = false; - - constructor(props: Props) { - super(props); - this.state = { - loading: false, - avatar: props.organization.avatar || '', - avatarImage: props.organization.avatar || '', - description: props.organization.description || '', - name: props.organization.name, - url: props.organization.url || '' - }; - this.changeAvatarImage = debounce(this.changeAvatarImage, 500); - } - - componentDidMount() { - this.mounted = true; - } - - componentWillUnmount() { - this.mounted = false; - } - - handleAvatarInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const { value } = event.target; - this.setState({ avatar: value }); - this.changeAvatarImage(value); - }; - - changeAvatarImage = (value: string) => { - this.setState({ avatarImage: value }); - }; - - handleSubmit = (event: React.FormEvent<HTMLFormElement>) => { - event.preventDefault(); - const changes = { - avatar: this.state.avatar, - description: this.state.description, - name: this.state.name, - url: this.state.url - }; - this.setState({ loading: true }); - this.props - .updateOrganization(this.props.organization.key, changes) - .then(this.stopLoading, this.stopLoading); - }; - - stopLoading = () => { - if (this.mounted) { - this.setState({ loading: false }); - } - }; - - render() { - const { currentUser, organization } = this.props; - const title = translate('organization.settings'); - - const showBinding = hasAdvancedALMIntegration(currentUser); - const showDelete = organization.actions && organization.actions.delete; - - return ( - <div className="page page-limited"> - <Helmet defer={false} title={title} /> - - <header className="page-header"> - <h1 className="page-title">{title}</h1> - </header> - - <div className="boxed-group boxed-group-inner"> - <h2 className="boxed-title">{translate('organization.details')}</h2> - <form onSubmit={this.handleSubmit}> - <div className="form-field"> - <label htmlFor="organization-name"> - {translate('organization.name')} - <em className="mandatory">*</em> - </label> - <input - className="input-super-large" - disabled={this.state.loading} - id="organization-name" - maxLength={255} - name="name" - onChange={e => this.setState({ name: e.target.value })} - required={true} - type="text" - value={this.state.name} - /> - <div className="form-field-description"> - {translate('organization.name.description')} - </div> - </div> - <div className="form-field"> - <label htmlFor="organization-avatar">{translate('organization.avatar')}</label> - <input - className="input-super-large" - disabled={this.state.loading} - id="organization-avatar" - maxLength={256} - name="avatar" - onChange={this.handleAvatarInputChange} - placeholder={translate('onboarding.create_organization.avatar.placeholder')} - type="text" - value={this.state.avatar} - /> - <div className="form-field-description"> - {translate('organization.avatar.description')} - </div> - {(this.state.avatarImage || this.state.name) && ( - <div className="spacer-top"> - <div className="little-spacer-bottom"> - {translate('organization.avatar.preview')} - {':'} - </div> - <OrganizationAvatar - organization={{ - avatar: this.state.avatarImage || undefined, - name: this.state.name || '' - }} - /> - </div> - )} - </div> - <div className="form-field"> - <label htmlFor="organization-description">{translate('description')}</label> - <textarea - className="input-super-large" - disabled={this.state.loading} - id="organization-description" - maxLength={256} - name="description" - onChange={e => this.setState({ description: e.target.value })} - rows={3} - value={this.state.description} - /> - <div className="form-field-description"> - {translate('organization.description.description')} - </div> - </div> - <div className="form-field"> - <label htmlFor="organization-url">{translate('organization.url')}</label> - <input - className="input-super-large" - disabled={this.state.loading} - id="organization-url" - maxLength={256} - name="url" - onChange={e => this.setState({ url: e.target.value })} - type="text" - value={this.state.url} - /> - <div className="form-field-description"> - {translate('organization.url.description')} - </div> - </div> - <SubmitButton disabled={this.state.loading}>{translate('save')}</SubmitButton> - {this.state.loading && <i className="spinner spacer-left" />} - </form> - </div> - - {showBinding && <OrganizationBind currentUser={currentUser} organization={organization} />} - - {showDelete && <OrganizationDelete organization={organization} />} - </div> - ); - } -} - -const mapDispatchToProps = { updateOrganization: updateOrganization as any }; - -export default connect(null, mapDispatchToProps)(whenLoggedIn(OrganizationEdit)); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css deleted file mode 100644 index 69466316b93..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.css +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -.organization-empty { - margin: 100px auto 0; - width: 472px; -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx deleted file mode 100644 index 6e639e557bf..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationEmpty.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { Button } from 'sonar-ui-common/components/controls/buttons'; -import OnboardingAddMembersIcon from 'sonar-ui-common/components/icons/OnboardingAddMembersIcon'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { Router, withRouter } from '../../../components/hoc/withRouter'; -import './OrganizationEmpty.css'; - -interface Props { - organization: T.Organization; - router: Pick<Router, 'push'>; -} - -export class OrganizationEmpty extends React.PureComponent<Props> { - handleAddMembersClick = () => { - const { organization } = this.props; - this.props.router.push(`/organizations/${organization.key}/members`); - }; - - render() { - const { organization } = this.props; - const memberSyncActivated = organization.alm && organization.alm.membersSync; - - return ( - <div className="organization-empty"> - <h3 className="text-center">{translate('onboarding.create_organization.ready')}</h3> - <div className="display-flex-space-around huge-spacer-top"> - {!memberSyncActivated && ( - <Button className="button-huge" onClick={this.handleAddMembersClick}> - <OnboardingAddMembersIcon /> - <p className="medium spacer-top">{translate('organization.members.add.multiple')}</p> - </Button> - )} - </div> - </div> - ); - } -} - -export default withRouter(OrganizationEmpty); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.tsx deleted file mode 100644 index 73cc251b8d2..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationGroupCheckbox.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as classNames from 'classnames'; -import * as React from 'react'; -import Checkbox from 'sonar-ui-common/components/controls/Checkbox'; - -interface Props { - group: T.Group; - checked: boolean; - onCheck: (name: string, checked: boolean) => void; -} - -export default class OrganizationGroupCheckbox extends React.PureComponent<Props> { - onCheck = (checked: boolean) => { - const { group } = this.props; - if (!group.default) { - this.props.onCheck(group.name, checked); - } - }; - - toggleCheck = () => { - this.onCheck(!this.props.checked); - }; - - render() { - const { group } = this.props; - return ( - <li - className={classNames('capitalize list-item-checkable-link', { disabled: group.default })} - onClick={this.toggleCheck}> - <Checkbox checked={this.props.checked} onCheck={this.onCheck} /> {group.name} - </li> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx deleted file mode 100644 index 6e97d53198c..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationPage.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { Location } from 'history'; -import * as React from 'react'; -import { Helmet } from 'react-helmet-async'; -import { connect } from 'react-redux'; -import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; -import NotFound from '../../../app/components/NotFound'; -import { fetchOrganization } from '../../../store/rootActions'; -import { - getCurrentUser, - getMyOrganizations, - getOrganizationByKey, - Store -} from '../../../store/rootReducer'; -import OrganizationNavigation from '../navigation/OrganizationNavigation'; - -interface OwnProps { - children?: React.ReactNode; - location: Location; - params: { organizationKey: string }; -} - -interface StateProps { - currentUser: T.CurrentUser; - organization?: T.Organization; - userOrganizations: T.Organization[]; -} - -interface DispatchToProps { - fetchOrganization: (organizationKey: string) => Promise<void>; -} - -type Props = OwnProps & StateProps & DispatchToProps; - -interface State { - loading: boolean; -} - -export class OrganizationPage extends React.PureComponent<Props, State> { - mounted = false; - state: State = { loading: true }; - - componentDidMount() { - this.mounted = true; - this.updateOrganization(this.props.params.organizationKey); - } - - componentWillReceiveProps(nextProps: Props) { - if (nextProps.params.organizationKey !== this.props.params.organizationKey) { - this.updateOrganization(nextProps.params.organizationKey); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - stopLoading = () => { - if (this.mounted) { - this.setState({ loading: false }); - } - }; - - updateOrganization = (organizationKey: string) => { - this.setState({ loading: true }); - this.props.fetchOrganization(organizationKey).then(this.stopLoading, this.stopLoading); - }; - - render() { - const { organization } = this.props; - - if (!organization || !organization.actions || organization.actions.admin == null) { - if (this.state.loading) { - return null; - } else { - return <NotFound withContainer={false} />; - } - } - - return ( - <div> - <Helmet - defaultTitle={organization.name} - defer={false} - titleTemplate={`%s - ${organization.name}`} - /> - <Suggestions suggestions="organization_space" /> - <OrganizationNavigation - currentUser={this.props.currentUser} - location={this.props.location} - organization={organization} - userOrganizations={this.props.userOrganizations} - /> - {this.props.children} - </div> - ); - } -} - -const mapStateToProps = (state: Store, ownProps: OwnProps) => ({ - currentUser: getCurrentUser(state), - organization: getOrganizationByKey(state, ownProps.params.organizationKey), - userOrganizations: getMyOrganizations(state) -}); - -const mapDispatchToProps = { fetchOrganization: fetchOrganization as any }; - -export default connect(mapStateToProps, mapDispatchToProps)(OrganizationPage); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx b/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx deleted file mode 100644 index c53f19f768d..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/OrganizationProjects.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; -import AllProjectsContainer from '../../projects/components/AllProjectsContainer'; - -interface Props { - location: { pathname: string; query: T.Dict<string> }; - organization: T.Organization; -} - -export default function OrganizationProjects(props: Props) { - return ( - <> - <AllProjectsContainer isFavorite={false} organization={props.organization} /> - <Suggestions suggestions="organization_projects" /> - </> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationAccessContainer-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationAccessContainer-test.tsx deleted file mode 100644 index 2854c474a80..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationAccessContainer-test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import { Location } from 'history'; -import * as React from 'react'; -import { hasAdminAccess, OrganizationAccess } from '../OrganizationAccessContainer'; - -jest.mock('../../../../app/utils/handleRequiredAuthorization', () => ({ default: jest.fn() })); - -const locationMock = {} as Location; - -const currentUser = { - isLoggedIn: false -}; - -const loggedInUser = { - isLoggedIn: true, - login: 'luke', - name: 'Skywalker', - showOnboardingTutorial: false -}; - -const organization: T.Organization = { - actions: { admin: false }, - key: 'foo', - name: 'Foo', - projectVisibility: 'public' -}; - -const adminOrganization: T.Organization = { ...organization, actions: { admin: true } }; - -describe('component', () => { - it('should render children', () => { - expect( - shallow( - <OrganizationAccess - currentUser={loggedInUser} - hasAccess={() => true} - location={locationMock} - organization={adminOrganization}> - <div>hello</div> - </OrganizationAccess> - ) - ).toMatchSnapshot(); - }); - - it('should not render anything', () => { - expect( - shallow( - <OrganizationAccess - currentUser={loggedInUser} - hasAccess={() => false} - location={locationMock} - organization={adminOrganization}> - <div>hello</div> - </OrganizationAccess> - ).type() - ).toBeNull(); - }); -}); - -describe('access functions', () => { - it('should correctly handle access to admin only space', () => { - expect(hasAdminAccess({ currentUser: loggedInUser, organization: adminOrganization })).toBe( - true - ); - expect(hasAdminAccess({ currentUser, organization: adminOrganization })).toBe(false); - expect(hasAdminAccess({ currentUser: loggedInUser, organization })).toBe(false); - }); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationBind-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationBind-test.tsx deleted file mode 100644 index 0d84f412338..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationBind-test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { save } from 'sonar-ui-common/helpers/storage'; -import { getAlmAppInfo } from '../../../../api/alm-integration'; -import { - mockAlmApplication, - mockLoggedInUser, - mockOrganization -} from '../../../../helpers/testMocks'; -import { - BIND_ORGANIZATION_KEY, - BIND_ORGANIZATION_REDIRECT_TO_ORG_TIMESTAMP -} from '../../../create/organization/utils'; -import OrganizationBind from '../OrganizationBind'; - -jest.mock('../../../../api/alm-integration', () => ({ - getAlmAppInfo: jest.fn(() => Promise.resolve({ application: mockAlmApplication() })) -})); - -jest.mock('sonar-ui-common/helpers/storage', () => ({ - save: jest.fn() -})); - -jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() })); - -beforeEach(() => { - (getAlmAppInfo as jest.Mock<any>).mockClear(); -}); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should save state when handling Install App click', () => { - const orgKey = '56346'; - shallowRender({ organization: mockOrganization({ key: orgKey }) }) - .instance() - .handleInstallAppClick(); - - expect(save).toBeCalledTimes(2); - expect(save).nthCalledWith(1, BIND_ORGANIZATION_KEY, orgKey); - const secondCallArguments = (save as jest.Mock<any>).mock.calls[1]; - expect(secondCallArguments[0]).toBe(BIND_ORGANIZATION_REDIRECT_TO_ORG_TIMESTAMP); -}); - -function shallowRender(props: Partial<OrganizationBind['props']> = {}) { - return shallow<OrganizationBind>( - <OrganizationBind - currentUser={mockLoggedInUser()} - organization={mockOrganization()} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx deleted file mode 100644 index 3fc08d7c461..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationDelete-test.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import { getOrganizationBilling } from '../../../../api/organizations'; -import { isSonarCloud } from '../../../../helpers/system'; -import { OrganizationDelete } from '../OrganizationDelete'; - -jest.mock('../../../../api/organizations', () => ({ - getOrganizationBilling: jest.fn(() => - Promise.resolve({ nclocCount: 1000, subscription: { status: 'active', trial: true } }) - ) -})); - -jest.mock('../../../../helpers/system', () => ({ isSonarCloud: jest.fn() })); - -beforeEach(() => { - (getOrganizationBilling as jest.Mock<any>).mockClear(); -}); - -it('smoke test', () => { - expect(getWrapper()).toMatchSnapshot(); -}); - -it('should redirect the page', async () => { - (isSonarCloud as jest.Mock).mockImplementation(() => false); - const deleteOrganization = jest.fn(() => Promise.resolve()); - const replace = jest.fn(); - const wrapper = getWrapper({ deleteOrganization, router: { replace } }); - (wrapper.instance() as OrganizationDelete).onDelete(); - await waitAndUpdate(wrapper); - expect(deleteOrganization).toHaveBeenCalledWith('foo'); - expect(replace).toHaveBeenCalledWith('/'); -}); - -it('should show a info message for paying organization', async () => { - (isSonarCloud as jest.Mock).mockImplementation(() => true); - const wrapper = getWrapper({}); - await waitAndUpdate(wrapper); - expect(getOrganizationBilling).toHaveBeenCalledWith('foo'); - expect(wrapper).toMatchSnapshot(); -}); - -function getWrapper(props: Partial<OrganizationDelete['props']> = {}) { - return shallow( - <OrganizationDelete - deleteOrganization={jest.fn(() => Promise.resolve())} - organization={{ key: 'foo', name: 'Foo' }} - router={{ replace: jest.fn() }} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEdit-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEdit-test.tsx deleted file mode 100644 index f7abfe2a723..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEdit-test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockLoggedInUser } from '../../../../helpers/testMocks'; -import { OrganizationEdit } from '../OrganizationEdit'; - -it('smoke test', () => { - const organization = { key: 'foo', name: 'Foo' }; - const wrapper = shallow( - <OrganizationEdit - currentUser={mockLoggedInUser()} - organization={organization} - updateOrganization={jest.fn()} - /> - ); - expect(wrapper).toMatchSnapshot(); - - wrapper.setState({ - avatar: 'foo-avatar', - avatarImage: 'foo-avatar-image', - description: 'foo-description', - name: 'New Foo', - url: 'foo-url' - }); - expect(wrapper).toMatchSnapshot(); - - wrapper.setState({ loading: true }); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEmpty-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEmpty-test.tsx deleted file mode 100644 index 37757840977..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationEmpty-test.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { click } from 'sonar-ui-common/helpers/testUtils'; -import { - mockOrganization, - mockOrganizationWithAlm, - mockRouter -} from '../../../../helpers/testMocks'; -import { OrganizationEmpty } from '../OrganizationEmpty'; - -const organization: T.Organization = mockOrganization(); - -it('should render', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should add members', () => { - const push = jest.fn(); - const wrapper = shallowRender({ router: mockRouter({ push }) }); - click(wrapper.find('Button').last()); - expect(push).toBeCalledWith('/organizations/foo/members'); -}); - -it('should hide add members button when member sync activated', () => { - expect( - shallowRender({ organization: mockOrganizationWithAlm({}, { membersSync: true }) }) - ).toMatchSnapshot(); -}); - -function shallowRender(props: Partial<OrganizationEmpty['props']> = {}) { - return shallow( - <OrganizationEmpty organization={organization} router={mockRouter()} {...props} /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.tsx deleted file mode 100644 index b59ae17fac4..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationGroupCheckbox-test.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import OrganizationGroupCheckbox from '../OrganizationGroupCheckbox'; - -const group = { - id: 7, - name: 'professionals', - description: '', - membersCount: 12, - default: false -}; - -it('should render unchecked', () => { - const wrapper = shallow( - <OrganizationGroupCheckbox checked={false} group={group} onCheck={jest.fn()} /> - ); - expect(wrapper).toMatchSnapshot(); -}); - -it('should be able to toggle check', () => { - const onCheck = jest.fn().mockImplementation((_group, checked) => wrapper.setProps({ checked })); - const wrapper = shallow( - <OrganizationGroupCheckbox checked={true} group={group} onCheck={onCheck} /> - ); - expect(wrapper).toMatchSnapshot(); - (wrapper.instance() as OrganizationGroupCheckbox).toggleCheck(); - expect(onCheck.mock.calls).toMatchSnapshot(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should disabled default groups', () => { - const onCheck = jest.fn().mockImplementation((_group, checked) => wrapper.setProps({ checked })); - const wrapper = shallow( - <OrganizationGroupCheckbox - checked={true} - group={{ ...group, default: true }} - onCheck={onCheck} - /> - ); - expect(wrapper).toMatchSnapshot(); - (wrapper.instance() as OrganizationGroupCheckbox).toggleCheck(); - expect(onCheck.mock.calls.length).toBe(0); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationPage-test.tsx b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationPage-test.tsx deleted file mode 100644 index ce6fa7061e3..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/OrganizationPage-test.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import { Location } from 'history'; -import * as React from 'react'; -import { OrganizationPage } from '../OrganizationPage'; - -const fetchOrganization = jest.fn().mockResolvedValue(undefined); - -beforeEach(() => { - fetchOrganization.mockClear(); -}); - -it('smoke test', () => { - const wrapper = getWrapper(); - expect(wrapper.type()).toBeNull(); - - const organization = { actions: { admin: false }, key: 'foo', name: 'Foo', isDefault: false }; - wrapper.setProps({ organization }); - expect(wrapper).toMatchSnapshot(); -}); - -it('not found', () => { - const wrapper = getWrapper(); - wrapper.setState({ loading: false }); - expect(wrapper).toMatchSnapshot(); -}); - -it('should correctly update when the organization changes', () => { - const wrapper = getWrapper(); - wrapper.setProps({ params: { organizationKey: 'bar' } }); - expect(fetchOrganization).toHaveBeenCalledTimes(2); - expect(fetchOrganization.mock.calls).toMatchSnapshot(); -}); - -function getWrapper(props = {}) { - return shallow( - <OrganizationPage - currentUser={{ isLoggedIn: false }} - fetchOrganization={fetchOrganization} - location={{ pathname: 'foo' } as Location} - params={{ organizationKey: 'foo' }} - userOrganizations={[]} - {...props}> - <div>hello</div> - </OrganizationPage> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationAccessContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationAccessContainer-test.tsx.snap deleted file mode 100644 index ae075289ee9..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationAccessContainer-test.tsx.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`component should render children 1`] = ` -<div - location={Object {}} - organization={ - Object { - "actions": Object { - "admin": true, - }, - "key": "foo", - "name": "Foo", - "projectVisibility": "public", - } - } -> - hello -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationBind-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationBind-test.tsx.snap deleted file mode 100644 index 69821637e05..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationBind-test.tsx.snap +++ /dev/null @@ -1,23 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<div - className="boxed-group boxed-group-inner" -> - <h2 - className="boxed-title" - > - organization.bind_to_x. - </h2> - <p - className="spacer-bottom" - > - organization.binding_with_x_easy_sync. - </p> - <p - className="big-spacer-bottom" - > - organization.app_will_be_installed_on_x. - </p> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationDelete-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationDelete-test.tsx.snap deleted file mode 100644 index c2dee60e8f3..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationDelete-test.tsx.snap +++ /dev/null @@ -1,118 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should show a info message for paying organization 1`] = ` -<div - className="boxed-group boxed-group-inner" -> - <h2 - className="boxed-title" - > - organization.delete - </h2> - <p - className="big-spacer-bottom width-50" - > - <InstanceMessage - message="organization.delete.description" - /> - </p> - <ConfirmButton - confirmButtonText="delete" - confirmDisable={true} - isDestructive={true} - modalBody={ - <div> - <Alert - variant="warning" - > - organization.delete.sonarcloud.paid_plan_info - </Alert> - <p> - organization.delete.question - </p> - <div - className="spacer-top" - > - <label - htmlFor="downgrade-organization-name" - > - billing.downgrade.modal.type_to_proceed - </label> - <div - className="little-spacer-top" - > - <input - autoFocus={true} - className="input-super-large" - id="downgrade-organization-name" - onChange={[Function]} - type="text" - value="" - /> - </div> - </div> - </div> - } - modalHeader="organization.delete_x.Foo" - onConfirm={[Function]} - > - <Component /> - </ConfirmButton> -</div> -`; - -exports[`smoke test 1`] = ` -<div - className="boxed-group boxed-group-inner" -> - <h2 - className="boxed-title" - > - organization.delete - </h2> - <p - className="big-spacer-bottom width-50" - > - <InstanceMessage - message="organization.delete.description" - /> - </p> - <ConfirmButton - confirmButtonText="delete" - confirmDisable={true} - isDestructive={true} - modalBody={ - <div> - <p> - organization.delete.question - </p> - <div - className="spacer-top" - > - <label - htmlFor="downgrade-organization-name" - > - billing.downgrade.modal.type_to_proceed - </label> - <div - className="little-spacer-top" - > - <input - autoFocus={true} - className="input-super-large" - id="downgrade-organization-name" - onChange={[Function]} - type="text" - value="" - /> - </div> - </div> - </div> - } - modalHeader="organization.delete_x.Foo" - onConfirm={[Function]} - > - <Component /> - </ConfirmButton> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap deleted file mode 100644 index fbb063779aa..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEdit-test.tsx.snap +++ /dev/null @@ -1,484 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`smoke test 1`] = ` -<div - className="page page-limited" -> - <Helmet - defer={false} - encodeSpecialCharacters={true} - title="organization.settings" - /> - <header - className="page-header" - > - <h1 - className="page-title" - > - organization.settings - </h1> - </header> - <div - className="boxed-group boxed-group-inner" - > - <h2 - className="boxed-title" - > - organization.details - </h2> - <form - onSubmit={[Function]} - > - <div - className="form-field" - > - <label - htmlFor="organization-name" - > - organization.name - <em - className="mandatory" - > - * - </em> - </label> - <input - className="input-super-large" - disabled={false} - id="organization-name" - maxLength={255} - name="name" - onChange={[Function]} - required={true} - type="text" - value="Foo" - /> - <div - className="form-field-description" - > - organization.name.description - </div> - </div> - <div - className="form-field" - > - <label - htmlFor="organization-avatar" - > - organization.avatar - </label> - <input - className="input-super-large" - disabled={false} - id="organization-avatar" - maxLength={256} - name="avatar" - onChange={[Function]} - placeholder="onboarding.create_organization.avatar.placeholder" - type="text" - value="" - /> - <div - className="form-field-description" - > - organization.avatar.description - </div> - <div - className="spacer-top" - > - <div - className="little-spacer-bottom" - > - organization.avatar.preview - : - </div> - <OrganizationAvatar - organization={ - Object { - "avatar": undefined, - "name": "Foo", - } - } - /> - </div> - </div> - <div - className="form-field" - > - <label - htmlFor="organization-description" - > - description - </label> - <textarea - className="input-super-large" - disabled={false} - id="organization-description" - maxLength={256} - name="description" - onChange={[Function]} - rows={3} - value="" - /> - <div - className="form-field-description" - > - organization.description.description - </div> - </div> - <div - className="form-field" - > - <label - htmlFor="organization-url" - > - organization.url - </label> - <input - className="input-super-large" - disabled={false} - id="organization-url" - maxLength={256} - name="url" - onChange={[Function]} - type="text" - value="" - /> - <div - className="form-field-description" - > - organization.url.description - </div> - </div> - <SubmitButton - disabled={false} - > - save - </SubmitButton> - </form> - </div> -</div> -`; - -exports[`smoke test 2`] = ` -<div - className="page page-limited" -> - <Helmet - defer={false} - encodeSpecialCharacters={true} - title="organization.settings" - /> - <header - className="page-header" - > - <h1 - className="page-title" - > - organization.settings - </h1> - </header> - <div - className="boxed-group boxed-group-inner" - > - <h2 - className="boxed-title" - > - organization.details - </h2> - <form - onSubmit={[Function]} - > - <div - className="form-field" - > - <label - htmlFor="organization-name" - > - organization.name - <em - className="mandatory" - > - * - </em> - </label> - <input - className="input-super-large" - disabled={false} - id="organization-name" - maxLength={255} - name="name" - onChange={[Function]} - required={true} - type="text" - value="New Foo" - /> - <div - className="form-field-description" - > - organization.name.description - </div> - </div> - <div - className="form-field" - > - <label - htmlFor="organization-avatar" - > - organization.avatar - </label> - <input - className="input-super-large" - disabled={false} - id="organization-avatar" - maxLength={256} - name="avatar" - onChange={[Function]} - placeholder="onboarding.create_organization.avatar.placeholder" - type="text" - value="foo-avatar" - /> - <div - className="form-field-description" - > - organization.avatar.description - </div> - <div - className="spacer-top" - > - <div - className="little-spacer-bottom" - > - organization.avatar.preview - : - </div> - <OrganizationAvatar - organization={ - Object { - "avatar": "foo-avatar-image", - "name": "New Foo", - } - } - /> - </div> - </div> - <div - className="form-field" - > - <label - htmlFor="organization-description" - > - description - </label> - <textarea - className="input-super-large" - disabled={false} - id="organization-description" - maxLength={256} - name="description" - onChange={[Function]} - rows={3} - value="foo-description" - /> - <div - className="form-field-description" - > - organization.description.description - </div> - </div> - <div - className="form-field" - > - <label - htmlFor="organization-url" - > - organization.url - </label> - <input - className="input-super-large" - disabled={false} - id="organization-url" - maxLength={256} - name="url" - onChange={[Function]} - type="text" - value="foo-url" - /> - <div - className="form-field-description" - > - organization.url.description - </div> - </div> - <SubmitButton - disabled={false} - > - save - </SubmitButton> - </form> - </div> -</div> -`; - -exports[`smoke test 3`] = ` -<div - className="page page-limited" -> - <Helmet - defer={false} - encodeSpecialCharacters={true} - title="organization.settings" - /> - <header - className="page-header" - > - <h1 - className="page-title" - > - organization.settings - </h1> - </header> - <div - className="boxed-group boxed-group-inner" - > - <h2 - className="boxed-title" - > - organization.details - </h2> - <form - onSubmit={[Function]} - > - <div - className="form-field" - > - <label - htmlFor="organization-name" - > - organization.name - <em - className="mandatory" - > - * - </em> - </label> - <input - className="input-super-large" - disabled={true} - id="organization-name" - maxLength={255} - name="name" - onChange={[Function]} - required={true} - type="text" - value="New Foo" - /> - <div - className="form-field-description" - > - organization.name.description - </div> - </div> - <div - className="form-field" - > - <label - htmlFor="organization-avatar" - > - organization.avatar - </label> - <input - className="input-super-large" - disabled={true} - id="organization-avatar" - maxLength={256} - name="avatar" - onChange={[Function]} - placeholder="onboarding.create_organization.avatar.placeholder" - type="text" - value="foo-avatar" - /> - <div - className="form-field-description" - > - organization.avatar.description - </div> - <div - className="spacer-top" - > - <div - className="little-spacer-bottom" - > - organization.avatar.preview - : - </div> - <OrganizationAvatar - organization={ - Object { - "avatar": "foo-avatar-image", - "name": "New Foo", - } - } - /> - </div> - </div> - <div - className="form-field" - > - <label - htmlFor="organization-description" - > - description - </label> - <textarea - className="input-super-large" - disabled={true} - id="organization-description" - maxLength={256} - name="description" - onChange={[Function]} - rows={3} - value="foo-description" - /> - <div - className="form-field-description" - > - organization.description.description - </div> - </div> - <div - className="form-field" - > - <label - htmlFor="organization-url" - > - organization.url - </label> - <input - className="input-super-large" - disabled={true} - id="organization-url" - maxLength={256} - name="url" - onChange={[Function]} - type="text" - value="foo-url" - /> - <div - className="form-field-description" - > - organization.url.description - </div> - </div> - <SubmitButton - disabled={true} - > - save - </SubmitButton> - <i - className="spinner spacer-left" - /> - </form> - </div> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap deleted file mode 100644 index 446b6447e13..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationEmpty-test.tsx.snap +++ /dev/null @@ -1,43 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should hide add members button when member sync activated 1`] = ` -<div - className="organization-empty" -> - <h3 - className="text-center" - > - onboarding.create_organization.ready - </h3> - <div - className="display-flex-space-around huge-spacer-top" - /> -</div> -`; - -exports[`should render 1`] = ` -<div - className="organization-empty" -> - <h3 - className="text-center" - > - onboarding.create_organization.ready - </h3> - <div - className="display-flex-space-around huge-spacer-top" - > - <Button - className="button-huge" - onClick={[Function]} - > - <OnboardingAddMembersIcon /> - <p - className="medium spacer-top" - > - organization.members.add.multiple - </p> - </Button> - </div> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.tsx.snap deleted file mode 100644 index e97cc7b40c0..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationGroupCheckbox-test.tsx.snap +++ /dev/null @@ -1,70 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should be able to toggle check 1`] = ` -<li - className="capitalize list-item-checkable-link" - onClick={[Function]} -> - <Checkbox - checked={true} - onCheck={[Function]} - thirdState={false} - /> - - professionals -</li> -`; - -exports[`should be able to toggle check 2`] = ` -Array [ - Array [ - "professionals", - false, - ], -] -`; - -exports[`should be able to toggle check 3`] = ` -<li - className="capitalize list-item-checkable-link" - onClick={[Function]} -> - <Checkbox - checked={false} - onCheck={[Function]} - thirdState={false} - /> - - professionals -</li> -`; - -exports[`should disabled default groups 1`] = ` -<li - className="capitalize list-item-checkable-link disabled" - onClick={[Function]} -> - <Checkbox - checked={true} - onCheck={[Function]} - thirdState={false} - /> - - professionals -</li> -`; - -exports[`should render unchecked 1`] = ` -<li - className="capitalize list-item-checkable-link" - onClick={[Function]} -> - <Checkbox - checked={false} - onCheck={[Function]} - thirdState={false} - /> - - professionals -</li> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.tsx.snap deleted file mode 100644 index df00c8a8a01..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/components/__tests__/__snapshots__/OrganizationPage-test.tsx.snap +++ /dev/null @@ -1,58 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`not found 1`] = ` -<NotFound - withContainer={false} -/> -`; - -exports[`should correctly update when the organization changes 1`] = ` -Array [ - Array [ - "foo", - ], - Array [ - "bar", - ], -] -`; - -exports[`smoke test 1`] = ` -<div> - <Helmet - defaultTitle="Foo" - defer={false} - encodeSpecialCharacters={true} - titleTemplate="%s - Foo" - /> - <Suggestions - suggestions="organization_space" - /> - <OrganizationNavigation - currentUser={ - Object { - "isLoggedIn": false, - } - } - location={ - Object { - "pathname": "foo", - } - } - organization={ - Object { - "actions": Object { - "admin": false, - }, - "isDefault": false, - "key": "foo", - "name": "Foo", - } - } - userOrganizations={Array []} - /> - <div> - hello - </div> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx deleted file mode 100644 index 64059954432..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigation.tsx +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import ContextNavBar from 'sonar-ui-common/components/ui/ContextNavBar'; -import { rawSizes } from '../../../app/theme'; -import OrganizationNavigationHeader from './OrganizationNavigationHeader'; -import OrganizationNavigationMenuContainer from './OrganizationNavigationMenuContainer'; -import OrganizationNavigationMeta from './OrganizationNavigationMeta'; - -interface Props { - currentUser: T.CurrentUser; - location: { pathname: string }; - organization: T.Organization; - userOrganizations: T.Organization[]; -} - -export default function OrganizationNavigation({ - currentUser, - location, - organization, - userOrganizations -}: Props) { - return ( - <ContextNavBar height={rawSizes.contextNavHeightRaw} id="context-navigation"> - <div className="navbar-context-justified"> - <OrganizationNavigationHeader - currentUser={currentUser} - organization={organization} - organizations={userOrganizations} - /> - <OrganizationNavigationMeta - currentUser={currentUser} - organization={organization} - userOrganizations={userOrganizations} - /> - </div> - <OrganizationNavigationMenuContainer location={location} organization={organization} /> - </ContextNavBar> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx deleted file mode 100644 index de85692ddb8..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationAdministration.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as classNames from 'classnames'; -import * as React from 'react'; -import { Link } from 'react-router'; -import Dropdown from 'sonar-ui-common/components/controls/Dropdown'; -import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon'; -import { translate } from 'sonar-ui-common/helpers/l10n'; - -interface Props { - location: { pathname: string }; - organization: T.Organization; -} - -const ADMIN_PATHS = [ - 'edit', - 'groups', - 'permissions', - 'permission_templates', - 'projects_management', - 'webhooks' -]; - -export default function OrganizationNavigationAdministration({ location, organization }: Props) { - const { adminPages = [] } = organization; - const adminPathsWithExtensions = adminPages.map(e => `extension/${e.key}`).concat(ADMIN_PATHS); - const adminActive = adminPathsWithExtensions.some(path => - location.pathname.endsWith(`organizations/${organization.key}/${path}`) - ); - - return ( - <Dropdown - overlay={ - <ul className="menu"> - <li> - <Link activeClassName="active" to={`/organizations/${organization.key}/edit`}> - {translate('organization.settings')} - </Link> - </li> - {adminPages.map(extension => ( - <li key={extension.key}> - <Link - activeClassName="active" - to={`/organizations/${organization.key}/extension/${extension.key}`}> - {extension.name} - </Link> - </li> - ))} - <li> - <Link activeClassName="active" to={`/organizations/${organization.key}/groups`}> - {translate('user_groups.page')} - </Link> - </li> - <li> - <Link activeClassName="active" to={`/organizations/${organization.key}/permissions`}> - {translate('permissions.page')} - </Link> - </li> - <li> - <Link - activeClassName="active" - to={`/organizations/${organization.key}/permission_templates`}> - {translate('permission_templates')} - </Link> - </li> - <li> - <Link - activeClassName="active" - to={`/organizations/${organization.key}/projects_management`}> - {translate('projects_management')} - </Link> - </li> - <li> - <Link activeClassName="active" to={`/organizations/${organization.key}/webhooks`}> - {translate('webhooks.page')} - </Link> - </li> - </ul> - } - tagName="li"> - <a - className={classNames('dropdown-toggle', { active: adminActive })} - href="#" - id="organization-navigation-admin"> - {translate('layout.settings')} - <DropdownIcon className="little-spacer-left" /> - </a> - </Dropdown> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationExtensions.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationExtensions.tsx deleted file mode 100644 index ec22ccc6ca8..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationExtensions.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as classNames from 'classnames'; -import * as React from 'react'; -import { Link } from 'react-router'; -import Dropdown from 'sonar-ui-common/components/controls/Dropdown'; -import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon'; -import { translate } from 'sonar-ui-common/helpers/l10n'; - -interface Props { - location: { pathname: string }; - organization: T.Organization; -} - -export default function OrganizationNavigationExtensions({ location, organization }: Props) { - const extensions = organization.pages || []; - if (extensions.length === 0) { - return null; - } - const active = extensions.some( - extension => - location.pathname === `/organizations/${organization.key}/extension/${extension.key}` - ); - - return ( - <Dropdown - overlay={ - <ul className="menu"> - {extensions.map(extension => ( - <li key={extension.key}> - <Link - activeClassName="active" - to={`/organizations/${organization.key}/extension/${extension.key}`}> - {extension.name} - </Link> - </li> - ))} - </ul> - } - tagName="li"> - <a - className={classNames('dropdown-toggle', { active })} - href="#" - id="organization-navigation-more"> - {translate('more')} - <DropdownIcon className="little-spacer-left" /> - </a> - </Dropdown> - ); -} 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 deleted file mode 100644 index b3f05a89636..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationHeader.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { sortBy } from 'lodash'; -import * as React from 'react'; -import { Link } from 'react-router'; -import Dropdown from 'sonar-ui-common/components/controls/Dropdown'; -import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; -import DropdownIcon from 'sonar-ui-common/components/icons/DropdownIcon'; -import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import { getBaseUrl } from 'sonar-ui-common/helpers/urls'; -import OrganizationAvatar from '../../../components/common/OrganizationAvatar'; -import OrganizationListItem from '../../../components/ui/OrganizationListItem'; -import { - getUserAlmKey, - hasAdvancedALMIntegration, - sanitizeAlmId -} from '../../../helpers/almIntegrations'; - -export interface Props { - currentUser: T.CurrentUser; - organization: T.Organization; - organizations: T.Organization[]; -} - -export default function OrganizationNavigationHeader({ - currentUser, - organization, - organizations -}: Props) { - const other = organizations.filter(o => o.key !== organization.key); - const isAdmin = organization.actions && organization.actions.admin; - - let almKey; - let tooltipContent; - let tooltipIconSrc; - if (organization.alm) { - almKey = sanitizeAlmId(organization.alm.key); - tooltipContent = ( - <> - <p>{translateWithParameters('organization.bound_to_x', translate(almKey))}</p> - <hr className="spacer-top spacer-bottom" /> - <a href={organization.alm.url} rel="noopener noreferrer" target="_blank"> - {translateWithParameters('organization.see_on_x', translate(almKey))} - </a> - </> - ); - tooltipIconSrc = `${getBaseUrl()}/images/sonarcloud/${almKey}.svg`; - } else if (hasAdvancedALMIntegration(currentUser)) { - almKey = getUserAlmKey(currentUser) || ''; - tooltipContent = ( - <> - <p>{translateWithParameters('organization.not_bound_to_x', translate(almKey))}</p> - {isAdmin && ( - <> - <hr className="spacer-top spacer-bottom" /> - <Link to={`/organizations/${organization.key}/edit`}> - {translate('organization.go_to_settings_to_bind')} - </Link> - </> - )} - </> - ); - tooltipIconSrc = `${getBaseUrl()}/images/sonarcloud/${almKey}-unbound.svg`; - } - - return ( - <header className="navbar-context-header"> - <OrganizationAvatar organization={organization} /> - {other.length ? ( - <Dropdown - className="display-inline-block" - overlay={ - <ul className="menu"> - {sortBy(other, org => org.name.toLowerCase()).map(organization => ( - <OrganizationListItem key={organization.key} organization={organization} /> - ))} - </ul> - }> - <a - className="display-inline-flex-center spacer-left link-base-color link-no-underline" - href="#"> - {organization.name} - <DropdownIcon className="little-spacer-left" /> - </a> - </Dropdown> - ) : ( - <span className="spacer-left">{organization.name}</span> - )} - {almKey && ( - <Tooltip mouseLeaveDelay={0.25} overlay={tooltipContent}> - <img - alt={translate(almKey)} - className="text-middle spacer-left" - height={16} - src={tooltipIconSrc} - width={16} - /> - </Tooltip> - )} - {organization.description != null && ( - <div className="navbar-context-description"> - <p className="text-limited text-top" title={organization.description}> - {organization.description} - </p> - </div> - )} - </header> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx deleted file mode 100644 index 796c7af8302..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMenuContainer.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Link } from 'react-router'; -import NavBarTabs from 'sonar-ui-common/components/ui/NavBarTabs'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { hasPrivateAccess, isCurrentUserMemberOf } from '../../../helpers/organizations'; -import { getQualityGatesUrl } from '../../../helpers/urls'; -import { getCurrentUser, getMyOrganizations, Store } from '../../../store/rootReducer'; -import OrganizationNavigationAdministration from './OrganizationNavigationAdministration'; -import OrganizationNavigationExtensions from './OrganizationNavigationExtensions'; - -interface StateToProps { - currentUser: T.CurrentUser; - userOrganizations: T.Organization[]; -} - -interface OwnProps { - location: { pathname: string }; - organization: T.Organization; -} - -type Props = OwnProps & StateToProps; - -export function OrganizationNavigationMenu({ - currentUser, - location, - organization, - userOrganizations -}: Props) { - const hasPrivateRights = hasPrivateAccess(currentUser, organization, userOrganizations); - const { actions = {} } = organization; - return ( - <NavBarTabs className="navbar-context-tabs"> - <li> - <Link activeClassName="active" to={`/organizations/${organization.key}/projects`}> - {translate('projects.page')} - </Link> - </li> - <li> - <Link - activeClassName="active" - to={{ - pathname: `/organizations/${organization.key}/issues`, - query: { resolved: 'false' } - }}> - {translate('issues.page')} - </Link> - </li> - {hasPrivateRights && ( - <li> - <Link activeClassName="active" to={`/organizations/${organization.key}/quality_profiles`}> - {translate('quality_profiles.page')} - </Link> - </li> - )} - <li> - <Link activeClassName="active" to={`/organizations/${organization.key}/rules`}> - {translate('coding_rules.page')} - </Link> - </li> - {hasPrivateRights && ( - <li> - <Link activeClassName="active" to={getQualityGatesUrl(organization.key)}> - {translate('quality_gates.page')} - </Link> - </li> - )} - {isCurrentUserMemberOf(currentUser, organization, userOrganizations) && ( - <li> - <Link activeClassName="active" to={`/organizations/${organization.key}/members`}> - {translate('organization.members.page')} - </Link> - </li> - )} - <OrganizationNavigationExtensions location={location} organization={organization} /> - {actions.admin && ( - <OrganizationNavigationAdministration location={location} organization={organization} /> - )} - </NavBarTabs> - ); -} - -const mapStateToProps = (state: Store) => ({ - currentUser: getCurrentUser(state), - userOrganizations: getMyOrganizations(state) -}); - -export default connect(mapStateToProps)(OrganizationNavigationMenu); diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx deleted file mode 100644 index a6b877cdc1d..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/OrganizationNavigationMeta.tsx +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import HomePageSelect from '../../../components/controls/HomePageSelect'; -import DocTooltip from '../../../components/docs/DocTooltip'; -import { hasPrivateAccess, isPaidOrganization } from '../../../helpers/organizations'; -import { isSonarCloud } from '../../../helpers/system'; - -interface Props { - currentUser: T.CurrentUser; - organization: T.Organization; - userOrganizations: T.Organization[]; -} - -export default function OrganizationNavigationMeta({ - currentUser, - organization, - userOrganizations -}: Props) { - const onSonarCloud = isSonarCloud(); - return ( - <div className="navbar-context-meta"> - {organization.url != null && ( - <a - className="spacer-right text-limited" - href={organization.url} - rel="nofollow" - title={organization.url}> - {organization.url} - </a> - )} - {onSonarCloud && - isPaidOrganization(organization) && - hasPrivateAccess(currentUser, organization, userOrganizations) && ( - <DocTooltip - className="spacer-right" - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/organizations/subscription-paid-plan.md' - )}> - <div className="badge">{translate('organization.paid_plan.badge')}</div> - </DocTooltip> - )} - <div className="text-muted"> - <strong>{translate('organization.key')}:</strong> {organization.key} - </div> - {onSonarCloud && ( - <div className="navbar-context-meta-secondary"> - <HomePageSelect currentPage={{ type: 'ORGANIZATION', organization: organization.key }} /> - </div> - )} - </div> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx deleted file mode 100644 index ec2bf5e6303..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigation-test.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import OrganizationNavigation from '../OrganizationNavigation'; - -it('render', () => { - expect( - shallow( - <OrganizationNavigation - currentUser={{ isLoggedIn: false }} - location={{ pathname: '/organizations/foo' }} - organization={{ - key: 'foo', - name: 'Foo', - projectVisibility: 'public' - }} - userOrganizations={[]} - /> - ) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationAdministration-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationAdministration-test.tsx deleted file mode 100644 index d30527f99cb..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationAdministration-test.tsx +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import OrganizationNavigationAdministration from '../OrganizationNavigationAdministration'; - -it('renders', () => { - const wrapper = shallow( - <OrganizationNavigationAdministration - location={{ pathname: '' }} - organization={{ - key: 'foo', - name: 'Foo', - projectVisibility: 'public' - }} - /> - ); - expect(wrapper.find('Dropdown')).toMatchSnapshot(); -}); 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 deleted file mode 100644 index fd23910634d..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationHeader-test.tsx +++ /dev/null @@ -1,101 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { - mockCurrentUser, - mockLoggedInUser, - mockOrganizationWithAlm -} from '../../../../helpers/testMocks'; -import OrganizationNavigationHeader, { Props } from '../OrganizationNavigationHeader'; - -it('renders', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('renders with alm integration', () => { - expect( - shallowRender({ organization: mockOrganizationWithAlm({ projectVisibility: 'public' }) }) - ).toMatchSnapshot(); -}); - -it('renders for external user w/o alm integration', () => { - expect( - shallowRender({ currentUser: mockLoggedInUser({ externalProvider: 'github' }) }) - ).toMatchSnapshot(); -}); - -it('renders with the organization tooltip for an admin user of an organization', () => { - expect( - shallowRender({ - currentUser: mockLoggedInUser({ - externalProvider: 'github' - }), - organization: { - actions: { admin: true }, - key: 'org1', - name: 'org1', - projectVisibility: 'public' - } - }).find('Tooltip') - ).toMatchSnapshot(); -}); - -it('renders without the organization tooltip for a non-admin user of an organization', () => { - expect( - shallowRender({ - currentUser: mockLoggedInUser({ - externalProvider: 'github' - }), - organization: { - actions: { admin: false }, - key: 'org1', - name: 'org1', - projectVisibility: 'public' - } - }).find('Tooltip') - ).toMatchSnapshot(); -}); - -it('renders dropdown', () => { - const organizations: T.Organization[] = [ - { actions: { admin: true }, key: 'org1', name: 'org1', projectVisibility: 'public' }, - { actions: { admin: false }, key: 'org2', name: 'org2', projectVisibility: 'public' } - ]; - const wrapper = shallowRender({ - organizations - }); - expect(wrapper.find('Dropdown')).toMatchSnapshot(); -}); - -function shallowRender(props: Partial<Props> = {}) { - return shallow( - <OrganizationNavigationHeader - currentUser={mockCurrentUser()} - organization={{ - key: 'foo', - name: 'Foo', - projectVisibility: 'public' - }} - organizations={[]} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenuContainer-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenuContainer-test.tsx deleted file mode 100644 index ec811e5ff0c..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMenuContainer-test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { hasPrivateAccess, isCurrentUserMemberOf } from '../../../../helpers/organizations'; -import { OrganizationNavigationMenu } from '../OrganizationNavigationMenuContainer'; - -jest.mock('../../../../helpers/organizations', () => ({ - isCurrentUserMemberOf: jest.fn().mockReturnValue(true), - hasPrivateAccess: jest.fn().mockReturnValue(true) -})); - -const organization: T.Organization = { - key: 'foo', - name: 'Foo', - projectVisibility: 'public' -}; - -const loggedInUser = { - isLoggedIn: true, - login: 'luke', - name: 'Skywalker', - showOnboardingTutorial: false -}; - -beforeEach(() => { - (isCurrentUserMemberOf as jest.Mock<any>).mockClear(); - (hasPrivateAccess as jest.Mock<any>).mockClear(); -}); - -it('renders', () => { - expect( - shallow( - <OrganizationNavigationMenu - currentUser={loggedInUser} - location={{ pathname: '' }} - organization={organization} - userOrganizations={[organization]} - /> - ) - ).toMatchSnapshot(); -}); - -it('renders for admin', () => { - expect( - shallow( - <OrganizationNavigationMenu - currentUser={loggedInUser} - location={{ pathname: '' }} - organization={{ ...organization, actions: { admin: true } }} - userOrganizations={[organization]} - /> - ) - ).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx deleted file mode 100644 index 851f649cf75..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/OrganizationNavigationMeta-test.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import OrganizationNavigationMeta from '../OrganizationNavigationMeta'; - -jest.mock('../../../../helpers/system', () => ({ isSonarCloud: () => true })); - -const organization: T.Organization = { key: 'foo', name: 'Foo', subscription: 'FREE' }; - -it('renders', () => { - expect( - shallow( - <OrganizationNavigationMeta - currentUser={{ isLoggedIn: false }} - organization={organization} - userOrganizations={[]} - /> - ) - ).toMatchSnapshot(); -}); - -it('renders with private badge', () => { - expect( - shallow( - <OrganizationNavigationMeta - currentUser={{ isLoggedIn: true }} - organization={{ ...organization, subscription: 'PAID' }} - userOrganizations={[organization]} - /> - ) - .find('DocTooltip') - .exists() - ).toBe(true); -}); diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap deleted file mode 100644 index 1db61a2ee48..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigation-test.tsx.snap +++ /dev/null @@ -1,57 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`render 1`] = ` -<ContextNavBar - height={72} - id="context-navigation" -> - <div - className="navbar-context-justified" - > - <OrganizationNavigationHeader - currentUser={ - Object { - "isLoggedIn": false, - } - } - organization={ - Object { - "key": "foo", - "name": "Foo", - "projectVisibility": "public", - } - } - organizations={Array []} - /> - <OrganizationNavigationMeta - currentUser={ - Object { - "isLoggedIn": false, - } - } - organization={ - Object { - "key": "foo", - "name": "Foo", - "projectVisibility": "public", - } - } - userOrganizations={Array []} - /> - </div> - <Connect(OrganizationNavigationMenu) - location={ - Object { - "pathname": "/organizations/foo", - } - } - organization={ - Object { - "key": "foo", - "name": "Foo", - "projectVisibility": "public", - } - } - /> -</ContextNavBar> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap deleted file mode 100644 index da8dbbc2c87..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationAdministration-test.tsx.snap +++ /dev/null @@ -1,84 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders 1`] = ` -<Dropdown - overlay={ - <ul - className="menu" - > - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/edit" - > - organization.settings - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/groups" - > - user_groups.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/permissions" - > - permissions.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/permission_templates" - > - permission_templates - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/projects_management" - > - projects_management - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/webhooks" - > - webhooks.page - </Link> - </li> - </ul> - } - tagName="li" -> - <a - className="dropdown-toggle" - href="#" - id="organization-navigation-admin" - > - layout.settings - <DropdownIcon - className="little-spacer-left" - /> - </a> -</Dropdown> -`; 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 deleted file mode 100644 index 0394cb0be1d..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationHeader-test.tsx.snap +++ /dev/null @@ -1,216 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders 1`] = ` -<header - className="navbar-context-header" -> - <OrganizationAvatar - organization={ - Object { - "key": "foo", - "name": "Foo", - "projectVisibility": "public", - } - } - /> - <span - className="spacer-left" - > - Foo - </span> -</header> -`; - -exports[`renders dropdown 1`] = ` -<Dropdown - className="display-inline-block" - overlay={ - <ul - className="menu" - > - <OrganizationListItem - organization={ - Object { - "actions": Object { - "admin": true, - }, - "key": "org1", - "name": "org1", - "projectVisibility": "public", - } - } - /> - <OrganizationListItem - organization={ - Object { - "actions": Object { - "admin": false, - }, - "key": "org2", - "name": "org2", - "projectVisibility": "public", - } - } - /> - </ul> - } -> - <a - className="display-inline-flex-center spacer-left link-base-color link-no-underline" - href="#" - > - Foo - <DropdownIcon - className="little-spacer-left" - /> - </a> -</Dropdown> -`; - -exports[`renders for external user w/o alm integration 1`] = ` -<header - className="navbar-context-header" -> - <OrganizationAvatar - organization={ - Object { - "key": "foo", - "name": "Foo", - "projectVisibility": "public", - } - } - /> - <span - className="spacer-left" - > - Foo - </span> - <Tooltip - mouseLeaveDelay={0.25} - overlay={ - <React.Fragment> - <p> - organization.not_bound_to_x.github - </p> - </React.Fragment> - } - > - <img - alt="github" - className="text-middle spacer-left" - height={16} - src="/images/sonarcloud/github-unbound.svg" - width={16} - /> - </Tooltip> -</header> -`; - -exports[`renders with alm integration 1`] = ` -<header - className="navbar-context-header" -> - <OrganizationAvatar - organization={ - Object { - "alm": Object { - "key": "github", - "membersSync": false, - "personal": false, - "url": "https://github.com/foo", - }, - "key": "foo", - "name": "Foo", - "projectVisibility": "public", - } - } - /> - <span - className="spacer-left" - > - Foo - </span> - <Tooltip - mouseLeaveDelay={0.25} - overlay={ - <React.Fragment> - <p> - organization.bound_to_x.github - </p> - <hr - className="spacer-top spacer-bottom" - /> - <a - href="https://github.com/foo" - rel="noopener noreferrer" - target="_blank" - > - organization.see_on_x.github - </a> - </React.Fragment> - } - > - <img - alt="github" - className="text-middle spacer-left" - height={16} - src="/images/sonarcloud/github.svg" - width={16} - /> - </Tooltip> -</header> -`; - -exports[`renders with the organization tooltip for an admin user of an organization 1`] = ` -<Tooltip - mouseLeaveDelay={0.25} - overlay={ - <React.Fragment> - <p> - organization.not_bound_to_x.github - </p> - <React.Fragment> - <hr - className="spacer-top spacer-bottom" - /> - <Link - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/org1/edit" - > - organization.go_to_settings_to_bind - </Link> - </React.Fragment> - </React.Fragment> - } -> - <img - alt="github" - className="text-middle spacer-left" - height={16} - src="/images/sonarcloud/github-unbound.svg" - width={16} - /> -</Tooltip> -`; - -exports[`renders without the organization tooltip for a non-admin user of an organization 1`] = ` -<Tooltip - mouseLeaveDelay={0.25} - overlay={ - <React.Fragment> - <p> - organization.not_bound_to_x.github - </p> - </React.Fragment> - } -> - <img - alt="github" - className="text-middle spacer-left" - height={16} - src="/images/sonarcloud/github-unbound.svg" - width={16} - /> -</Tooltip> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap deleted file mode 100644 index 46f062c94ae..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMenuContainer-test.tsx.snap +++ /dev/null @@ -1,205 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders 1`] = ` -<NavBarTabs - className="navbar-context-tabs" -> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/projects" - > - projects.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to={ - Object { - "pathname": "/organizations/foo/issues", - "query": Object { - "resolved": "false", - }, - } - } - > - issues.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/quality_profiles" - > - quality_profiles.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/rules" - > - coding_rules.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to={ - Object { - "pathname": "/organizations/foo/quality_gates", - } - } - > - quality_gates.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/members" - > - organization.members.page - </Link> - </li> - <OrganizationNavigationExtensions - location={ - Object { - "pathname": "", - } - } - organization={ - Object { - "key": "foo", - "name": "Foo", - "projectVisibility": "public", - } - } - /> -</NavBarTabs> -`; - -exports[`renders for admin 1`] = ` -<NavBarTabs - className="navbar-context-tabs" -> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/projects" - > - projects.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to={ - Object { - "pathname": "/organizations/foo/issues", - "query": Object { - "resolved": "false", - }, - } - } - > - issues.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/quality_profiles" - > - quality_profiles.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/rules" - > - coding_rules.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to={ - Object { - "pathname": "/organizations/foo/quality_gates", - } - } - > - quality_gates.page - </Link> - </li> - <li> - <Link - activeClassName="active" - onlyActiveOnIndex={false} - style={Object {}} - to="/organizations/foo/members" - > - organization.members.page - </Link> - </li> - <OrganizationNavigationExtensions - location={ - Object { - "pathname": "", - } - } - organization={ - Object { - "actions": Object { - "admin": true, - }, - "key": "foo", - "name": "Foo", - "projectVisibility": "public", - } - } - /> - <OrganizationNavigationAdministration - location={ - Object { - "pathname": "", - } - } - organization={ - Object { - "actions": Object { - "admin": true, - }, - "key": "foo", - "name": "Foo", - "projectVisibility": "public", - } - } - /> -</NavBarTabs> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap b/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap deleted file mode 100644 index f70dd859642..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/navigation/__tests__/__snapshots__/OrganizationNavigationMeta-test.tsx.snap +++ /dev/null @@ -1,30 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders 1`] = ` -<div - className="navbar-context-meta" -> - <div - className="text-muted" - > - <strong> - organization.key - : - </strong> - - foo - </div> - <div - className="navbar-context-meta-secondary" - > - <Connect(HomePageSelect) - currentPage={ - Object { - "organization": "foo", - "type": "ORGANIZATION", - } - } - /> - </div> -</div> -`; diff --git a/server/sonar-web/src/main/js/apps/organizations/routes.ts b/server/sonar-web/src/main/js/apps/organizations/routes.ts deleted file mode 100644 index 1efff137c9f..00000000000 --- a/server/sonar-web/src/main/js/apps/organizations/routes.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { RedirectFunction, RouterState } from 'react-router'; -import { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'; -import codingRulesRoutes from '../coding-rules/routes'; -import qualityGatesRoutes from '../quality-gates/routes'; -import qualityProfilesRoutes from '../quality-profiles/routes'; -import webhooksRoutes from '../webhooks/routes'; - -const OrganizationContainer = lazyLoadComponent(() => import('./components/OrganizationContainer')); - -const routes = [ - { - path: ':organizationKey', - component: lazyLoadComponent(() => import('./components/OrganizationPage')), - childRoutes: [ - { - indexRoute: { - onEnter(nextState: RouterState, replace: RedirectFunction) { - const { params } = nextState; - replace(`/organizations/${params.organizationKey}/projects`); - } - } - }, - { - path: 'projects', - component: OrganizationContainer, - childRoutes: [ - { - indexRoute: { - component: lazyLoadComponent(() => import('./components/OrganizationProjects')) - } - } - ] - }, - { - path: 'issues', - component: OrganizationContainer, - childRoutes: [ - { - indexRoute: { - component: lazyLoadComponent(() => import('../issues/components/AppContainer')) - } - } - ] - }, - { - path: 'rules', - component: OrganizationContainer, - childRoutes: codingRulesRoutes - }, - { - path: 'members', - component: lazyLoadComponent(() => - import('../organizationMembers/OrganizationMembersContainer') - ) - }, - { - path: 'quality_profiles', - childRoutes: qualityProfilesRoutes - }, - { - path: 'quality_gates', - component: OrganizationContainer, - childRoutes: qualityGatesRoutes - }, - { - component: lazyLoadComponent(() => import('./components/OrganizationAccessContainer')), - childRoutes: [ - { - path: 'edit', - component: lazyLoadComponent(() => import('./components/OrganizationEdit')) - }, - { - path: 'groups', - component: lazyLoadComponent(() => import('../groups/components/App')) - }, - { - path: 'permissions', - component: lazyLoadComponent(() => import('../permissions/global/components/App')) - }, - { - path: 'permission_templates', - component: lazyLoadComponent(() => import('../permission-templates/components/App')) - }, - { - path: 'projects_management', - component: lazyLoadComponent(() => import('../projectsManagement/AppContainer')) - }, - { path: 'webhooks', childRoutes: webhooksRoutes }, - { - path: 'extension/:pluginKey/:extensionKey', - component: lazyLoadComponent(() => - import('../../app/components/extensions/OrganizationPageExtension') - ) - } - ] - } - ] - } -]; - -export default routes; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelIssueMeasureRow.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelIssueMeasureRow.tsx index a1c1d291cd1..965132f2536 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelIssueMeasureRow.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanelIssueMeasureRow.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { translate } from 'sonar-ui-common/helpers/l10n'; import { BranchLike } from '../../../types/branch-like'; import { ComponentQualifier } from '../../../types/component'; import { IssueType } from '../../../types/issues'; @@ -68,9 +69,9 @@ export default function MeasuresPanelIssueMeasureRow(props: MeasuresPanelIssueMe <IssueLabel branchLike={branchLike} component={component} - docTooltip={ + helpTooltip={ type === IssueType.SecurityHotspot - ? import(/* webpackMode: "eager" */ 'Docs/tooltips/metrics/security-hotspots.md') + ? translate('metric.security_hotspots.full_description') : undefined } measures={measures} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx index a6397861005..790c6cde485 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGatePanel.tsx @@ -23,7 +23,6 @@ import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import { Alert } from 'sonar-ui-common/components/ui/Alert'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../../components/docs/DocTooltip'; import { QualityGateStatus } from '../../../types/quality-gates'; import QualityGatePanelSection from './QualityGatePanelSection'; @@ -57,11 +56,13 @@ export function QualityGatePanel(props: QualityGatePanelProps) { <div className="overview-panel" data-test="overview__quality-gate-panel"> <h2 className="overview-panel-title display-inline-flex-center"> {translate('overview.quality_gate')}{' '} - <DocTooltip + <HelpTooltip className="little-spacer-left" - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/project-homepage-quality-gate.md' - )} + overlay={ + <div className="big-padded-top big-padded-bottom"> + {translate('overview.quality_gate.help')} + </div> + } /> </h2> diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelIssueMeasureRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelIssueMeasureRow-test.tsx.snap index 39b67ec8b20..14229e28720 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelIssueMeasureRow-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanelIssueMeasureRow-test.tsx.snap @@ -927,7 +927,7 @@ exports[`should render correctly for projects: Hotspot 1`] = ` "tags": Array [], } } - docTooltip={Promise {}} + helpTooltip="metric.security_hotspots.full_description" measures={ Array [ Object { diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap index 1e720788806..e9ae25c6488 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/QualityGatePanel-test.tsx.snap @@ -10,9 +10,15 @@ exports[`should render correctly for applications 1`] = ` > overview.quality_gate - <DocTooltip + <HelpTooltip className="little-spacer-left" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + overview.quality_gate.help + </div> + } /> </h2> <div @@ -191,9 +197,15 @@ exports[`should render correctly for applications 2`] = ` > overview.quality_gate - <DocTooltip + <HelpTooltip className="little-spacer-left" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + overview.quality_gate.help + </div> + } /> </h2> <div @@ -325,9 +337,15 @@ exports[`should render correctly for projects 1`] = ` > overview.quality_gate - <DocTooltip + <HelpTooltip className="little-spacer-left" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + overview.quality_gate.help + </div> + } /> </h2> <div @@ -424,9 +442,15 @@ exports[`should render correctly for projects 2`] = ` > overview.quality_gate - <DocTooltip + <HelpTooltip className="little-spacer-left" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + overview.quality_gate.help + </div> + } /> </h2> <div @@ -460,9 +484,15 @@ exports[`should render correctly for projects 3`] = ` > overview.quality_gate - <DocTooltip + <HelpTooltip className="little-spacer-left" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + overview.quality_gate.help + </div> + } /> </h2> <Alert diff --git a/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx b/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx index f2c86205aa6..efe265073f9 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/IssueLabel.tsx @@ -19,9 +19,9 @@ */ import * as React from 'react'; import { Link } from 'react-router'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { formatMeasure, localizeMetric } from 'sonar-ui-common/helpers/measures'; -import DocTooltip from '../../../components/docs/DocTooltip'; import { getLeakValue } from '../../../components/measure/utils'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { findMeasure } from '../../../helpers/measures'; @@ -33,14 +33,14 @@ import { getIssueIconClass, getIssueMetricKey } from '../utils'; export interface IssueLabelProps { branchLike?: BranchLike; component: T.Component; - docTooltip?: Promise<{ default: string }>; + helpTooltip?: string; measures: T.MeasureEnhanced[]; type: IssueType; useDiffMetric?: boolean; } export function IssueLabel(props: IssueLabelProps) { - const { branchLike, component, docTooltip, measures, type, useDiffMetric = false } = props; + const { branchLike, component, helpTooltip, measures, type, useDiffMetric = false } = props; const metric = getIssueMetricKey(type, useDiffMetric); const measure = findMeasure(measures, metric); const iconClass = getIssueIconClass(type); @@ -74,7 +74,7 @@ export function IssueLabel(props: IssueLabelProps) { )} {React.createElement(iconClass, { className: 'big-spacer-left little-spacer-right' })} {localizeMetric(metric)} - {docTooltip && <DocTooltip className="little-spacer-left" doc={docTooltip} />} + {helpTooltip && <HelpTooltip className="little-spacer-left" overlay={helpTooltip} />} </> ); } diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx index 40f07a5621c..fb9e2f09513 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx @@ -55,7 +55,7 @@ it('should render correctly for vulnerabilities', () => { }); it('should render correctly for hotspots', () => { - const docTooltip = Promise.resolve({ default: 'tooltip text' }); + const helpTooltip = 'tooltip text'; const type = IssueType.SecurityHotspot; const measures = [ mockMeasureEnhanced({ metric: mockMetric({ key: MetricKey.security_hotspots }) }), @@ -63,14 +63,14 @@ it('should render correctly for hotspots', () => { ]; expect( shallowRender({ - docTooltip, + helpTooltip, measures, type }) ).toMatchSnapshot(); expect( shallowRender({ - docTooltip, + helpTooltip, measures, type, useDiffMetric: true diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap index 39ca9c58551..7a16e77ae4e 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/__snapshots__/IssueLabel-test.tsx.snap @@ -139,9 +139,9 @@ exports[`should render correctly for hotspots 1`] = ` className="big-spacer-left little-spacer-right" /> metric.security_hotspots.name - <DocTooltip + <HelpTooltip className="little-spacer-left" - doc={Promise {}} + overlay="tooltip text" /> </Fragment> `; @@ -173,9 +173,9 @@ exports[`should render correctly for hotspots 2`] = ` className="big-spacer-left little-spacer-right" /> metric.new_security_hotspots.name - <DocTooltip + <HelpTooltip className="little-spacer-left" - doc={Promise {}} + overlay="tooltip text" /> </Fragment> `; diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx index df49eb32417..a1133a223f9 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/PullRequestOverview.tsx @@ -26,7 +26,6 @@ import { Alert } from 'sonar-ui-common/components/ui/Alert'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { isDefined } from 'sonar-ui-common/helpers/types'; import { getMeasuresWithMetrics } from '../../../api/measures'; -import DocTooltip from '../../../components/docs/DocTooltip'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { enhanceConditionWithMeasure, enhanceMeasuresWithMetrics } from '../../../helpers/measures'; import { fetchBranchStatus } from '../../../store/rootActions'; @@ -186,13 +185,15 @@ export class PullRequestOverview extends React.PureComponent<Props, State> { )} <div className="display-flex-row"> <div className="big-spacer-right"> - <h2 className="overview-panel-title spacer-bottom small"> + <h2 className="overview-panel-title spacer-bottom small display-inline-flex-center"> {translate('overview.quality_gate')} - <DocTooltip - className="spacer-left" - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/project-homepage-quality-gate.md' - )} + <HelpTooltip + className="little-spacer-left" + overlay={ + <div className="big-padded-top big-padded-bottom"> + {translate('overview.quality_gate.help')} + </div> + } /> </h2> <LargeQualityGateBadge component={component} level={status} /> diff --git a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap index 1ba05b2bde0..1d0e382c8ba 100644 --- a/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/__snapshots__/PullRequestOverview-test.tsx.snap @@ -14,12 +14,18 @@ exports[`should render correctly for a failed QG 1`] = ` className="big-spacer-right" > <h2 - className="overview-panel-title spacer-bottom small" + className="overview-panel-title spacer-bottom small display-inline-flex-center" > overview.quality_gate - <DocTooltip - className="spacer-left" - doc={Promise {}} + <HelpTooltip + className="little-spacer-left" + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + overview.quality_gate.help + </div> + } /> </h2> <Memo(LargeQualityGateBadge) @@ -1369,12 +1375,18 @@ exports[`should render correctly for a passed QG 1`] = ` className="big-spacer-right" > <h2 - className="overview-panel-title spacer-bottom small" + className="overview-panel-title spacer-bottom small display-inline-flex-center" > overview.quality_gate - <DocTooltip - className="spacer-left" - doc={Promise {}} + <HelpTooltip + className="little-spacer-left" + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + overview.quality_gate.help + </div> + } /> </h2> <Memo(LargeQualityGateBadge) diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx index 586e1503a1e..c76000ebf5b 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx @@ -23,7 +23,6 @@ import { Helmet } from 'react-helmet-async'; import { translate } from 'sonar-ui-common/helpers/l10n'; import * as api from '../../../../api/permissions'; import VisibilitySelector from '../../../../components/common/VisibilitySelector'; -import UpgradeOrganizationBox from '../../../create/components/UpgradeOrganizationBox'; import '../../styles.css'; import AllHoldersList from './AllHoldersList'; import PageHeader from './PageHeader'; @@ -31,9 +30,7 @@ import PublicProjectDisclaimer from './PublicProjectDisclaimer'; interface Props { component: T.Component; - fetchOrganization: (organization: string) => void; onComponentChange: (changes: Partial<T.Component>) => void; - organization?: T.Organization; } interface State { @@ -88,7 +85,6 @@ export default class App extends React.PureComponent<Props, State> { projectKey: component.key, q: query || undefined, permission: selectedPermission, - organization: component.organization, p: userPage }) : Promise.resolve({ paging: undefined, users: [] }); @@ -99,7 +95,6 @@ export default class App extends React.PureComponent<Props, State> { projectKey: component.key, q: query || undefined, permission: selectedPermission, - organization: component.organization, p: groupsPage }) : Promise.resolve({ paging: undefined, groups: [] }); @@ -207,8 +202,7 @@ export default class App extends React.PureComponent<Props, State> { .grantPermissionToGroup({ projectKey: this.props.component.key, groupName: group, - permission, - organization: this.props.component.organization + permission }) .then(this.stopLoading, () => { if (this.mounted) { @@ -232,8 +226,7 @@ export default class App extends React.PureComponent<Props, State> { .grantPermissionToUser({ projectKey: this.props.component.key, login: user, - permission, - organization: this.props.component.organization + permission }) .then(this.stopLoading, () => { if (this.mounted) { @@ -257,8 +250,7 @@ export default class App extends React.PureComponent<Props, State> { .revokePermissionFromGroup({ projectKey: this.props.component.key, groupName: group, - permission, - organization: this.props.component.organization + permission }) .then(this.stopLoading, () => { if (this.mounted) { @@ -282,8 +274,7 @@ export default class App extends React.PureComponent<Props, State> { .revokePermissionFromUser({ projectKey: this.props.component.key, login: user, - permission, - organization: this.props.component.organization + permission }) .then(this.stopLoading, () => { if (this.mounted) { @@ -297,16 +288,6 @@ export default class App extends React.PureComponent<Props, State> { return Promise.resolve(); }; - handleOrganizationUpgrade = () => { - const { component, organization } = this.props; - if (organization) { - this.props.onComponentChange({ - configuration: { ...component.configuration, canUpdateProjectVisibilityToPrivate: true } - }); - this.props.fetchOrganization(organization.key); - } - }; - handleVisibilityChange = (visibility: string) => { if (visibility === 'public') { this.openDisclaimer(); @@ -356,19 +337,10 @@ export default class App extends React.PureComponent<Props, State> { }; render() { - const { component, organization } = this.props; + const { component } = this.props; const canTurnToPrivate = component.configuration && component.configuration.canUpdateProjectVisibilityToPrivate; - let showUpgradeBox; - if (organization && !canTurnToPrivate) { - const isOrgAdmin = organization.actions && organization.actions.admin; - showUpgradeBox = - isOrgAdmin && - this.props.component.qualifier === 'TRK' && - !organization.canUpdateProjectsVisibilityToPrivate; - } - return ( <div className="page page-limited" id="project-permissions-page"> <Helmet defer={false} title={translate('permissions.page')} /> @@ -385,13 +357,6 @@ export default class App extends React.PureComponent<Props, State> { onChange={this.handleVisibilityChange} visibility={component.visibility} /> - {showUpgradeBox && organization && ( - <UpgradeOrganizationBox - className="big-spacer-bottom" - onOrganizationUpgrade={this.handleOrganizationUpgrade} - organization={organization} - /> - )} {this.state.disclaimer && ( <PublicProjectDisclaimer component={component} diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts b/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts index e69f476a356..83c89b028a5 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts @@ -18,20 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { connect } from 'react-redux'; -import { fetchOrganization } from '../../../../store/rootActions'; -import { getCurrentUser, getOrganizationByKey, Store } from '../../../../store/rootReducer'; +import { getCurrentUser, Store } from '../../../../store/rootReducer'; import App from './App'; -interface OwnProps { - component: T.Component; - onComponentChange: (changes: Partial<T.Component>) => void; -} - -const mapStateToProps = (state: Store, { component }: OwnProps) => ({ - currentUser: getCurrentUser(state), - organization: getOrganizationByKey(state, component.organization) +const mapStateToProps = (state: Store) => ({ + currentUser: getCurrentUser(state) }); -const mapDispatchToProps = { fetchOrganization }; - -export default connect(mapStateToProps, mapDispatchToProps)(App); +export default connect(mapStateToProps)(App); diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx index 9e88b16affe..70e03bfd265 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/App-test.tsx @@ -26,7 +26,7 @@ import { revokePermissionFromGroup, revokePermissionFromUser } from '../../../../../api/permissions'; -import { mockComponent, mockOrganization } from '../../../../../helpers/testMocks'; +import { mockComponent } from '../../../../../helpers/testMocks'; import App from '../App'; jest.mock('../../../../../api/permissions', () => ({ @@ -98,8 +98,7 @@ describe('should manage state correctly', () => { const apiPayload = { projectKey: 'my-project', groupName: 'Anyone', - permission: 'foo', - organization: 'foo' + permission: 'foo' }; instance.grantPermissionToGroup('Anyone', 'foo'); @@ -132,8 +131,7 @@ describe('should manage state correctly', () => { const apiPayload = { projectKey: 'my-project', login: 'user1', - permission: 'foo', - organization: 'foo' + permission: 'foo' }; instance.grantPermissionToUser('user1', 'foo'); @@ -150,13 +148,5 @@ describe('should manage state correctly', () => { }); function shallowRender(props: Partial<App['props']> = {}) { - return shallow<App>( - <App - component={mockComponent()} - fetchOrganization={jest.fn()} - onComponentChange={jest.fn()} - organization={mockOrganization()} - {...props} - /> - ); + return shallow<App>(<App component={mockComponent()} onComponentChange={jest.fn()} {...props} />); } diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/Header.tsx b/server/sonar-web/src/main/js/apps/projectQualityGate/Header.tsx index 220d6be9e5c..d2fee01125e 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityGate/Header.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityGate/Header.tsx @@ -18,19 +18,21 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../components/docs/DocTooltip'; export default function Header() { return ( <header className="page-header"> <div className="page-title display-flex-center"> <h1>{translate('project_quality_gate.page')}</h1> - <DocTooltip + <HelpTooltip className="spacer-left" - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/quality-gate-projects.md' - )} + overlay={ + <div className="big-padded-top big-padded-bottom"> + {translate('quality_gates.projects.help')} + </div> + } /> </div> <div className="page-description">{translate('project_quality_gate.page.description')}</div> diff --git a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/Header-test.tsx.snap index bcc91125263..234232fc945 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/Header-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectQualityGate/__tests__/__snapshots__/Header-test.tsx.snap @@ -10,9 +10,15 @@ exports[`renders 1`] = ` <h1> project_quality_gate.page </h1> - <DocTooltip + <HelpTooltip className="spacer-left" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + quality_gates.projects.help + </div> + } /> </div> <div diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/Header.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/Header.tsx index 610686aec8d..10f8741af9b 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/Header.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/Header.tsx @@ -18,19 +18,21 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../components/docs/DocTooltip'; export default function Header() { return ( <header className="page-header"> <div className="page-title display-flex-center"> <h1>{translate('project_quality_profiles.page')}</h1> - <DocTooltip + <HelpTooltip className="spacer-left" - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/quality-profiles/quality-profile-projects.md' - )} + overlay={ + <div className="big-padded-top big-padded-bottom"> + {translate('quality_profiles.list.projects.help')} + </div> + } /> </div> <div className="page-description"> diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Header-test.tsx.snap index b59e95a99e2..6ab35969262 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Header-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/__tests__/__snapshots__/Header-test.tsx.snap @@ -10,9 +10,15 @@ exports[`renders 1`] = ` <h1> project_quality_profiles.page </h1> - <DocTooltip + <HelpTooltip className="spacer-left" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + quality_profiles.list.projects.help + </div> + } /> </div> <div diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx index 368695adb41..8d69cf678bf 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx @@ -27,7 +27,6 @@ import { translate } from 'sonar-ui-common/helpers/l10n'; import { createProject } from '../../api/components'; import VisibilitySelector from '../../components/common/VisibilitySelector'; import { getProjectUrl } from '../../helpers/urls'; -import UpgradeOrganizationBox from '../create/components/UpgradeOrganizationBox'; interface Props { onClose: () => void; @@ -196,18 +195,6 @@ export default class CreateProjectForm extends React.PureComponent<Props, State> visibility={this.state.visibility} /> </div> - {organization.actions && - organization.actions.admin && - !organization.canUpdateProjectsVisibilityToPrivate && ( - <div className="spacer-top"> - <UpgradeOrganizationBox - className="width-100" - insideModal={true} - onOrganizationUpgrade={this.props.onOrganizationUpgrade} - organization={organization} - /> - </div> - )} </div> <footer className="modal-foot"> diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap index 9879d2fd163..bf16aa754b7 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/CreateProjectForm-test.tsx.snap @@ -78,25 +78,6 @@ exports[`creates project 1`] = ` visibility="public" /> </div> - <div - className="spacer-top" - > - <UpgradeOrganizationBox - className="width-100" - insideModal={true} - onOrganizationUpgrade={[MockFunction]} - organization={ - Object { - "actions": Object { - "admin": true, - }, - "key": "org", - "name": "org", - "projectVisibility": "public", - } - } - /> - </div> </div> <footer className="modal-foot" @@ -196,25 +177,6 @@ exports[`creates project 2`] = ` visibility="private" /> </div> - <div - className="spacer-top" - > - <UpgradeOrganizationBox - className="width-100" - insideModal={true} - onOrganizationUpgrade={[MockFunction]} - organization={ - Object { - "actions": Object { - "admin": true, - }, - "key": "org", - "name": "org", - "projectVisibility": "public", - } - } - /> - </div> </div> <footer className="modal-foot" @@ -314,25 +276,6 @@ exports[`creates project 3`] = ` visibility="private" /> </div> - <div - className="spacer-top" - > - <UpgradeOrganizationBox - className="width-100" - insideModal={true} - onOrganizationUpgrade={[MockFunction]} - organization={ - Object { - "actions": Object { - "admin": true, - }, - "key": "org", - "name": "org", - "projectVisibility": "public", - } - } - /> - </div> </div> <footer className="modal-foot" diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx index b0a7b2a8c7f..b83d4572a43 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/BuiltInQualityGateBadge.tsx @@ -19,8 +19,8 @@ */ import * as classNames from 'classnames'; import * as React from 'react'; +import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../../components/docs/DocTooltip'; interface Props { className?: string; @@ -28,11 +28,8 @@ interface Props { export default function BuiltInQualityGateBadge({ className }: Props) { return ( - <DocTooltip - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/built-in-quality-gate.md' - )}> + <Tooltip overlay={translate('quality_gates.built_in.help')}> <div className={classNames('badge', className)}>{translate('quality_gates.built_in')}</div> - </DocTooltip> + </Tooltip> ); } diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx index b7385ef07f7..d90c53ebe2a 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/Conditions.tsx @@ -24,7 +24,7 @@ import ModalButton from 'sonar-ui-common/components/controls/ModalButton'; import { Alert } from 'sonar-ui-common/components/ui/Alert'; import { getLocalizedMetricName, translate } from 'sonar-ui-common/helpers/l10n'; import { isDiffMetric } from 'sonar-ui-common/helpers/measures'; -import DocTooltip from '../../../components/docs/DocTooltip'; +import DocumentationTooltip from '../../../components/common/DocumentationTooltip'; import { withAppState } from '../../../components/hoc/withAppState'; import { MetricKey } from '../../../types/metrics'; import Condition from './Condition'; @@ -165,11 +165,15 @@ export class Conditions extends React.PureComponent<Props> { <header className="display-flex-center spacer-bottom"> <h3>{translate('quality_gates.conditions')}</h3> - <DocTooltip + <DocumentationTooltip className="spacer-left" - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/quality-gate-conditions.md' - )} + content={translate('quality_gates.conditions.help')} + links={[ + { + href: '/documentation/user-guide/clean-as-you-code/', + label: translate('quality_gates.conditions.help.link') + } + ]} /> </header> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx index 1c45dfad50a..5966b0cb1bc 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsContent.tsx @@ -18,8 +18,8 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../../components/docs/DocTooltip'; import Conditions from './Conditions'; import Projects from './Projects'; @@ -56,11 +56,13 @@ export function DetailsContent(props: DetailsContentProps) { <div className="quality-gate-section" id="quality-gate-projects"> <header className="display-flex-center spacer-bottom"> <h3>{translate('quality_gates.projects')}</h3> - <DocTooltip + <HelpTooltip className="spacer-left" - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/quality-gate-projects.md' - )} + overlay={ + <div className="big-padded-top big-padded-bottom"> + {translate('quality_gates.projects.help')} + </div> + } /> </header> {isDefault ? ( diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx index f9858b45947..8fc6c24c0b3 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/ListHeader.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { Button } from 'sonar-ui-common/components/controls/buttons'; import ModalButton from 'sonar-ui-common/components/controls/ModalButton'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../../components/docs/DocTooltip'; +import DocumentationTooltip from '../../../components/common/DocumentationTooltip'; import CreateQualityGateForm from './CreateQualityGateForm'; interface Props { @@ -54,9 +54,15 @@ export default function ListHeader({ canCreate, refreshQualityGates, organizatio <div className="display-flex-center"> <h1 className="page-title">{translate('quality_gates.page')}</h1> - <DocTooltip + <DocumentationTooltip className="spacer-left" - doc={import(/* webpackMode: "eager" */ 'Docs/tooltips/quality-gates/quality-gate.md')} + content={translate('quality_gates.help')} + links={[ + { + href: '/documentation/user-guide/quality-gates/', + label: translate('learn_more') + } + ]} /> </div> </header> diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/BuiltInQualityGateBadge-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/BuiltInQualityGateBadge-test.tsx.snap index 76f55d531f8..04bf352dbe8 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/BuiltInQualityGateBadge-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/BuiltInQualityGateBadge-test.tsx.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should render correctly 1`] = ` -<DocTooltip - doc={Promise {}} +<Tooltip + overlay="quality_gates.built_in.help" > <div className="badge" > quality_gates.built_in </div> -</DocTooltip> +</Tooltip> `; diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap index 14bf9466228..cb7ff83d304 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/Conditions-test.tsx.snap @@ -10,9 +10,17 @@ exports[`should render correctly 1`] = ` <h3> quality_gates.conditions </h3> - <DocTooltip + <DocumentationTooltip className="spacer-left" - doc={Promise {}} + content="quality_gates.conditions.help" + links={ + Array [ + Object { + "href": "/documentation/user-guide/clean-as-you-code/", + "label": "quality_gates.conditions.help.link", + }, + ] + } /> </header> <div @@ -129,9 +137,17 @@ exports[`should render correctly for no conditions 1`] = ` <h3> quality_gates.conditions </h3> - <DocTooltip + <DocumentationTooltip className="spacer-left" - doc={Promise {}} + content="quality_gates.conditions.help" + links={ + Array [ + Object { + "href": "/documentation/user-guide/clean-as-you-code/", + "label": "quality_gates.conditions.help.link", + }, + ] + } /> </header> <div @@ -152,9 +168,17 @@ exports[`should render correctly with an updated condition 1`] = ` <h3> quality_gates.conditions </h3> - <DocTooltip + <DocumentationTooltip className="spacer-left" - doc={Promise {}} + content="quality_gates.conditions.help" + links={ + Array [ + Object { + "href": "/documentation/user-guide/clean-as-you-code/", + "label": "quality_gates.conditions.help.link", + }, + ] + } /> </header> <div @@ -271,9 +295,17 @@ exports[`should render correctly with new code conditions 1`] = ` <h3> quality_gates.conditions </h3> - <DocTooltip + <DocumentationTooltip className="spacer-left" - doc={Promise {}} + content="quality_gates.conditions.help" + links={ + Array [ + Object { + "href": "/documentation/user-guide/clean-as-you-code/", + "label": "quality_gates.conditions.help.link", + }, + ] + } /> </header> <div @@ -500,9 +532,17 @@ exports[`should render the add conditions button and modal 1`] = ` <h3> quality_gates.conditions </h3> - <DocTooltip + <DocumentationTooltip className="spacer-left" - doc={Promise {}} + content="quality_gates.conditions.help" + links={ + Array [ + Object { + "href": "/documentation/user-guide/clean-as-you-code/", + "label": "quality_gates.conditions.help.link", + }, + ] + } /> </header> <div diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap index c98a62ada70..59ba0ebfd65 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/DetailsContent-test.tsx.snap @@ -27,9 +27,15 @@ exports[`should render correctly: is default 1`] = ` <h3> quality_gates.projects </h3> - <DocTooltip + <HelpTooltip className="spacer-left" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + quality_gates.projects.help + </div> + } /> </header> quality_gates.projects_for_default @@ -64,9 +70,15 @@ exports[`should render correctly: is not default 1`] = ` <h3> quality_gates.projects </h3> - <DocTooltip + <HelpTooltip className="spacer-left" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + quality_gates.projects.help + </div> + } /> </header> <Projects diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap index f12ac9a8ce6..0913eca851b 100644 --- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/__snapshots__/ListHeader-test.tsx.snap @@ -12,9 +12,17 @@ exports[`should render correctly 1`] = ` > quality_gates.page </h1> - <DocTooltip + <DocumentationTooltip className="spacer-left" - doc={Promise {}} + content="quality_gates.help" + links={ + Array [ + Object { + "href": "/documentation/user-guide/quality-gates/", + "label": "learn_more", + }, + ] + } /> </div> </header> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx index 6d9faab7932..5119f24816d 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/details/ProfileRules.tsx @@ -21,10 +21,10 @@ import { keyBy } from 'lodash'; import * as React from 'react'; import { Link } from 'react-router'; import { Button } from 'sonar-ui-common/components/controls/buttons'; +import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; import { translate } from 'sonar-ui-common/helpers/l10n'; import { getQualityProfile } from '../../../api/quality-profiles'; import { searchRules, takeFacet } from '../../../api/rules'; -import DocTooltip from '../../../components/docs/DocTooltip'; import { getRulesUrl } from '../../../helpers/urls'; import { Profile } from '../types'; import ProfileRulesDeprecatedWarning from './ProfileRulesDeprecatedWarning'; @@ -197,14 +197,11 @@ export default class ProfileRules extends React.PureComponent<Props, State> { {/* in such cases it's better to show the button but disable it with a tooltip */} {actions.copy && profile.isBuiltIn && ( <div className="text-right big-spacer-top"> - <DocTooltip - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/quality-profiles/activate-rules-in-built-in-profile.md' - )}> + <Tooltip overlay={translate('quality_profiles.activate_more.help.built_in')}> <Button className="disabled js-activate-rules"> {translate('quality_profiles.activate_more')} </Button> - </DocTooltip> + </Tooltip> </div> )} </div> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx index ba36aed88ab..f6f39ea33f9 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesList.tsx @@ -20,9 +20,9 @@ import { Location } from 'history'; import { groupBy, pick, sortBy } from 'lodash'; import * as React from 'react'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; import { Alert } from 'sonar-ui-common/components/ui/Alert'; import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../../components/docs/DocTooltip'; import { Profile } from '../types'; import ProfilesListHeader from './ProfilesListHeader'; import ProfilesListRow from './ProfilesListRow'; @@ -64,11 +64,13 @@ export default class ProfilesList extends React.PureComponent<Props> { </th> <th className="text-right nowrap"> {translate('quality_profiles.list.projects')} - <DocTooltip + <HelpTooltip className="table-cell-doc" - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/quality-profiles/quality-profile-projects.md' - )} + overlay={ + <div className="big-padded-top big-padded-bottom"> + {translate('quality_profiles.list.projects.help')} + </div> + } /> </th> <th className="text-right nowrap">{translate('quality_profiles.list.rules')}</th> diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx index fc2cb985b35..3ea27dcec1f 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/ProfilesListRow.tsx @@ -22,7 +22,6 @@ import { Link } from 'react-router'; import Tooltip from 'sonar-ui-common/components/controls/Tooltip'; import DateFromNow from 'sonar-ui-common/components/intl/DateFromNow'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import DocTooltip from '../../../components/docs/DocTooltip'; import { getRulesUrl } from '../../../helpers/urls'; import BuiltInQualityProfileBadge from '../components/BuiltInQualityProfileBadge'; import ProfileActions from '../components/ProfileActions'; @@ -76,12 +75,9 @@ export function ProfilesListRow(props: ProfilesListRowProps) { <td className="quality-profiles-table-projects thin nowrap text-middle text-right"> {profile.isDefault ? ( - <DocTooltip - doc={import( - /* webpackMode: "eager" */ 'Docs/tooltips/quality-profiles/default-quality-profile.md' - )}> + <Tooltip overlay={translate('quality_profiles.list.default.help')}> <span className="badge">{translate('default')}</span> - </DocTooltip> + </Tooltip> ) : ( <span>{profile.projectCount}</span> )} diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesList-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesList-test.tsx.snap index 23e63a5ccbe..e374ba8697e 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesList-test.tsx.snap @@ -36,9 +36,15 @@ exports[`should render correctly 1`] = ` className="text-right nowrap" > quality_profiles.list.projects - <DocTooltip + <HelpTooltip className="table-cell-doc" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + quality_profiles.list.projects.help + </div> + } /> </th> <th @@ -106,9 +112,15 @@ exports[`should render correctly 1`] = ` className="text-right nowrap" > quality_profiles.list.projects - <DocTooltip + <HelpTooltip className="table-cell-doc" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + quality_profiles.list.projects.help + </div> + } /> </th> <th @@ -197,9 +209,15 @@ exports[`should render correctly 2`] = ` className="text-right nowrap" > quality_profiles.list.projects - <DocTooltip + <HelpTooltip className="table-cell-doc" - doc={Promise {}} + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + quality_profiles.list.projects.help + </div> + } /> </th> <th diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap index f4761ea4483..9d4c7e133e0 100644 --- a/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/quality-profiles/home/__tests__/__snapshots__/ProfilesListRow-test.tsx.snap @@ -249,15 +249,15 @@ exports[`should render correctly: default profile 1`] = ` <td className="quality-profiles-table-projects thin nowrap text-middle text-right" > - <DocTooltip - doc={Promise {}} + <Tooltip + overlay="quality_profiles.list.default.help" > <span className="badge" > default </span> - </DocTooltip> + </Tooltip> </td> <td className="quality-profiles-table-rules thin nowrap text-middle text-right" diff --git a/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx b/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx new file mode 100644 index 00000000000..c2ede9b95f1 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx @@ -0,0 +1,72 @@ +/* + * SonarQube + * Copyright (C) 2009-2020 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; +import DetachIcon from 'sonar-ui-common/components/icons/DetachIcon'; +import { isWebUri } from 'valid-url'; + +export interface DocumentationTooltipProps { + children?: React.ReactNode; + className?: string; + content?: React.ReactNode; + links?: Array<{ href: string; label: string }>; + title?: string; +} + +export default function DocumentationTooltip(props: DocumentationTooltipProps) { + const { className, content, links, title } = props; + + return ( + <HelpTooltip + className={className} + overlay={ + <div className="big-padded-top big-padded-bottom"> + {title && ( + <div className="spacer-bottom"> + <strong>{title}</strong> + </div> + )} + + {content && <p>{content}</p>} + + {links && ( + <> + <hr className="big-spacer-top big-spacer-bottom" /> + + {links.map(({ href, label }) => ( + <div className="little-spacer-bottom" key={label}> + <a + className="display-inline-flex-center link-with-icon" + href={href} + rel="noopener noreferrer" + target="_blank"> + {isWebUri(href) && <DetachIcon size={14} className="spacer-right" />} + <span>{label}</span> + </a> + </div> + ))} + </> + )} + </div> + }> + {props.children} + </HelpTooltip> + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/FreeCardPlan-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx index f3bcba95619..fc7ae766eb8 100644 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/FreeCardPlan-test.tsx +++ b/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx @@ -19,20 +19,22 @@ */ import { shallow } from 'enzyme'; import * as React from 'react'; -import FreeCardPlan from '../FreeCardPlan'; +import DocumentationTooltip, { DocumentationTooltipProps } from '../DocumentationTooltip'; -it('should render', () => { - expect(shallow(<FreeCardPlan hasWarning={false} />)).toMatchSnapshot(); -}); - -it('should render with warning', () => { +it('renders correctly', () => { + expect(shallowRender()).toMatchSnapshot('basic'); expect( - shallow(<FreeCardPlan almName="GitHub" hasWarning={true} selected={true} />) - ).toMatchSnapshot(); + shallowRender({ + links: [ + { href: 'http://link.tosome.place', label: 'external link' }, + { href: '/documentation/guide', label: 'internal link' } + ] + }) + ).toMatchSnapshot('with links'); + expect(shallowRender({ title: undefined })).toMatchSnapshot('no title'); + expect(shallowRender({ content: undefined })).toMatchSnapshot('no content'); }); -it('should render disabled with info', () => { - expect( - shallow(<FreeCardPlan almName="GitHub" disabled={true} hasWarning={false} />) - ).toMatchSnapshot(); -}); +function shallowRender(props: Partial<DocumentationTooltipProps> = {}) { + return shallow(<DocumentationTooltip content="content" title="title" {...props} />); +} diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap new file mode 100644 index 00000000000..423376fa2ce --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/DocumentationTooltip-test.tsx.snap @@ -0,0 +1,112 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly: basic 1`] = ` +<HelpTooltip + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + <div + className="spacer-bottom" + > + <strong> + title + </strong> + </div> + <p> + content + </p> + </div> + } +/> +`; + +exports[`renders correctly: no content 1`] = ` +<HelpTooltip + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + <div + className="spacer-bottom" + > + <strong> + title + </strong> + </div> + </div> + } +/> +`; + +exports[`renders correctly: no title 1`] = ` +<HelpTooltip + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + <p> + content + </p> + </div> + } +/> +`; + +exports[`renders correctly: with links 1`] = ` +<HelpTooltip + overlay={ + <div + className="big-padded-top big-padded-bottom" + > + <div + className="spacer-bottom" + > + <strong> + title + </strong> + </div> + <p> + content + </p> + <React.Fragment> + <hr + className="big-spacer-top big-spacer-bottom" + /> + <div + className="little-spacer-bottom" + > + <a + className="display-inline-flex-center link-with-icon" + href="http://link.tosome.place" + rel="noopener noreferrer" + target="_blank" + > + <DetachIcon + className="spacer-right" + size={14} + /> + <span> + external link + </span> + </a> + </div> + <div + className="little-spacer-bottom" + > + <a + className="display-inline-flex-center link-with-icon" + href="/documentation/guide" + rel="noopener noreferrer" + target="_blank" + > + <span> + internal link + </span> + </a> + </div> + </React.Fragment> + </div> + } +/> +`; diff --git a/server/sonar-web/src/main/js/components/docs/DocTooltip.tsx b/server/sonar-web/src/main/js/components/docs/DocTooltip.tsx deleted file mode 100644 index d3f096a8957..00000000000 --- a/server/sonar-web/src/main/js/components/docs/DocTooltip.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; -import { lazyLoadComponent } from 'sonar-ui-common/components/lazyLoadComponent'; -import { filterContent } from '../../helpers/markdown'; - -const DocMarkdownBlock = lazyLoadComponent(() => import('./DocMarkdownBlock'), 'DocMarkdownBlock'); - -interface Props { - className?: string; - children?: React.ReactNode; - // Use as `import(/* webpackMode: "eager" */ 'Docs/tooltips/foo/bar.md')` - doc: Promise<{ default: string }>; - overlayProps?: T.Dict<string>; -} - -interface State { - content?: string; -} - -export default class DocTooltip extends React.PureComponent<Props, State> { - mounted = false; - state: State = {}; - - componentDidMount() { - this.mounted = true; - this.props.doc.then( - ({ default: content }) => { - if (this.mounted) { - this.setState({ content }); - } - }, - () => {} - ); - } - - componentWillUnmount() { - this.mounted = false; - } - - render() { - return this.state.content ? ( - <HelpTooltip - className={this.props.className} - overlay={ - <div className="abs-width-300"> - <DocMarkdownBlock - childProps={this.props.overlayProps} - className="cut-margins" - content={filterContent(this.state.content)} - isTooltip={true} - /> - </div> - }> - {this.props.children} - </HelpTooltip> - ) : ( - this.props.children || null - ); - } -} diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltip-test.tsx b/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltip-test.tsx deleted file mode 100644 index 36f6b8ada06..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/DocTooltip-test.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2020 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import DocTooltip from '../DocTooltip'; - -it('should render', async () => { - const wrapper = shallow(<DocTooltip doc={Promise.resolve({ default: 'this is *bold* text' })} />); - expect(wrapper).toMatchSnapshot(); - await waitAndUpdate(wrapper); - expect(wrapper).toMatchSnapshot(); -}); diff --git a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltip-test.tsx.snap b/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltip-test.tsx.snap deleted file mode 100644 index cb2fe21b9d9..00000000000 --- a/server/sonar-web/src/main/js/components/docs/__tests__/__snapshots__/DocTooltip-test.tsx.snap +++ /dev/null @@ -1,19 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render 1`] = `""`; - -exports[`should render 2`] = ` -<HelpTooltip - overlay={ - <div - className="abs-width-300" - > - <DocMarkdownBlock - className="cut-margins" - content="this is *bold* text" - isTooltip={true} - /> - </div> - } -/> -`; |