Просмотр исходного кода

Feature/jay/hardening feb 19 (#1297)

* SONARCLOUD-441 Add COBOL to languages
* SONARCLOUD-435 Validate fields immediately
* SONARCLOUD-367 Add pricing to footer
* SONARCLOUD-169 Fix image size issue
tags/7.7
Jeremy 5 лет назад
Родитель
Сommit
c2f8c292d9
25 измененных файлов: 187 добавлений и 563 удалений
  1. 3
    0
      server/sonar-web/public/images/languages/cobol.svg
  2. 3
    0
      server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx
  3. 11
    0
      server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap
  4. 6
    6
      server/sonar-web/src/main/js/apps/about/sonarcloud/BranchAnalysis.tsx
  5. 5
    9
      server/sonar-web/src/main/js/apps/about/sonarcloud/components/Footer.tsx
  6. 2
    1
      server/sonar-web/src/main/js/apps/about/sonarcloud/utils.ts
  7. 0
    95
      server/sonar-web/src/main/js/apps/create/components/OrganizationDescriptionInput.tsx
  8. 2
    13
      server/sonar-web/src/main/js/apps/create/components/OrganizationKeyInput.tsx
  9. 0
    96
      server/sonar-web/src/main/js/apps/create/components/OrganizationNameInput.tsx
  10. 2
    13
      server/sonar-web/src/main/js/apps/create/components/ProjectKeyInput.tsx
  11. 0
    100
      server/sonar-web/src/main/js/apps/create/components/ProjectNameInput.tsx
  12. 0
    39
      server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationDescriptionInput-test.tsx
  13. 0
    37
      server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationNameInput-test.tsx
  14. 0
    37
      server/sonar-web/src/main/js/apps/create/components/__tests__/ProjectNameInput-test.tsx
  15. 0
    23
      server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationDescriptionInput-test.tsx.snap
  16. 0
    2
      server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationKeyInput-test.tsx.snap
  17. 0
    24
      server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationNameInput-test.tsx.snap
  18. 0
    2
      server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectKeyInput-test.tsx.snap
  19. 0
    27
      server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectNameInput-test.tsx.snap
  20. 33
    14
      server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsForm.tsx
  21. 43
    8
      server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/OrganizationDetailsForm-test.tsx.snap
  22. 32
    10
      server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx
  23. 2
    2
      server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx
  24. 40
    3
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap
  25. 3
    2
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 3
- 0
server/sonar-web/public/images/languages/cobol.svg Просмотреть файл

@@ -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>

+ 3
- 0
server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx Просмотреть файл

@@ -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>

+ 11
- 0
server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap Просмотреть файл

@@ -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"
>

+ 6
- 6
server/sonar-web/src/main/js/apps/about/sonarcloud/BranchAnalysis.tsx Просмотреть файл

@@ -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

+ 5
- 9
server/sonar-web/src/main/js/apps/about/sonarcloud/components/Footer.tsx Просмотреть файл

@@ -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

+ 2
- 1
server/sonar-web/src/main/js/apps/about/sonarcloud/utils.ts Просмотреть файл

@@ -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 }
];

+ 0
- 95
server/sonar-web/src/main/js/apps/create/components/OrganizationDescriptionInput.tsx Просмотреть файл

@@ -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>
);
}
}

+ 2
- 13
server/sonar-web/src/main/js/apps/create/components/OrganizationKeyInput.tsx Просмотреть файл

@@ -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}
/>

+ 0
- 96
server/sonar-web/src/main/js/apps/create/components/OrganizationNameInput.tsx Просмотреть файл

@@ -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>
);
}
}

+ 2
- 13
server/sonar-web/src/main/js/apps/create/components/ProjectKeyInput.tsx Просмотреть файл

@@ -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}
/>

+ 0
- 100
server/sonar-web/src/main/js/apps/create/components/ProjectNameInput.tsx Просмотреть файл

@@ -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>
);
}
}

+ 0
- 39
server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationDescriptionInput-test.tsx Просмотреть файл

@@ -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);
});

+ 0
- 37
server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationNameInput-test.tsx Просмотреть файл

@@ -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);
});

+ 0
- 37
server/sonar-web/src/main/js/apps/create/components/__tests__/ProjectNameInput-test.tsx Просмотреть файл

@@ -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);
});

+ 0
- 23
server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationDescriptionInput-test.tsx.snap Просмотреть файл

@@ -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`;

+ 0
- 2
server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationKeyInput-test.tsx.snap Просмотреть файл

@@ -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"
/>

+ 0
- 24
server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/OrganizationNameInput-test.tsx.snap Просмотреть файл

@@ -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`;

+ 0
- 2
server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectKeyInput-test.tsx.snap Просмотреть файл

@@ -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"
/>

+ 0
- 27
server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectNameInput-test.tsx.snap Просмотреть файл

@@ -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`;

+ 33
- 14
server/sonar-web/src/main/js/apps/create/organization/OrganizationDetailsForm.tsx Просмотреть файл

@@ -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} />

+ 43
- 8
server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/OrganizationDetailsForm-test.tsx.snap Просмотреть файл

@@ -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"

+ 32
- 10
server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx Просмотреть файл

@@ -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

+ 2
- 2
server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx Просмотреть файл

@@ -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({

+ 40
- 3
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap Просмотреть файл

@@ -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}
>

+ 3
- 2
sonar-core/src/main/resources/org/sonar/l10n/core.properties Просмотреть файл

@@ -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

Загрузка…
Отмена
Сохранить