Browse Source

SONAR-12884 Improve the validation messages when creating a project

tags/8.4.0.35506
Wouter Admiraal 4 years ago
parent
commit
d811642a8b

+ 21
- 30
server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx View File

@@ -25,6 +25,10 @@ import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { createProject, doesComponentExists } from '../../../api/components';
import ProjectKeyInput from '../../../components/common/ProjectKeyInput';
import { validateProjectKey } from '../../../helpers/projects';
import { ProjectKeyValidationResult } from '../../../types/component';
import { PROJECT_NAME_MAX_LEN } from './constants';
import CreateProjectPageHeader from './CreateProjectPageHeader';
import './ManualProjectCreate.css';

@@ -153,15 +157,19 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
};

validateKey = (projectKey: string) => {
return projectKey.length > 400 || !/^[\w-.:]*[a-zA-Z]+[\w-.:]*$/.test(projectKey)
? translate('onboarding.create_project.project_key.error')
: undefined;
const result = validateProjectKey(projectKey);
return result === ProjectKeyValidationResult.Valid
? undefined
: translate('onboarding.create_project.project_key.error', result);
};

validateName = (projectName: string) => {
return projectName.length === 0 || projectName.length > 255
? translate('onboarding.create_project.display_name.error')
: undefined;
if (projectName.length === 0) {
return translate('onboarding.create_project.display_name.error.empty');
} else if (projectName.length > PROJECT_NAME_MAX_LEN) {
return translate('onboarding.create_project.display_name.error.too_long');
}
return undefined;
};

render() {
@@ -175,8 +183,6 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
validating
} = this.state;
const { branchesEnabled } = this.props;
const projectKeyIsInvalid = touched && projectKeyError !== undefined;
const projectKeyIsValid = touched && !validating && projectKeyError === undefined;
const projectNameIsInvalid = touched && projectNameError !== undefined;
const projectNameIsValid = touched && projectNameError === undefined;

@@ -190,30 +196,15 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
<div className="create-project-manual">
<div className="flex-1 huge-spacer-right">
<form className="manual-project-create" onSubmit={this.handleFormSubmit}>
<ValidationInput
className="form-field"
description={translate('onboarding.create_project.project_key.description')}
<ProjectKeyInput
error={projectKeyError}
help={translate('onboarding.create_project.project_key.help')}
id="project-key"
isInvalid={projectKeyIsInvalid}
isValid={projectKeyIsValid}
label={translate('onboarding.create_project.project_key')}
required={true}>
<input
autoFocus={true}
className={classNames('input-super-large', {
'is-invalid': projectKeyIsInvalid,
'is-valid': projectKeyIsValid
})}
id="project-key"
maxLength={400}
minLength={1}
onChange={this.handleProjectKeyChange}
type="text"
value={projectKey}
/>
</ValidationInput>
onProjectKeyChange={this.handleProjectKeyChange}
projectKey={projectKey}
touched={touched}
validating={validating}
/>

<ValidationInput
className="form-field"
@@ -231,7 +222,7 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
'is-valid': projectNameIsValid
})}
id="project-name"
maxLength={255}
maxLength={PROJECT_NAME_MAX_LEN}
minLength={1}
onChange={this.handleProjectNameChange}
type="text"

+ 59
- 47
server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx View File

@@ -20,8 +20,15 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { shallow } from 'enzyme';
import * as React from 'react';
import { SubmitButton } from 'sonar-ui-common/components/controls/buttons';
import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput';
import { change, submit, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { createProject } from '../../../../api/components';
import { createProject, doesComponentExists } from '../../../../api/components';
import ProjectKeyInput from '../../../../components/common/ProjectKeyInput';
import { validateProjectKey } from '../../../../helpers/projects';
import { mockEvent } from '../../../../helpers/testMocks';
import { ProjectKeyValidationResult } from '../../../../types/component';
import { PROJECT_NAME_MAX_LEN } from '../constants';
import ManualProjectCreate from '../ManualProjectCreate';

jest.mock('../../../../api/components', () => ({
@@ -31,9 +38,10 @@ jest.mock('../../../../api/components', () => ({
.mockImplementation(({ component }) => Promise.resolve(component === 'exists'))
}));

jest.mock('../../../../helpers/system', () => ({
isSonarCloud: jest.fn().mockReturnValue(true)
}));
jest.mock('../../../../helpers/projects', () => {
const { ProjectKeyValidationResult } = jest.requireActual('../../../../types/component');
return { validateProjectKey: jest.fn(() => ProjectKeyValidationResult.Valid) };
});

beforeEach(() => {
jest.clearAllMocks();
@@ -47,9 +55,14 @@ it('should correctly create a project', async () => {
const onProjectCreate = jest.fn();
const wrapper = shallowRender({ onProjectCreate });

change(wrapper.find('input#project-key'), 'bar');
wrapper
.find(ProjectKeyInput)
.props()
.onProjectKeyChange(mockEvent({ currentTarget: { value: 'bar' } }));
change(wrapper.find('input#project-name'), 'Bar');
expect(wrapper.find('SubmitButton').prop('disabled')).toBe(false);
expect(wrapper.find(SubmitButton).props().disabled).toBe(false);
expect(validateProjectKey).toBeCalledWith('bar');
expect(doesComponentExists).toBeCalledWith({ component: 'bar' });

submit(wrapper.find('form'));
expect(createProject).toBeCalledWith({
@@ -61,73 +74,72 @@ it('should correctly create a project', async () => {
expect(onProjectCreate).toBeCalledWith(['bar']);
});

it('should not display any status when the key is not defined', () => {
const wrapper = shallowRender();
const projectKeyInput = wrapper.find('ValidationInput').first();
expect(projectKeyInput.prop('isInvalid')).toBe(false);
expect(projectKeyInput.prop('isValid')).toBe(false);
});

it('should not display any status when the name is not defined', () => {
const wrapper = shallowRender();
const projectKeyInput = wrapper.find('ValidationInput').last();
expect(projectKeyInput.prop('isInvalid')).toBe(false);
expect(projectKeyInput.prop('isValid')).toBe(false);
const projectNameInput = wrapper.find(ValidationInput);
expect(projectNameInput.props().isInvalid).toBe(false);
expect(projectNameInput.props().isValid).toBe(false);
});

it('should have an error when the key is invalid', () => {
(validateProjectKey as jest.Mock).mockReturnValueOnce(ProjectKeyValidationResult.TooLong);
const wrapper = shallowRender();
change(wrapper.find('input#project-key'), 'KEy-with#speci@l_char');
expect(
wrapper
.find('ValidationInput')
.first()
.prop('isInvalid')
).toBe(true);
const instance = wrapper.instance();
instance.handleProjectKeyChange(mockEvent());
expect(wrapper.find(ProjectKeyInput).props().error).toBe(
`onboarding.create_project.project_key.error.${ProjectKeyValidationResult.TooLong}`
);
});

it('should have an error when the key already exists', async () => {
const wrapper = shallowRender();
change(wrapper.find('input#project-key'), 'exists');

wrapper.instance().handleProjectKeyChange(mockEvent({ currentTarget: { value: 'exists' } }));
await waitAndUpdate(wrapper);
expect(
wrapper
.find('ValidationInput')
.first()
.prop('isInvalid')
).toBe(true);
expect(wrapper.state().projectKeyError).toBe('onboarding.create_project.project_key.taken');
});

it('should ignore promise return if value has been changed in the meantime', async () => {
(validateProjectKey as jest.Mock)
.mockReturnValueOnce(ProjectKeyValidationResult.Valid)
.mockReturnValueOnce(ProjectKeyValidationResult.InvalidChar);
const wrapper = shallowRender();
const instance = wrapper.instance();

change(wrapper.find('input#project-key'), 'exists');
change(wrapper.find('input#project-key'), 'exists%');
instance.handleProjectKeyChange(mockEvent({ currentTarget: { value: 'exists' } }));
instance.handleProjectKeyChange(mockEvent({ currentTarget: { value: 'exists%' } }));

await waitAndUpdate(wrapper);

expect(wrapper.state('touched')).toBe(true);
expect(wrapper.state('projectKeyError')).toBe('onboarding.create_project.project_key.error');
expect(wrapper.state().touched).toBe(true);
expect(wrapper.state().projectKeyError).toBe(
`onboarding.create_project.project_key.error.${ProjectKeyValidationResult.InvalidChar}`
);
});

it('should autofill the name based on the key', () => {
const wrapper = shallowRender();
change(wrapper.find('input#project-key'), 'bar');
expect(wrapper.find('input#project-name').prop('value')).toBe('bar');
wrapper.instance().handleProjectKeyChange(mockEvent({ currentTarget: { value: 'bar' } }));
expect(wrapper.find('input#project-name').props().value).toBe('bar');
});

it('should have an error when the name is empty', () => {
it('should have an error when the name is incorrect', () => {
const wrapper = shallowRender();
change(wrapper.find('input#project-key'), 'bar');
change(wrapper.find('input#project-name'), '');
expect(
wrapper
.find('ValidationInput')
.last()
.prop('isInvalid')
).toBe(true);
expect(wrapper.state('projectNameError')).toBe('onboarding.create_project.display_name.error');
wrapper.setState({ touched: true });
const instance = wrapper.instance();

instance.handleProjectNameChange(mockEvent({ currentTarget: { value: '' } }));
expect(wrapper.find(ValidationInput).props().isInvalid).toBe(true);
expect(wrapper.state().projectNameError).toBe(
'onboarding.create_project.display_name.error.empty'
);

instance.handleProjectNameChange(
mockEvent({ currentTarget: { value: new Array(PROJECT_NAME_MAX_LEN + 1).fill('a').join('') } })
);
expect(wrapper.find(ValidationInput).props().isInvalid).toBe(true);
expect(wrapper.state().projectNameError).toBe(
'onboarding.create_project.display_name.error.too_long'
);
});

function shallowRender(props: Partial<ManualProjectCreate['props']> = {}) {

+ 6
- 19
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap View File

@@ -15,27 +15,14 @@ exports[`should render correctly 1`] = `
className="manual-project-create"
onSubmit={[Function]}
>
<ValidationInput
className="form-field"
description="onboarding.create_project.project_key.description"
<ProjectKeyInput
help="onboarding.create_project.project_key.help"
id="project-key"
isInvalid={false}
isValid={false}
label="onboarding.create_project.project_key"
required={true}
>
<input
autoFocus={true}
className="input-super-large"
id="project-key"
maxLength={400}
minLength={1}
onChange={[Function]}
type="text"
value=""
/>
</ValidationInput>
onProjectKeyChange={[Function]}
projectKey=""
touched={false}
validating={false}
/>
<ValidationInput
className="form-field"
description="onboarding.create_project.display_name.description"

+ 21
- 0
server/sonar-web/src/main/js/apps/create/project/constants.ts View File

@@ -0,0 +1,21 @@
/*
* SonarQube
* Copyright (C) 2009-2020 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.
*/

export const PROJECT_NAME_MAX_LEN = 255;

+ 71
- 0
server/sonar-web/src/main/js/components/common/ProjectKeyInput.tsx View File

@@ -0,0 +1,71 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import * as classNames from 'classnames';
import * as React from 'react';
import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { PROJECT_KEY_MAX_LEN } from '../../helpers/constants';

export interface ProjectKeyInputProps {
error?: string;
help?: string;
label?: string;
onProjectKeyChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
projectKey?: string;
touched: boolean;
validating?: boolean;
}

export default function ProjectKeyInput(props: ProjectKeyInputProps) {
const { error, help, label, placeholder, projectKey, touched, validating } = props;

const isInvalid = touched && error !== undefined;
const isValid = touched && !validating && error === undefined;

return (
<ValidationInput
className="form-field"
description={translate('onboarding.create_project.project_key.description')}
error={error}
help={help}
id="project-key"
isInvalid={isInvalid}
isValid={isValid}
label={label}
required={label !== undefined}>
<input
autoFocus={true}
className={classNames('input-super-large', {
'is-invalid': isInvalid,
'is-valid': isValid
})}
id="project-key"
maxLength={PROJECT_KEY_MAX_LEN}
minLength={1}
onChange={props.onProjectKeyChange}
placeholder={placeholder}
type="text"
value={projectKey}
/>
</ValidationInput>
);
}

+ 48
- 0
server/sonar-web/src/main/js/components/common/__tests__/ProjectKeyInput-test.tsx View File

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2020 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

import { shallow } from 'enzyme';
import * as React from 'react';
import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput';
import ProjectKeyInput, { ProjectKeyInputProps } from '../ProjectKeyInput';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ projectKey: 'foo' })).toMatchSnapshot('with value');
expect(
shallowRender({ help: 'foo.help', label: 'foo.label', placeholder: 'foo.placeholder' })
).toMatchSnapshot('with label, help, and placeholder');
expect(shallowRender({ touched: true })).toMatchSnapshot('valid');
expect(shallowRender({ touched: true, error: 'bar.baz' })).toMatchSnapshot('invalid');
expect(shallowRender({ touched: true, validating: true })).toMatchSnapshot('validating');
});

it('should not display any status when the key is not defined', () => {
const wrapper = shallowRender();
const input = wrapper.find(ValidationInput);
expect(input.props().isInvalid).toBe(false);
expect(input.props().isValid).toBe(false);
});

function shallowRender(props: Partial<ProjectKeyInputProps> = {}) {
return shallow<ProjectKeyInputProps>(
<ProjectKeyInput onProjectKeyChange={jest.fn()} touched={false} {...props} />
);
}

+ 132
- 0
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/ProjectKeyInput-test.tsx.snap View File

@@ -0,0 +1,132 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: default 1`] = `
<ValidationInput
className="form-field"
description="onboarding.create_project.project_key.description"
id="project-key"
isInvalid={false}
isValid={false}
required={false}
>
<input
autoFocus={true}
className="input-super-large"
id="project-key"
maxLength={400}
minLength={1}
onChange={[MockFunction]}
type="text"
/>
</ValidationInput>
`;

exports[`should render correctly: invalid 1`] = `
<ValidationInput
className="form-field"
description="onboarding.create_project.project_key.description"
error="bar.baz"
id="project-key"
isInvalid={true}
isValid={false}
required={false}
>
<input
autoFocus={true}
className="input-super-large is-invalid"
id="project-key"
maxLength={400}
minLength={1}
onChange={[MockFunction]}
type="text"
/>
</ValidationInput>
`;

exports[`should render correctly: valid 1`] = `
<ValidationInput
className="form-field"
description="onboarding.create_project.project_key.description"
id="project-key"
isInvalid={false}
isValid={true}
required={false}
>
<input
autoFocus={true}
className="input-super-large is-valid"
id="project-key"
maxLength={400}
minLength={1}
onChange={[MockFunction]}
type="text"
/>
</ValidationInput>
`;

exports[`should render correctly: validating 1`] = `
<ValidationInput
className="form-field"
description="onboarding.create_project.project_key.description"
id="project-key"
isInvalid={false}
isValid={false}
required={false}
>
<input
autoFocus={true}
className="input-super-large"
id="project-key"
maxLength={400}
minLength={1}
onChange={[MockFunction]}
type="text"
/>
</ValidationInput>
`;

exports[`should render correctly: with label, help, and placeholder 1`] = `
<ValidationInput
className="form-field"
description="onboarding.create_project.project_key.description"
help="foo.help"
id="project-key"
isInvalid={false}
isValid={false}
label="foo.label"
required={true}
>
<input
autoFocus={true}
className="input-super-large"
id="project-key"
maxLength={400}
minLength={1}
onChange={[MockFunction]}
placeholder="foo.placeholder"
type="text"
/>
</ValidationInput>
`;

exports[`should render correctly: with value 1`] = `
<ValidationInput
className="form-field"
description="onboarding.create_project.project_key.description"
id="project-key"
isInvalid={false}
isValid={false}
required={false}
>
<input
autoFocus={true}
className="input-super-large"
id="project-key"
maxLength={400}
minLength={1}
onChange={[MockFunction]}
type="text"
value="foo"
/>
</ValidationInput>
`;

+ 42
- 0
server/sonar-web/src/main/js/helpers/__tests__/projects-test.ts View File

@@ -0,0 +1,42 @@
/*
* SonarQube
* Copyright (C) 2009-2020 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 { ProjectKeyValidationResult } from '../../types/component';
import { PROJECT_KEY_MAX_LEN } from '../constants';
import { validateProjectKey } from '../projects';

describe('validateProjectKey', () => {
it('should correctly flag an invalid key', () => {
// Cannot have special characters except whitelist.
expect(validateProjectKey('foo/bar')).toBe(ProjectKeyValidationResult.InvalidChar);
// Cannot contain only numbers.
expect(validateProjectKey('123')).toBe(ProjectKeyValidationResult.OnlyDigits);
// Cannot be more than 400 chars long.
expect(validateProjectKey(new Array(PROJECT_KEY_MAX_LEN + 1).fill('a').join(''))).toBe(
ProjectKeyValidationResult.TooLong
);
// Cannot be empty.
expect(validateProjectKey('')).toBe(ProjectKeyValidationResult.Empty);
});

it('should not flag a valid key', () => {
expect(validateProjectKey('foo:bar_baz-12.is')).toBe(ProjectKeyValidationResult.Valid);
expect(validateProjectKey('12:34')).toBe(ProjectKeyValidationResult.Valid);
});
});

+ 2
- 0
server/sonar-web/src/main/js/helpers/constants.ts View File

@@ -53,3 +53,5 @@ export const RATING_COLORS = [
colors.orange,
colors.red
];

export const PROJECT_KEY_MAX_LEN = 400;

+ 39
- 0
server/sonar-web/src/main/js/helpers/projects.ts View File

@@ -0,0 +1,39 @@
/*
* SonarQube
* Copyright (C) 2009-2020 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 { ProjectKeyValidationResult } from '../types/component';
import { PROJECT_KEY_MAX_LEN } from './constants';

export function validateProjectKey(projectKey: string): ProjectKeyValidationResult {
// This is the regex used on the backend:
// [\p{Alnum}\-_.:]*[\p{Alpha}\-_.:]+[\p{Alnum}\-_.:]*
// See sonar-core/src/main/java/org/sonar/core/component/ComponentKeys.java
const regex = /^[\w\-.:]*[a-z\-_.:]+[\w\-.:]*$/i;
if (projectKey.length === 0) {
return ProjectKeyValidationResult.Empty;
} else if (projectKey.length > PROJECT_KEY_MAX_LEN) {
return ProjectKeyValidationResult.TooLong;
} else if (regex.test(projectKey)) {
return ProjectKeyValidationResult.Valid;
} else {
return /^[0-9]+$/.test(projectKey)
? ProjectKeyValidationResult.OnlyDigits
: ProjectKeyValidationResult.InvalidChar;
}
}

+ 12
- 5
server/sonar-web/src/main/js/types/component.ts View File

@@ -17,6 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
export enum Visibility {
Public = 'public',
Private = 'private'
}

export enum ComponentQualifier {
Application = 'APP',
@@ -30,6 +34,14 @@ export enum ComponentQualifier {
TestFile = 'UTS'
}

export enum ProjectKeyValidationResult {
Valid = 'valid',
Empty = 'empty',
TooLong = 'too_long',
InvalidChar = 'invalid_char',
OnlyDigits = 'only_digits'
}

export function isPortfolioLike(componentQualifier?: string | ComponentQualifier) {
return Boolean(
componentQualifier &&
@@ -39,8 +51,3 @@ export function isPortfolioLike(componentQualifier?: string | ComponentQualifier
].includes(componentQualifier)
);
}

export enum Visibility {
Public = 'public',
Private = 'private'
}

+ 7
- 3
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -3077,12 +3077,16 @@ onboarding.project_analysis.guide_to_integrate_pipelines=follow the guide to int

onboarding.create_project.setup_manually=Create manually
onboarding.create_project.project_key=Project key
onboarding.create_project.project_key.description=Up to 400 characters. All letters, digits, dash, underscore, period or colon.
onboarding.create_project.project_key.error=The provided value doesn't match the expected format.
onboarding.create_project.project_key.description=Up to 400 characters. Allowed characters are alphanumeric, '-' (dash), '_' (underscore), '.' (period) and ':' (colon), with at least one non-digit.
onboarding.create_project.project_key.error.empty=You must provide at least one character.
onboarding.create_project.project_key.error.too_long=The provided key is too long.
onboarding.create_project.project_key.error.invalid_char=The provided key contains invalid characters.
onboarding.create_project.project_key.error.only_digits=The provided key contains only digits.
onboarding.create_project.project_key.help=Your project key is a unique identifier for your project. If you are using Maven, make sure the key matches the "groupId:artifactId" format.
onboarding.create_project.project_key.taken=This project key is already taken.
onboarding.create_project.display_name=Display name
onboarding.create_project.display_name.error=The display name is required.
onboarding.create_project.display_name.error.empty=The display name is required.
onboarding.create_project.display_name.error.too_long=The display name is too long.
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 set up

Loading…
Cancel
Save