* SONARCLOUD-441 Add COBOL to languages * SONARCLOUD-435 Validate fields immediately * SONARCLOUD-367 Add pricing to footer * SONARCLOUD-169 Fix image size issuetags/7.7
@@ -0,0 +1,3 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 66.199 15.34"> | |||
<path data-name="Tracé 11999" d="M12.174 10.162a5.283 5.283 0 0 1-1.779 3.794 6.248 6.248 0 0 1-4.232 1.384 5.618 5.618 0 0 1-4.517-1.933A7.925 7.925 0 0 1 0 8.101v-.913a8.95 8.95 0 0 1 .759-3.794A5.686 5.686 0 0 1 2.928.877 6.075 6.075 0 0 1 6.204 0a6.087 6.087 0 0 1 4.163 1.384 5.677 5.677 0 0 1 1.825 3.886H9.116a3.118 3.118 0 0 0-.8-2.1 2.981 2.981 0 0 0-2.107-.651 2.614 2.614 0 0 0-2.3 1.1 6.178 6.178 0 0 0-.784 3.42v1.128a6.612 6.612 0 0 0 .733 3.538 2.565 2.565 0 0 0 2.312 1.118 3.037 3.037 0 0 0 2.128-.651 2.941 2.941 0 0 0 .8-2.015zm14.281-2.153a9.013 9.013 0 0 1-.781 3.865 5.869 5.869 0 0 1-2.228 2.561 6.169 6.169 0 0 1-3.327.9 6.211 6.211 0 0 1-3.312-.892 5.926 5.926 0 0 1-2.256-2.548 8.755 8.755 0 0 1-.81-3.809v-.738a8.965 8.965 0 0 1 .795-3.881A5.917 5.917 0 0 1 16.782.893a6.177 6.177 0 0 1 3.317-.9 6.177 6.177 0 0 1 3.317.9 5.917 5.917 0 0 1 2.246 2.574 8.94 8.94 0 0 1 .795 3.871zm-3.117-.674a6.283 6.283 0 0 0-.841-3.568 2.74 2.74 0 0 0-2.4-1.22 2.741 2.741 0 0 0-2.389 1.2 6.211 6.211 0 0 0-.851 3.532v.728a6.361 6.361 0 0 0 .841 3.548 2.733 2.733 0 0 0 2.42 1.261 2.708 2.708 0 0 0 2.379-1.215 6.324 6.324 0 0 0 .841-3.543zm5.352 7.8V.205h5.23a6.867 6.867 0 0 1 4.122 1.041 3.559 3.559 0 0 1 1.4 3.051 3.373 3.373 0 0 1-.564 1.933 3.258 3.258 0 0 1-1.564 1.225 3.152 3.152 0 0 1 1.81 1.159 3.426 3.426 0 0 1 .661 2.133 3.94 3.94 0 0 1-1.374 3.26 6.186 6.186 0 0 1-3.917 1.128zm3.076-6.5v4.03h2.636a2.543 2.543 0 0 0 1.7-.518 1.776 1.776 0 0 0 .61-1.43 1.875 1.875 0 0 0-2.126-2.082zm0-2.174h2.277q2.331-.042 2.331-1.857a1.715 1.715 0 0 0-.59-1.461 3.1 3.1 0 0 0-1.861-.446h-2.156zm22.59 1.548a9.013 9.013 0 0 1-.782 3.865 5.869 5.869 0 0 1-2.228 2.561 6.169 6.169 0 0 1-3.327.9 6.211 6.211 0 0 1-3.312-.892 5.926 5.926 0 0 1-2.256-2.543 8.755 8.755 0 0 1-.81-3.809v-.738a8.965 8.965 0 0 1 .795-3.881A5.917 5.917 0 0 1 44.682.898a6.177 6.177 0 0 1 3.317-.9 6.177 6.177 0 0 1 3.317.9 5.917 5.917 0 0 1 2.246 2.574 8.94 8.94 0 0 1 .795 3.871zm-3.117-.674a6.283 6.283 0 0 0-.841-3.568 2.74 2.74 0 0 0-2.4-1.22 2.741 2.741 0 0 0-2.389 1.2 6.211 6.211 0 0 0-.851 3.532v.728a6.361 6.361 0 0 0 .841 3.548 2.733 2.733 0 0 0 2.42 1.261 2.708 2.708 0 0 0 2.379-1.215 6.324 6.324 0 0 0 .841-3.543zm8.429 5.332H66.2v2.468h-9.609V.205h3.076z" fill="#2e3032"/> | |||
</svg> |
@@ -56,6 +56,9 @@ export default function GlobalFooterSonarCloud() { | |||
{translate('footer.terms')} | |||
</Link> | |||
</li> | |||
<li className="page-footer-menu-item"> | |||
<Link to="/about/pricing/">{translate('footer.pricing')}</Link> | |||
</li> | |||
<li className="page-footer-menu-item"> | |||
<Link to="/documentation/privacy/">{translate('footer.privacy')}</Link> | |||
</li> |
@@ -55,6 +55,17 @@ exports[`should render correctly 1`] = ` | |||
footer.terms | |||
</Link> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> | |||
<Link | |||
onlyActiveOnIndex={false} | |||
style={Object {}} | |||
to="/about/pricing/" | |||
> | |||
footer.pricing | |||
</Link> | |||
</li> | |||
<li | |||
className="page-footer-menu-item" | |||
> |
@@ -55,13 +55,13 @@ export default function BranchAnalysis() { | |||
<li className="sc-feature sc-branch-feature"> | |||
<img | |||
alt="" | |||
className="sc-branch-feature-right" | |||
className="sc-branch-feature-right flex-0" | |||
height="270" | |||
src={`${getBaseUrl()}/images/sonarcloud/branch-01.png`} | |||
srcSet={`${getBaseUrl()}/images/sonarcloud/branch-01.png 1x, ${getBaseUrl()}/images/sonarcloud/branch-01@2x.png 2x`} | |||
width="463" | |||
/> | |||
<div> | |||
<div className="flex-1"> | |||
<h3 className="sc-feature-title">Analyze branches and pull requests</h3> | |||
<p className="sc-feature-description"> | |||
For all project branches (main, maintenance, version, feature, etc.), you get the | |||
@@ -78,7 +78,7 @@ export default function BranchAnalysis() { | |||
</div> | |||
</li> | |||
<li className="sc-feature sc-branch-feature"> | |||
<div> | |||
<div className="flex-1"> | |||
<h3 className="sc-feature-title">Decorate PRs on Azure DevOps and GitHub</h3> | |||
<p className="sc-feature-description"> | |||
Pull requests get decorated directly on Azure DevOps and GitHub. The result of the | |||
@@ -89,7 +89,7 @@ export default function BranchAnalysis() { | |||
</div> | |||
<img | |||
alt="" | |||
className="sc-branch-feature-left" | |||
className="sc-branch-feature-left flex-0" | |||
height="390" | |||
src={`${getBaseUrl()}/images/sonarcloud/branch-02.png`} | |||
srcSet={`${getBaseUrl()}/images/sonarcloud/branch-02.png 1x, ${getBaseUrl()}/images/sonarcloud/branch-02@2x.png 2x`} | |||
@@ -99,13 +99,13 @@ export default function BranchAnalysis() { | |||
<li className="sc-feature sc-branch-feature"> | |||
<img | |||
alt="" | |||
className="sc-branch-feature-right" | |||
className="sc-branch-feature-right flex-0" | |||
height="169" | |||
src={`${getBaseUrl()}/images/sonarcloud/branch-03.png`} | |||
srcSet={`${getBaseUrl()}/images/sonarcloud/branch-03.png 1x, ${getBaseUrl()}/images/sonarcloud/branch-03@2x.png 2x`} | |||
width="460" | |||
/> | |||
<div> | |||
<div className="flex-1"> | |||
<h3 className="sc-feature-title">Add a check in GitHub</h3> | |||
<p className="sc-feature-description"> | |||
Finally, a check can be added to the PR to provide the Quality Gate status of the |
@@ -81,15 +81,6 @@ export default function Footer() { | |||
<div className="sc-footer-nav-column"> | |||
<h4 className="sc-footer-nav-column-title">About</h4> | |||
<ul> | |||
<li className="spacer-top"> | |||
<a | |||
className="sc-footer-link" | |||
href="https://www.sonarsource.com/" | |||
rel="noopener noreferrer" | |||
target="_blank"> | |||
SonarSource | |||
</a> | |||
</li> | |||
<li className="spacer-top"> | |||
<Link | |||
className="sc-footer-link" | |||
@@ -99,6 +90,11 @@ export default function Footer() { | |||
Terms | |||
</Link> | |||
</li> | |||
<li className="spacer-top"> | |||
<Link className="sc-footer-link" to="/about/pricing/"> | |||
Pricing | |||
</Link> | |||
</li> | |||
<li className="spacer-top"> | |||
<Link className="sc-footer-link" to="/documentation/privacy/"> | |||
Privacy |
@@ -71,5 +71,6 @@ export const LANGUAGES = [ | |||
{ name: 'T-SQL', file: 't-sql.svg', width: 53 }, | |||
{ name: 'PL/SQL', file: 'pl-sql.svg', width: 65 }, | |||
{ name: 'VB', file: 'vb.svg', width: 55 }, | |||
{ name: 'XML', file: 'xml.svg', width: 67 } | |||
{ name: 'XML', file: 'xml.svg', width: 67 }, | |||
{ name: 'COBOL', file: 'cobol.svg', width: 65 } | |||
]; |
@@ -1,95 +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 React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import ValidationInput from '../../../components/controls/ValidationInput'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
initialValue?: string; | |||
onChange: (value: string | undefined) => void; | |||
} | |||
interface State { | |||
editing: boolean; | |||
error?: string; | |||
touched: boolean; | |||
value: string; | |||
} | |||
export default class OrganizationDescriptionInput extends React.PureComponent<Props, State> { | |||
state: State = { error: undefined, editing: false, touched: false, value: '' }; | |||
componentDidMount() { | |||
if (this.props.initialValue) { | |||
const error = this.validateDescription(this.props.initialValue); | |||
this.setState({ error, touched: Boolean(error), value: this.props.initialValue }); | |||
} | |||
} | |||
handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { | |||
const { value } = event.currentTarget; | |||
const error = this.validateDescription(value); | |||
this.setState({ error, touched: true, value }); | |||
this.props.onChange(error === undefined ? value : undefined); | |||
}; | |||
handleBlur = () => { | |||
this.setState({ editing: false }); | |||
}; | |||
handleFocus = () => { | |||
this.setState({ editing: true }); | |||
}; | |||
validateDescription(description: string) { | |||
if (description.length > 256) { | |||
return translate('onboarding.create_organization.description.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 | |||
error={this.state.error} | |||
id="organization-description" | |||
isInvalid={isInvalid} | |||
isValid={isValid} | |||
label={translate('onboarding.create_organization.description')}> | |||
<textarea | |||
className={classNames('input-super-large', 'text-middle', { | |||
'is-invalid': isInvalid, | |||
'is-valid': isValid | |||
})} | |||
id="organization-description" | |||
maxLength={256} | |||
onBlur={this.handleBlur} | |||
onChange={this.handleChange} | |||
onFocus={this.handleFocus} | |||
rows={3} | |||
value={this.state.value} | |||
/> | |||
</ValidationInput> | |||
); | |||
} | |||
} |
@@ -31,7 +31,6 @@ interface Props { | |||
} | |||
interface State { | |||
editing: boolean; | |||
error?: string; | |||
touched: boolean; | |||
validating: boolean; | |||
@@ -42,7 +41,7 @@ export default class OrganizationKeyInput extends React.PureComponent<Props, Sta | |||
mounted = false; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { error: undefined, editing: false, touched: false, validating: false, value: '' }; | |||
this.state = { error: undefined, touched: false, validating: false, value: '' }; | |||
this.checkFreeKey = debounce(this.checkFreeKey, 250); | |||
} | |||
@@ -90,14 +89,6 @@ export default class OrganizationKeyInput extends React.PureComponent<Props, Sta | |||
this.validateKey(value); | |||
}; | |||
handleBlur = () => { | |||
this.setState({ editing: false }); | |||
}; | |||
handleFocus = () => { | |||
this.setState({ editing: true }); | |||
}; | |||
validateKey(key: string) { | |||
if (key.length > 255 || !/^[a-z0-9][a-z0-9-]*[a-z0-9]?$/.test(key)) { | |||
this.setState({ | |||
@@ -111,7 +102,7 @@ export default class OrganizationKeyInput extends React.PureComponent<Props, Sta | |||
} | |||
render() { | |||
const isInvalid = this.state.touched && !this.state.editing && this.state.error !== undefined; | |||
const isInvalid = this.state.touched && this.state.error !== undefined; | |||
const isValid = this.state.touched && !this.state.validating && this.state.error === undefined; | |||
return ( | |||
<ValidationInput | |||
@@ -133,9 +124,7 @@ export default class OrganizationKeyInput extends React.PureComponent<Props, Sta | |||
})} | |||
id="organization-key" | |||
maxLength={255} | |||
onBlur={this.handleBlur} | |||
onChange={this.handleChange} | |||
onFocus={this.handleFocus} | |||
type="text" | |||
value={this.state.value} | |||
/> |
@@ -1,96 +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 React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import ValidationInput from '../../../components/controls/ValidationInput'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
initialValue?: string; | |||
onChange: (value: string | undefined) => void; | |||
} | |||
interface State { | |||
editing: boolean; | |||
error?: string; | |||
touched: boolean; | |||
value: string; | |||
} | |||
export default class OrganizationNameInput 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_organization.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 | |||
description={translate('onboarding.create_organization.display_name.description')} | |||
error={this.state.error} | |||
id="organization-display-name" | |||
isInvalid={isInvalid} | |||
isValid={isValid} | |||
label={translate('onboarding.create_organization.display_name')}> | |||
<input | |||
className={classNames('input-super-large', 'text-middle', { | |||
'is-invalid': isInvalid, | |||
'is-valid': isValid | |||
})} | |||
id="organization-display-name" | |||
maxLength={255} | |||
onBlur={this.handleBlur} | |||
onChange={this.handleChange} | |||
onFocus={this.handleFocus} | |||
type="text" | |||
value={this.state.value} | |||
/> | |||
</ValidationInput> | |||
); | |||
} | |||
} |
@@ -31,7 +31,6 @@ interface Props { | |||
} | |||
interface State { | |||
editing: boolean; | |||
error?: string; | |||
touched: boolean; | |||
validating: boolean; | |||
@@ -42,7 +41,7 @@ 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.state = { error: undefined, touched: false, validating: false, value: '' }; | |||
this.checkFreeKey = debounce(this.checkFreeKey, 250); | |||
} | |||
@@ -90,14 +89,6 @@ export default class ProjectKeyInput extends React.PureComponent<Props, State> { | |||
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({ | |||
@@ -111,7 +102,7 @@ export default class ProjectKeyInput extends React.PureComponent<Props, State> { | |||
} | |||
render() { | |||
const isInvalid = this.state.touched && !this.state.editing && this.state.error !== undefined; | |||
const isInvalid = this.state.touched && this.state.error !== undefined; | |||
const isValid = this.state.touched && !this.state.validating && this.state.error === undefined; | |||
return ( | |||
<ValidationInput | |||
@@ -133,9 +124,7 @@ export default class ProjectKeyInput extends React.PureComponent<Props, State> { | |||
id="project-key" | |||
maxLength={400} | |||
minLength={1} | |||
onBlur={this.handleBlur} | |||
onChange={this.handleChange} | |||
onFocus={this.handleFocus} | |||
type="text" | |||
value={this.state.value} | |||
/> |
@@ -1,100 +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 React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import ValidationInput from '../../../components/controls/ValidationInput'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
className?: string; | |||
value?: string; | |||
onChange: (value: string | undefined) => void; | |||
} | |||
interface State { | |||
editing: boolean; | |||
error?: string; | |||
touched: boolean; | |||
} | |||
export default class ProjectNameInput extends React.PureComponent<Props, State> { | |||
state: State = { error: undefined, editing: false, touched: false }; | |||
componentDidMount() { | |||
if (this.props.value) { | |||
const error = this.validateName(this.props.value); | |||
this.setState({ error, touched: Boolean(error) }); | |||
} | |||
} | |||
handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
const { value } = event.currentTarget; | |||
const error = this.validateName(value); | |||
this.setState({ error, touched: true }); | |||
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.props.value !== ''; | |||
return ( | |||
<ValidationInput | |||
className={this.props.className} | |||
description={translate('onboarding.create_project.display_name.description')} | |||
error={this.state.error} | |||
help={translate('onboarding.create_project.display_name.help')} | |||
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} | |||
type="text" | |||
value={this.props.value || ''} | |||
/> | |||
</ValidationInput> | |||
); | |||
} | |||
} |
@@ -1,39 +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 React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import OrganizationDescriptionInput from '../OrganizationDescriptionInput'; | |||
it('should render correctly', () => { | |||
const wrapper = shallow( | |||
<OrganizationDescriptionInput initialValue="My description" onChange={jest.fn()} /> | |||
); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.setState({ touched: true }); | |||
expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot(); | |||
}); | |||
it('should have an error when description is too long', () => { | |||
expect( | |||
shallow(<OrganizationDescriptionInput initialValue={'x'.repeat(260)} onChange={jest.fn()} />) | |||
.find('ValidationInput') | |||
.prop('isInvalid') | |||
).toBe(true); | |||
}); |
@@ -1,37 +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 React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import OrganizationNameInput from '../OrganizationNameInput'; | |||
it('should render correctly', () => { | |||
const wrapper = shallow(<OrganizationNameInput initialValue="Org 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(<OrganizationNameInput initialValue={'x'.repeat(256)} onChange={jest.fn()} />) | |||
.find('ValidationInput') | |||
.prop('isInvalid') | |||
).toBe(true); | |||
}); |
@@ -1,37 +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 React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import ProjectNameInput from '../ProjectNameInput'; | |||
it('should render correctly', () => { | |||
const wrapper = shallow(<ProjectNameInput onChange={jest.fn()} value="Project Name" />); | |||
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 onChange={jest.fn()} value={'x'.repeat(501)} />) | |||
.find('ValidationInput') | |||
.prop('isInvalid') | |||
).toBe(true); | |||
}); |
@@ -1,23 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<ValidationInput | |||
id="organization-description" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_organization.description" | |||
> | |||
<textarea | |||
className="input-super-large text-middle" | |||
id="organization-description" | |||
maxLength={256} | |||
onBlur={[Function]} | |||
onChange={[Function]} | |||
onFocus={[Function]} | |||
rows={3} | |||
value="My description" | |||
/> | |||
</ValidationInput> | |||
`; | |||
exports[`should render correctly 2`] = `true`; |
@@ -21,9 +21,7 @@ exports[`should render correctly 1`] = ` | |||
className="input-super-large" | |||
id="organization-key" | |||
maxLength={255} | |||
onBlur={[Function]} | |||
onChange={[Function]} | |||
onFocus={[Function]} | |||
type="text" | |||
value="key" | |||
/> |
@@ -1,24 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<ValidationInput | |||
description="onboarding.create_organization.display_name.description" | |||
id="organization-display-name" | |||
isInvalid={false} | |||
isValid={false} | |||
label="onboarding.create_organization.display_name" | |||
> | |||
<input | |||
className="input-super-large text-middle" | |||
id="organization-display-name" | |||
maxLength={255} | |||
onBlur={[Function]} | |||
onChange={[Function]} | |||
onFocus={[Function]} | |||
type="text" | |||
value="Org Name" | |||
/> | |||
</ValidationInput> | |||
`; | |||
exports[`should render correctly 2`] = `true`; |
@@ -16,9 +16,7 @@ exports[`should render correctly 1`] = ` | |||
id="project-key" | |||
maxLength={400} | |||
minLength={1} | |||
onBlur={[Function]} | |||
onChange={[Function]} | |||
onFocus={[Function]} | |||
type="text" | |||
value="key" | |||
/> |
@@ -1,27 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<ValidationInput | |||
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} | |||
> | |||
<input | |||
className="input-super-large" | |||
id="project-name" | |||
maxLength={500} | |||
minLength={1} | |||
onBlur={[Function]} | |||
onChange={[Function]} | |||
onFocus={[Function]} | |||
type="text" | |||
value="Project Name" | |||
/> | |||
</ValidationInput> | |||
`; | |||
exports[`should render correctly 2`] = `true`; |
@@ -21,9 +21,7 @@ import * as React from 'react'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import DropdownIcon from '../../../components/icons-components/DropdownIcon'; | |||
import OrganizationAvatarInput from '../components/OrganizationAvatarInput'; | |||
import OrganizationDescriptionInput from '../components/OrganizationDescriptionInput'; | |||
import OrganizationKeyInput from '../components/OrganizationKeyInput'; | |||
import OrganizationNameInput from '../components/OrganizationNameInput'; | |||
import OrganizationUrlInput from '../components/OrganizationUrlInput'; | |||
import { ResetButtonLink, SubmitButton } from '../../../components/ui/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
@@ -93,16 +91,16 @@ export default class OrganizationDetailsForm extends React.PureComponent<Props, | |||
this.setState({ avatar }); | |||
}; | |||
handleDescriptionUpdate = (description: string | undefined) => { | |||
this.setState({ description }); | |||
handleDescriptionUpdate = (event: React.ChangeEvent<HTMLTextAreaElement>) => { | |||
this.setState({ description: event.currentTarget.value }); | |||
}; | |||
handleKeyUpdate = (key: string | undefined) => { | |||
this.setState({ key }); | |||
}; | |||
handleNameUpdate = (name: string | undefined) => { | |||
this.setState({ name }); | |||
handleNameUpdate = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
this.setState({ name: event.currentTarget.value }); | |||
}; | |||
handleUrlUpdate = (url: string | undefined) => { | |||
@@ -152,10 +150,22 @@ export default class OrganizationDetailsForm extends React.PureComponent<Props, | |||
</div> | |||
<div className="js-additional-info" hidden={!this.state.additional}> | |||
<div className="big-spacer-top"> | |||
<OrganizationNameInput | |||
initialValue={this.state.name} | |||
onChange={this.handleNameUpdate} | |||
/> | |||
<label htmlFor="organization-display-name"> | |||
<strong>{translate('onboarding.create_organization.display_name')}</strong> | |||
</label> | |||
<div className="little-spacer-top"> | |||
<input | |||
className="input-super-large text-middle" | |||
id="organization-display-name" | |||
maxLength={255} | |||
onChange={this.handleNameUpdate} | |||
type="text" | |||
value={this.state.name} | |||
/> | |||
</div> | |||
<div className="note abs-width-400"> | |||
{translate('onboarding.create_organization.display_name.description')} | |||
</div> | |||
</div> | |||
<div className="big-spacer-top"> | |||
<OrganizationAvatarInput | |||
@@ -165,10 +175,19 @@ export default class OrganizationDetailsForm extends React.PureComponent<Props, | |||
/> | |||
</div> | |||
<div className="big-spacer-top"> | |||
<OrganizationDescriptionInput | |||
initialValue={this.state.description} | |||
onChange={this.handleDescriptionUpdate} | |||
/> | |||
<label htmlFor="organization-description"> | |||
<strong>{translate('onboarding.create_organization.description')}</strong> | |||
</label> | |||
<div className="little-spacer-top"> | |||
<textarea | |||
className="input-super-large text-middle" | |||
id="organization-description" | |||
maxLength={256} | |||
onChange={this.handleDescriptionUpdate} | |||
rows={3} | |||
value={this.state.description} | |||
/> | |||
</div> | |||
</div> | |||
<div className="big-spacer-top"> | |||
<OrganizationUrlInput initialValue={this.state.url} onChange={this.handleUrlUpdate} /> |
@@ -28,10 +28,30 @@ exports[`should render form 1`] = ` | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<OrganizationNameInput | |||
initialValue="" | |||
onChange={[Function]} | |||
/> | |||
<label | |||
htmlFor="organization-display-name" | |||
> | |||
<strong> | |||
onboarding.create_organization.display_name | |||
</strong> | |||
</label> | |||
<div | |||
className="little-spacer-top" | |||
> | |||
<input | |||
className="input-super-large text-middle" | |||
id="organization-display-name" | |||
maxLength={255} | |||
onChange={[Function]} | |||
type="text" | |||
value="" | |||
/> | |||
</div> | |||
<div | |||
className="note abs-width-400" | |||
> | |||
onboarding.create_organization.display_name.description | |||
</div> | |||
</div> | |||
<div | |||
className="big-spacer-top" | |||
@@ -45,10 +65,25 @@ exports[`should render form 1`] = ` | |||
<div | |||
className="big-spacer-top" | |||
> | |||
<OrganizationDescriptionInput | |||
initialValue="" | |||
onChange={[Function]} | |||
/> | |||
<label | |||
htmlFor="organization-description" | |||
> | |||
<strong> | |||
onboarding.create_organization.description | |||
</strong> | |||
</label> | |||
<div | |||
className="little-spacer-top" | |||
> | |||
<textarea | |||
className="input-super-large text-middle" | |||
id="organization-description" | |||
maxLength={256} | |||
onChange={[Function]} | |||
rows={3} | |||
value="" | |||
/> | |||
</div> | |||
</div> | |||
<div | |||
className="big-spacer-top" |
@@ -20,15 +20,15 @@ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import OrganizationInput from './OrganizationInput'; | |||
import { createProject } from '../../../api/components'; | |||
import DeferredSpinner from '../../../components/common/DeferredSpinner'; | |||
import { SubmitButton } from '../../../components/ui/buttons'; | |||
import { createProject } from '../../../api/components'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import ProjectKeyInput from '../components/ProjectKeyInput'; | |||
import ProjectNameInput from '../components/ProjectNameInput'; | |||
import VisibilitySelector from '../../../components/common/VisibilitySelector'; | |||
import { isSonarCloud } from '../../../helpers/system'; | |||
import UpgradeOrganizationBox from '../components/UpgradeOrganizationBox'; | |||
import HelpTooltip from '../../../components/controls/HelpTooltip'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { isSonarCloud } from '../../../helpers/system'; | |||
import './ManualProjectCreate.css'; | |||
interface Props { | |||
@@ -149,7 +149,8 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat | |||
); | |||
}; | |||
handleProjectNameChange = (projectName: string | undefined) => { | |||
handleProjectNameChange = (event: React.ChangeEvent<HTMLInputElement>) => { | |||
const projectName = event.currentTarget.value.trim(); | |||
this.setState({ projectName, projectNameChanged: true }); | |||
}; | |||
@@ -185,11 +186,32 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat | |||
initialValue={this.state.projectKey} | |||
onChange={this.handleProjectKeyChange} | |||
/> | |||
<ProjectNameInput | |||
className="form-field" | |||
onChange={this.handleProjectNameChange} | |||
value={this.state.projectName} | |||
/> | |||
<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> | |||
{isSonarCloud() && | |||
selectedOrganization && ( | |||
<div |
@@ -45,7 +45,7 @@ it('should correctly create a public project', async () => { | |||
wrapper.find('withRouter(OrganizationInput)').prop<Function>('onChange')({ key: 'foo' }); | |||
change(wrapper.find('ProjectKeyInput'), 'bar'); | |||
change(wrapper.find('ProjectNameInput'), 'Bar'); | |||
change(wrapper.find('input#project-name'), 'Bar'); | |||
expect(wrapper.find('SubmitButton').prop('disabled')).toBe(false); | |||
submit(wrapper.find('form')); | |||
@@ -66,7 +66,7 @@ it('should correctly create a private project', async () => { | |||
wrapper.find('withRouter(OrganizationInput)').prop<Function>('onChange')({ key: 'bar' }); | |||
change(wrapper.find('ProjectKeyInput'), 'bar'); | |||
change(wrapper.find('ProjectNameInput'), 'Bar'); | |||
change(wrapper.find('input#project-name'), 'Bar'); | |||
submit(wrapper.find('form')); | |||
expect(createProject).toBeCalledWith({ |
@@ -32,10 +32,47 @@ exports[`should render correctly 1`] = ` | |||
className="form-field" | |||
onChange={[Function]} | |||
/> | |||
<ProjectNameInput | |||
<div | |||
className="form-field" | |||
onChange={[Function]} | |||
/> | |||
> | |||
<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" | |||
/> | |||
</div> | |||
<div | |||
className="note abs-width-400" | |||
> | |||
onboarding.create_project.display_name.description | |||
</div> | |||
</div> | |||
<SubmitButton | |||
disabled={true} | |||
> |
@@ -2724,6 +2724,7 @@ footer.help=Help | |||
footer.license=LGPL v3 | |||
footer.news=News | |||
footer.plugins=Plugins | |||
footer.pricing=Pricing | |||
footer.privacy=Privacy | |||
footer.production_database_explanation=The embedded database will not scale, it will not support upgrading to newer versions of {instance}, and there is no support for migrating your data out of it into a different database engine. | |||
footer.production_database_warning=Embedded database should be used for evaluation purposes only | |||
@@ -2780,8 +2781,8 @@ onboarding.create_project.project_key.help=Your project key is a unique identifi | |||
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.display_name.help=Leave empty to use the project key as project display name. Note that some scanners might override the value you provide. | |||
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} | |||
onboarding.create_project.see_project=See the project | |||
onboarding.create_project.select_repositories=Select repositories |