return getJSON('/api/components/tree', data).catch(throwGlobalError); | return getJSON('/api/components/tree', data).catch(throwGlobalError); | ||||
} | } | ||||
export function doesComponentExists( | |||||
data: { component: string } & BranchParameters | |||||
): Promise<boolean> { | |||||
return getJSON('/api/components/show', data).then( | |||||
({ component }) => component !== undefined, | |||||
() => false | |||||
); | |||||
} | |||||
export function getComponentShow(data: { component: string } & BranchParameters): Promise<any> { | export function getComponentShow(data: { component: string } & BranchParameters): Promise<any> { | ||||
return getJSON('/api/components/show', data).catch(throwGlobalError); | return getJSON('/api/components/show', data).catch(throwGlobalError); | ||||
} | } |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2018 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 * as classNames from 'classnames'; | |||||
import { debounce } from 'lodash'; | |||||
import ValidationInput from '../../../components/controls/ValidationInput'; | |||||
import { doesComponentExists } from '../../../api/components'; | |||||
import { translate } from '../../../helpers/l10n'; | |||||
interface Props { | |||||
className?: string; | |||||
initialValue?: string; | |||||
onChange: (value: string | undefined) => void; | |||||
} | |||||
interface State { | |||||
editing: boolean; | |||||
error?: string; | |||||
touched: boolean; | |||||
validating: boolean; | |||||
value: string; | |||||
} | |||||
export default class ProjectKeyInput extends React.PureComponent<Props, State> { | |||||
mounted = false; | |||||
constructor(props: Props) { | |||||
super(props); | |||||
this.state = { error: undefined, editing: false, touched: false, validating: false, value: '' }; | |||||
this.checkFreeKey = debounce(this.checkFreeKey, 250); | |||||
} | |||||
componentDidMount() { | |||||
this.mounted = true; | |||||
if (this.props.initialValue !== undefined) { | |||||
this.setState({ value: this.props.initialValue }); | |||||
this.validateKey(this.props.initialValue); | |||||
} | |||||
} | |||||
componentWillUnmount() { | |||||
this.mounted = false; | |||||
} | |||||
checkFreeKey = (key: string) => { | |||||
this.setState({ validating: true }); | |||||
return doesComponentExists({ component: key }) | |||||
.then(alreadyExist => { | |||||
if (this.mounted) { | |||||
if (!alreadyExist) { | |||||
this.setState({ error: undefined, validating: false }); | |||||
this.props.onChange(key); | |||||
} else { | |||||
this.setState({ | |||||
error: translate('onboarding.create_project.project_key.taken'), | |||||
touched: true, | |||||
validating: false | |||||
}); | |||||
this.props.onChange(undefined); | |||||
} | |||||
} | |||||
}) | |||||
.catch(() => { | |||||
if (this.mounted) { | |||||
this.setState({ error: undefined, validating: false }); | |||||
this.props.onChange(key); | |||||
} | |||||
}); | |||||
}; | |||||
handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||||
const { value } = event.currentTarget; | |||||
this.setState({ touched: true, value }); | |||||
this.validateKey(value); | |||||
}; | |||||
handleBlur = () => { | |||||
this.setState({ editing: false }); | |||||
}; | |||||
handleFocus = () => { | |||||
this.setState({ editing: true }); | |||||
}; | |||||
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 | |||||
}); | |||||
this.props.onChange(undefined); | |||||
} else { | |||||
this.checkFreeKey(key); | |||||
} | |||||
} | |||||
render() { | |||||
const isInvalid = this.state.touched && !this.state.editing && 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} | |||||
onBlur={this.handleBlur} | |||||
onChange={this.handleChange} | |||||
onFocus={this.handleFocus} | |||||
type="text" | |||||
value={this.state.value} | |||||
/> | |||||
</ValidationInput> | |||||
); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2018 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 * as classNames from 'classnames'; | |||||
import ValidationInput from '../../../components/controls/ValidationInput'; | |||||
import { translate } from '../../../helpers/l10n'; | |||||
interface Props { | |||||
className?: string; | |||||
initialValue?: string; | |||||
onChange: (value: string | undefined) => void; | |||||
} | |||||
interface State { | |||||
editing: boolean; | |||||
error?: string; | |||||
touched: boolean; | |||||
value: string; | |||||
} | |||||
export default class ProjectNameInput extends React.PureComponent<Props, State> { | |||||
state: State = { error: undefined, editing: false, touched: false, value: '' }; | |||||
componentDidMount() { | |||||
if (this.props.initialValue) { | |||||
const error = this.validateName(this.props.initialValue); | |||||
this.setState({ error, touched: Boolean(error), value: this.props.initialValue }); | |||||
} | |||||
} | |||||
handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||||
const { value } = event.currentTarget; | |||||
const error = this.validateName(value); | |||||
this.setState({ error, touched: true, value }); | |||||
this.props.onChange(error === undefined ? value : undefined); | |||||
}; | |||||
handleBlur = () => { | |||||
this.setState({ editing: false }); | |||||
}; | |||||
handleFocus = () => { | |||||
this.setState({ editing: true }); | |||||
}; | |||||
validateName(name: string) { | |||||
if (name.length > 255) { | |||||
return translate('onboarding.create_project.display_name.error'); | |||||
} | |||||
return undefined; | |||||
} | |||||
render() { | |||||
const isInvalid = this.state.touched && !this.state.editing && this.state.error !== undefined; | |||||
const isValid = this.state.touched && this.state.error === undefined && this.state.value !== ''; | |||||
return ( | |||||
<ValidationInput | |||||
className={this.props.className} | |||||
description={translate('onboarding.create_project.display_name.description')} | |||||
error={this.state.error} | |||||
id="project-name" | |||||
isInvalid={isInvalid} | |||||
isValid={isValid} | |||||
label={translate('onboarding.create_project.display_name')} | |||||
required={true}> | |||||
<input | |||||
className={classNames('input-super-large', { | |||||
'is-invalid': isInvalid, | |||||
'is-valid': isValid | |||||
})} | |||||
id="project-name" | |||||
maxLength={500} | |||||
minLength={1} | |||||
onBlur={this.handleBlur} | |||||
onChange={this.handleChange} | |||||
onFocus={this.handleFocus} | |||||
required={true} | |||||
type="text" | |||||
value={this.state.value} | |||||
/> | |||||
</ValidationInput> | |||||
); | |||||
} | |||||
} |
expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot(); | expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot(); | ||||
}); | }); | ||||
it('should have an error when description is too long', () => { | |||||
it('should have an error when name is too long', () => { | |||||
expect( | expect( | ||||
shallow(<OrganizationNameInput initialValue={'x'.repeat(256)} onChange={jest.fn()} />) | shallow(<OrganizationNameInput initialValue={'x'.repeat(256)} onChange={jest.fn()} />) | ||||
.find('ValidationInput') | .find('ValidationInput') |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; | |||||
import ProjectKeyInput from '../ProjectKeyInput'; | |||||
import { doesComponentExists } from '../../../../api/components'; | |||||
import { waitAndUpdate } from '../../../../helpers/testUtils'; | |||||
jest.mock('../../../../api/components', () => ({ | |||||
doesComponentExists: jest.fn().mockResolvedValue(false) | |||||
})); | |||||
beforeEach(() => { | |||||
(doesComponentExists as jest.Mock<any>).mockClear(); | |||||
}); | |||||
it('should render correctly', () => { | |||||
const wrapper = shallow(<ProjectKeyInput initialValue="key" onChange={jest.fn()} />); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
wrapper.setState({ touched: true }); | |||||
expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot(); | |||||
}); | |||||
it('should not display any status when the key is not defined', async () => { | |||||
const wrapper = shallow(<ProjectKeyInput onChange={jest.fn()} />); | |||||
await waitAndUpdate(wrapper); | |||||
expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(false); | |||||
expect(wrapper.find('ValidationInput').prop('isValid')).toBe(false); | |||||
}); | |||||
it('should have an error when the key is invalid', async () => { | |||||
const wrapper = shallow( | |||||
<ProjectKeyInput initialValue="KEy-with#speci@l_char" onChange={jest.fn()} /> | |||||
); | |||||
await waitAndUpdate(wrapper); | |||||
expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(true); | |||||
}); | |||||
it('should have an error when the key already exists', async () => { | |||||
(doesComponentExists as jest.Mock<any>).mockResolvedValue(true); | |||||
const wrapper = shallow(<ProjectKeyInput initialValue="" onChange={jest.fn()} />); | |||||
await waitAndUpdate(wrapper); | |||||
expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(true); | |||||
}); |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2018 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 { shallow } from 'enzyme'; | |||||
import ProjectNameInput from '../ProjectNameInput'; | |||||
it('should render correctly', () => { | |||||
const wrapper = shallow(<ProjectNameInput initialValue="Project Name" onChange={jest.fn()} />); | |||||
expect(wrapper).toMatchSnapshot(); | |||||
wrapper.setState({ touched: true }); | |||||
expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot(); | |||||
}); | |||||
it('should have an error when name is too long', () => { | |||||
expect( | |||||
shallow(<ProjectNameInput initialValue={'x'.repeat(501)} onChange={jest.fn()} />) | |||||
.find('ValidationInput') | |||||
.prop('isInvalid') | |||||
).toBe(true); | |||||
}); |
// 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} | |||||
onBlur={[Function]} | |||||
onChange={[Function]} | |||||
onFocus={[Function]} | |||||
type="text" | |||||
value="key" | |||||
/> | |||||
</ValidationInput> | |||||
`; | |||||
exports[`should render correctly 2`] = `true`; |
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||||
exports[`should render correctly 1`] = ` | |||||
<ValidationInput | |||||
description="onboarding.create_project.display_name.description" | |||||
id="project-name" | |||||
isInvalid={false} | |||||
isValid={false} | |||||
label="onboarding.create_project.display_name" | |||||
required={true} | |||||
> | |||||
<input | |||||
className="input-super-large" | |||||
id="project-name" | |||||
maxLength={500} | |||||
minLength={1} | |||||
onBlur={[Function]} | |||||
onChange={[Function]} | |||||
onFocus={[Function]} | |||||
required={true} | |||||
type="text" | |||||
value="Project Name" | |||||
/> | |||||
</ValidationInput> | |||||
`; | |||||
exports[`should render correctly 2`] = `true`; |
import OrganizationInput from './OrganizationInput'; | import OrganizationInput from './OrganizationInput'; | ||||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | import DeferredSpinner from '../../../components/common/DeferredSpinner'; | ||||
import { SubmitButton } from '../../../components/ui/buttons'; | import { SubmitButton } from '../../../components/ui/buttons'; | ||||
import { createProject } from '../../../api/components'; | |||||
import { LoggedInUser, Organization } from '../../../app/types'; | import { LoggedInUser, Organization } from '../../../app/types'; | ||||
import { translate } from '../../../helpers/l10n'; | import { translate } from '../../../helpers/l10n'; | ||||
import { createProject } from '../../../api/components'; | |||||
import ProjectKeyInput from '../components/ProjectKeyInput'; | |||||
import ProjectNameInput from '../components/ProjectNameInput'; | |||||
interface Props { | interface Props { | ||||
currentUser: LoggedInUser; | currentUser: LoggedInUser; | ||||
} | } | ||||
interface State { | interface State { | ||||
projectName: string; | |||||
projectKey: string; | |||||
projectName?: string; | |||||
projectKey?: string; | |||||
selectedOrganization: string; | selectedOrganization: string; | ||||
submitting: boolean; | submitting: boolean; | ||||
} | } | ||||
type ValidState = State & Required<Pick<State, 'projectName' | 'projectKey'>>; | |||||
export default class ManualProjectCreate extends React.PureComponent<Props, State> { | export default class ManualProjectCreate extends React.PureComponent<Props, State> { | ||||
mounted = false; | mounted = false; | ||||
constructor(props: Props) { | constructor(props: Props) { | ||||
super(props); | super(props); | ||||
this.state = { | this.state = { | ||||
projectName: '', | |||||
projectKey: '', | |||||
selectedOrganization: this.getInitialSelectedOrganization(props), | selectedOrganization: this.getInitialSelectedOrganization(props), | ||||
submitting: false | submitting: false | ||||
}; | }; | ||||
this.mounted = false; | this.mounted = false; | ||||
} | } | ||||
canSubmit(state: State): state is ValidState { | |||||
return Boolean(state.projectKey && state.projectName && state.selectedOrganization); | |||||
} | |||||
getInitialSelectedOrganization(props: Props) { | getInitialSelectedOrganization(props: Props) { | ||||
if (props.organization) { | if (props.organization) { | ||||
return props.organization; | return props.organization; | ||||
handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => { | handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => { | ||||
event.preventDefault(); | event.preventDefault(); | ||||
if (this.isValid()) { | |||||
const { projectKey, projectName, selectedOrganization } = this.state; | |||||
const { state } = this; | |||||
if (this.canSubmit(state)) { | |||||
this.setState({ submitting: true }); | this.setState({ submitting: true }); | ||||
createProject({ | createProject({ | ||||
project: projectKey, | |||||
name: projectName, | |||||
organization: selectedOrganization | |||||
project: state.projectKey, | |||||
name: state.projectName, | |||||
organization: state.selectedOrganization | |||||
}).then( | }).then( | ||||
({ project }) => this.props.onProjectCreate([project.key]), | ({ project }) => this.props.onProjectCreate([project.key]), | ||||
() => { | () => { | ||||
this.setState({ selectedOrganization: key }); | this.setState({ selectedOrganization: key }); | ||||
}; | }; | ||||
handleProjectNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||||
this.setState({ projectName: event.currentTarget.value }); | |||||
}; | |||||
handleProjectKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||||
this.setState({ projectKey: event.currentTarget.value }); | |||||
handleProjectNameChange = (projectName?: string) => { | |||||
this.setState({ projectName }); | |||||
}; | }; | ||||
isValid = () => { | |||||
const { projectKey, projectName, selectedOrganization } = this.state; | |||||
return Boolean(projectKey && projectName && selectedOrganization); | |||||
handleProjectKeyChange = (projectKey?: string) => { | |||||
this.setState({ projectKey }); | |||||
}; | }; | ||||
render() { | render() { | ||||
organization={this.state.selectedOrganization} | organization={this.state.selectedOrganization} | ||||
organizations={this.props.userOrganizations} | organizations={this.props.userOrganizations} | ||||
/> | /> | ||||
<div className="form-field"> | |||||
<label htmlFor="project-name"> | |||||
{translate('onboarding.create_project.project_name')} | |||||
<em className="mandatory">*</em> | |||||
</label> | |||||
<input | |||||
className="input-super-large" | |||||
id="project-name" | |||||
maxLength={400} | |||||
minLength={1} | |||||
onChange={this.handleProjectNameChange} | |||||
required={true} | |||||
type="text" | |||||
value={this.state.projectName} | |||||
/> | |||||
</div> | |||||
<div className="form-field"> | |||||
<label htmlFor="project-key"> | |||||
{translate('onboarding.create_project.project_key')} | |||||
<em className="mandatory">*</em> | |||||
</label> | |||||
<input | |||||
className="input-super-large" | |||||
id="project-key" | |||||
maxLength={400} | |||||
minLength={1} | |||||
onChange={this.handleProjectKeyChange} | |||||
required={true} | |||||
type="text" | |||||
value={this.state.projectKey} | |||||
/> | |||||
</div> | |||||
<SubmitButton disabled={!this.isValid() || submitting}>{translate('setup')}</SubmitButton> | |||||
<ProjectKeyInput | |||||
className="form-field" | |||||
initialValue={this.state.projectKey} | |||||
onChange={this.handleProjectKeyChange} | |||||
/> | |||||
<ProjectNameInput | |||||
className="form-field" | |||||
initialValue={this.state.projectName} | |||||
onChange={this.handleProjectNameChange} | |||||
/> | |||||
<SubmitButton disabled={!this.canSubmit(this.state) || submitting}> | |||||
{translate('setup')} | |||||
</SubmitButton> | |||||
<DeferredSpinner className="spacer-left" loading={submitting} /> | <DeferredSpinner className="spacer-left" loading={submitting} /> | ||||
</form> | </form> | ||||
</> | </> |
const onProjectCreate = jest.fn(); | const onProjectCreate = jest.fn(); | ||||
const wrapper = getWrapper({ onProjectCreate }); | const wrapper = getWrapper({ onProjectCreate }); | ||||
wrapper.find('withRouter(OrganizationInput)').prop<Function>('onChange')({ key: 'foo' }); | wrapper.find('withRouter(OrganizationInput)').prop<Function>('onChange')({ key: 'foo' }); | ||||
change(wrapper.find('#project-name'), 'Bar'); | |||||
expect(wrapper.find('SubmitButton')).toMatchSnapshot(); | |||||
change(wrapper.find('#project-key'), 'bar'); | |||||
expect(wrapper.find('SubmitButton')).toMatchSnapshot(); | |||||
change(wrapper.find('ProjectKeyInput'), 'bar'); | |||||
expect(wrapper.find('SubmitButton').prop('disabled')).toBe(true); | |||||
change(wrapper.find('ProjectNameInput'), 'Bar'); | |||||
expect(wrapper.find('SubmitButton').prop('disabled')).toBe(false); | |||||
submit(wrapper.find('form')); | submit(wrapper.find('form')); | ||||
expect(createProject).toBeCalledWith({ project: 'bar', name: 'Bar', organization: 'foo' }); | expect(createProject).toBeCalledWith({ project: 'bar', name: 'Bar', organization: 'foo' }); |
// Jest Snapshot v1, https://goo.gl/fbAQLP | // Jest Snapshot v1, https://goo.gl/fbAQLP | ||||
exports[`should correctly create a project 1`] = ` | |||||
<SubmitButton | |||||
disabled={true} | |||||
> | |||||
setup | |||||
</SubmitButton> | |||||
`; | |||||
exports[`should correctly create a project 2`] = ` | |||||
<SubmitButton | |||||
disabled={false} | |||||
> | |||||
setup | |||||
</SubmitButton> | |||||
`; | |||||
exports[`should render correctly 1`] = ` | exports[`should render correctly 1`] = ` | ||||
<Fragment> | <Fragment> | ||||
<form | <form | ||||
] | ] | ||||
} | } | ||||
/> | /> | ||||
<div | |||||
<ProjectKeyInput | |||||
className="form-field" | className="form-field" | ||||
> | |||||
<label | |||||
htmlFor="project-name" | |||||
> | |||||
onboarding.create_project.project_name | |||||
<em | |||||
className="mandatory" | |||||
> | |||||
* | |||||
</em> | |||||
</label> | |||||
<input | |||||
className="input-super-large" | |||||
id="project-name" | |||||
maxLength={400} | |||||
minLength={1} | |||||
onChange={[Function]} | |||||
required={true} | |||||
type="text" | |||||
value="" | |||||
/> | |||||
</div> | |||||
<div | |||||
onChange={[Function]} | |||||
/> | |||||
<ProjectNameInput | |||||
className="form-field" | className="form-field" | ||||
> | |||||
<label | |||||
htmlFor="project-key" | |||||
> | |||||
onboarding.create_project.project_key | |||||
<em | |||||
className="mandatory" | |||||
> | |||||
* | |||||
</em> | |||||
</label> | |||||
<input | |||||
className="input-super-large" | |||||
id="project-key" | |||||
maxLength={400} | |||||
minLength={1} | |||||
onChange={[Function]} | |||||
required={true} | |||||
type="text" | |||||
value="" | |||||
/> | |||||
</div> | |||||
onChange={[Function]} | |||||
/> | |||||
<SubmitButton | <SubmitButton | ||||
disabled={true} | disabled={true} | ||||
> | > |
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import HelpTooltip from './HelpTooltip'; | |||||
import AlertErrorIcon from '../icons-components/AlertErrorIcon'; | import AlertErrorIcon from '../icons-components/AlertErrorIcon'; | ||||
import AlertSuccessIcon from '../icons-components/AlertSuccessIcon'; | import AlertSuccessIcon from '../icons-components/AlertSuccessIcon'; | ||||
interface Props { | interface Props { | ||||
description?: string; | description?: string; | ||||
children: React.ReactNode; | children: React.ReactNode; | ||||
className?: string; | |||||
error: string | undefined; | error: string | undefined; | ||||
help?: string; | |||||
id: string; | id: string; | ||||
isInvalid: boolean; | isInvalid: boolean; | ||||
isValid: boolean; | isValid: boolean; | ||||
export default function ValidationInput(props: Props) { | export default function ValidationInput(props: Props) { | ||||
const hasError = props.isInvalid && props.error !== undefined; | const hasError = props.isInvalid && props.error !== undefined; | ||||
return ( | return ( | ||||
<div> | |||||
<div className={props.className}> | |||||
<label htmlFor={props.id}> | <label htmlFor={props.id}> | ||||
<strong>{props.label}</strong> | |||||
{props.required && <em className="mandatory">*</em>} | |||||
<span className="text-middle"> | |||||
<strong>{props.label}</strong> | |||||
{props.required && <em className="mandatory">*</em>} | |||||
</span> | |||||
{props.help && <HelpTooltip className="spacer-left" overlay={props.help} />} | |||||
</label> | </label> | ||||
<div className="little-spacer-top spacer-bottom"> | <div className="little-spacer-top spacer-bottom"> | ||||
{props.children} | {props.children} |
<ValidationInput | <ValidationInput | ||||
description="My description" | description="My description" | ||||
error={undefined} | error={undefined} | ||||
help="Help message" | |||||
id="field-id" | id="field-id" | ||||
isInvalid={false} | isInvalid={false} | ||||
isValid={false} | isValid={false} |
<label | <label | ||||
htmlFor="field-id" | htmlFor="field-id" | ||||
> | > | ||||
<strong> | |||||
Field label | |||||
</strong> | |||||
<em | |||||
className="mandatory" | |||||
<span | |||||
className="text-middle" | |||||
> | > | ||||
* | |||||
</em> | |||||
<strong> | |||||
Field label | |||||
</strong> | |||||
<em | |||||
className="mandatory" | |||||
> | |||||
* | |||||
</em> | |||||
</span> | |||||
<HelpTooltip | |||||
className="spacer-left" | |||||
overlay="Help message" | |||||
/> | |||||
</label> | </label> | ||||
<div | <div | ||||
className="little-spacer-top spacer-bottom" | className="little-spacer-top spacer-bottom" | ||||
<label | <label | ||||
htmlFor="field-id" | htmlFor="field-id" | ||||
> | > | ||||
<strong> | |||||
Field label | |||||
</strong> | |||||
<em | |||||
className="mandatory" | |||||
<span | |||||
className="text-middle" | |||||
> | > | ||||
* | |||||
</em> | |||||
<strong> | |||||
Field label | |||||
</strong> | |||||
<em | |||||
className="mandatory" | |||||
> | |||||
* | |||||
</em> | |||||
</span> | |||||
</label> | </label> | ||||
<div | <div | ||||
className="little-spacer-top spacer-bottom" | className="little-spacer-top spacer-bottom" | ||||
<label | <label | ||||
htmlFor="field-id" | htmlFor="field-id" | ||||
> | > | ||||
<strong> | |||||
Field label | |||||
</strong> | |||||
<span | |||||
className="text-middle" | |||||
> | |||||
<strong> | |||||
Field label | |||||
</strong> | |||||
</span> | |||||
</label> | </label> | ||||
<div | <div | ||||
className="little-spacer-top spacer-bottom" | className="little-spacer-top spacer-bottom" |
} | } | ||||
export function change(element: ShallowWrapper | ReactWrapper, value: string, event = {}): void { | export function change(element: ShallowWrapper | ReactWrapper, value: string, event = {}): void { | ||||
element.simulate('change', { | |||||
target: { value }, | |||||
currentTarget: { value }, | |||||
...event | |||||
}); | |||||
// `type()` returns a component constructor for a composite element and string for DOM nodes | |||||
if (typeof element.type() === 'function') { | |||||
element.prop<Function>('onChange')(value); | |||||
// TODO find out if `root` is a public api | |||||
// https://github.com/airbnb/enzyme/blob/master/packages/enzyme/src/ReactWrapper.js#L109 | |||||
(element as any).root().update(); | |||||
} else { | |||||
element.simulate('change', { | |||||
target: { value }, | |||||
currentTarget: { value }, | |||||
...event | |||||
}); | |||||
} | |||||
} | } | ||||
export function keydown(keyCode: number): void { | export function keydown(keyCode: number): void { |
onboarding.create_project.install_app_description.github=We need you to install the SonarCloud GitHub application on one of your organization in order to select which repositories you want to analyze. | onboarding.create_project.install_app_description.github=We need you to install the SonarCloud GitHub application on one of your organization in order to select which repositories you want to analyze. | ||||
onboarding.create_project.organization=Organization | onboarding.create_project.organization=Organization | ||||
onboarding.create_project.project_key=Project key | onboarding.create_project.project_key=Project key | ||||
onboarding.create_project.project_name=Project name | |||||
onboarding.create_project.project_key.description=Up to 400 characters. All letters, digits, dash, underscore, point or colon. | |||||
onboarding.create_project.project_key.error=The provided value doesn't match the expected format. | |||||
onboarding.create_project.project_key.help=Your project key is a unique identifier for your project. | |||||
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.description=Up to 500 characters | |||||
onboarding.create_project.repository_imported=Already imported: {link} | onboarding.create_project.repository_imported=Already imported: {link} | ||||
onboarding.create_project.see_project=See the project | onboarding.create_project.see_project=See the project | ||||
onboarding.create_project.select_repositories=Select repositories | onboarding.create_project.select_repositories=Select repositories |