diff options
41 files changed, 868 insertions, 691 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/NewCodePeriodsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/NewCodePeriodsServiceMock.ts index 042d78ad54a..43a20156098 100644 --- a/server/sonar-web/src/main/js/api/mocks/NewCodePeriodsServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/NewCodePeriodsServiceMock.ts @@ -93,6 +93,10 @@ export default class NewCodePeriodsServiceMock { return this.reply({ newCodePeriods: this.#listBranchesNewCode }); }; + setNewCodePeriod = (newCodePeriod: NewCodePeriod) => { + this.#newCodePeriod = newCodePeriod; + }; + reset = () => { this.#newCodePeriod = cloneDeep(this.#defaultNewCodePeriod); this.#listBranchesNewCode = cloneDeep(this.#defaultListBranchesNewCode); diff --git a/server/sonar-web/src/main/js/apps/create/project/AzurePersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/Azure/AzurePersonalAccessTokenForm.tsx index 24a81c34506..ba4c2a86cde 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AzurePersonalAccessTokenForm.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Azure/AzurePersonalAccessTokenForm.tsx @@ -20,13 +20,13 @@ import classNames from 'classnames'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../components/common/Link'; -import { SubmitButton } from '../../../components/controls/buttons'; -import ValidationInput from '../../../components/controls/ValidationInput'; -import { Alert } from '../../../components/ui/Alert'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate } from '../../../helpers/l10n'; -import { AlmSettingsInstance } from '../../../types/alm-settings'; +import Link from '../../../../components/common/Link'; +import ValidationInput from '../../../../components/controls/ValidationInput'; +import { SubmitButton } from '../../../../components/controls/buttons'; +import { Alert } from '../../../../components/ui/Alert'; +import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; +import { translate } from '../../../../helpers/l10n'; +import { AlmSettingsInstance } from '../../../../types/alm-settings'; export interface AzurePersonalAccessTokenFormProps { almSetting: AlmSettingsInstance; diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectAccordion.tsx b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectAccordion.tsx index 0d96324e76b..bf202803792 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectAccordion.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectAccordion.tsx @@ -20,18 +20,18 @@ import classNames from 'classnames'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import { colors } from '../../../app/theme'; -import Link from '../../../components/common/Link'; -import BoxedGroupAccordion from '../../../components/controls/BoxedGroupAccordion'; -import ListFooter from '../../../components/controls/ListFooter'; -import Radio from '../../../components/controls/Radio'; -import CheckIcon from '../../../components/icons/CheckIcon'; -import { Alert } from '../../../components/ui/Alert'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate } from '../../../helpers/l10n'; -import { getProjectUrl, queryToSearch } from '../../../helpers/urls'; -import { AzureProject, AzureRepository } from '../../../types/alm-integration'; -import { CreateProjectModes } from './types'; +import { colors } from '../../../../app/theme'; +import Link from '../../../../components/common/Link'; +import BoxedGroupAccordion from '../../../../components/controls/BoxedGroupAccordion'; +import ListFooter from '../../../../components/controls/ListFooter'; +import Radio from '../../../../components/controls/Radio'; +import CheckIcon from '../../../../components/icons/CheckIcon'; +import { Alert } from '../../../../components/ui/Alert'; +import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; +import { translate } from '../../../../helpers/l10n'; +import { getProjectUrl, queryToSearch } from '../../../../helpers/urls'; +import { AzureProject, AzureRepository } from '../../../../types/alm-integration'; +import { CreateProjectModes } from '../types'; export interface AzureProjectAccordionProps { importing: boolean; diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreate.tsx index 94a98a331ed..8c8fb3fa673 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreate.tsx @@ -25,13 +25,13 @@ import { importAzureRepository, searchAzureRepositories, setAlmPersonalAccessToken, -} from '../../../api/alm-integrations'; -import { Location, Router } from '../../../components/hoc/withRouter'; -import { AzureProject, AzureRepository } from '../../../types/alm-integration'; -import { AlmSettingsInstance } from '../../../types/alm-settings'; -import { Dict } from '../../../types/types'; +} from '../../../../api/alm-integrations'; +import { Location, Router } from '../../../../components/hoc/withRouter'; +import { AzureProject, AzureRepository } from '../../../../types/alm-integration'; +import { AlmSettingsInstance } from '../../../../types/alm-settings'; +import { Dict } from '../../../../types/types'; +import { tokenExistedBefore } from '../utils'; import AzureCreateProjectRenderer from './AzureProjectCreateRenderer'; -import { tokenExistedBefore } from './utils'; interface Props { canAdmin: boolean; diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreateRenderer.tsx index d3ad3e456b6..cbb051484bc 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectCreateRenderer.tsx @@ -19,23 +19,24 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../components/common/Link'; -import { Button } from '../../../components/controls/buttons'; -import SearchBox from '../../../components/controls/SearchBox'; -import { Alert } from '../../../components/ui/Alert'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; -import { getGlobalSettingsUrl } from '../../../helpers/urls'; -import { AzureProject, AzureRepository } from '../../../types/alm-integration'; -import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; -import { Dict } from '../../../types/types'; -import { ALM_INTEGRATION_CATEGORY } from '../../settings/constants'; -import AlmSettingsInstanceDropdown from './AlmSettingsInstanceDropdown'; +import Link from '../../../../components/common/Link'; +import SearchBox from '../../../../components/controls/SearchBox'; +import { Button } from '../../../../components/controls/buttons'; +import { Alert } from '../../../../components/ui/Alert'; +import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; +import { translate } from '../../../../helpers/l10n'; +import { getBaseUrl } from '../../../../helpers/system'; +import { getGlobalSettingsUrl } from '../../../../helpers/urls'; +import { AzureProject, AzureRepository } from '../../../../types/alm-integration'; +import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; +import { Dict } from '../../../../types/types'; +import { ALM_INTEGRATION_CATEGORY } from '../../../settings/constants'; +import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown'; +import CreateProjectPageHeader from '../components/CreateProjectPageHeader'; +import InstanceNewCodeDefinitionComplianceWarning from '../components/InstanceNewCodeDefinitionComplianceWarning'; +import WrongBindingCountAlert from '../components/WrongBindingCountAlert'; import AzurePersonalAccessTokenForm from './AzurePersonalAccessTokenForm'; import AzureProjectsList from './AzureProjectsList'; -import CreateProjectPageHeader from './CreateProjectPageHeader'; -import WrongBindingCountAlert from './WrongBindingCountAlert'; export interface AzureProjectCreateRendererProps { canAdmin?: boolean; @@ -164,6 +165,8 @@ export default function AzureProjectCreateRenderer(props: AzureProjectCreateRend </div> ) : ( <> + <InstanceNewCodeDefinitionComplianceWarning /> + <div className="huge-spacer-bottom"> <SearchBox onChange={props.onSearch} diff --git a/server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectsList.tsx index 8fa25a3cfad..a393fe25c5c 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AzureProjectsList.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Azure/AzureProjectsList.tsx @@ -20,15 +20,15 @@ import { uniqBy } from 'lodash'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../components/common/Link'; -import ListFooter from '../../../components/controls/ListFooter'; -import { Alert } from '../../../components/ui/Alert'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { queryToSearch } from '../../../helpers/urls'; -import { AzureProject, AzureRepository } from '../../../types/alm-integration'; -import { Dict } from '../../../types/types'; +import Link from '../../../../components/common/Link'; +import ListFooter from '../../../../components/controls/ListFooter'; +import { Alert } from '../../../../components/ui/Alert'; +import { translate, translateWithParameters } from '../../../../helpers/l10n'; +import { queryToSearch } from '../../../../helpers/urls'; +import { AzureProject, AzureRepository } from '../../../../types/alm-integration'; +import { Dict } from '../../../../types/types'; +import { CreateProjectModes } from '../types'; import AzureProjectAccordion from './AzureProjectAccordion'; -import { CreateProjectModes } from './types'; export interface AzureProjectsListProps { importing: boolean; diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreate.tsx index 9f7fe3b8742..dd4bf7a1c96 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreate.tsx @@ -21,11 +21,11 @@ import * as React from 'react'; import { importBitbucketCloudRepository, searchForBitbucketCloudRepositories, -} from '../../../api/alm-integrations'; -import { Location, Router } from '../../../components/hoc/withRouter'; -import { BitbucketCloudRepository } from '../../../types/alm-integration'; -import { AlmSettingsInstance } from '../../../types/alm-settings'; -import { Paging } from '../../../types/types'; +} from '../../../../api/alm-integrations'; +import { Location, Router } from '../../../../components/hoc/withRouter'; +import { BitbucketCloudRepository } from '../../../../types/alm-integration'; +import { AlmSettingsInstance } from '../../../../types/alm-settings'; +import { Paging } from '../../../../types/types'; import BitbucketCloudProjectCreateRenderer from './BitbucketCloudProjectCreateRender'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreateRender.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx index fcd4d48e483..28356e43449 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudProjectCreateRender.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudProjectCreateRender.tsx @@ -18,15 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; -import { BitbucketCloudRepository } from '../../../types/alm-integration'; -import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; -import AlmSettingsInstanceDropdown from './AlmSettingsInstanceDropdown'; +import { translate } from '../../../../helpers/l10n'; +import { getBaseUrl } from '../../../../helpers/system'; +import { BitbucketCloudRepository } from '../../../../types/alm-integration'; +import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; +import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown'; +import CreateProjectPageHeader from '../components/CreateProjectPageHeader'; +import PersonalAccessTokenForm from '../components/PersonalAccessTokenForm'; +import WrongBindingCountAlert from '../components/WrongBindingCountAlert'; import BitbucketCloudSearchForm from './BitbucketCloudSearchForm'; -import CreateProjectPageHeader from './CreateProjectPageHeader'; -import PersonalAccessTokenForm from './PersonalAccessTokenForm'; -import WrongBindingCountAlert from './WrongBindingCountAlert'; export interface BitbucketCloudProjectCreateRendererProps { importingSlug?: string; diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx new file mode 100644 index 00000000000..d52b70a176a --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloud/BitbucketCloudSearchForm.tsx @@ -0,0 +1,194 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 '../../../../components/common/Link'; +import SearchBox from '../../../../components/controls/SearchBox'; +import Tooltip from '../../../../components/controls/Tooltip'; +import { Button } from '../../../../components/controls/buttons'; +import CheckIcon from '../../../../components/icons/CheckIcon'; +import QualifierIcon from '../../../../components/icons/QualifierIcon'; +import { Alert } from '../../../../components/ui/Alert'; +import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; +import { translate, translateWithParameters } from '../../../../helpers/l10n'; +import { formatMeasure } from '../../../../helpers/measures'; +import { getProjectUrl, queryToSearch } from '../../../../helpers/urls'; +import { BitbucketCloudRepository } from '../../../../types/alm-integration'; +import { ComponentQualifier } from '../../../../types/component'; +import { MetricType } from '../../../../types/metrics'; +import InstanceNewCodeDefinitionComplianceWarning from '../components/InstanceNewCodeDefinitionComplianceWarning'; +import { CreateProjectModes } from '../types'; + +export interface BitbucketCloudSearchFormProps { + importingSlug?: string; + isLastPage: boolean; + loadingMore: boolean; + onImport: (repositorySlug: string) => void; + onLoadMore: () => void; + onSearch: (searchQuery: string) => void; + repositories?: BitbucketCloudRepository[]; + searching: boolean; + searchQuery: string; +} + +function getRepositoryUrl(workspace: string, slug: string) { + return `https://bitbucket.org/${workspace}/${slug}`; +} + +export default function BitbucketCloudSearchForm(props: BitbucketCloudSearchFormProps) { + const { + importingSlug, + isLastPage, + loadingMore, + repositories = [], + searching, + searchQuery, + } = props; + + if (repositories.length === 0 && searchQuery.length === 0 && !searching) { + return ( + <Alert className="spacer-top" variant="warning"> + <FormattedMessage + defaultMessage={translate('onboarding.create_project.bitbucketcloud.no_projects')} + id="onboarding.create_project.bitbucketcloud.no_projects" + values={{ + link: ( + <Link + to={{ + pathname: '/projects/create', + search: queryToSearch({ mode: CreateProjectModes.BitbucketCloud, resetPat: 1 }), + }} + > + {translate('onboarding.create_project.update_your_token')} + </Link> + ), + }} + /> + </Alert> + ); + } + + return ( + <> + <InstanceNewCodeDefinitionComplianceWarning /> + <div className="boxed-group big-padded create-project-import"> + <SearchBox + className="spacer" + loading={searching} + minLength={3} + onChange={props.onSearch} + placeholder={translate('onboarding.create_project.search_prompt')} + /> + + <hr /> + + {repositories.length === 0 ? ( + <div className="padded">{translate('no_results')}</div> + ) : ( + <table className="data zebra zebra-hover"> + <tbody> + {repositories.map((repository) => ( + <tr key={repository.uuid}> + <td> + <Tooltip overlay={repository.slug}> + <strong className="project-name display-inline-block text-ellipsis"> + {repository.sqProjectKey ? ( + <Link to={getProjectUrl(repository.sqProjectKey)}> + <QualifierIcon + className="spacer-right" + qualifier={ComponentQualifier.Project} + /> + {repository.name} + </Link> + ) : ( + repository.name + )} + </strong> + </Tooltip> + <br /> + <Tooltip overlay={repository.projectKey}> + <span className="text-muted project-path display-inline-block text-ellipsis"> + {repository.projectKey} + </span> + </Tooltip> + </td> + <td> + <Link + className="display-inline-flex-center big-spacer-right" + to={getRepositoryUrl(repository.workspace, repository.slug)} + target="_blank" + > + {translate('onboarding.create_project.bitbucketcloud.link')} + </Link> + </td> + {repository.sqProjectKey ? ( + <td> + <span className="display-flex-center display-flex-justify-end already-set-up"> + <CheckIcon className="little-spacer-right" size={12} /> + {translate('onboarding.create_project.repository_imported')} + </span> + </td> + ) : ( + <td className="text-right"> + <Button + disabled={Boolean(importingSlug)} + onClick={() => { + props.onImport(repository.slug); + }} + > + {translate('onboarding.create_project.set_up')} + <DeferredSpinner + className="spacer-left" + loading={importingSlug === repository.slug} + /> + </Button> + </td> + )} + </tr> + ))} + </tbody> + </table> + )} + <footer className="spacer-top note text-center"> + {isLastPage && + translateWithParameters( + 'x_of_y_shown', + formatMeasure(repositories.length, MetricType.Integer, null), + formatMeasure(repositories.length, MetricType.Integer, null) + )} + {!isLastPage && ( + <Button + className="spacer-left" + disabled={loadingMore} + data-test="show-more" + onClick={props.onLoadMore} + > + {translate('show_more')} + </Button> + )} + <DeferredSpinner + className="text-bottom spacer-left position-absolute" + loading={loadingMore} + /> + </footer> + </div> + </> + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx deleted file mode 100644 index 0e420e9864b..00000000000 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketCloudSearchForm.tsx +++ /dev/null @@ -1,185 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 '../../../components/common/Link'; -import { Button } from '../../../components/controls/buttons'; -import SearchBox from '../../../components/controls/SearchBox'; -import Tooltip from '../../../components/controls/Tooltip'; -import CheckIcon from '../../../components/icons/CheckIcon'; -import QualifierIcon from '../../../components/icons/QualifierIcon'; -import { Alert } from '../../../components/ui/Alert'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { formatMeasure } from '../../../helpers/measures'; -import { getProjectUrl, queryToSearch } from '../../../helpers/urls'; -import { BitbucketCloudRepository } from '../../../types/alm-integration'; -import { ComponentQualifier } from '../../../types/component'; -import { CreateProjectModes } from './types'; - -export interface BitbucketCloudSearchFormProps { - importingSlug?: string; - isLastPage: boolean; - loadingMore: boolean; - onImport: (repositorySlug: string) => void; - onLoadMore: () => void; - onSearch: (searchQuery: string) => void; - repositories?: BitbucketCloudRepository[]; - searching: boolean; - searchQuery: string; -} - -function getRepositoryUrl(workspace: string, slug: string) { - return `https://bitbucket.org/${workspace}/${slug}`; -} - -export default function BitbucketCloudSearchForm(props: BitbucketCloudSearchFormProps) { - const { - importingSlug, - isLastPage, - loadingMore, - repositories = [], - searching, - searchQuery, - } = props; - - if (repositories.length === 0 && searchQuery.length === 0 && !searching) { - return ( - <Alert className="spacer-top" variant="warning"> - <FormattedMessage - defaultMessage={translate('onboarding.create_project.bitbucketcloud.no_projects')} - id="onboarding.create_project.bitbucketcloud.no_projects" - values={{ - link: ( - <Link - to={{ - pathname: '/projects/create', - search: queryToSearch({ mode: CreateProjectModes.BitbucketCloud, resetPat: 1 }), - }} - > - {translate('onboarding.create_project.update_your_token')} - </Link> - ), - }} - /> - </Alert> - ); - } - - return ( - <div className="boxed-group big-padded create-project-import"> - <SearchBox - className="spacer" - loading={searching} - minLength={3} - onChange={props.onSearch} - placeholder={translate('onboarding.create_project.search_prompt')} - /> - - <hr /> - - {repositories.length === 0 ? ( - <div className="padded">{translate('no_results')}</div> - ) : ( - <table className="data zebra zebra-hover"> - <tbody> - {repositories.map((repository) => ( - <tr key={repository.uuid}> - <td> - <Tooltip overlay={repository.slug}> - <strong className="project-name display-inline-block text-ellipsis"> - {repository.sqProjectKey ? ( - <Link to={getProjectUrl(repository.sqProjectKey)}> - <QualifierIcon - className="spacer-right" - qualifier={ComponentQualifier.Project} - /> - {repository.name} - </Link> - ) : ( - repository.name - )} - </strong> - </Tooltip> - <br /> - <Tooltip overlay={repository.projectKey}> - <span className="text-muted project-path display-inline-block text-ellipsis"> - {repository.projectKey} - </span> - </Tooltip> - </td> - <td> - <Link - className="display-inline-flex-center big-spacer-right" - to={getRepositoryUrl(repository.workspace, repository.slug)} - target="_blank" - > - {translate('onboarding.create_project.bitbucketcloud.link')} - </Link> - </td> - {repository.sqProjectKey ? ( - <td> - <span className="display-flex-center display-flex-justify-end already-set-up"> - <CheckIcon className="little-spacer-right" size={12} /> - {translate('onboarding.create_project.repository_imported')} - </span> - </td> - ) : ( - <td className="text-right"> - <Button - disabled={Boolean(importingSlug)} - onClick={() => { - props.onImport(repository.slug); - }} - > - {translate('onboarding.create_project.set_up')} - {importingSlug === repository.slug && ( - <DeferredSpinner className="spacer-left" /> - )} - </Button> - </td> - )} - </tr> - ))} - </tbody> - </table> - )} - <footer className="spacer-top note text-center"> - {isLastPage && - translateWithParameters( - 'x_of_y_shown', - formatMeasure(repositories.length, 'INT', null), - formatMeasure(repositories.length, 'INT', null) - )} - {!isLastPage && ( - <Button - className="spacer-left" - disabled={loadingMore} - data-test="show-more" - onClick={props.onLoadMore} - > - {translate('show_more')} - </Button> - )} - {loadingMore && <DeferredSpinner className="text-bottom spacer-left position-absolute" />} - </footer> - </div> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketImportRepositoryForm.tsx index 6e00c7ece7c..8b22d219f30 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketImportRepositoryForm.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketImportRepositoryForm.tsx @@ -19,19 +19,20 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../components/common/Link'; -import SearchBox from '../../../components/controls/SearchBox'; -import { Alert } from '../../../components/ui/Alert'; -import { translate } from '../../../helpers/l10n'; -import { queryToSearch } from '../../../helpers/urls'; +import Link from '../../../../components/common/Link'; +import SearchBox from '../../../../components/controls/SearchBox'; +import { Alert } from '../../../../components/ui/Alert'; +import { translate } from '../../../../helpers/l10n'; +import { queryToSearch } from '../../../../helpers/urls'; import { BitbucketProject, BitbucketProjectRepositories, BitbucketRepository, -} from '../../../types/alm-integration'; +} from '../../../../types/alm-integration'; +import InstanceNewCodeDefinitionComplianceWarning from '../components/InstanceNewCodeDefinitionComplianceWarning'; +import { CreateProjectModes } from '../types'; import BitbucketRepositories from './BitbucketRepositories'; import BitbucketSearchResults from './BitbucketSearchResults'; -import { CreateProjectModes } from './types'; export interface BitbucketImportRepositoryFormProps { disableRepositories: boolean; @@ -79,6 +80,8 @@ export default function BitbucketImportRepositoryForm(props: BitbucketImportRepo return ( <div className="create-project-import-bbs"> + <InstanceNewCodeDefinitionComplianceWarning /> + <SearchBox onChange={props.onSearch} placeholder={translate('onboarding.create_project.search_repositories_by_name')} diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectAccordion.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectAccordion.tsx index 4eaa9943698..df14cad4b27 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectAccordion.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectAccordion.tsx @@ -20,16 +20,16 @@ import classNames from 'classnames'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import { colors } from '../../../app/theme'; -import Link from '../../../components/common/Link'; -import BoxedGroupAccordion from '../../../components/controls/BoxedGroupAccordion'; -import Radio from '../../../components/controls/Radio'; -import CheckIcon from '../../../components/icons/CheckIcon'; -import { Alert } from '../../../components/ui/Alert'; -import { translate, translateWithParameters } from '../../../helpers/l10n'; -import { getProjectUrl, queryToSearch } from '../../../helpers/urls'; -import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration'; -import { CreateProjectModes } from './types'; +import { colors } from '../../../../app/theme'; +import Link from '../../../../components/common/Link'; +import BoxedGroupAccordion from '../../../../components/controls/BoxedGroupAccordion'; +import Radio from '../../../../components/controls/Radio'; +import CheckIcon from '../../../../components/icons/CheckIcon'; +import { Alert } from '../../../../components/ui/Alert'; +import { translate, translateWithParameters } from '../../../../helpers/l10n'; +import { getProjectUrl, queryToSearch } from '../../../../helpers/urls'; +import { BitbucketProject, BitbucketRepository } from '../../../../types/alm-integration'; +import { CreateProjectModes } from '../types'; export interface BitbucketProjectAccordionProps { disableRepositories: boolean; diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectCreate.tsx index 7719f31f5df..af60cb12b31 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectCreate.tsx @@ -23,16 +23,16 @@ import { getBitbucketServerRepositories, importBitbucketServerProject, searchForBitbucketServerRepositories, -} from '../../../api/alm-integrations'; -import { Location, Router } from '../../../components/hoc/withRouter'; +} from '../../../../api/alm-integrations'; +import { Location, Router } from '../../../../components/hoc/withRouter'; import { BitbucketProject, BitbucketProjectRepositories, BitbucketRepository, -} from '../../../types/alm-integration'; -import { AlmSettingsInstance } from '../../../types/alm-settings'; +} from '../../../../types/alm-integration'; +import { AlmSettingsInstance } from '../../../../types/alm-settings'; +import { DEFAULT_BBS_PAGE_SIZE } from '../constants'; import BitbucketCreateProjectRenderer from './BitbucketProjectCreateRenderer'; -import { DEFAULT_BBS_PAGE_SIZE } from './constants'; interface Props { canAdmin: boolean; diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectCreateRenderer.tsx index 2acbe01a87e..679c30c06e5 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketProjectCreateRenderer.tsx @@ -18,21 +18,21 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { Button } from '../../../components/controls/buttons'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; +import { Button } from '../../../../components/controls/buttons'; +import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; +import { translate } from '../../../../helpers/l10n'; +import { getBaseUrl } from '../../../../helpers/system'; import { BitbucketProject, BitbucketProjectRepositories, BitbucketRepository, -} from '../../../types/alm-integration'; -import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; -import AlmSettingsInstanceDropdown from './AlmSettingsInstanceDropdown'; +} from '../../../../types/alm-integration'; +import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; +import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown'; +import CreateProjectPageHeader from '../components/CreateProjectPageHeader'; +import PersonalAccessTokenForm from '../components/PersonalAccessTokenForm'; +import WrongBindingCountAlert from '../components/WrongBindingCountAlert'; import BitbucketImportRepositoryForm from './BitbucketImportRepositoryForm'; -import CreateProjectPageHeader from './CreateProjectPageHeader'; -import PersonalAccessTokenForm from './PersonalAccessTokenForm'; -import WrongBindingCountAlert from './WrongBindingCountAlert'; export interface BitbucketProjectCreateRendererProps { selectedAlmInstance?: AlmSettingsInstance; diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketRepositories.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketRepositories.tsx index 463050306bd..a7a225b510b 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketRepositories.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketRepositories.tsx @@ -19,13 +19,13 @@ */ import { uniq, without } from 'lodash'; import * as React from 'react'; -import { ButtonLink } from '../../../components/controls/buttons'; -import { translate } from '../../../helpers/l10n'; +import { ButtonLink } from '../../../../components/controls/buttons'; +import { translate } from '../../../../helpers/l10n'; import { BitbucketProject, BitbucketProjectRepositories, BitbucketRepository, -} from '../../../types/alm-integration'; +} from '../../../../types/alm-integration'; import BitbucketProjectAccordion from './BitbucketProjectAccordion'; export interface BitbucketRepositoriesProps { diff --git a/server/sonar-web/src/main/js/apps/create/project/BitbucketSearchResults.tsx b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketSearchResults.tsx index 89870c0e954..93d71e08c68 100644 --- a/server/sonar-web/src/main/js/apps/create/project/BitbucketSearchResults.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/BitbucketServer/BitbucketSearchResults.tsx @@ -18,10 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { Alert } from '../../../components/ui/Alert'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate } from '../../../helpers/l10n'; -import { BitbucketProject, BitbucketRepository } from '../../../types/alm-integration'; +import { Alert } from '../../../../components/ui/Alert'; +import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; +import { translate } from '../../../../helpers/l10n'; +import { BitbucketProject, BitbucketRepository } from '../../../../types/alm-integration'; import BitbucketProjectAccordion from './BitbucketProjectAccordion'; export interface BitbucketSearchResultsProps { diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx index 08fc333e5d7..1c92d7895a1 100644 --- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx @@ -32,13 +32,13 @@ import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; import { AppState } from '../../../types/appstate'; import { Feature } from '../../../types/features'; import AlmBindingDefinitionForm from '../../settings/components/almIntegration/AlmBindingDefinitionForm'; -import AzureProjectCreate from './AzureProjectCreate'; -import BitbucketCloudProjectCreate from './BitbucketCloudProjectCreate'; -import BitbucketProjectCreate from './BitbucketProjectCreate'; +import AzureProjectCreate from './Azure/AzureProjectCreate'; +import BitbucketCloudProjectCreate from './BitbucketCloud/BitbucketCloudProjectCreate'; +import BitbucketProjectCreate from './BitbucketServer/BitbucketProjectCreate'; import CreateProjectModeSelection from './CreateProjectModeSelection'; -import GitHubProjectCreate from './GitHubProjectCreate'; -import GitlabProjectCreate from './GitlabProjectCreate'; -import ManualProjectCreate from './ManualProjectCreate'; +import GitHubProjectCreate from './Github/GitHubProjectCreate'; +import GitlabProjectCreate from './Gitlab/GitlabProjectCreate'; +import ManualProjectCreate from './manual/ManualProjectCreate'; import './style.css'; import { CreateProjectModes } from './types'; diff --git a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx index 0174873e06f..ec40e48c34c 100644 --- a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx @@ -25,12 +25,12 @@ import { getGithubOrganizations, getGithubRepositories, importGithubRepository, -} from '../../../api/alm-integrations'; -import { Location, Router } from '../../../components/hoc/withRouter'; -import { getHostUrl } from '../../../helpers/urls'; -import { GithubOrganization, GithubRepository } from '../../../types/alm-integration'; -import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; -import { Paging } from '../../../types/types'; +} from '../../../../api/alm-integrations'; +import { Location, Router } from '../../../../components/hoc/withRouter'; +import { getHostUrl } from '../../../../helpers/urls'; +import { GithubOrganization, GithubRepository } from '../../../../types/alm-integration'; +import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; +import { Paging } from '../../../../types/types'; import GitHubProjectCreateRenderer from './GitHubProjectCreateRenderer'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx index 95b7fbbcf4a..ed2d238aad2 100644 --- a/server/sonar-web/src/main/js/apps/create/project/GitHubProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx @@ -21,26 +21,27 @@ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import { colors } from '../../../app/theme'; -import Link from '../../../components/common/Link'; -import { Button } from '../../../components/controls/buttons'; -import ListFooter from '../../../components/controls/ListFooter'; -import Radio from '../../../components/controls/Radio'; -import SearchBox from '../../../components/controls/SearchBox'; -import Select, { LabelValueSelectOption } from '../../../components/controls/Select'; -import CheckIcon from '../../../components/icons/CheckIcon'; -import QualifierIcon from '../../../components/icons/QualifierIcon'; -import { Alert } from '../../../components/ui/Alert'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; -import { getProjectUrl } from '../../../helpers/urls'; -import { GithubOrganization, GithubRepository } from '../../../types/alm-integration'; -import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; -import { ComponentQualifier } from '../../../types/component'; -import { Paging } from '../../../types/types'; -import AlmSettingsInstanceDropdown from './AlmSettingsInstanceDropdown'; -import CreateProjectPageHeader from './CreateProjectPageHeader'; +import { colors } from '../../../../app/theme'; +import Link from '../../../../components/common/Link'; +import ListFooter from '../../../../components/controls/ListFooter'; +import Radio from '../../../../components/controls/Radio'; +import SearchBox from '../../../../components/controls/SearchBox'; +import Select, { LabelValueSelectOption } from '../../../../components/controls/Select'; +import { Button } from '../../../../components/controls/buttons'; +import CheckIcon from '../../../../components/icons/CheckIcon'; +import QualifierIcon from '../../../../components/icons/QualifierIcon'; +import { Alert } from '../../../../components/ui/Alert'; +import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; +import { translate } from '../../../../helpers/l10n'; +import { getBaseUrl } from '../../../../helpers/system'; +import { getProjectUrl } from '../../../../helpers/urls'; +import { GithubOrganization, GithubRepository } from '../../../../types/alm-integration'; +import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; +import { ComponentQualifier } from '../../../../types/component'; +import { Paging } from '../../../../types/types'; +import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown'; +import CreateProjectPageHeader from '../components/CreateProjectPageHeader'; +import InstanceNewCodeDefinitionComplianceWarning from '../components/InstanceNewCodeDefinitionComplianceWarning'; export interface GitHubProjectCreateRendererProps { canAdmin: boolean; @@ -255,44 +256,49 @@ export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRe )} {!error && ( - <DeferredSpinner loading={loadingOrganizations}> - <div className="form-field"> - <label htmlFor="github-choose-organization"> - {translate('onboarding.create_project.github.choose_organization')} - </label> - {organizations.length > 0 ? ( - <Select - inputId="github-choose-organization" - className="input-super-large" - options={organizations.map(orgToOption)} - onChange={({ value }: LabelValueSelectOption) => props.onSelectOrganization(value)} - value={selectedOrganization ? orgToOption(selectedOrganization) : null} - /> - ) : ( - !loadingOrganizations && ( - <Alert className="spacer-top" variant="error"> - {canAdmin ? ( - <FormattedMessage - id="onboarding.create_project.github.no_orgs_admin" - defaultMessage={translate('onboarding.create_project.github.no_orgs_admin')} - values={{ - link: ( - <Link to="/admin/settings?category=almintegration"> - {translate( - 'onboarding.create_project.github.warning.message_admin.link' - )} - </Link> - ), - }} - /> - ) : ( - translate('onboarding.create_project.github.no_orgs') - )} - </Alert> - ) - )} - </div> - </DeferredSpinner> + <> + <InstanceNewCodeDefinitionComplianceWarning /> + <DeferredSpinner loading={loadingOrganizations}> + <div className="form-field"> + <label htmlFor="github-choose-organization"> + {translate('onboarding.create_project.github.choose_organization')} + </label> + {organizations.length > 0 ? ( + <Select + inputId="github-choose-organization" + className="input-super-large" + options={organizations.map(orgToOption)} + onChange={({ value }: LabelValueSelectOption) => + props.onSelectOrganization(value) + } + value={selectedOrganization ? orgToOption(selectedOrganization) : null} + /> + ) : ( + !loadingOrganizations && ( + <Alert className="spacer-top" variant="error"> + {canAdmin ? ( + <FormattedMessage + id="onboarding.create_project.github.no_orgs_admin" + defaultMessage={translate('onboarding.create_project.github.no_orgs_admin')} + values={{ + link: ( + <Link to="/admin/settings?category=almintegration"> + {translate( + 'onboarding.create_project.github.warning.message_admin.link' + )} + </Link> + ), + }} + /> + ) : ( + translate('onboarding.create_project.github.no_orgs') + )} + </Alert> + ) + )} + </div> + </DeferredSpinner> + </> )} {renderRepositoryList(props)} diff --git a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreate.tsx index 7e4f7af64a3..45b7aa4e8d7 100644 --- a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreate.tsx @@ -18,11 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { getGitlabProjects, importGitlabProject } from '../../../api/alm-integrations'; -import { Location, Router } from '../../../components/hoc/withRouter'; -import { GitlabProject } from '../../../types/alm-integration'; -import { AlmSettingsInstance } from '../../../types/alm-settings'; -import { Paging } from '../../../types/types'; +import { getGitlabProjects, importGitlabProject } from '../../../../api/alm-integrations'; +import { Location, Router } from '../../../../components/hoc/withRouter'; +import { GitlabProject } from '../../../../types/alm-integration'; +import { AlmSettingsInstance } from '../../../../types/alm-settings'; +import { Paging } from '../../../../types/types'; import GitlabProjectCreateRenderer from './GitlabProjectCreateRenderer'; interface Props { diff --git a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreateRenderer.tsx index a6f97855f14..366f5610d70 100644 --- a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectCreateRenderer.tsx @@ -18,16 +18,16 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; -import { GitlabProject } from '../../../types/alm-integration'; -import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; -import { Paging } from '../../../types/types'; -import AlmSettingsInstanceDropdown from './AlmSettingsInstanceDropdown'; -import CreateProjectPageHeader from './CreateProjectPageHeader'; +import { translate } from '../../../../helpers/l10n'; +import { getBaseUrl } from '../../../../helpers/system'; +import { GitlabProject } from '../../../../types/alm-integration'; +import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; +import { Paging } from '../../../../types/types'; +import AlmSettingsInstanceDropdown from '../components/AlmSettingsInstanceDropdown'; +import CreateProjectPageHeader from '../components/CreateProjectPageHeader'; +import PersonalAccessTokenForm from '../components/PersonalAccessTokenForm'; +import WrongBindingCountAlert from '../components/WrongBindingCountAlert'; import GitlabProjectSelectionForm from './GitlabProjectSelectionForm'; -import PersonalAccessTokenForm from './PersonalAccessTokenForm'; -import WrongBindingCountAlert from './WrongBindingCountAlert'; export interface GitlabProjectCreateRendererProps { canAdmin?: boolean; diff --git a/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectSelectionForm.tsx b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectSelectionForm.tsx new file mode 100644 index 00000000000..56f3c8ebb39 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/Gitlab/GitlabProjectSelectionForm.tsx @@ -0,0 +1,173 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 '../../../../components/common/Link'; +import ListFooter from '../../../../components/controls/ListFooter'; +import SearchBox from '../../../../components/controls/SearchBox'; +import Tooltip from '../../../../components/controls/Tooltip'; +import { Button } from '../../../../components/controls/buttons'; +import CheckIcon from '../../../../components/icons/CheckIcon'; +import QualifierIcon from '../../../../components/icons/QualifierIcon'; +import { Alert } from '../../../../components/ui/Alert'; +import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; +import { translate } from '../../../../helpers/l10n'; +import { getProjectUrl, queryToSearch } from '../../../../helpers/urls'; +import { GitlabProject } from '../../../../types/alm-integration'; +import { ComponentQualifier } from '../../../../types/component'; +import { Paging } from '../../../../types/types'; +import InstanceNewCodeDefinitionComplianceWarning from '../components/InstanceNewCodeDefinitionComplianceWarning'; +import { CreateProjectModes } from '../types'; + +export interface GitlabProjectSelectionFormProps { + importingGitlabProjectId?: string; + loadingMore: boolean; + onImport: (gitlabProjectId: string) => void; + onLoadMore: () => void; + onSearch: (searchQuery: string) => void; + projects?: GitlabProject[]; + projectsPaging: Paging; + searching: boolean; + searchQuery: string; +} + +export default function GitlabProjectSelectionForm(props: GitlabProjectSelectionFormProps) { + const { + importingGitlabProjectId, + loadingMore, + projects = [], + projectsPaging, + searching, + searchQuery, + } = props; + + if (projects.length === 0 && searchQuery.length === 0 && !searching) { + return ( + <Alert className="spacer-top" variant="warning"> + <FormattedMessage + defaultMessage={translate('onboarding.create_project.gitlab.no_projects')} + id="onboarding.create_project.gitlab.no_projects" + values={{ + link: ( + <Link + to={{ + pathname: '/projects/create', + search: queryToSearch({ mode: CreateProjectModes.GitLab, resetPat: 1 }), + }} + > + {translate('onboarding.create_project.update_your_token')} + </Link> + ), + }} + /> + </Alert> + ); + } + + return ( + <> + <InstanceNewCodeDefinitionComplianceWarning /> + <div className="boxed-group big-padded create-project-import"> + <SearchBox + className="spacer" + loading={searching} + minLength={3} + onChange={props.onSearch} + placeholder={translate('onboarding.create_project.search_prompt')} + /> + + <hr /> + + {projects.length === 0 ? ( + <div className="padded">{translate('no_results')}</div> + ) : ( + <table className="data zebra zebra-hover"> + <tbody> + {projects.map((project) => ( + <tr key={project.id}> + <td> + <Tooltip overlay={project.slug}> + <strong className="project-name display-inline-block text-ellipsis"> + {project.sqProjectKey ? ( + <Link to={getProjectUrl(project.sqProjectKey)}> + <QualifierIcon + className="spacer-right" + qualifier={ComponentQualifier.Project} + /> + {project.sqProjectName} + </Link> + ) : ( + project.name + )} + </strong> + </Tooltip> + <br /> + <Tooltip overlay={project.pathSlug}> + <span className="text-muted project-path display-inline-block text-ellipsis"> + {project.pathName} + </span> + </Tooltip> + </td> + <td> + <Link + className="display-inline-flex-center big-spacer-right" + to={project.url} + target="_blank" + > + {translate('onboarding.create_project.gitlab.link')} + </Link> + </td> + {project.sqProjectKey ? ( + <td> + <span className="display-flex-center display-flex-justify-end already-set-up"> + <CheckIcon className="little-spacer-right" size={12} /> + {translate('onboarding.create_project.repository_imported')} + </span> + </td> + ) : ( + <td className="text-right"> + <Button + disabled={!!importingGitlabProjectId} + onClick={() => props.onImport(project.id)} + > + {translate('onboarding.create_project.set_up')} + <DeferredSpinner + className="spacer-left" + loading={importingGitlabProjectId === project.id} + /> + </Button> + </td> + )} + </tr> + ))} + </tbody> + </table> + )} + <ListFooter + count={projects.length} + loadMore={props.onLoadMore} + loading={loadingMore} + pageSize={projectsPaging.pageSize} + total={projectsPaging.total} + /> + </div> + </> + ); +} diff --git a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx b/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx deleted file mode 100644 index aad16aae6ec..00000000000 --- a/server/sonar-web/src/main/js/apps/create/project/GitlabProjectSelectionForm.tsx +++ /dev/null @@ -1,168 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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 '../../../components/common/Link'; -import { Button } from '../../../components/controls/buttons'; -import ListFooter from '../../../components/controls/ListFooter'; -import SearchBox from '../../../components/controls/SearchBox'; -import Tooltip from '../../../components/controls/Tooltip'; -import CheckIcon from '../../../components/icons/CheckIcon'; -import QualifierIcon from '../../../components/icons/QualifierIcon'; -import { Alert } from '../../../components/ui/Alert'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate } from '../../../helpers/l10n'; -import { getProjectUrl, queryToSearch } from '../../../helpers/urls'; -import { GitlabProject } from '../../../types/alm-integration'; -import { ComponentQualifier } from '../../../types/component'; -import { Paging } from '../../../types/types'; -import { CreateProjectModes } from './types'; - -export interface GitlabProjectSelectionFormProps { - importingGitlabProjectId?: string; - loadingMore: boolean; - onImport: (gitlabProjectId: string) => void; - onLoadMore: () => void; - onSearch: (searchQuery: string) => void; - projects?: GitlabProject[]; - projectsPaging: Paging; - searching: boolean; - searchQuery: string; -} - -export default function GitlabProjectSelectionForm(props: GitlabProjectSelectionFormProps) { - const { - importingGitlabProjectId, - loadingMore, - projects = [], - projectsPaging, - searching, - searchQuery, - } = props; - - if (projects.length === 0 && searchQuery.length === 0 && !searching) { - return ( - <Alert className="spacer-top" variant="warning"> - <FormattedMessage - defaultMessage={translate('onboarding.create_project.gitlab.no_projects')} - id="onboarding.create_project.gitlab.no_projects" - values={{ - link: ( - <Link - to={{ - pathname: '/projects/create', - search: queryToSearch({ mode: CreateProjectModes.GitLab, resetPat: 1 }), - }} - > - {translate('onboarding.create_project.update_your_token')} - </Link> - ), - }} - /> - </Alert> - ); - } - - return ( - <div className="boxed-group big-padded create-project-import"> - <SearchBox - className="spacer" - loading={searching} - minLength={3} - onChange={props.onSearch} - placeholder={translate('onboarding.create_project.search_prompt')} - /> - - <hr /> - - {projects.length === 0 ? ( - <div className="padded">{translate('no_results')}</div> - ) : ( - <table className="data zebra zebra-hover"> - <tbody> - {projects.map((project) => ( - <tr key={project.id}> - <td> - <Tooltip overlay={project.slug}> - <strong className="project-name display-inline-block text-ellipsis"> - {project.sqProjectKey ? ( - <Link to={getProjectUrl(project.sqProjectKey)}> - <QualifierIcon - className="spacer-right" - qualifier={ComponentQualifier.Project} - /> - {project.sqProjectName} - </Link> - ) : ( - project.name - )} - </strong> - </Tooltip> - <br /> - <Tooltip overlay={project.pathSlug}> - <span className="text-muted project-path display-inline-block text-ellipsis"> - {project.pathName} - </span> - </Tooltip> - </td> - <td> - <Link - className="display-inline-flex-center big-spacer-right" - to={project.url} - target="_blank" - > - {translate('onboarding.create_project.gitlab.link')} - </Link> - </td> - {project.sqProjectKey ? ( - <td> - <span className="display-flex-center display-flex-justify-end already-set-up"> - <CheckIcon className="little-spacer-right" size={12} /> - {translate('onboarding.create_project.repository_imported')} - </span> - </td> - ) : ( - <td className="text-right"> - <Button - disabled={!!importingGitlabProjectId} - onClick={() => props.onImport(project.id)} - > - {translate('onboarding.create_project.set_up')} - {importingGitlabProjectId === project.id && ( - <DeferredSpinner className="spacer-left" /> - )} - </Button> - </td> - )} - </tr> - ))} - </tbody> - </table> - )} - <ListFooter - count={projects.length} - loadMore={props.onLoadMore} - loading={loadingMore} - pageSize={projectsPaging.pageSize} - total={projectsPaging.total} - /> - </div> - ); -} diff --git a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.css b/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.css deleted file mode 100644 index 4ca8f2a4cc9..00000000000 --- a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.css +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 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. - */ -.manual-project-create { - max-width: 700px; -} - -.manual-project-create .button { - margin-top: var(--gridSize); -} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx index 0ace653f9fe..a3e4a6b838f 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/Azure-it.tsx @@ -26,6 +26,7 @@ import { byLabelText, byRole, byText } from 'testing-library-selector'; import { searchAzureRepositories } from '../../../../api/alm-integrations'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; +import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServiceMock'; import { renderApp } from '../../../../helpers/testReactTestingUtils'; import CreateProjectPage, { CreateProjectPageProps } from '../CreateProjectPage'; @@ -34,6 +35,7 @@ jest.mock('../../../../api/alm-settings'); let almIntegrationHandler: AlmIntegrationsServiceMock; let almSettingsHandler: AlmSettingsServiceMock; +let newCodePeriodHandler: NewCodePeriodsServiceMock; const ui = { azureCreateProjectButton: byText('onboarding.create_project.select_method.azure'), @@ -46,12 +48,14 @@ const ui = { beforeAll(() => { almIntegrationHandler = new AlmIntegrationsServiceMock(); almSettingsHandler = new AlmSettingsServiceMock(); + newCodePeriodHandler = new NewCodePeriodsServiceMock(); }); beforeEach(() => { jest.clearAllMocks(); almIntegrationHandler.reset(); almSettingsHandler.reset(); + newCodePeriodHandler.reset(); }); it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx index e8d1fd22e81..6c66849c475 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/Bitbucket-it.tsx @@ -26,6 +26,7 @@ import { byLabelText, byRole, byText } from 'testing-library-selector'; import { searchForBitbucketServerRepositories } from '../../../../api/alm-integrations'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; +import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServiceMock'; import { renderApp } from '../../../../helpers/testReactTestingUtils'; import CreateProjectPage, { CreateProjectPageProps } from '../CreateProjectPage'; @@ -34,6 +35,7 @@ jest.mock('../../../../api/alm-settings'); let almIntegrationHandler: AlmIntegrationsServiceMock; let almSettingsHandler: AlmSettingsServiceMock; +let newCodePeriodHandler: NewCodePeriodsServiceMock; const ui = { bitbucketServerCreateProjectButton: byText('onboarding.create_project.select_method.bitbucket'), @@ -46,12 +48,14 @@ const ui = { beforeAll(() => { almIntegrationHandler = new AlmIntegrationsServiceMock(); almSettingsHandler = new AlmSettingsServiceMock(); + newCodePeriodHandler = new NewCodePeriodsServiceMock(); }); beforeEach(() => { jest.clearAllMocks(); almIntegrationHandler.reset(); almSettingsHandler.reset(); + newCodePeriodHandler.reset(); }); it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx index c82b35c2b86..c301a25e9df 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx @@ -26,6 +26,7 @@ import { byLabelText, byRole, byText } from 'testing-library-selector'; import { searchForBitbucketCloudRepositories } from '../../../../api/alm-integrations'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; +import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServiceMock'; import { renderApp } from '../../../../helpers/testReactTestingUtils'; import CreateProjectPage, { CreateProjectPageProps } from '../CreateProjectPage'; @@ -34,6 +35,7 @@ jest.mock('../../../../api/alm-settings'); let almIntegrationHandler: AlmIntegrationsServiceMock; let almSettingsHandler: AlmSettingsServiceMock; +let newCodePeriodHandler: NewCodePeriodsServiceMock; const ui = { bitbucketCloudCreateProjectButton: byText( @@ -48,12 +50,14 @@ const ui = { beforeAll(() => { almIntegrationHandler = new AlmIntegrationsServiceMock(); almSettingsHandler = new AlmSettingsServiceMock(); + newCodePeriodHandler = new NewCodePeriodsServiceMock(); }); beforeEach(() => { jest.clearAllMocks(); almIntegrationHandler.reset(); almSettingsHandler.reset(); + newCodePeriodHandler.reset(); }); it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx index 9685d20f822..481a7db84ca 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitHub-it.tsx @@ -26,6 +26,7 @@ import { byLabelText, byText } from 'testing-library-selector'; import { getGithubRepositories } from '../../../../api/alm-integrations'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; +import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServiceMock'; import { renderApp } from '../../../../helpers/testReactTestingUtils'; import CreateProjectPage from '../CreateProjectPage'; @@ -36,6 +37,7 @@ const original = window.location; let almIntegrationHandler: AlmIntegrationsServiceMock; let almSettingsHandler: AlmSettingsServiceMock; +let newCodePeriodHandler: NewCodePeriodsServiceMock; const ui = { githubCreateProjectButton: byText('onboarding.create_project.select_method.github'), @@ -50,12 +52,14 @@ beforeAll(() => { }); almIntegrationHandler = new AlmIntegrationsServiceMock(); almSettingsHandler = new AlmSettingsServiceMock(); + newCodePeriodHandler = new NewCodePeriodsServiceMock(); }); beforeEach(() => { jest.clearAllMocks(); almIntegrationHandler.reset(); almSettingsHandler.reset(); + newCodePeriodHandler.reset(); }); afterAll(() => { diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx index 88644cb4e44..0e71dbb86ff 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx @@ -25,7 +25,10 @@ import { byLabelText, byRole, byText } from 'testing-library-selector'; import { getGitlabProjects } from '../../../../api/alm-integrations'; import AlmIntegrationsServiceMock from '../../../../api/mocks/AlmIntegrationsServiceMock'; import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; +import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServiceMock'; +import { mockNewCodePeriod } from '../../../../helpers/mocks/new-code-period'; import { renderApp } from '../../../../helpers/testReactTestingUtils'; +import { NewCodePeriodSettingType } from '../../../../types/types'; import CreateProjectPage, { CreateProjectPageProps } from '../CreateProjectPage'; jest.mock('../../../../api/alm-integrations'); @@ -33,6 +36,7 @@ jest.mock('../../../../api/alm-settings'); let almIntegrationHandler: AlmIntegrationsServiceMock; let almSettingsHandler: AlmSettingsServiceMock; +let newCodePeriodHandler: NewCodePeriodsServiceMock; const ui = { gitlabCreateProjectButton: byText('onboarding.create_project.select_method.gitlab'), @@ -46,12 +50,14 @@ const ui = { beforeAll(() => { almIntegrationHandler = new AlmIntegrationsServiceMock(); almSettingsHandler = new AlmSettingsServiceMock(); + newCodePeriodHandler = new NewCodePeriodsServiceMock(); }); beforeEach(() => { jest.clearAllMocks(); almIntegrationHandler.reset(); almSettingsHandler.reset(); + newCodePeriodHandler.reset(); }); it('should ask for PAT when it is not set yet and show the import project feature afterwards', async () => { @@ -178,6 +184,24 @@ it('should show no result message when there are no projects', async () => { ); }); +it('should display a warning if the instance default new code definition is not CaYC compliant', async () => { + const user = userEvent.setup(); + newCodePeriodHandler.setNewCodePeriod( + mockNewCodePeriod({ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '91' }) + ); + renderCreateProject(); + await act(async () => { + await user.click(ui.gitlabCreateProjectButton.get()); + await selectEvent.select(ui.instanceSelector.get(), [/conf-final-2/]); + }); + + expect(screen.getByText('Gitlab project 1')).toBeInTheDocument(); + expect(screen.getByText('Gitlab project 2')).toBeInTheDocument(); + expect(screen.getByRole('alert')).toHaveTextContent( + 'onboarding.create_project.new_code_option.warning.title' + ); +}); + function renderCreateProject(props: Partial<CreateProjectPageProps> = {}) { renderApp('project/create', <CreateProjectPage {...props} />); } diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx index 1ac0b03a7f6..98a05291a2a 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx @@ -21,8 +21,9 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { createProject, doesComponentExists } from '../../../../api/components'; +import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServiceMock'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; -import ManualProjectCreate from '../ManualProjectCreate'; +import ManualProjectCreate from '../manual/ManualProjectCreate'; jest.mock('../../../../api/components', () => ({ createProject: jest.fn().mockResolvedValue({ project: { key: 'bar', name: 'Bar' } }), @@ -35,8 +36,15 @@ jest.mock('../../../../api/settings', () => ({ getValue: jest.fn().mockResolvedValue({ value: 'main' }), })); +let newCodePeriodHandler: NewCodePeriodsServiceMock; + +beforeAll(() => { + newCodePeriodHandler = new NewCodePeriodsServiceMock(); +}); + beforeEach(() => { jest.clearAllMocks(); + newCodePeriodHandler.reset(); }); it('should show branch information', async () => { diff --git a/server/sonar-web/src/main/js/apps/create/project/AlmSettingsInstanceDropdown.tsx b/server/sonar-web/src/main/js/apps/create/project/components/AlmSettingsInstanceDropdown.tsx index 34d348c2c9f..b7c5c13c9c9 100644 --- a/server/sonar-web/src/main/js/apps/create/project/AlmSettingsInstanceDropdown.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/components/AlmSettingsInstanceDropdown.tsx @@ -18,9 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import AlmSettingsInstanceSelector from '../../../components/devops-platform/AlmSettingsInstanceSelector'; -import { hasMessage, translate, translateWithParameters } from '../../../helpers/l10n'; -import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; +import AlmSettingsInstanceSelector from '../../../../components/devops-platform/AlmSettingsInstanceSelector'; +import { hasMessage, translate, translateWithParameters } from '../../../../helpers/l10n'; +import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; export interface AlmSettingsInstanceDropdownProps { almKey: AlmKeys; diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPageHeader.tsx b/server/sonar-web/src/main/js/apps/create/project/components/CreateProjectPageHeader.tsx index f5d4517ff10..f5d4517ff10 100644 --- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/components/CreateProjectPageHeader.tsx diff --git a/server/sonar-web/src/main/js/apps/create/project/components/InstanceNewCodeDefinitionComplianceWarning.tsx b/server/sonar-web/src/main/js/apps/create/project/components/InstanceNewCodeDefinitionComplianceWarning.tsx new file mode 100644 index 00000000000..7e0f2cc78c4 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/components/InstanceNewCodeDefinitionComplianceWarning.tsx @@ -0,0 +1,95 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 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 { getNewCodePeriod } from '../../../../api/newCodePeriod'; +import { AppStateContextProviderProps } from '../../../../app/components/app-state/AppStateContextProvider'; +import withAppStateContext from '../../../../app/components/app-state/withAppStateContext'; +import DocLink from '../../../../components/common/DocLink'; +import Link from '../../../../components/common/Link'; +import { Alert } from '../../../../components/ui/Alert'; +import { translate } from '../../../../helpers/l10n'; +import { isNewCodeDefinitionCompliant } from '../../../../helpers/periods'; + +export type InstanceNewCodeDefinitionComplianceWarningProps = AppStateContextProviderProps; + +export function InstanceNewCodeDefinitionComplianceWarning({ + appState: { canAdmin }, +}: InstanceNewCodeDefinitionComplianceWarningProps) { + const [isCompliant, setIsCompliant] = React.useState(true); + + React.useEffect(() => { + async function fetchInstanceNCDOptionCompliance() { + const newCodeDefinition = await getNewCodePeriod(); + setIsCompliant(isNewCodeDefinitionCompliant(newCodeDefinition)); + } + + fetchInstanceNCDOptionCompliance(); + }, []); + + if (isCompliant) { + return null; + } + + return ( + <Alert className="huge-spacer-bottom sw-max-w-[700px]" variant="warning"> + <p className="sw-mb-2 sw-font-bold"> + {translate('onboarding.create_project.new_code_option.warning.title')} + </p> + <p className="sw-mb-2"> + <FormattedMessage + id="onboarding.create_project.new_code_option.warning.explanation" + defaultMessage={translate( + 'onboarding.create_project.new_code_option.warning.explanation' + )} + values={{ + action: canAdmin ? ( + <FormattedMessage + id="onboarding.create_project.new_code_option.warning.explanation.action.admin" + defaultMessage={translate( + 'onboarding.create_project.new_code_option.warning.explanation.action.admin' + )} + values={{ + link: ( + <Link to="/admin/settings?category=new_code_period"> + {translate( + 'onboarding.create_project.new_code_option.warning.explanation.action.admin.link' + )} + </Link> + ), + }} + /> + ) : ( + translate('onboarding.create_project.new_code_option.warning.explanation.action') + ), + }} + /> + </p> + <p> + {translate('learn_more')}: + <DocLink to="/project-administration/defining-new-code/"> + {translate('onboarding.create_project.new_code_option.warning.learn_more.link')} + </DocLink> + </p> + </Alert> + ); +} + +export default withAppStateContext(InstanceNewCodeDefinitionComplianceWarning); diff --git a/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx b/server/sonar-web/src/main/js/apps/create/project/components/PersonalAccessTokenForm.tsx index 589b8c72264..edba42d8ad4 100644 --- a/server/sonar-web/src/main/js/apps/create/project/PersonalAccessTokenForm.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/components/PersonalAccessTokenForm.tsx @@ -23,15 +23,15 @@ import { FormattedMessage } from 'react-intl'; import { checkPersonalAccessTokenIsValid, setAlmPersonalAccessToken, -} from '../../../api/alm-integrations'; -import { SubmitButton } from '../../../components/controls/buttons'; -import ValidationInput from '../../../components/controls/ValidationInput'; -import { Alert } from '../../../components/ui/Alert'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate } from '../../../helpers/l10n'; -import { getBaseUrl } from '../../../helpers/system'; -import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; -import { tokenExistedBefore } from './utils'; +} from '../../../../api/alm-integrations'; +import ValidationInput from '../../../../components/controls/ValidationInput'; +import { SubmitButton } from '../../../../components/controls/buttons'; +import { Alert } from '../../../../components/ui/Alert'; +import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; +import { translate } from '../../../../helpers/l10n'; +import { getBaseUrl } from '../../../../helpers/system'; +import { AlmKeys, AlmSettingsInstance } from '../../../../types/alm-settings'; +import { tokenExistedBefore } from '../utils'; interface Props { almSetting: AlmSettingsInstance; diff --git a/server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.tsx b/server/sonar-web/src/main/js/apps/create/project/components/WrongBindingCountAlert.tsx index 7588b6cbf33..215cdf261c3 100644 --- a/server/sonar-web/src/main/js/apps/create/project/WrongBindingCountAlert.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/components/WrongBindingCountAlert.tsx @@ -19,12 +19,12 @@ */ import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import Link from '../../../components/common/Link'; -import { Alert } from '../../../components/ui/Alert'; -import { translate } from '../../../helpers/l10n'; -import { getGlobalSettingsUrl } from '../../../helpers/urls'; -import { AlmKeys } from '../../../types/alm-settings'; -import { ALM_INTEGRATION_CATEGORY } from '../../settings/constants'; +import Link from '../../../../components/common/Link'; +import { Alert } from '../../../../components/ui/Alert'; +import { translate } from '../../../../helpers/l10n'; +import { getGlobalSettingsUrl } from '../../../../helpers/urls'; +import { AlmKeys } from '../../../../types/alm-settings'; +import { ALM_INTEGRATION_CATEGORY } from '../../../settings/constants'; export interface WrongBindingCountAlertProps { alm: AlmKeys; diff --git a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx index 44b290ce91d..5a611d858af 100644 --- a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx @@ -21,22 +21,22 @@ import classNames from 'classnames'; import { debounce, isEmpty } from 'lodash'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; -import { createProject, doesComponentExists } from '../../../api/components'; -import { getValue } from '../../../api/settings'; -import DocLink from '../../../components/common/DocLink'; -import ProjectKeyInput from '../../../components/common/ProjectKeyInput'; -import ValidationInput from '../../../components/controls/ValidationInput'; -import { SubmitButton } from '../../../components/controls/buttons'; -import { Alert } from '../../../components/ui/Alert'; -import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation'; -import { translate } from '../../../helpers/l10n'; -import { PROJECT_KEY_INVALID_CHARACTERS, validateProjectKey } from '../../../helpers/projects'; -import { ProjectKeyValidationResult } from '../../../types/component'; -import { GlobalSettingKeys } from '../../../types/settings'; -import CreateProjectPageHeader from './CreateProjectPageHeader'; -import './ManualProjectCreate.css'; -import { PROJECT_NAME_MAX_LEN } from './constants'; +import { createProject, doesComponentExists } from '../../../../api/components'; +import { getValue } from '../../../../api/settings'; +import DocLink from '../../../../components/common/DocLink'; +import ProjectKeyInput from '../../../../components/common/ProjectKeyInput'; +import ValidationInput from '../../../../components/controls/ValidationInput'; +import { SubmitButton } from '../../../../components/controls/buttons'; +import { Alert } from '../../../../components/ui/Alert'; +import DeferredSpinner from '../../../../components/ui/DeferredSpinner'; +import MandatoryFieldsExplanation from '../../../../components/ui/MandatoryFieldsExplanation'; +import { translate } from '../../../../helpers/l10n'; +import { PROJECT_KEY_INVALID_CHARACTERS, validateProjectKey } from '../../../../helpers/projects'; +import { ProjectKeyValidationResult } from '../../../../types/component'; +import { GlobalSettingKeys } from '../../../../types/settings'; +import CreateProjectPageHeader from '../components/CreateProjectPageHeader'; +import InstanceNewCodeDefinitionComplianceWarning from '../components/InstanceNewCodeDefinitionComplianceWarning'; +import { PROJECT_NAME_MAX_LEN } from '../constants'; interface Props { branchesEnabled: boolean; @@ -235,94 +235,90 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat <> <CreateProjectPageHeader title={translate('onboarding.create_project.setup_manually')} /> - <div className="create-project-manual"> - <div className="flex-1 huge-spacer-right"> - <form className="manual-project-create" onSubmit={this.handleFormSubmit}> - <MandatoryFieldsExplanation className="big-spacer-bottom" /> + <InstanceNewCodeDefinitionComplianceWarning /> - <ValidationInput - className="form-field" - description={translate('onboarding.create_project.display_name.description')} - error={projectNameError} - labelHtmlFor="project-name" - isInvalid={projectNameIsInvalid} - isValid={projectNameIsValid} - label={translate('onboarding.create_project.display_name')} - required={true} - > - <input - className={classNames('input-super-large', { - 'is-invalid': projectNameIsInvalid, - 'is-valid': projectNameIsValid, - })} - id="project-name" - maxLength={PROJECT_NAME_MAX_LEN} - minLength={1} - onChange={(e) => this.handleProjectNameChange(e.currentTarget.value, true)} - type="text" - value={projectName} - autoFocus={true} - /> - </ValidationInput> - <ProjectKeyInput - error={projectKeyError} - label={translate('onboarding.create_project.project_key')} - onProjectKeyChange={(e) => this.handleProjectKeyChange(e.currentTarget.value, true)} - projectKey={projectKey} - touched={touched} - validating={validatingProjectKey} - /> + <form id="create-project-manual" onSubmit={this.handleFormSubmit}> + <MandatoryFieldsExplanation className="big-spacer-bottom" /> + + <ValidationInput + className="form-field" + description={translate('onboarding.create_project.display_name.description')} + error={projectNameError} + labelHtmlFor="project-name" + isInvalid={projectNameIsInvalid} + isValid={projectNameIsValid} + label={translate('onboarding.create_project.display_name')} + required={true} + > + <input + className={classNames('input-super-large', { + 'is-invalid': projectNameIsInvalid, + 'is-valid': projectNameIsValid, + })} + id="project-name" + maxLength={PROJECT_NAME_MAX_LEN} + minLength={1} + onChange={(e) => this.handleProjectNameChange(e.currentTarget.value, true)} + type="text" + value={projectName} + autoFocus={true} + /> + </ValidationInput> + <ProjectKeyInput + error={projectKeyError} + label={translate('onboarding.create_project.project_key')} + onProjectKeyChange={(e) => this.handleProjectKeyChange(e.currentTarget.value, true)} + projectKey={projectKey} + touched={touched} + validating={validatingProjectKey} + /> - <ValidationInput - className="form-field" - description={ - <FormattedMessage - id="onboarding.create_project.main_branch_name.description" - defaultMessage={translate( - 'onboarding.create_project.main_branch_name.description' - )} - values={{ - learn_more: ( - <DocLink to="/analyzing-source-code/branches/branch-analysis"> - {translate('learn_more')} - </DocLink> - ), - }} - /> - } - error={mainBranchNameError} - labelHtmlFor="main-branch-name" - isInvalid={mainBranchNameIsInvalid} - isValid={mainBranchNameIsValid} - label={translate('onboarding.create_project.main_branch_name')} - required={true} - > - <input - id="main-branch-name" - className={classNames('input-super-large', { - 'is-invalid': mainBranchNameIsInvalid, - 'is-valid': mainBranchNameIsValid, - })} - minLength={1} - onChange={(e) => this.handleBranchNameChange(e.currentTarget.value, true)} - type="text" - value={mainBranchName} - /> - </ValidationInput> + <ValidationInput + className="form-field" + description={ + <FormattedMessage + id="onboarding.create_project.main_branch_name.description" + defaultMessage={translate('onboarding.create_project.main_branch_name.description')} + values={{ + learn_more: ( + <DocLink to="/analyzing-source-code/branches/branch-analysis"> + {translate('learn_more')} + </DocLink> + ), + }} + /> + } + error={mainBranchNameError} + labelHtmlFor="main-branch-name" + isInvalid={mainBranchNameIsInvalid} + isValid={mainBranchNameIsValid} + label={translate('onboarding.create_project.main_branch_name')} + required={true} + > + <input + id="main-branch-name" + className={classNames('input-super-large', { + 'is-invalid': mainBranchNameIsInvalid, + 'is-valid': mainBranchNameIsValid, + })} + minLength={1} + onChange={(e) => this.handleBranchNameChange(e.currentTarget.value, true)} + type="text" + value={mainBranchName} + /> + </ValidationInput> - <SubmitButton disabled={!this.canSubmit(this.state) || submitting}> - {translate('set_up')} - </SubmitButton> - <DeferredSpinner className="spacer-left" loading={submitting} /> - </form> + <SubmitButton disabled={!this.canSubmit(this.state) || submitting}> + {translate('set_up')} + </SubmitButton> + <DeferredSpinner className="spacer-left" loading={submitting} /> + </form> - {branchesEnabled && ( - <Alert variant="info" display="inline" className="big-spacer-top"> - {translate('onboarding.create_project.pr_decoration.information')} - </Alert> - )} - </div> - </div> + {branchesEnabled && ( + <Alert variant="info" display="inline" className="big-spacer-top"> + {translate('onboarding.create_project.pr_decoration.information')} + </Alert> + )} </> ); } diff --git a/server/sonar-web/src/main/js/apps/create/project/style.css b/server/sonar-web/src/main/js/apps/create/project/style.css index 1ee624a8394..c5355fd75d3 100644 --- a/server/sonar-web/src/main/js/apps/create/project/style.css +++ b/server/sonar-web/src/main/js/apps/create/project/style.css @@ -33,11 +33,6 @@ filter: grayscale(100%); } -.create-project-manual { - display: flex !important; - justify-content: space-between; -} - .create-project-azdo-repo { width: 410px; min-height: 40px; diff --git a/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts index 59e62a8bf8d..73736fd7d7d 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/periods-test.ts @@ -17,8 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { NewCodePeriodSettingType } from '../../types/types'; -import { getPeriodLabel } from '../periods'; +import { NewCodePeriod, NewCodePeriodSettingType } from '../../types/types'; +import { getPeriodLabel, isNewCodeDefinitionCompliant } from '../periods'; import { mockPeriod } from '../testMocks'; const formatter = jest.fn((v) => v); @@ -110,3 +110,19 @@ describe('getPeriodLabel', () => { expect(formatter).toHaveBeenCalledTimes(1); }); }); + +describe('isNewCodeDefinitionCompliant', () => { + it.each([ + [{ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '0' }, false], + [{ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '15' }, true], + [{ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '91' }, false], + [{ type: NewCodePeriodSettingType.PREVIOUS_VERSION }, true], + [{ type: NewCodePeriodSettingType.REFERENCE_BRANCH }, true], + [{ type: NewCodePeriodSettingType.SPECIFIC_ANALYSIS }, true], + ])( + 'should test for new code definition compliance properly', + (newCodePeriod: NewCodePeriod, result: boolean) => { + expect(isNewCodeDefinitionCompliant(newCodePeriod)).toEqual(result); + } + ); +}); diff --git a/server/sonar-web/src/main/js/helpers/periods.ts b/server/sonar-web/src/main/js/helpers/periods.ts index 376a61c2a9c..2ffa7a88a80 100644 --- a/server/sonar-web/src/main/js/helpers/periods.ts +++ b/server/sonar-web/src/main/js/helpers/periods.ts @@ -20,7 +20,7 @@ import { parseDate } from '../helpers/dates'; import { translate, translateWithParameters } from '../helpers/l10n'; import { ApplicationPeriod } from '../types/application'; -import { NewCodePeriodSettingType, Period } from '../types/types'; +import { NewCodePeriod, NewCodePeriodSettingType, Period } from '../types/types'; export function getPeriodLabel( period: Period | undefined, @@ -68,3 +68,19 @@ export function isApplicationPeriod( ): period is ApplicationPeriod { return (period as ApplicationPeriod).project !== undefined; } + +const MIN_NUMBER_OF_DAYS = 1; +const MAX_NUMBER_OF_DAYS = 90; + +export function isNewCodeDefinitionCompliant(newCodePeriod: NewCodePeriod) { + switch (newCodePeriod.type) { + case NewCodePeriodSettingType.NUMBER_OF_DAYS: + return ( + newCodePeriod.value !== undefined && + MIN_NUMBER_OF_DAYS <= +newCodePeriod.value && + +newCodePeriod.value <= MAX_NUMBER_OF_DAYS + ); + default: + return true; + } +} diff --git a/server/sonar-web/src/main/js/types/types.ts b/server/sonar-web/src/main/js/types/types.ts index 94901ddc87a..0edbcf7a554 100644 --- a/server/sonar-web/src/main/js/types/types.ts +++ b/server/sonar-web/src/main/js/types/types.ts @@ -398,7 +398,7 @@ export interface MyProject { } export interface NewCodePeriod { - type?: NewCodePeriodSettingType; + type: NewCodePeriodSettingType; value?: string; effectiveValue?: string; inherited?: boolean; diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index 26561038619..19f4a4e8f7f 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -3801,6 +3801,13 @@ onboarding.create_project.gitlab.title=Gitlab project onboarding onboarding.create_project.gitlab.no_projects=No projects could be fetched from Gitlab. Contact your system administrator, or {link}. onboarding.create_project.gitlab.link=See on GitLab +onboarding.create_project.new_code_option.warning.title=Your global new code definition is not compliant with the Clean as You Code methodology +onboarding.create_project.new_code_option.warning.explanation=New projects use the global new code definition by default. {action} so that new projects benefit from the Clean as You Code methodology by default. +onboarding.create_project.new_code_option.warning.explanation.action=We recommend that you ask an administrator of this SonarQube instance to update the global new code definition +onboarding.create_project.new_code_option.warning.explanation.action.admin=We recommend that you update the global new code definition under {link} +onboarding.create_project.new_code_option.warning.explanation.action.admin.link=General Settings - New Code +onboarding.create_project.new_code_option.warning.learn_more.link=Defining New Code + onboarding.token.header=Provide a token onboarding.token.text=The token is used to identify you when an analysis is performed. If it has been compromised, you can revoke it at any point in time in your {link}. onboarding.token.text.PROJECT_ANALYSIS_TOKEN=The project token is used to identify you when an analysis is performed. If it has been compromised, you can revoke it at any point in time in your {link}. |