diff options
author | Andrey Luiz <andrey.luiz@sonarsource.com> | 2023-06-09 15:58:56 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-06-14 09:51:06 +0000 |
commit | 200ef3a99587a0679748e14d8889e61ebc1ef3d1 (patch) | |
tree | c06c092756225312e0c19c420bf7fee298936775 /server/sonar-web/src/main/js/apps/create | |
parent | 451a379605676df360745519038b5ae2770a00ea (diff) | |
download | sonarqube-200ef3a99587a0679748e14d8889e61ebc1ef3d1.tar.gz sonarqube-200ef3a99587a0679748e14d8889e61ebc1ef3d1.zip |
SONAR-19453 New code definition is made part of the manual project creation (#8486)
Diffstat (limited to 'server/sonar-web/src/main/js/apps/create')
5 files changed, 375 insertions, 72 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 53d317ad3af..379b9a2b27b 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 @@ -17,30 +17,37 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { noop } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; +import { FormattedMessage } from 'react-intl'; import { getAlmSettings } from '../../../api/alm-settings'; import withAppStateContext from '../../../app/components/app-state/withAppStateContext'; import withAvailableFeatures, { WithAvailableFeaturesProps, } from '../../../app/components/available-features/withAvailableFeatures'; import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; +import DocLink from '../../../components/common/DocLink'; +import { ButtonLink, SubmitButton } from '../../../components/controls/buttons'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; +import NewCodeDefinitionSelector from '../../../components/new-code-definition/NewCodeDefinitionSelector'; import { translate } from '../../../helpers/l10n'; import { getProjectUrl } from '../../../helpers/urls'; import { AlmKeys, AlmSettingsInstance } from '../../../types/alm-settings'; import { AppState } from '../../../types/appstate'; import { Feature } from '../../../types/features'; +import { NewCodePeriodWithCompliance } from '../../../types/types'; import AlmBindingDefinitionForm from '../../settings/components/almIntegration/AlmBindingDefinitionForm'; import AzureProjectCreate from './Azure/AzureProjectCreate'; import BitbucketCloudProjectCreate from './BitbucketCloud/BitbucketCloudProjectCreate'; import BitbucketProjectCreate from './BitbucketServer/BitbucketProjectCreate'; +import CreateProjectPageHeader from './components/CreateProjectPageHeader'; import CreateProjectModeSelection from './CreateProjectModeSelection'; import GitHubProjectCreate from './Github/GitHubProjectCreate'; import GitlabProjectCreate from './Gitlab/GitlabProjectCreate'; import ManualProjectCreate from './manual/ManualProjectCreate'; import './style.css'; -import { CreateProjectModes } from './types'; +import { CreateProjectApiCallback, CreateProjectModes } from './types'; export interface CreateProjectPageProps extends WithAvailableFeaturesProps { appState: AppState; @@ -55,7 +62,9 @@ interface State { githubSettings: AlmSettingsInstance[]; gitlabSettings: AlmSettingsInstance[]; loading: boolean; + isProjectSetupDone: boolean; creatingAlmDefinition?: AlmKeys; + selectedNcd: NewCodePeriodWithCompliance | null; } const PROJECT_MODE_FOR_ALM_KEY = { @@ -68,6 +77,8 @@ const PROJECT_MODE_FOR_ALM_KEY = { export class CreateProjectPage extends React.PureComponent<CreateProjectPageProps, State> { mounted = false; + createProjectFnRef: CreateProjectApiCallback | null = null; + state: State = { azureSettings: [], bitbucketSettings: [], @@ -75,6 +86,8 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp githubSettings: [], gitlabSettings: [], loading: true, + isProjectSetupDone: false, + selectedNcd: null, }; componentDidMount() { @@ -124,6 +137,21 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp this.props.router.push(getProjectUrl(projectKey)); }; + handleManualProjectCreate = () => { + const { selectedNcd } = this.state; + if (this.createProjectFnRef && selectedNcd) { + this.createProjectFnRef(selectedNcd.type, selectedNcd.value).then( + ({ project }) => this.handleProjectCreate(project.key), + noop + ); + } + }; + + handleProjectSetupDone = (createProject: CreateProjectApiCallback) => { + this.createProjectFnRef = createProject; + this.setState({ isProjectSetupDone: true }); + }; + handleOnCancelCreation = () => { this.setState({ creatingAlmDefinition: undefined }); }; @@ -146,6 +174,16 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp } }; + handleNcdChanged = (ncd: NewCodePeriodWithCompliance) => { + this.setState({ + selectedNcd: ncd, + }); + }; + + handleGoBack = () => { + this.setState({ isProjectSetupDone: false }); + }; + renderProjectCreation(mode?: CreateProjectModes) { const { appState: { canAdmin }, @@ -227,7 +265,7 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp return ( <ManualProjectCreate branchesEnabled={branchSupportEnabled} - onProjectCreate={this.handleProjectCreate} + onProjectSetupDone={this.handleProjectSetupDone} /> ); } @@ -251,9 +289,59 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp } } + renderNcdSelection() { + const { appState } = this.props; + const { selectedNcd } = this.state; + + return ( + <div id="project-ncd-selection"> + <CreateProjectPageHeader + title={translate('onboarding.create_project.new_code_definition.title')} + /> + + <h1 className="sw-mb-4">{translate('onboarding.create_project.new_code_definition')}</h1> + <p className="sw-mb-2"> + {translate('onboarding.create_project.new_code_definition.description')} + </p> + <p className="sw-mb-2"> + {translate('onboarding.create_project.new_code_definition.description1')} + </p> + + <p className="sw-mb-2"> + <FormattedMessage + defaultMessage={translate('onboarding.create_project.new_code_definition.description2')} + id="onboarding.create_project.new_code_definition.description2" + values={{ + link: ( + <DocLink to="/project-administration/defining-new-code/"> + {translate('onboarding.create_project.new_code_definition.description2.link')} + </DocLink> + ), + }} + /> + </p> + + <NewCodeDefinitionSelector + canAdmin={appState.canAdmin} + onNcdChanged={this.handleNcdChanged} + /> + + <div className="sw-flex sw-flex-row sw-gap-2 sw-mt-4"> + <ButtonLink onClick={this.handleGoBack}>{translate('back')}</ButtonLink> + <SubmitButton + onClick={this.handleManualProjectCreate} + disabled={!selectedNcd?.isCompliant} + > + {translate('onboarding.create_project.new_code_definition.create_project')} + </SubmitButton> + </div> + </div> + ); + } + render() { const { location } = this.props; - const { creatingAlmDefinition } = this.state; + const { creatingAlmDefinition, isProjectSetupDone } = this.state; const mode: CreateProjectModes | undefined = location.query?.mode; return ( @@ -261,7 +349,8 @@ export class CreateProjectPage extends React.PureComponent<CreateProjectPageProp <Helmet title={translate('onboarding.create_project.select_method')} titleTemplate="%s" /> <A11ySkipTarget anchor="create_project_main" /> <div className="page page-limited huge-spacer-bottom position-relative" id="create-project"> - {this.renderProjectCreation(mode)} + {isProjectSetupDone ? this.renderNcdSelection() : this.renderProjectCreation(mode)} + {creatingAlmDefinition && ( <AlmBindingDefinitionForm alm={creatingAlmDefinition} 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 new file mode 100644 index 00000000000..06bb6cb7c3e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/Manual-it.tsx @@ -0,0 +1,248 @@ +/* + * 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 userEvent from '@testing-library/user-event'; +import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'; +import * as React from 'react'; +import { byRole, byText } from 'testing-library-selector'; +import AlmSettingsServiceMock from '../../../../api/mocks/AlmSettingsServiceMock'; +import { getNewCodePeriod } from '../../../../api/newCodePeriod'; +import { mockProject } from '../../../../helpers/mocks/projects'; +import { mockAppState } from '../../../../helpers/testMocks'; +import { renderApp } from '../../../../helpers/testReactTestingUtils'; +import { NewCodePeriodSettingType } from '../../../../types/types'; +import CreateProjectPage, { CreateProjectPageProps } from '../CreateProjectPage'; +import NewCodePeriodsServiceMock from '../../../../api/mocks/NewCodePeriodsServiceMock'; + +jest.mock('../../../../api/alm-settings'); +jest.mock('../../../../api/newCodePeriod'); +jest.mock('../../../../api/components', () => ({ + ...jest.requireActual('../../../../api/components'), + setupManualProjectCreation: jest + .fn() + .mockReturnValue(() => Promise.resolve({ project: mockProject() })), + doesComponentExists: jest + .fn() + .mockImplementation(({ component }) => Promise.resolve(component === 'exists')), +})); +jest.mock('../../../../api/settings', () => ({ + getValue: jest.fn().mockResolvedValue({ value: 'main' }), +})); + +const ui = { + manualCreateProjectOption: byText('onboarding.create_project.select_method.manual'), + manualProjectHeader: byText('onboarding.create_project.setup_manually'), + displayNameField: byRole('textbox', { + name: /onboarding.create_project.display_name/, + }), + projectNextButton: byRole('button', { name: 'next' }), + newCodeDefinitionHeader: byText('onboarding.create_project.new_code_definition.title'), + newCodeDefinitionBackButton: byRole('button', { name: 'back' }), + inheritGlobalNcdRadio: byRole('radio', { name: 'new_code_definition.global_setting' }), + projectCreateButton: byRole('button', { + name: 'onboarding.create_project.new_code_definition.create_project', + }), + overrideNcdRadio: byRole('radio', { name: 'new_code_definition.specific_setting' }), + ncdOptionPreviousVersionRadio: byRole('radio', { + name: /new_code_definition.previous_version/, + }), + ncdOptionRefBranchRadio: byRole('radio', { + name: /new_code_definition.reference_branch/, + }), + ncdOptionDaysRadio: byRole('radio', { + name: /new_code_definition.number_days/, + }), + ncdOptionDaysInput: byRole('textbox', { + name: /new_code_definition.number_days.specify_days/, + }), + ncdOptionDaysInputError: byText('new_code_definition.number_days.invalid.1.90'), + ncdWarningTextAdmin: byText('new_code_definition.compliance.warning.explanation.admin'), + ncdWarningText: byText('new_code_definition.compliance.warning.explanation'), + projectDashboardText: byText('/dashboard?id=foo'), +}; + +async function fillFormAndNext(displayName: string, user: UserEvent) { + await user.click(ui.manualCreateProjectOption.get()); + + expect(ui.manualProjectHeader.get()).toBeInTheDocument(); + + await user.click(ui.displayNameField.get()); + await user.keyboard(displayName); + + expect(ui.projectNextButton.get()).toBeEnabled(); + await user.click(ui.projectNextButton.get()); +} + +let almSettingsHandler: AlmSettingsServiceMock; +let newCodePeriodHandler: NewCodePeriodsServiceMock; + +beforeAll(() => { + almSettingsHandler = new AlmSettingsServiceMock(); + newCodePeriodHandler = new NewCodePeriodsServiceMock(); +}); + +beforeEach(() => { + jest.clearAllMocks(); + almSettingsHandler.reset(); + newCodePeriodHandler.reset(); +}); + +it('should fill form and move to NCD selection and back', async () => { + const user = userEvent.setup(); + renderCreateProject(); + await fillFormAndNext('test', user); + + expect(ui.newCodeDefinitionHeader.get()).toBeInTheDocument(); + + expect(ui.newCodeDefinitionBackButton.get()).toBeInTheDocument(); + await user.click(ui.newCodeDefinitionBackButton.get()); + + expect(ui.manualProjectHeader.get()).toBeInTheDocument(); + + // TODO this must work at some point + // expect(ui.displayNameField.get()).toHaveValue('test'); +}); + +it('should select the global NCD when it is compliant', async () => { + jest + .mocked(getNewCodePeriod) + .mockResolvedValue({ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '30' }); + const user = userEvent.setup(); + renderCreateProject(); + await fillFormAndNext('test', user); + + expect(ui.newCodeDefinitionHeader.get()).toBeInTheDocument(); + expect(ui.inheritGlobalNcdRadio.get()).toBeInTheDocument(); + expect(ui.inheritGlobalNcdRadio.get()).toBeEnabled(); + expect(ui.projectCreateButton.get()).toBeDisabled(); + + await user.click(ui.inheritGlobalNcdRadio.get()); + + expect(ui.projectCreateButton.get()).toBeEnabled(); +}); + +it('global NCD option should be disabled if not compliant', async () => { + jest + .mocked(getNewCodePeriod) + .mockResolvedValue({ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '96' }); + const user = userEvent.setup(); + renderCreateProject(); + await fillFormAndNext('test', user); + + expect(ui.newCodeDefinitionHeader.get()).toBeInTheDocument(); + expect(ui.inheritGlobalNcdRadio.get()).toBeInTheDocument(); + expect(ui.inheritGlobalNcdRadio.get()).toHaveClass('disabled'); + expect(ui.projectCreateButton.get()).toBeDisabled(); +}); + +it.each([ + { canAdmin: true, message: ui.ncdWarningTextAdmin }, + { canAdmin: false, message: ui.ncdWarningText }, +])( + 'should show warning message when global NCD is not compliant', + async ({ canAdmin, message }) => { + jest + .mocked(getNewCodePeriod) + .mockResolvedValue({ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '96' }); + const user = userEvent.setup(); + renderCreateProject({ appState: mockAppState({ canAdmin }) }); + await fillFormAndNext('test', user); + + expect(message.get()).toBeInTheDocument(); + } +); + +it.each([ui.ncdOptionRefBranchRadio, ui.ncdOptionPreviousVersionRadio])( + 'should override the global NCD and pick a compliant NCD', + async (option) => { + jest + .mocked(getNewCodePeriod) + .mockResolvedValue({ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '96' }); + const user = userEvent.setup(); + renderCreateProject(); + await fillFormAndNext('test', user); + + expect(ui.newCodeDefinitionHeader.get()).toBeInTheDocument(); + expect(ui.inheritGlobalNcdRadio.get()).toBeInTheDocument(); + expect(ui.inheritGlobalNcdRadio.get()).toHaveClass('disabled'); + expect(ui.projectCreateButton.get()).toBeDisabled(); + expect(ui.overrideNcdRadio.get()).not.toHaveClass('disabled'); + expect(option.get()).toHaveClass('disabled'); + + await user.click(ui.overrideNcdRadio.get()); + expect(option.get()).not.toHaveClass('disabled'); + + await user.click(option.get()); + + expect(ui.projectCreateButton.get()).toBeEnabled(); + } +); + +it('number of days should show error message if value is not a number', async () => { + jest + .mocked(getNewCodePeriod) + .mockResolvedValue({ type: NewCodePeriodSettingType.NUMBER_OF_DAYS, value: '60' }); + const user = userEvent.setup(); + renderCreateProject(); + await fillFormAndNext('test', user); + + expect(ui.projectCreateButton.get()).toBeDisabled(); + expect(ui.overrideNcdRadio.get()).not.toHaveClass('disabled'); + expect(ui.ncdOptionDaysRadio.get()).toHaveClass('disabled'); + + await user.click(ui.overrideNcdRadio.get()); + expect(ui.ncdOptionDaysRadio.get()).not.toHaveClass('disabled'); + + await user.click(ui.ncdOptionDaysRadio.get()); + + expect(ui.ncdOptionDaysInput.get()).toBeInTheDocument(); + expect(ui.ncdOptionDaysInput.get()).toHaveValue('30'); + expect(ui.projectCreateButton.get()).toBeEnabled(); + + await user.click(ui.ncdOptionDaysInput.get()); + await user.keyboard('abc'); + + expect(ui.ncdOptionDaysInputError.get()).toBeInTheDocument(); + expect(ui.projectCreateButton.get()).toBeDisabled(); + + await user.clear(ui.ncdOptionDaysInput.get()); + await user.click(ui.ncdOptionDaysInput.get()); + await user.keyboard('30'); + + expect(ui.ncdOptionDaysInputError.query()).not.toBeInTheDocument(); + expect(ui.projectCreateButton.get()).toBeEnabled(); +}); + +it('the project onboarding page should be displayed when the project is created', async () => { + newCodePeriodHandler.setNewCodePeriod({ type: NewCodePeriodSettingType.NUMBER_OF_DAYS }); + const user = userEvent.setup(); + renderCreateProject(); + await fillFormAndNext('testing', user); + + await user.click(ui.overrideNcdRadio.get()); + + expect(ui.projectCreateButton.get()).toBeEnabled(); + await user.click(ui.projectCreateButton.get()); + + expect(await ui.projectDashboardText.find()).toBeInTheDocument(); +}); + +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 98a05291a2a..8bcecd0dd45 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 @@ -20,13 +20,17 @@ 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 { byRole } from 'testing-library-selector'; +import { doesComponentExists } from '../../../../api/components'; import { renderComponent } from '../../../../helpers/testReactTestingUtils'; import ManualProjectCreate from '../manual/ManualProjectCreate'; +const ui = { + nextButton: byRole('button', { name: 'next' }), +}; + jest.mock('../../../../api/components', () => ({ - createProject: jest.fn().mockResolvedValue({ project: { key: 'bar', name: 'Bar' } }), + setupManualProjectCreation: jest.fn(), doesComponentExists: jest .fn() .mockImplementation(({ component }) => Promise.resolve(component === 'exists')), @@ -36,15 +40,8 @@ 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 () => { @@ -68,7 +65,7 @@ it('should validate form input', async () => { expect( screen.getByRole('textbox', { name: 'onboarding.create_project.project_key field_required' }) ).toHaveValue('test'); - expect(screen.getByRole('button', { name: 'set_up' })).toBeEnabled(); + expect(ui.nextButton.get()).toBeEnabled(); // Sanitize the key await user.click( @@ -93,7 +90,7 @@ it('should validate form input', async () => { expect( screen.getByText('onboarding.create_project.display_name.error.empty') ).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'set_up' })).toBeDisabled(); + expect(ui.nextButton.get()).toBeDisabled(); // Only key await user.click( @@ -142,8 +139,8 @@ it('should validate form input', async () => { it('should submit form input', async () => { const user = userEvent.setup(); - const onProjectCreate = jest.fn(); - renderManualProjectCreate({ onProjectCreate }); + const onProjectSetupDone = jest.fn(); + renderManualProjectCreate({ onProjectSetupDone }); // All input valid await user.click( @@ -152,38 +149,14 @@ it('should submit form input', async () => { }) ); await user.keyboard('test'); - await user.click(screen.getByRole('button', { name: 'set_up' })); - expect(createProject).toHaveBeenCalledWith({ - name: 'test', - project: 'test', - mainBranch: 'main', - }); - expect(onProjectCreate).toHaveBeenCalled(); -}); - -it('should handle create failure', async () => { - const user = userEvent.setup(); - (createProject as jest.Mock).mockRejectedValueOnce({}); - const onProjectCreate = jest.fn(); - renderManualProjectCreate({ onProjectCreate }); - - // All input valid - await user.click( - await screen.findByRole('textbox', { - name: 'onboarding.create_project.display_name field_required', - }) - ); - await user.keyboard('test'); - await user.click(screen.getByRole('button', { name: 'set_up' })); - - expect(onProjectCreate).not.toHaveBeenCalled(); + await user.click(ui.nextButton.get()); + expect(onProjectSetupDone).toHaveBeenCalled(); }); it('should handle component exists failure', async () => { const user = userEvent.setup(); - (doesComponentExists as jest.Mock).mockRejectedValueOnce({}); - const onProjectCreate = jest.fn(); - renderManualProjectCreate({ onProjectCreate }); + jest.mocked(doesComponentExists).mockRejectedValueOnce({}); + renderManualProjectCreate(); // All input valid await user.click( @@ -199,6 +172,6 @@ it('should handle component exists failure', async () => { function renderManualProjectCreate(props: Partial<ManualProjectCreate['props']> = {}) { renderComponent( - <ManualProjectCreate branchesEnabled={false} onProjectCreate={jest.fn()} {...props} /> + <ManualProjectCreate branchesEnabled={false} onProjectSetupDone={jest.fn()} {...props} /> ); } diff --git a/server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx index f2c681e6164..ef344ed928d 100644 --- a/server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx @@ -21,26 +21,25 @@ 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 { doesComponentExists, setupManualProjectCreation } 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'; +import { CreateProjectApiCallback } from '../types'; interface Props { branchesEnabled: boolean; - onProjectCreate: (projectKey: string) => void; + onProjectSetupDone: (createProject: CreateProjectApiCallback) => void; } interface State { @@ -54,7 +53,6 @@ interface State { mainBranchName: string; mainBranchNameError?: string; mainBranchNameTouched: boolean; - submitting: boolean; } const DEBOUNCE_DELAY = 250; @@ -69,7 +67,6 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat this.state = { projectKey: '', projectName: '', - submitting: false, projectKeyTouched: false, projectNameTouched: false, mainBranchName: 'main', @@ -132,18 +129,12 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat event.preventDefault(); const { projectKey, projectName, mainBranchName } = this.state; if (this.canSubmit(this.state)) { - this.setState({ submitting: true }); - createProject({ - project: projectKey, - name: (projectName || projectKey).trim(), - mainBranch: mainBranchName, - }).then( - ({ project }) => this.props.onProjectCreate(project.key), - () => { - if (this.mounted) { - this.setState({ submitting: false }); - } - } + this.props.onProjectSetupDone( + setupManualProjectCreation({ + project: projectKey, + name: (projectName || projectKey).trim(), + mainBranch: mainBranchName, + }) ); } }; @@ -221,7 +212,6 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat mainBranchName, mainBranchNameError, mainBranchNameTouched, - submitting, } = this.state; const { branchesEnabled } = this.props; @@ -235,8 +225,6 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat <> <CreateProjectPageHeader title={translate('onboarding.create_project.setup_manually')} /> - <InstanceNewCodeDefinitionComplianceWarning /> - <form id="create-project-manual" onSubmit={this.handleFormSubmit}> <MandatoryFieldsExplanation className="big-spacer-bottom" /> @@ -308,10 +296,7 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat /> </ValidationInput> - <SubmitButton disabled={!this.canSubmit(this.state) || submitting}> - {translate('set_up')} - </SubmitButton> - <DeferredSpinner className="spacer-left" loading={submitting} /> + <SubmitButton disabled={!this.canSubmit(this.state)}>{translate('next')}</SubmitButton> </form> {branchesEnabled && ( diff --git a/server/sonar-web/src/main/js/apps/create/project/types.ts b/server/sonar-web/src/main/js/apps/create/project/types.ts index f8c9fc9b054..ddd7c3700a4 100644 --- a/server/sonar-web/src/main/js/apps/create/project/types.ts +++ b/server/sonar-web/src/main/js/apps/create/project/types.ts @@ -17,6 +17,9 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ProjectBase } from '../../../api/components'; +import { NewCodePeriodSettingType } from '../../../types/types'; + export enum CreateProjectModes { Manual = 'manual', AzureDevOps = 'azure', @@ -25,3 +28,8 @@ export enum CreateProjectModes { GitHub = 'github', GitLab = 'gitlab', } + +export type CreateProjectApiCallback = ( + newCodeDefinitionType?: NewCodePeriodSettingType, + newCodeDefinitionValue?: string +) => Promise<{ project: ProjectBase }>; |