diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2019-08-27 12:11:41 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-09-10 20:21:02 +0200 |
commit | 2fba950fd31175b42a3b097ff58ddda9c76fddfa (patch) | |
tree | 8df1398f436efba2af3352efab33cb81a255237b | |
parent | f1ba685b86d6435ae21960ee6962b83c447dd3da (diff) | |
download | sonarqube-2fba950fd31175b42a3b097ff58ddda9c76fddfa.tar.gz sonarqube-2fba950fd31175b42a3b097ff58ddda9c76fddfa.zip |
SONAR-12360 Improve project creation validation
8 files changed, 269 insertions, 339 deletions
diff --git a/server/sonar-web/src/main/js/apps/create/components/ProjectKeyInput.tsx b/server/sonar-web/src/main/js/apps/create/components/ProjectKeyInput.tsx deleted file mode 100644 index 0fd857b8b2d..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/ProjectKeyInput.tsx +++ /dev/null @@ -1,129 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as classNames from 'classnames'; -import { debounce } from 'lodash'; -import * as React from 'react'; -import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput'; -import { translate } from 'sonar-ui-common/helpers/l10n'; -import { doesComponentExists } from '../../../api/components'; - -interface Props { - className?: string; - value: string; - onChange: (value: string | undefined) => void; -} - -interface State { - error?: string; - touched: boolean; - validating: boolean; -} - -export default class ProjectKeyInput extends React.PureComponent<Props, State> { - mounted = false; - constructor(props: Props) { - super(props); - this.state = { error: undefined, touched: false, validating: false }; - this.checkFreeKey = debounce(this.checkFreeKey, 250); - } - - componentDidMount() { - this.mounted = true; - if (this.props.value) { - this.validateKey(this.props.value); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - checkFreeKey = (key: string) => { - this.setState({ validating: true }); - return doesComponentExists({ component: key }) - .then(alreadyExist => { - if (this.mounted && key === this.props.value) { - if (!alreadyExist) { - this.setState({ error: undefined, validating: false }); - } else { - this.setState({ - error: translate('onboarding.create_project.project_key.taken'), - touched: true, - validating: false - }); - } - } - }) - .catch(() => { - if (this.mounted && key === this.props.value) { - this.setState({ error: undefined, validating: false }); - } - }); - }; - - handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const { value } = event.currentTarget; - this.setState({ touched: true }); - this.validateKey(value); - this.props.onChange(value); - }; - - validateKey(key: string) { - if (key.length > 400 || !/^[\w-.:]*[a-zA-Z]+[\w-.:]*$/.test(key)) { - this.setState({ - error: translate('onboarding.create_project.project_key.error'), - touched: true - }); - } else { - this.checkFreeKey(key); - } - } - - render() { - const isInvalid = this.state.touched && this.state.error !== undefined; - const isValid = this.state.touched && !this.state.validating && this.state.error === undefined; - return ( - <ValidationInput - className={this.props.className} - description={translate('onboarding.create_project.project_key.description')} - error={this.state.error} - help={translate('onboarding.create_project.project_key.help')} - id="project-key" - isInvalid={isInvalid} - isValid={isValid} - label={translate('onboarding.create_project.project_key')} - required={true}> - <input - autoFocus={true} - className={classNames('input-super-large', { - 'is-invalid': isInvalid, - 'is-valid': isValid - })} - id="project-key" - maxLength={400} - minLength={1} - onChange={this.handleChange} - type="text" - value={this.props.value} - /> - </ValidationInput> - ); - } -} diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/ProjectKeyInput-test.tsx b/server/sonar-web/src/main/js/apps/create/components/__tests__/ProjectKeyInput-test.tsx deleted file mode 100644 index 4f0214d7e9a..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/ProjectKeyInput-test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2019 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; -import ProjectKeyInput from '../ProjectKeyInput'; - -jest.useFakeTimers(); - -jest.mock('../../../../api/components', () => ({ - doesComponentExists: jest - .fn() - .mockImplementation(({ component }) => Promise.resolve(component === 'exists')) -})); - -it('should render correctly', async () => { - const wrapper = shallow(<ProjectKeyInput onChange={jest.fn()} value="key" />); - expect(wrapper).toMatchSnapshot(); - wrapper.setState({ touched: true }); - await waitAndUpdate(wrapper); - expect(wrapper.find('ValidationInput').prop('isValid')).toBe(true); -}); - -it('should not display any status when the key is not defined', async () => { - const wrapper = shallow(<ProjectKeyInput onChange={jest.fn()} value="" />); - await waitAndUpdate(wrapper); - expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(false); - expect(wrapper.find('ValidationInput').prop('isValid')).toBe(false); -}); - -it('should have an error when the key is invalid', async () => { - const wrapper = shallow(<ProjectKeyInput onChange={jest.fn()} value="KEy-with#speci@l_char" />); - await waitAndUpdate(wrapper); - expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(true); -}); - -it('should have an error when the key already exists', async () => { - const wrapper = shallow(<ProjectKeyInput onChange={jest.fn()} value="exists" />); - await waitAndUpdate(wrapper); - - jest.runAllTimers(); - await new Promise(setImmediate); - expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(true); -}); - -it('should handle Change', async () => { - const onChange = jest.fn(); - const wrapper = shallow(<ProjectKeyInput onChange={onChange} value="" />); - await waitAndUpdate(wrapper); - - wrapper.find('input').simulate('change', { currentTarget: { value: 'key' } }); - - expect(wrapper.state('touched')).toBe(true); - expect(onChange).toBeCalledWith('key'); -}); - -it('should ignore promise return if value has been changed in the meantime', async () => { - const onChange = (value: string) => wrapper.setProps({ value }); - const wrapper = shallow(<ProjectKeyInput onChange={onChange} value="" />); - await waitAndUpdate(wrapper); - - wrapper.find('input').simulate('change', { currentTarget: { value: 'exists' } }); - wrapper.find('input').simulate('change', { currentTarget: { value: 'exists%' } }); - - jest.runAllTimers(); - await new Promise(setImmediate); - - expect(wrapper.state('touched')).toBe(true); - expect(wrapper.state('error')).toBe('onboarding.create_project.project_key.error'); -}); diff --git a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectKeyInput-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectKeyInput-test.tsx.snap deleted file mode 100644 index 58f34b9f7d4..00000000000 --- a/server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectKeyInput-test.tsx.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -<ValidationInput - description="onboarding.create_project.project_key.description" - help="onboarding.create_project.project_key.help" - id="project-key" - isInvalid={false} - isValid={false} - label="onboarding.create_project.project_key" - required={true} -> - <input - autoFocus={true} - className="input-super-large" - id="project-key" - maxLength={400} - minLength={1} - onChange={[Function]} - type="text" - value="key" - /> -</ValidationInput> -`; 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 index 54c67f4766d..5897ef1a9fa 100644 --- a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.css +++ b/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.css @@ -18,7 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ .manual-project-create { - max-width: 650px; + max-width: 700px; } .manual-project-create .visibility-select-option { diff --git a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx b/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx index 40513c90d50..485da00e2f8 100644 --- a/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx @@ -18,15 +18,15 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as classNames from 'classnames'; +import { debounce } from 'lodash'; import * as React from 'react'; import { SubmitButton } from 'sonar-ui-common/components/controls/buttons'; -import HelpTooltip from 'sonar-ui-common/components/controls/HelpTooltip'; +import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput'; import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner'; import { translate } from 'sonar-ui-common/helpers/l10n'; -import { createProject } from '../../../api/components'; +import { createProject, doesComponentExists } from '../../../api/components'; import VisibilitySelector from '../../../components/common/VisibilitySelector'; import { isSonarCloud } from '../../../helpers/system'; -import ProjectKeyInput from '../components/ProjectKeyInput'; import UpgradeOrganizationBox from '../components/UpgradeOrganizationBox'; import './ManualProjectCreate.css'; import OrganizationInput from './OrganizationInput'; @@ -42,10 +42,14 @@ interface Props { interface State { projectName: string; projectNameChanged: boolean; + projectNameError?: string; projectKey: string; + projectKeyError?: string; selectedOrganization?: T.Organization; selectedVisibility?: T.Visibility; submitting: boolean; + touched: boolean; + validating: boolean; } type ValidState = State & Required<Pick<State, 'projectKey' | 'projectName'>>; @@ -60,8 +64,11 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat projectName: '', projectNameChanged: false, selectedOrganization: this.getInitialSelectedOrganization(props), - submitting: false + submitting: false, + touched: false, + validating: false }; + this.checkFreeKey = debounce(this.checkFreeKey, 250); } componentDidMount() { @@ -72,13 +79,46 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat this.mounted = false; } + checkFreeKey = (key: string) => { + return doesComponentExists({ component: key }) + .then(alreadyExist => { + if (this.mounted && key === this.state.projectKey) { + if (!alreadyExist) { + this.setState({ projectKeyError: undefined, validating: false }); + } else { + this.setState({ + projectKeyError: translate('onboarding.create_project.project_key.taken'), + touched: true, + validating: false + }); + } + } + }) + .catch(() => { + if (this.mounted && key === this.state.projectKey) { + this.setState({ projectKeyError: undefined, validating: false }); + } + }); + }; + canChoosePrivate = (selectedOrganization: T.Organization | undefined) => { return Boolean(selectedOrganization && selectedOrganization.subscription === 'PAID'); }; canSubmit(state: State): state is ValidState { + const { + projectKey, + projectKeyError, + projectName, + projectNameError, + selectedOrganization + } = state; return Boolean( - state.projectKey && state.projectName && (!isSonarCloud() || state.selectedOrganization) + projectKeyError === undefined && + projectNameError === undefined && + projectKey.length > 0 && + projectName.length > 0 && + (!isSonarCloud() || selectedOrganization) ); } @@ -151,24 +191,67 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat ); }; - handleProjectNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { - const projectName = event.currentTarget.value; - this.setState({ projectName, projectNameChanged: true }); + handleProjectKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const projectKey = event.currentTarget.value || ''; + const projectKeyError = this.validateKey(projectKey); + + this.setState(prevState => { + const projectName = prevState.projectNameChanged ? prevState.projectName : projectKey; + return { + projectKey, + projectKeyError, + projectName, + projectNameError: this.validateName(projectName), + touched: true, + validating: projectKeyError === undefined + }; + }); + + if (projectKeyError === undefined) { + this.checkFreeKey(projectKey); + } }; - handleProjectKeyChange = (projectKey: string) => { - this.setState(state => ({ - projectKey, - projectName: state.projectNameChanged ? state.projectName : projectKey || '' - })); + handleProjectNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { + const projectName = event.currentTarget.value; + this.setState({ + projectName, + projectNameChanged: true, + projectNameError: this.validateName(projectName) + }); }; handleVisibilityChange = (selectedVisibility: T.Visibility) => { this.setState({ selectedVisibility }); }; + validateKey = (projectKey: string) => { + return projectKey.length > 400 || !/^[\w-.:]*[a-zA-Z]+[\w-.:]*$/.test(projectKey) + ? translate('onboarding.create_project.project_key.error') + : undefined; + }; + + validateName = (projectName: string) => { + return projectName.length === 0 || projectName.length > 255 + ? translate('onboarding.create_project.display_name.error') + : undefined; + }; + render() { - const { selectedOrganization, submitting } = this.state; + const { + projectKey, + projectKeyError, + projectName, + projectNameError, + selectedOrganization, + submitting, + touched, + validating + } = this.state; + const projectKeyIsInvalid = touched && projectKeyError !== undefined; + const projectKeyIsValid = touched && !validating && projectKeyError === undefined; + const projectNameIsInvalid = touched && projectNameError !== undefined; + const projectNameIsValid = touched && projectNameError === undefined; const canChoosePrivate = this.canChoosePrivate(selectedOrganization); return ( @@ -182,37 +265,56 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat organizations={this.props.userOrganizations} /> )} - <ProjectKeyInput + + <ValidationInput className="form-field" - onChange={this.handleProjectKeyChange} - value={this.state.projectKey} - /> - <div className="form-field"> - <label htmlFor="project-name"> - <span className="text-middle"> - <strong>{translate('onboarding.create_project.display_name')}</strong> - <em className="mandatory">*</em> - </span> - <HelpTooltip - className="spacer-left" - overlay={translate('onboarding.create_project.display_name.help')} - /> - </label> - <div className="little-spacer-top spacer-bottom"> - <input - className="input-super-large" - id="project-name" - maxLength={255} - minLength={1} - onChange={this.handleProjectNameChange} - type="text" - value={this.state.projectName} - /> - </div> - <div className="note abs-width-400"> - {translate('onboarding.create_project.display_name.description')} - </div> - </div> + description={translate('onboarding.create_project.project_key.description')} + error={projectKeyError} + help={translate('onboarding.create_project.project_key.help')} + id="project-key" + isInvalid={projectKeyIsInvalid} + isValid={projectKeyIsValid} + label={translate('onboarding.create_project.project_key')} + required={true}> + <input + autoFocus={true} + className={classNames('input-super-large', { + 'is-invalid': projectKeyIsInvalid, + 'is-valid': projectKeyIsValid + })} + id="project-key" + maxLength={400} + minLength={1} + onChange={this.handleProjectKeyChange} + type="text" + value={projectKey} + /> + </ValidationInput> + + <ValidationInput + className="form-field" + description={translate('onboarding.create_project.display_name.description')} + error={projectNameError} + help={translate('onboarding.create_project.display_name.help')} + id="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={255} + minLength={1} + onChange={this.handleProjectNameChange} + type="text" + value={projectName} + /> + </ValidationInput> + {isSonarCloud() && selectedOrganization && ( <div className={classNames('visibility-select-wrapper', { @@ -226,6 +328,7 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat /> </div> )} + <SubmitButton disabled={!this.canSubmit(this.state) || submitting}> {translate('set_up')} </SubmitButton> 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 d0815b405c7..93ec3cfb70f 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 @@ -17,6 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +/* eslint-disable sonarjs/no-duplicate-string */ import { shallow } from 'enzyme'; import * as React from 'react'; import { change, submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils'; @@ -24,7 +25,10 @@ import { createProject } from '../../../../api/components'; import ManualProjectCreate from '../ManualProjectCreate'; jest.mock('../../../../api/components', () => ({ - createProject: jest.fn().mockResolvedValue({ project: { key: 'bar', name: 'Bar' } }) + createProject: jest.fn().mockResolvedValue({ project: { key: 'bar', name: 'Bar' } }), + doesComponentExists: jest + .fn() + .mockImplementation(({ component }) => Promise.resolve(component === 'exists')) })); jest.mock('../../../../helpers/system', () => ({ @@ -32,19 +36,19 @@ jest.mock('../../../../helpers/system', () => ({ })); beforeEach(() => { - (createProject as jest.Mock<any>).mockClear(); + jest.clearAllMocks(); }); it('should render correctly', () => { - expect(getWrapper()).toMatchSnapshot(); + expect(shallowRender()).toMatchSnapshot(); }); it('should correctly create a public project', async () => { const onProjectCreate = jest.fn(); - const wrapper = getWrapper({ onProjectCreate }); + const wrapper = shallowRender({ onProjectCreate }); wrapper.find('withRouter(OrganizationInput)').prop<Function>('onChange')({ key: 'foo' }); - change(wrapper.find('ProjectKeyInput'), 'bar'); + change(wrapper.find('input#project-key'), 'bar'); change(wrapper.find('input#project-name'), 'Bar'); expect(wrapper.find('SubmitButton').prop('disabled')).toBe(false); @@ -62,10 +66,10 @@ it('should correctly create a public project', async () => { it('should correctly create a private project', async () => { const onProjectCreate = jest.fn(); - const wrapper = getWrapper({ onProjectCreate }); + const wrapper = shallowRender({ onProjectCreate }); wrapper.find('withRouter(OrganizationInput)').prop<Function>('onChange')({ key: 'bar' }); - change(wrapper.find('ProjectKeyInput'), 'bar'); + change(wrapper.find('input#project-key'), 'bar'); change(wrapper.find('input#project-name'), 'Bar'); submit(wrapper.find('form')); @@ -80,8 +84,77 @@ it('should correctly create a private project', async () => { expect(onProjectCreate).toBeCalledWith(['bar']); }); -function getWrapper(props = {}) { - return shallow( +it('should not display any status when the key is not defined', () => { + const wrapper = shallowRender(); + const projectKeyInput = wrapper.find('ValidationInput').first(); + expect(projectKeyInput.prop('isInvalid')).toBe(false); + expect(projectKeyInput.prop('isValid')).toBe(false); +}); + +it('should not display any status when the name is not defined', () => { + const wrapper = shallowRender(); + const projectKeyInput = wrapper.find('ValidationInput').last(); + expect(projectKeyInput.prop('isInvalid')).toBe(false); + expect(projectKeyInput.prop('isValid')).toBe(false); +}); + +it('should have an error when the key is invalid', () => { + const wrapper = shallowRender(); + change(wrapper.find('input#project-key'), 'KEy-with#speci@l_char'); + expect( + wrapper + .find('ValidationInput') + .first() + .prop('isInvalid') + ).toBe(true); +}); + +it('should have an error when the key already exists', async () => { + const wrapper = shallowRender(); + change(wrapper.find('input#project-key'), 'exists'); + + await waitAndUpdate(wrapper); + expect( + wrapper + .find('ValidationInput') + .first() + .prop('isInvalid') + ).toBe(true); +}); + +it('should ignore promise return if value has been changed in the meantime', async () => { + const wrapper = shallowRender(); + + change(wrapper.find('input#project-key'), 'exists'); + change(wrapper.find('input#project-key'), 'exists%'); + + await waitAndUpdate(wrapper); + + expect(wrapper.state('touched')).toBe(true); + expect(wrapper.state('projectKeyError')).toBe('onboarding.create_project.project_key.error'); +}); + +it('should autofill the name based on the key', () => { + const wrapper = shallowRender(); + change(wrapper.find('input#project-key'), 'bar'); + expect(wrapper.find('input#project-name').prop('value')).toBe('bar'); +}); + +it('should have an error when the name is empty', () => { + const wrapper = shallowRender(); + change(wrapper.find('input#project-key'), 'bar'); + change(wrapper.find('input#project-name'), ''); + expect( + wrapper + .find('ValidationInput') + .last() + .prop('isInvalid') + ).toBe(true); + expect(wrapper.state('projectNameError')).toBe('onboarding.create_project.display_name.error'); +}); + +function shallowRender(props: Partial<ManualProjectCreate['props']> = {}) { + return shallow<ManualProjectCreate>( <ManualProjectCreate currentUser={{ groups: [], isLoggedIn: true, login: 'foo', name: 'Foo', scmAccounts: [] }} fetchMyOrganizations={jest.fn()} diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap index 3f8d2aaee08..a68eafd3480 100644 --- a/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap @@ -28,53 +28,47 @@ exports[`should render correctly 1`] = ` ] } /> - <ProjectKeyInput + <ValidationInput className="form-field" - onChange={[Function]} - value="" - /> - <div + description="onboarding.create_project.project_key.description" + help="onboarding.create_project.project_key.help" + id="project-key" + isInvalid={false} + isValid={false} + label="onboarding.create_project.project_key" + required={true} + > + <input + autoFocus={true} + className="input-super-large" + id="project-key" + maxLength={400} + minLength={1} + onChange={[Function]} + type="text" + value="" + /> + </ValidationInput> + <ValidationInput className="form-field" + description="onboarding.create_project.display_name.description" + help="onboarding.create_project.display_name.help" + id="project-name" + isInvalid={false} + isValid={false} + label="onboarding.create_project.display_name" + required={true} > - <label - htmlFor="project-name" - > - <span - className="text-middle" - > - <strong> - onboarding.create_project.display_name - </strong> - <em - className="mandatory" - > - * - </em> - </span> - <HelpTooltip - className="spacer-left" - overlay="onboarding.create_project.display_name.help" - /> - </label> - <div - className="little-spacer-top spacer-bottom" - > - <input - className="input-super-large" - id="project-name" - maxLength={255} - minLength={1} - onChange={[Function]} - type="text" - value="" - /> - </div> - <div - className="note abs-width-400" - > - onboarding.create_project.display_name.description - </div> - </div> + <input + className="input-super-large" + id="project-name" + maxLength={255} + minLength={1} + onChange={[Function]} + type="text" + value="" + /> + </ValidationInput> <SubmitButton disabled={true} > 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 d7c8865ddcc..9bb347d45f2 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -2857,7 +2857,7 @@ onboarding.create_project.project_key.error=The provided value doesn't match the onboarding.create_project.project_key.help=Your project key is a unique identifier for your project. If you are using Maven, make sure the key matches the "groupId:artifactId" format. onboarding.create_project.project_key.taken=This project key is already taken. onboarding.create_project.display_name=Display name -onboarding.create_project.display_name.error=The provided value doesn't match the expected format. +onboarding.create_project.display_name.error=The display name is required. onboarding.create_project.display_name.description=Up to 255 characters onboarding.create_project.display_name.help=Some scanners might override the value you provide. onboarding.create_project.repository_imported=Already imported: {link} |