diff options
author | guillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com> | 2023-10-11 11:04:43 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-10-18 20:03:05 +0000 |
commit | c2232d5ecdc3fa0f67f916e82734b7bd2061772b (patch) | |
tree | dae1e39f7f1c95674c48e21e6e28d4a7daa0e3ed | |
parent | 15f3164036ac36318cc68fd40eab0a4633ea3675 (diff) | |
download | sonarqube-c2232d5ecdc3fa0f67f916e82734b7bd2061772b.tar.gz sonarqube-c2232d5ecdc3fa0f67f916e82734b7bd2061772b.zip |
SONAR-20708 Update New Code period page to support bulk import
13 files changed, 98 insertions, 32 deletions
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 a6cb0f013fd..ead4d439536 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 @@ -57,6 +57,7 @@ interface State { gitlabSettings: AlmSettingsInstance[]; loading: boolean; creatingAlmDefinition?: AlmKeys; + nbrOfProjects?: number; } const PROJECT_MODE_FOR_ALM_KEY = { @@ -137,10 +138,12 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp this.setState({ creatingAlmDefinition: alm }); }; - handleProjectSetupDone = (createProject: CreateProjectApiCallback) => { + handleProjectSetupDone = (createProject: CreateProjectApiCallback, nbrOfProjects?: number) => { const { location, router } = this.props; this.createProjectFnRef = createProject; + this.setState({ nbrOfProjects }); + location.query.setncd = 'true'; router.push(location); }; @@ -273,7 +276,7 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp render() { const { location, router } = this.props; - const { creatingAlmDefinition } = this.state; + const { creatingAlmDefinition, nbrOfProjects } = this.state; const mode: CreateProjectModes | undefined = location.query?.mode; const isProjectSetupDone = location.query?.setncd === 'true'; const gridLayoutStyle = mode ? 'sw-col-start-2 sw-col-span-10' : 'sw-col-span-12'; @@ -297,6 +300,7 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp <NewCodeDefinitionSelection router={router} createProjectFnRef={this.createProjectFnRef} + numberOfProjects={nbrOfProjects} /> </div> diff --git a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx index 70ca875fe66..67e742ceae2 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreate.tsx @@ -37,7 +37,7 @@ import GitHubProjectCreateRenderer from './GitHubProjectCreateRenderer'; interface Props { canAdmin: boolean; loadingBindings: boolean; - onProjectSetupDone: (createProject: CreateProjectApiCallback) => void; + onProjectSetupDone: (createProject: CreateProjectApiCallback, nbrOfProjects: number) => void; almInstances: AlmSettingsInstance[]; location: Location; router: Router; @@ -259,16 +259,17 @@ export default class GitHubProjectCreate extends React.Component<Props, State> { } }; - handleImportRepository = (repoKey: string) => { + handleImportRepository = (repoKeys: string[]) => { const { selectedOrganization, selectedAlmInstance } = this.state; - if (selectedAlmInstance && selectedOrganization && repoKey !== '') { + if (selectedAlmInstance && selectedOrganization && repoKeys.length > 0) { this.props.onProjectSetupDone( setupGithubProjectCreation({ almSetting: selectedAlmInstance.key, organization: selectedOrganization.key, - repositoryKey: repoKey, + repositoryKey: repoKeys.join(','), // TBD }), + repoKeys.length, ); } }; diff --git a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx index 35847eb760e..df96843b995 100644 --- a/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/Github/GitHubProjectCreateRenderer.tsx @@ -52,7 +52,7 @@ export interface GitHubProjectCreateRendererProps { loadingBindings: boolean; loadingOrganizations: boolean; loadingRepositories: boolean; - onImportRepository: (key: string) => void; + onImportRepository: (key: string[]) => void; onLoadMore: () => void; onSearch: (q: string) => void; onSelectOrganization: (key: string) => void; @@ -183,7 +183,7 @@ export default function GitHubProjectCreateRenderer(props: GitHubProjectCreateRe } const handleImport = () => { - props.onImportRepository(Array.from(selected).toString()); // TBD + props.onImportRepository(Array.from(selected)); }; const handleCheckAll = () => { 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 b5b52e25470..a3f008a94dd 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 @@ -116,13 +116,13 @@ it('should show import project feature when PAT is already set', async () => { await user.click(importButton); expect( - screen.getByRole('heading', { name: 'onboarding.create_project.new_code_definition.title' }), + screen.getByRole('heading', { name: 'onboarding.create_x_project.new_code_definition.title1' }), ).toBeInTheDocument(); await user.click(screen.getByRole('radio', { name: 'new_code_definition.global_setting' })); await user.click( screen.getByRole('button', { - name: 'onboarding.create_project.new_code_definition.create_project', + name: 'onboarding.create_project.new_code_definition.create_x_projects1', }), ); 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 85b5ad18a8f..51a28d6a081 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 @@ -136,13 +136,13 @@ it('should show import project feature when PAT is already set', async () => { await user.click(importButton); expect( - screen.getByRole('heading', { name: 'onboarding.create_project.new_code_definition.title' }), + screen.getByRole('heading', { name: 'onboarding.create_x_project.new_code_definition.title1' }), ).toBeInTheDocument(); await user.click(screen.getByRole('radio', { name: 'new_code_definition.global_setting' })); await user.click( screen.getByRole('button', { - name: 'onboarding.create_project.new_code_definition.create_project', + name: 'onboarding.create_project.new_code_definition.create_x_projects1', }), ); 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 d7644408fa7..31cc747648e 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 @@ -152,13 +152,13 @@ it('should show import project feature when PAT is already set', async () => { await user.click(setupButton); expect( - screen.getByRole('heading', { name: 'onboarding.create_project.new_code_definition.title' }), + screen.getByRole('heading', { name: 'onboarding.create_x_project.new_code_definition.title1' }), ).toBeInTheDocument(); await user.click(screen.getByRole('radio', { name: 'new_code_definition.global_setting' })); await user.click( screen.getByRole('button', { - name: 'onboarding.create_project.new_code_definition.create_project', + name: 'onboarding.create_project.new_code_definition.create_x_projects1', }), ); 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 4311a26b054..c80ae871168 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 @@ -52,9 +52,18 @@ const ui = { project3Checkbox: byRole('listitem', { name: 'Github repo 3' }).byRole('checkbox'), checkAll: byRole('checkbox', { name: 'onboarding.create_project.select_all_repositories' }), importButton: byRole('button', { name: 'onboarding.create_project.import' }), - newCodeTitle: byRole('heading', { name: 'onboarding.create_project.new_code_definition.title' }), + newCodeTitle: byRole('heading', { + name: 'onboarding.create_x_project.new_code_definition.title1', + }), + newCodeMultipleProjectTitle: byRole('heading', { + name: 'onboarding.create_x_project.new_code_definition.title2', + }), + changePeriodLaterInfo: byText('onboarding.create_projects.new_code_definition.change_info'), createProjectButton: byRole('button', { - name: 'onboarding.create_project.new_code_definition.create_project', + name: 'onboarding.create_project.new_code_definition.create_x_projects1', + }), + createProjectsButton: byRole('button', { + name: 'onboarding.create_project.new_code_definition.create_x_projects2', }), globalSettingRadio: byRole('radio', { name: 'new_code_definition.global_setting' }), }; @@ -202,9 +211,12 @@ it('should import several projects', async () => { expect(ui.importButton.get()).toBeInTheDocument(); await user.click(ui.importButton.get()); - expect(await ui.newCodeTitle.find()).toBeInTheDocument(); + expect(await ui.newCodeMultipleProjectTitle.find()).toBeInTheDocument(); + expect(ui.changePeriodLaterInfo.get()).toBeInTheDocument(); + expect(ui.createProjectsButton.get()).toBeDisabled(); - // TBD + await user.click(ui.globalSettingRadio.get()); + expect(ui.createProjectsButton.get()).toBeEnabled(); }); it('should show search filter when the authentication is successful', async () => { 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 442abc088b8..3516ac4ef33 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 @@ -123,13 +123,13 @@ it('should show import project feature when PAT is already set', async () => { await user.click(importButton); expect( - screen.getByRole('heading', { name: 'onboarding.create_project.new_code_definition.title' }), + screen.getByRole('heading', { name: 'onboarding.create_x_project.new_code_definition.title1' }), ).toBeInTheDocument(); await user.click(screen.getByRole('radio', { name: 'new_code_definition.global_setting' })); await user.click( screen.getByRole('button', { - name: 'onboarding.create_project.new_code_definition.create_project', + name: 'onboarding.create_project.new_code_definition.create_x_projects1', }), ); diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/Manual-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/Manual-it.tsx index 874dc3cc1a1..e54eb0096a7 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/Manual-it.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/Manual-it.tsx @@ -53,10 +53,10 @@ const ui = { name: /onboarding.create_project.display_name/, }), projectNextButton: byRole('button', { name: 'next' }), - newCodeDefinitionHeader: byText('onboarding.create_project.new_code_definition.title'), + newCodeDefinitionHeader: byText('onboarding.create_x_project.new_code_definition.title1'), inheritGlobalNcdRadio: byRole('radio', { name: 'new_code_definition.global_setting' }), projectCreateButton: byRole('button', { - name: 'onboarding.create_project.new_code_definition.create_project', + name: 'onboarding.create_project.new_code_definition.create_x_projects1', }), overrideNcdRadio: byRole('radio', { name: 'new_code_definition.specific_setting' }), ncdOptionPreviousVersionRadio: byRole('radio', { diff --git a/server/sonar-web/src/main/js/apps/create/project/components/AlmRepoItem.tsx b/server/sonar-web/src/main/js/apps/create/project/components/AlmRepoItem.tsx index 4ae059d21c1..f604a644227 100644 --- a/server/sonar-web/src/main/js/apps/create/project/components/AlmRepoItem.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/components/AlmRepoItem.tsx @@ -70,7 +70,7 @@ export default function AlmRepoItem({ onCheck, onImport, }: AlmRepoItemProps) { - const labelId = `${almKey.replace(/\s/g, '_')}-label`; + const labelId = `${almKey.toString().replace(/\s/g, '_')}-label`; return ( <RepositoryItem selected={selected} diff --git a/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx b/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx index b63dd993932..c9c56f65fa3 100644 --- a/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/components/NewCodeDefinitionSelection.tsx @@ -17,9 +17,10 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { ButtonPrimary, Link, Spinner, Title } from 'design-system'; +import { ButtonPrimary, ButtonSecondary, FlagMessage, Link, Spinner, Title } from 'design-system'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; +import { useNavigate } from 'react-router-dom'; import { Router } from '../../../../components/hoc/withRouter'; import NewCodeDefinitionSelector from '../../../../components/new-code-definition/NewCodeDefinitionSelector'; import { useDocUrl } from '../../../../helpers/docs'; @@ -32,16 +33,22 @@ import { CreateProjectApiCallback } from '../types'; interface Props { createProjectFnRef: CreateProjectApiCallback | null; router: Router; + numberOfProjects?: number; } export default function NewCodeDefinitionSelection(props: Props) { - const { createProjectFnRef, router } = props; + const { createProjectFnRef, router, numberOfProjects } = props; const [submitting, setSubmitting] = React.useState(false); const [selectedDefinition, selectDefinition] = React.useState<NewCodeDefinitiondWithCompliance>(); + const navigate = useNavigate(); + const getDocUrl = useDocUrl(); + const isMultipleProjects = numberOfProjects !== undefined && numberOfProjects !== 1; + const projectCount = isMultipleProjects ? numberOfProjects : 1; + const handleProjectCreation = React.useCallback(async () => { if (createProjectFnRef && selectedDefinition) { setSubmitting(true); @@ -58,7 +65,15 @@ export default function NewCodeDefinitionSelection(props: Props) { return ( <div id="project-ncd-selection" className="sw-body-sm"> - <Title>{translate('onboarding.create_project.new_code_definition.title')}</Title> + <Title> + <FormattedMessage + defaultMessage={translate('onboarding.create_x_project.new_code_definition.title')} + id="onboarding.create_x_project.new_code_definition.title" + values={{ + count: projectCount, + }} + /> + </Title> <p className="sw-mb-2"> <FormattedMessage @@ -74,15 +89,35 @@ export default function NewCodeDefinitionSelection(props: Props) { /> </p> - <NewCodeDefinitionSelector onNcdChanged={selectDefinition} /> + <NewCodeDefinitionSelector + onNcdChanged={selectDefinition} + isMultipleProjects={isMultipleProjects} + /> + + {isMultipleProjects && ( + <FlagMessage variant="info"> + {translate('onboarding.create_projects.new_code_definition.change_info')} + </FlagMessage> + )} <div className="sw-mt-10 sw-mb-8"> + <ButtonSecondary className="sw-mr-2" onClick={() => navigate(-1)}> + {translate('back')} + </ButtonSecondary> <ButtonPrimary onClick={handleProjectCreation} disabled={!selectedDefinition?.isCompliant || submitting} type="submit" > - {translate('onboarding.create_project.new_code_definition.create_project')} + <FormattedMessage + defaultMessage={translate( + 'onboarding.create_project.new_code_definition.create_x_projects', + )} + id="onboarding.create_project.new_code_definition.create_x_projects" + values={{ + count: projectCount, + }} + /> <Spinner className="sw-ml-2" loading={submitting} /> </ButtonPrimary> </div> diff --git a/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionSelector.tsx b/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionSelector.tsx index 78f310ae0f2..2a2f2a810ea 100644 --- a/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionSelector.tsx +++ b/server/sonar-web/src/main/js/components/new-code-definition/NewCodeDefinitionSelector.tsx @@ -45,10 +45,11 @@ import { NewCodeDefinitionLevels } from './utils'; interface Props { onNcdChanged: (ncd: NewCodeDefinitiondWithCompliance) => void; + isMultipleProjects?: boolean; } export default function NewCodeDefinitionSelector(props: Props) { - const { onNcdChanged } = props; + const { onNcdChanged, isMultipleProjects } = props; const [globalNcd, setGlobalNcd] = React.useState<NewCodeDefinition | null>(null); const [selectedNcdType, setSelectedNcdType] = React.useState<NewCodeDefinitionType | null>(null); @@ -101,7 +102,9 @@ export default function NewCodeDefinitionSelector(props: Props) { <PageContentFontWrapper> <p className="sw-mt-10"> <strong className="sw-body-md-highlight"> - {translate('new_code_definition.question')} + {isMultipleProjects + ? translate('new_code_definition.question.multiple_projects') + : translate('new_code_definition.question')} </strong> </p> <div className="sw-mt-7 sw-ml-1" role="radiogroup"> @@ -124,13 +127,19 @@ export default function NewCodeDefinitionSelector(props: Props) { </StyledGlobalSettingWrapper> <RadioButton - aria-label={translate('new_code_definition.specific_setting')} + aria-label={ + isMultipleProjects + ? translate('new_code_definition.specific_setting.multiple_projects') + : translate('new_code_definition.specific_setting') + } checked={Boolean(selectedNcdType && selectedNcdType !== NewCodeDefinitionType.Inherited)} className="sw-mt-12 sw-font-semibold" onCheck={() => handleNcdChanged(NewCodeDefinitionType.PreviousVersion)} value="specific" > - {translate('new_code_definition.specific_setting')} + {isMultipleProjects + ? translate('new_code_definition.specific_setting.multiple_projects') + : translate('new_code_definition.specific_setting')} </RadioButton> </div> 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 bad1ff52ecb..fb19ad89c0f 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -4031,8 +4031,10 @@ footer.web_api=Web API # #------------------------------------------------------------------------------ new_code_definition.question=Choose the baseline for new code for this project +new_code_definition.question.multiple_projects=Choose the baseline for new code for those projects new_code_definition.global_setting=Use the global setting new_code_definition.specific_setting=Define a specific setting for this project +new_code_definition.specific_setting.multiple_projects=Define a specific setting for your projects new_code_definition.previous_version=Previous version new_code_definition.previous_version.usecase=Recommended for projects following regular versions or releases. @@ -4192,9 +4194,12 @@ onboarding.create_project.x_repositories_selected={count} {count, plural, one {r onboarding.create_project.x_repository_created={count} {count, plural, one {repository} other {repositories}} will be created as a project on SonarQube onboarding.create_project.new_code_definition.title=Set up project for Clean as You Code +onboarding.create_x_project.new_code_definition.title=Set up {count, plural, one {project} other {# projects}} for Clean as You Code onboarding.create_project.new_code_definition.description=The new code definition sets which part of your code will be considered new code. This helps you focus attention on the most recent changes to your project, enabling you to follow the Clean as You Code methodology. Learn more: {link} onboarding.create_project.new_code_definition.description.link=Defining New Code onboarding.create_project.new_code_definition.create_project=Create project +onboarding.create_project.new_code_definition.create_x_projects=Create {count, plural, one {project} other {# projects}} +onboarding.create_projects.new_code_definition.change_info=You can change this for each project individually at any time in the project administration. onboarding.create_project.success=Congratulations! Your project has been created. |