Browse Source

SONAR-11637 General setting for new code period

tags/8.0
Jeremy Davis 4 years ago
parent
commit
035fbea593
19 changed files with 1109 additions and 5 deletions
  1. 37
    0
      server/sonar-web/src/main/js/api/newCodePeriod.ts
  2. 114
    0
      server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap
  3. 6
    0
      server/sonar-web/src/main/js/app/types.d.ts
  4. 69
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BaselineSettingDays-test.tsx
  5. 46
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BaselineSettingPreviousVersion-test.tsx
  6. 82
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BaselineSettingDays-test.tsx.snap
  7. 25
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BaselineSettingPreviousVersion-test.tsx.snap
  8. 59
    0
      server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx
  9. 41
    0
      server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx
  10. 11
    4
      server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx
  11. 10
    1
      server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx
  12. 216
    0
      server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx
  13. 41
    0
      server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx
  14. 49
    0
      server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx
  15. 136
    0
      server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-test.tsx
  16. 65
    0
      server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap
  17. 5
    0
      server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap
  18. 59
    0
      server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap
  19. 38
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 37
- 0
server/sonar-web/src/main/js/api/newCodePeriod.ts View File

@@ -0,0 +1,37 @@
/*
* 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 { getJSON, post } from 'sonar-ui-common/helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';

export function getNewCodePeriod(data?: {
project?: string;
branch?: string;
}): Promise<{ type: T.NewCodePeriodSettingType; inherited?: boolean; value?: string }> {
return getJSON('/api/new_code_periods/show', data).catch(throwGlobalError);
}

export function setNewCodePeriod(data: {
project?: string;
branch?: string;
type: T.NewCodePeriodSettingType;
value: string | null;
}): Promise<void> {
return post('/api/new_code_periods/set', data).catch(throwGlobalError);
}

+ 114
- 0
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/ComponentNavMenu-test.tsx.snap View File

@@ -164,6 +164,23 @@ exports[`should work for all qualifiers 1`] = `
project_branches.page
</Link>
</li>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/baseline",
"query": Object {
"id": "foo",
},
}
}
>
project_baseline.page
</Link>
</li>
<li>
<Link
activeClassName="active"
@@ -302,6 +319,23 @@ exports[`should work for all qualifiers 2`] = `
<ul
className="menu"
>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/baseline",
"query": Object {
"id": "foo",
},
}
}
>
project_baseline.page
</Link>
</li>
<li>
<Link
activeClassName="active"
@@ -417,6 +451,35 @@ exports[`should work for all qualifiers 3`] = `
project_activity.page
</Link>
</li>
<Dropdown
data-test="administration"
overlay={
<ul
className="menu"
>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/baseline",
"query": Object {
"id": "foo",
},
}
}
>
project_baseline.page
</Link>
</li>
</ul>
}
tagName="li"
>
<Component />
</Dropdown>
</NavBarTabs>
`;

@@ -515,6 +578,23 @@ exports[`should work for all qualifiers 4`] = `
<ul
className="menu"
>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/baseline",
"query": Object {
"id": "foo",
},
}
}
>
project_baseline.page
</Link>
</li>
<li>
<Link
activeClassName="active"
@@ -888,6 +968,23 @@ exports[`should work with extensions 2`] = `
project_branches.page
</Link>
</li>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/baseline",
"query": Object {
"id": "foo",
},
}
}
>
project_baseline.page
</Link>
</li>
<li>
<Link
activeClassName="active"
@@ -1040,6 +1137,23 @@ exports[`should work with multiple extensions 2`] = `
project_branches.page
</Link>
</li>
<li>
<Link
activeClassName="active"
onlyActiveOnIndex={false}
style={Object {}}
to={
Object {
"pathname": "/project/baseline",
"query": Object {
"id": "foo",
},
}
}
>
project_baseline.page
</Link>
</li>
<li>
<Link
activeClassName="active"

+ 6
- 0
server/sonar-web/src/main/js/app/types.d.ts View File

@@ -499,6 +499,12 @@ declare namespace T {
qualityGate?: string;
}

export type NewCodePeriodSettingType =
| 'PREVIOUS_VERSION'
| 'NUMBER_OF_DAYS'
| 'DATE'
| 'SPECIFIC_ANALYSIS';

export interface Notification {
channel: string;
organization?: string;

+ 69
- 0
server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BaselineSettingDays-test.tsx View File

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

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender({ isChanged: true })).toMatchSnapshot();
expect(shallowRender({ isChanged: true, isValid: false })).toMatchSnapshot();
});

it('should not display input when not selected', () => {
const wrapper = shallowRender({ selected: false });
expect(wrapper.find('ValidationInput')).toHaveLength(0);
});

it('should callback when clicked', () => {
const onSelect = jest.fn();
const wrapper = shallowRender({ onSelect, selected: false });

wrapper
.find('RadioCard')
.first()
.simulate('click');
expect(onSelect).toHaveBeenCalledWith('NUMBER_OF_DAYS');
});

it('should callback when changing days', () => {
const onChangeDays = jest.fn();
const wrapper = shallowRender({ onChangeDays });

wrapper
.find('input')
.first()
.simulate('change', { currentTarget: { value: '23' } });
expect(onChangeDays).toHaveBeenCalledWith('23');
});

function shallowRender(props: Partial<Props> = {}) {
return shallow(
<BaselineSettingDays
days="28"
isChanged={false}
isValid={true}
onChangeDays={jest.fn()}
onSelect={jest.fn()}
selected={true}
{...props}
/>
);
}

+ 46
- 0
server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BaselineSettingPreviousVersion-test.tsx View File

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

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
expect(shallowRender({ isDefault: true })).toMatchSnapshot();
});

it('should callback when clicked', () => {
const onSelect = jest.fn();
const wrapper = shallowRender({ onSelect, selected: false });

wrapper
.find('RadioCard')
.first()
.simulate('click');
expect(onSelect).toHaveBeenCalledWith('PREVIOUS_VERSION');
});

function shallowRender(props: Partial<Props> = {}) {
return shallow(
<BaselineSettingPreviousVersion onSelect={jest.fn()} selected={true} {...props} />
);
}

+ 82
- 0
server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BaselineSettingDays-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<RadioCard
onClick={[Function]}
selected={true}
title="baseline.number_days"
>
<p
className="big-spacer-bottom"
>
baseline.number_days.description
</p>
<ValidationInput
id="baseline_number_of_days"
isInvalid={false}
isValid={false}
label="baseline.specify_days"
required={true}
>
<input
onChange={[Function]}
type="text"
value="28"
/>
</ValidationInput>
</RadioCard>
`;

exports[`should render correctly 2`] = `
<RadioCard
onClick={[Function]}
selected={true}
title="baseline.number_days"
>
<p
className="big-spacer-bottom"
>
baseline.number_days.description
</p>
<ValidationInput
id="baseline_number_of_days"
isInvalid={false}
isValid={true}
label="baseline.specify_days"
required={true}
>
<input
onChange={[Function]}
type="text"
value="28"
/>
</ValidationInput>
</RadioCard>
`;

exports[`should render correctly 3`] = `
<RadioCard
onClick={[Function]}
selected={true}
title="baseline.number_days"
>
<p
className="big-spacer-bottom"
>
baseline.number_days.description
</p>
<ValidationInput
id="baseline_number_of_days"
isInvalid={true}
isValid={false}
label="baseline.specify_days"
required={true}
>
<input
onChange={[Function]}
type="text"
value="28"
/>
</ValidationInput>
</RadioCard>
`;

+ 25
- 0
server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BaselineSettingPreviousVersion-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<RadioCard
onClick={[Function]}
selected={true}
title="baseline.previous_version"
>
<p>
baseline.previous_version.description
</p>
</RadioCard>
`;

exports[`should render correctly 2`] = `
<RadioCard
onClick={[Function]}
selected={true}
title="baseline.previous_version (default)"
>
<p>
baseline.previous_version.description
</p>
</RadioCard>
`;

+ 59
- 0
server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingDays.tsx View File

@@ -0,0 +1,59 @@
/*
* 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 RadioCard from 'sonar-ui-common/components/controls/RadioCard';
import ValidationInput from 'sonar-ui-common/components/controls/ValidationInput';
import { translate } from 'sonar-ui-common/helpers/l10n';

export interface Props {
className?: string;
days: string;
isChanged: boolean;
isValid: boolean;
onChangeDays: (value: string) => void;
onSelect: (selection: T.NewCodePeriodSettingType) => void;
selected: boolean;
}

export default function BaselineSettingDays(props: Props) {
const { className, days, isChanged, isValid, onChangeDays, onSelect, selected } = props;
return (
<RadioCard
className={className}
onClick={() => onSelect('NUMBER_OF_DAYS')}
selected={selected}
title={translate('baseline.number_days')}>
<>
<p className="big-spacer-bottom">{translate('baseline.number_days.description')}</p>
{selected && (
<ValidationInput
error={undefined}
id="baseline_number_of_days"
isInvalid={isChanged && !isValid}
isValid={isChanged && isValid}
label={translate('baseline.specify_days')}
required={true}>
<input onChange={e => onChangeDays(e.currentTarget.value)} type="text" value={days} />
</ValidationInput>
)}
</>
</RadioCard>
);
}

+ 41
- 0
server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingPreviousVersion.tsx View File

@@ -0,0 +1,41 @@
/*
* 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 RadioCard from 'sonar-ui-common/components/controls/RadioCard';
import { translate } from 'sonar-ui-common/helpers/l10n';

export interface Props {
isDefault?: boolean;
onSelect: (selection: T.NewCodePeriodSettingType) => void;
selected: boolean;
}

export default function BaselineSettingPreviousVersion({ isDefault, onSelect, selected }: Props) {
return (
<RadioCard
onClick={() => onSelect('PREVIOUS_VERSION')}
selected={selected}
title={
translate('baseline.previous_version') + (isDefault ? ` (${translate('default')})` : '')
}>
<p>{translate('baseline.previous_version.description')}</p>
</RadioCard>
);
}

+ 11
- 4
server/sonar-web/src/main/js/apps/settings/components/AllCategoriesList.tsx View File

@@ -22,6 +22,7 @@ import { sortBy } from 'lodash';
import * as React from 'react';
import { connect } from 'react-redux';
import { IndexLink } from 'react-router';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getSettingsAppAllCategories, Store } from '../../../store/rootReducer';
import { getCategoryName } from '../utils';

@@ -37,6 +38,10 @@ interface Props {
selectedCategory: string;
}

const FIXED_CATEGORIES = [
{ key: 'new_code_period', name: translate('settings.new_code_period.category') }
];

export class CategoriesList extends React.PureComponent<Props> {
renderLink(category: Category) {
const { component, defaultCategory, selectedCategory } = this.props;
@@ -58,10 +63,12 @@ export class CategoriesList extends React.PureComponent<Props> {
}

render() {
const categoriesWithName = this.props.categories.map(key => ({
key,
name: getCategoryName(key)
}));
const categoriesWithName = this.props.categories
.map(key => ({
key,
name: getCategoryName(key)
}))
.concat(FIXED_CATEGORIES);
const sortedCategories = sortBy(categoriesWithName, category => category.name.toLowerCase());
return (
<ul className="side-tabs-menu">

+ 10
- 1
server/sonar-web/src/main/js/apps/settings/components/AppContainer.tsx View File

@@ -29,6 +29,7 @@ import { fetchSettings } from '../store/actions';
import '../styles.css';
import AllCategoriesList from './AllCategoriesList';
import CategoryDefinitionsList from './CategoryDefinitionsList';
import NewCodePeriod from './NewCodePeriod';
import PageHeader from './PageHeader';
import WildcardsHelp from './WildcardsHelp';

@@ -96,7 +97,15 @@ export class App extends React.PureComponent<Props & WithRouterProps, State> {
/>
</div>
<div className="side-tabs-main">
<CategoryDefinitionsList category={selectedCategory} component={this.props.component} />
{selectedCategory === 'new_code_period' ? (
<NewCodePeriod />
) : (
<CategoryDefinitionsList
category={selectedCategory}
component={this.props.component}
/>
)}

{selectedCategory === 'exclusions' && <WildcardsHelp />}
</div>
</div>

+ 216
- 0
server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx View File

@@ -0,0 +1,216 @@
/*
* 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 { FormattedMessage } from 'react-intl';
import { SubmitButton } from 'sonar-ui-common/components/controls/buttons';
import AlertSuccessIcon from 'sonar-ui-common/components/icons/AlertSuccessIcon';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod';
import BaselineSettingDays from '../../projectBaseline/components/BaselineSettingDays';
import BaselineSettingPreviousVersion from '../../projectBaseline/components/BaselineSettingPreviousVersion';
import { validateDays } from '../../projectBaseline/utils';

interface Props {}

interface State {
currentSetting?: T.NewCodePeriodSettingType;
days: string;
loading: boolean;
currentSettingValue?: string | number;
saving: boolean;
selected?: T.NewCodePeriodSettingType;
success: boolean;
}

const DEFAULT_SETTING = 'PREVIOUS_VERSION';

export default class NewCodePeriod extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
loading: true,
days: '30',
saving: false,
success: false
};

componentDidMount() {
this.mounted = true;
this.fetchNewCodePeriodSetting();
}

componentWillUnmount() {
this.mounted = false;
}

fetchNewCodePeriodSetting() {
getNewCodePeriod()
.then(({ type, value }) => {
const currentSetting = type || DEFAULT_SETTING;

this.setState(({ days }) => ({
currentSetting,
days: currentSetting === 'NUMBER_OF_DAYS' ? String(value) : days,
loading: false,
currentSettingValue: value,
selected: currentSetting
}));
})
.catch(() => {
this.setState({ loading: false });
});
}

onSelectDays = (days: string) => {
this.setState({ days, success: false });
};

onSelectSetting = (selected: T.NewCodePeriodSettingType) => {
this.setState({ selected, success: false });
};

onSubmit = (e: React.SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();

const { days, selected } = this.state;

const type = selected;
const value = type === 'NUMBER_OF_DAYS' ? days : null;

if (type) {
this.setState({ saving: true, success: false });
setNewCodePeriod({
type,
value
}).then(
() => {
this.setState({
saving: false,
currentSetting: type,
currentSettingValue: value || undefined,
success: true
});
},
() => {
this.setState({
saving: false
});
}
);
}
};

render() {
const {
currentSetting,
days,
loading,
currentSettingValue,
saving,
selected,
success
} = this.state;

const isChanged =
selected !== currentSetting ||
(selected === 'NUMBER_OF_DAYS' && String(days) !== currentSettingValue);

const isValid = selected !== 'NUMBER_OF_DAYS' || validateDays(days);

return (
<ul className="settings-sub-categories-list">
<li>
<ul className="settings-definitions-list">
<li>
<div className="settings-definition">
<div className="settings-definition-left">
<h3
className="settings-definition-name"
title={translate('settings.new_code_period.title')}>
{translate('settings.new_code_period.title')}
</h3>

<div className="small big-spacer-top">
<FormattedMessage
defaultMessage={translate('settings.new_code_period.description')}
id="settings.new_code_period.description"
values={{
link: (
<a href="/documentation/user-guide/fixing-the-water-leak/">
{translate('learn_more')}
</a>
)
}}
/>
<p className="spacer-top">
{translate('settings.new_code_period.description2')}
</p>
</div>
</div>

<div className="settings-definition-right">
{loading ? (
<DeferredSpinner />
) : (
<form onSubmit={this.onSubmit}>
<BaselineSettingPreviousVersion
isDefault={true}
onSelect={this.onSelectSetting}
selected={selected === 'PREVIOUS_VERSION'}
/>
<BaselineSettingDays
className="spacer-top"
days={days}
isChanged={isChanged}
isValid={isValid}
onChangeDays={this.onSelectDays}
onSelect={this.onSelectSetting}
selected={selected === 'NUMBER_OF_DAYS'}
/>
{isChanged && (
<div className="big-spacer-top">
<p className="spacer-bottom">
{translate('baseline.next_analysis_notice')}
</p>
<DeferredSpinner className="spacer-right" loading={saving} />
<SubmitButton disabled={saving || !isValid}>
{translate('save')}
</SubmitButton>
</div>
)}
{!saving && !loading && success && (
<div className="big-spacer-top">
<span className="text-success">
<AlertSuccessIcon className="spacer-right" />
{translate('settings.state.saved')}
</span>
</div>
)}
</form>
)}
</div>
</div>
</li>
</ul>
</li>
</ul>
);
}
}

+ 41
- 0
server/sonar-web/src/main/js/apps/settings/components/__tests__/AllCategoriesList-test.tsx View File

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

it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('li')).toHaveLength(3);
});

function shallowRender(props: Partial<CategoriesList['props']> = {}) {
const categories = ['COBOL', 'general'];

return shallow(
<CategoriesList
categories={categories}
defaultCategory="general"
selectedCategory=""
{...props}
/>
);
}

+ 49
- 0
server/sonar-web/src/main/js/apps/settings/components/__tests__/AppContainer-test.tsx View File

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

it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});

it('should render newCodePeriod correctly', () => {
const wrapper = shallowRender({
location: mockLocation({ query: { category: 'new_code_period' } })
});
expect(wrapper).toMatchSnapshot();
});

function shallowRender(props: Partial<App['props']> = {}) {
return shallow(
<App
defaultCategory="general"
fetchSettings={jest.fn().mockResolvedValue({})}
location={mockLocation()}
params={{}}
router={mockRouter()}
routes={[]}
{...props}
/>
);
}

+ 136
- 0
server/sonar-web/src/main/js/apps/settings/components/__tests__/NewCodePeriod-test.tsx View File

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

jest.mock('../../../../api/newCodePeriod', () => ({
getNewCodePeriod: jest.fn().mockResolvedValue({}),
setNewCodePeriod: jest.fn(() => Promise.resolve())
}));

beforeEach(() => {
jest.clearAllMocks();
});

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should load the current new code period on mount', async () => {
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(getNewCodePeriod).toHaveBeenCalledTimes(1);
expect(wrapper.state('currentSetting')).toBe('PREVIOUS_VERSION');
});

it('should load the current new code period with value on mount', async () => {
(getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });

const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(getNewCodePeriod).toHaveBeenCalledTimes(1);
expect(wrapper.state('currentSetting')).toBe('NUMBER_OF_DAYS');
expect(wrapper.state('days')).toBe('42');
});

it('should only show the save button after changing the setting', async () => {
(getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'PREVIOUS_VERSION' });

const wrapper = shallowRender();
await waitAndUpdate(wrapper);

expect(wrapper.state('selected')).toBe('PREVIOUS_VERSION');
expect(wrapper.find('SubmitButton')).toHaveLength(0);

wrapper.instance().onSelectSetting('NUMBER_OF_DAYS');
await waitAndUpdate(wrapper);

expect(wrapper.find('SubmitButton')).toHaveLength(1);
});

it('should disable the button if the days are invalid', async () => {
(getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });

const wrapper = shallowRender();
await waitAndUpdate(wrapper);

wrapper.instance().onSelectDays('asd');
await waitAndUpdate(wrapper);

expect(
wrapper
.find('SubmitButton')
.first()
.prop('disabled')
).toBe(true);

wrapper.instance().onSelectDays('23');
await waitAndUpdate(wrapper);

expect(
wrapper
.find('SubmitButton')
.first()
.prop('disabled')
).toBe(false);
});

it('should submit correctly', async () => {
(getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });

const preventDefault = jest.fn();

const wrapper = shallowRender();
await waitAndUpdate(wrapper);
wrapper.instance().onSelectSetting('PREVIOUS_VERSION');
await waitAndUpdate(wrapper);

wrapper.find('form').simulate('submit', { preventDefault });

expect(preventDefault).toBeCalledTimes(1);
expect(setNewCodePeriod).toBeCalledWith({ type: 'PREVIOUS_VERSION', value: null });
await waitAndUpdate(wrapper);
expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected'));
});

it('should submit correctly with days', async () => {
(getNewCodePeriod as jest.Mock).mockResolvedValue({ type: 'NUMBER_OF_DAYS', value: '42' });

const preventDefault = jest.fn();

const wrapper = shallowRender();
await waitAndUpdate(wrapper);
wrapper.instance().onSelectDays('66');
await waitAndUpdate(wrapper);

wrapper.find('form').simulate('submit', { preventDefault });

expect(preventDefault).toBeCalledTimes(1);
expect(setNewCodePeriod).toBeCalledWith({ type: 'NUMBER_OF_DAYS', value: '66' });
await waitAndUpdate(wrapper);
expect(wrapper.state('currentSetting')).toEqual(wrapper.state('selected'));
});

function shallowRender() {
return shallow<NewCodePeriod>(<NewCodePeriod />);
}

+ 65
- 0
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AllCategoriesList-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<ul
className="side-tabs-menu"
>
<li
key="COBOL"
>
<IndexLink
className=""
title="COBOL"
to={
Object {
"pathname": "/settings",
"query": Object {
"category": "cobol",
"id": undefined,
},
}
}
>
COBOL
</IndexLink>
</li>
<li
key="general"
>
<IndexLink
className=""
title="general"
to={
Object {
"pathname": "/settings",
"query": Object {
"category": undefined,
"id": undefined,
},
}
}
>
general
</IndexLink>
</li>
<li
key="new_code_period"
>
<IndexLink
className=""
title="settings.new_code_period.category"
to={
Object {
"pathname": "/settings",
"query": Object {
"category": "new_code_period",
"id": undefined,
},
}
}
>
settings.new_code_period.category
</IndexLink>
</li>
</ul>
`;

+ 5
- 0
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AppContainer-test.tsx.snap View File

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

exports[`should render correctly 1`] = `""`;

exports[`should render newCodePeriod correctly 1`] = `""`;

+ 59
- 0
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/NewCodePeriod-test.tsx.snap View File

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

exports[`should render correctly 1`] = `
<ul
className="settings-sub-categories-list"
>
<li>
<ul
className="settings-definitions-list"
>
<li>
<div
className="settings-definition"
>
<div
className="settings-definition-left"
>
<h3
className="settings-definition-name"
title="settings.new_code_period.title"
>
settings.new_code_period.title
</h3>
<div
className="small big-spacer-top"
>
<FormattedMessage
defaultMessage="settings.new_code_period.description"
id="settings.new_code_period.description"
values={
Object {
"link": <a
href="/documentation/user-guide/fixing-the-water-leak/"
>
learn_more
</a>,
}
}
/>
<p
className="spacer-top"
>
settings.new_code_period.description2
</p>
</div>
</div>
<div
className="settings-definition-right"
>
<DeferredSpinner
timeout={100}
/>
</div>
</div>
</li>
</ul>
</li>
</ul>
`;

+ 38
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -538,6 +538,38 @@ project_branches.page=Branches & Pull Requests
project_branches.page.description=Use this page to manage project branches and pull requests.
project_branches.page.life_time=Short-lived branches and pull requests are permanently deleted after {days} days without analysis.
project_branches.page.life_time.admin=You can adjust this value globally in {settings}.
project_baseline.page=New Code Period
project_baseline.page.description=Use this page to manage the New Code Period of your project. {link}
project_baseline.page.description.link=Learn More
project_baseline.page.description2=You can adjust this setting globally in {link}
project_baseline.page.description2.link=General Settings
project_baseline.default_setting=Project default setting
project_baseline.default_setting.description=This setting is the default for all branches of the project

baseline.previous_version=Previous version
baseline.previous_version.description=The New Code Period will begin with the analysis following the previous version.
baseline.number_days=Number of days
baseline.number_days.description=Specify a number of days to set a floating New Code Period window.
baseline.specific_date=Specific date
baseline.specific_date.description=Set a specific date as the start of the New Code Period. (First analysis on this date will be used)
baseline.specific_analysis=Specific analysis
baseline.specific_analysis.description=Choose an analysis as the baseline for the New Code Period.

baseline.specify_days=Specify a number of days
baseline.last_analysis_before=Last analysis before
baseline.next_analysis_notice=Changes will take effect after the next analysis

branch_list.branch=Branch
branch_list.current_setting=Current setting
branch_list.current_baseline=Current Baseline
branch_list.edit_settings=Edit settings

baseline.new_code_period_for_branch_x=New Code Period for {0}

baseline.analysis_from=Analysis from:
baseline.branch_analyses.ranges.30days=Last 30 days
baseline.branch_analyses.ranges.allTime=All time
baseline.no_analyses=No analyses

#------------------------------------------------------------------------------
#
@@ -880,6 +912,12 @@ settings.wildcards.single_char=Match a single character
settings.wildcards.matches=Matches
settings.wildcards.does_no_match=Does not match

settings.new_code_period.category=New Code Period
settings.new_code_period.title=Default New Code Period behavior
settings.new_code_period.description=The New Code Period is the period used to compare measures and track new issues. {link}
settings.new_code_period.description2=This setting is the default for all projects. A specific New Code Period setting can be configured at project level.


property.category.general=General
property.category.general.email=Email
property.category.general.duplications=Duplications

Loading…
Cancel
Save