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

SONAR-11658 branch setting for the new code period

tags/8.0
Jeremy Davis 4 лет назад
Родитель
Сommit
0d22031fff
24 измененных файлов: 1403 добавлений и 40 удалений
  1. 6
    0
      server/sonar-web/src/main/js/api/newCodePeriod.ts
  2. 7
    1
      server/sonar-web/src/main/js/api/projectActivity.ts
  3. 5
    0
      server/sonar-web/src/main/js/app/styles/init/misc.css
  4. 15
    1
      server/sonar-web/src/main/js/app/types.d.ts
  5. 3
    3
      server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/__snapshots__/ProjectActivityAnalysis-test.tsx.snap
  6. 3
    1
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/App-test.tsx
  7. 41
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BaselineSettingAnalysis-test.tsx
  8. 110
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BranchAnalysisList-test.tsx
  9. 120
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BranchBaselineSettingModal-test.tsx
  10. 117
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BranchList-test.tsx
  11. 10
    6
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/App-test.tsx.snap
  12. 15
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BaselineSettingAnalysis-test.tsx.snap
  13. 45
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BranchAnalysisList-test.tsx.snap
  14. 66
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BranchBaselineSettingModal-test.tsx.snap
  15. 118
    0
      server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/BranchList-test.tsx.snap
  16. 9
    4
      server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx
  17. 17
    13
      server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingAnalysis.tsx
  18. 265
    0
      server/sonar-web/src/main/js/apps/projectBaseline/components/BranchAnalysisList.tsx
  19. 191
    0
      server/sonar-web/src/main/js/apps/projectBaseline/components/BranchBaselineSettingModal.tsx
  20. 218
    0
      server/sonar-web/src/main/js/apps/projectBaseline/components/BranchList.tsx
  21. 7
    7
      server/sonar-web/src/main/js/apps/projectBaseline/styles.css
  22. 1
    3
      server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx
  23. 11
    1
      server/sonar-web/src/main/js/helpers/testMocks.ts
  24. 3
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 6
- 0
server/sonar-web/src/main/js/api/newCodePeriod.ts Просмотреть файл

@@ -39,3 +39,9 @@ export function setNewCodePeriod(data: {
export function resetNewCodePeriod(data: { project?: string; branch?: string }): Promise<void> {
return post('/api/new_code_periods/unset', data).catch(throwGlobalError);
}

export function listBranchesNewCodePeriod(data: {
project: string;
}): Promise<{ newCodePeriods: T.NewCodePeriodBranch[] }> {
return getJSON('/api/new_code_periods/list', data).catch(throwGlobalError);
}

+ 7
- 1
server/sonar-web/src/main/js/api/projectActivity.ts Просмотреть файл

@@ -21,7 +21,13 @@ import { getJSON, post, postJSON, RequestData } from 'sonar-ui-common/helpers/re
import throwGlobalError from '../app/utils/throwGlobalError';

export function getProjectActivity(
data: { project: string; category?: string; p?: number; ps?: number } & T.BranchParameters
data: {
project: string;
category?: string;
from?: string;
p?: number;
ps?: number;
} & T.BranchParameters
): Promise<{ analyses: T.Analysis[]; paging: T.Paging }> {
return getJSON('/api/project_analyses/search', data).catch(throwGlobalError);
}

+ 5
- 0
server/sonar-web/src/main/js/app/styles/init/misc.css Просмотреть файл

@@ -174,6 +174,11 @@ td.big-spacer-top {
padding-top: 16px !important;
}

td.huge-spacer-right,
th.huge-spacer-right {
padding-right: 40px !important;
}

.pull-left {
float: left !important;
}

+ 15
- 1
server/sonar-web/src/main/js/app/types.d.ts Просмотреть файл

@@ -114,6 +114,13 @@ declare namespace T {

export type BranchType = 'LONG' | 'SHORT';

export interface BranchWithNewCodePeriod extends Branch {
newCodePeriod?: {
type: NewCodePeriodSettingType;
value: string | null;
};
}

export interface Breadcrumb {
key: string;
name: string;
@@ -499,10 +506,17 @@ declare namespace T {
qualityGate?: string;
}

export interface NewCodePeriodBranch {
projectKey: string;
branchKey: string;
inherited?: boolean;
type?: NewCodePeriodSettingType;
value?: string | null;
}

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

export interface Notification {

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

@@ -361,7 +361,7 @@ exports[`should show the correct admin options 2`] = `
addEventButtonText="project_activity.add_custom_event"
analysis={
Object {
"date": 2017-03-01T08:36:01.000Z,
"date": 2017-03-01T08:37:01.000Z,
"events": Array [],
"key": "foo",
"projectVersion": "1.0",
@@ -442,7 +442,7 @@ exports[`should show the correct admin options 3`] = `
<RemoveAnalysisForm
analysis={
Object {
"date": 2017-03-01T08:36:01.000Z,
"date": 2017-03-01T08:37:01.000Z,
"events": Array [],
"key": "foo",
"projectVersion": "1.0",
@@ -526,7 +526,7 @@ exports[`should show the correct admin options 4`] = `
addEventButtonText="project_activity.add_version"
analysis={
Object {
"date": 2017-03-01T08:36:01.000Z,
"date": 2017-03-01T08:37:01.000Z,
"events": Array [],
"key": "foo",
"projectVersion": "1.0",

+ 3
- 1
server/sonar-web/src/main/js/apps/projectBaseline/__tests__/App-test.tsx Просмотреть файл

@@ -92,5 +92,7 @@ it('should handle errors gracefully', async () => {
});

function shallowRender(props: Partial<App['props']> = {}) {
return shallow<App>(<App canAdmin={true} component={mockComponent()} {...props} />);
return shallow<App>(
<App branchLikes={[]} canAdmin={true} component={mockComponent()} {...props} />
);
}

+ 41
- 0
server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BaselineSettingAnalysis-test.tsx Просмотреть файл

@@ -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 BaselineSettingAnalysis, { Props } from '../components/BaselineSettingAnalysis';

it('should render correctly', () => {
expect(shallowRender()).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('SPECIFIC_ANALYSIS');
});

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

+ 110
- 0
server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BranchAnalysisList-test.tsx Просмотреть файл

@@ -0,0 +1,110 @@
/*
* 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 { subDays } from 'date-fns';
import { shallow } from 'enzyme';
import * as React from 'react';
import { toShortNotSoISOString } from 'sonar-ui-common/helpers/dates';
import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { getProjectActivity } from '../../../api/projectActivity';
import { mockAnalysis, mockAnalysisEvent } from '../../../helpers/testMocks';
import BranchAnalysisList from '../components/BranchAnalysisList';

jest.mock('../../../api/projectActivity', () => ({
getProjectActivity: jest.fn().mockResolvedValue({
analyses: []
})
}));

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

it('should render correctly', async () => {
(getProjectActivity as jest.Mock).mockResolvedValueOnce({
analyses: [
mockAnalysis({
key: '4',
date: '2017-03-02T10:36:01+0100',
projectVersion: '4.2'
}),
mockAnalysis({
key: '3',
date: '2017-03-02T09:36:01+0100',
events: [mockAnalysisEvent()],
projectVersion: '4.2'
}),
mockAnalysis({
key: '2',
events: [
mockAnalysisEvent(),
mockAnalysisEvent({ category: 'VERSION', qualityGate: undefined })
],
projectVersion: '4.1'
}),
mockAnalysis({ key: '1', projectVersion: '4.1' })
]
});

const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});

it('should reload analyses after range change', () => {
const wrapper = shallowRender();

wrapper.instance().handleRangeChange({ value: 20 });

expect(getProjectActivity).toBeCalledWith({
branch: 'master',
project: 'project1',
from: toShortNotSoISOString(subDays(new Date(), 20))
});
});

it('should register the badge nodes', () => {
const wrapper = shallowRender();

const element = document.createElement('div');

wrapper.instance().registerBadgeNode('4.3')(element);

expect(element.getAttribute('originOffsetTop')).not.toBeNull();
});

it('should handle scroll', () => {
const wrapper = shallowRender();

wrapper.instance().handleScroll({ currentTarget: { scrollTop: 12 } } as any);

expect(wrapper.state('scroll')).toBe(12);
});

function shallowRender(props: Partial<BranchAnalysisList['props']> = {}) {
return shallow<BranchAnalysisList>(
<BranchAnalysisList
analysis="analysis1"
branch="master"
component="project1"
onSelectAnalysis={jest.fn()}
{...props}
/>
);
}

+ 120
- 0
server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BranchBaselineSettingModal-test.tsx Просмотреть файл

@@ -0,0 +1,120 @@
/*
* 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 { mockEvent, waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
import { setNewCodePeriod } from '../../../api/newCodePeriod';
import { mockMainBranch } from '../../../helpers/testMocks';
import BranchBaselineSettingModal from '../components/BranchBaselineSettingModal';

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

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

it('should display the branch analysis list when necessary', () => {
const wrapper = shallowRender();

wrapper.setState({ selected: 'SPECIFIC_ANALYSIS' });

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

it('should save correctly', async () => {
const branch = mockMainBranch({ name: 'branchname' });
const component = 'compKey';
const wrapper = shallowRender({
branch,
component
});

wrapper.setState({ analysis: 'analysis572893', selected: 'SPECIFIC_ANALYSIS' });
await waitAndUpdate(wrapper);

wrapper.instance().handleSubmit(mockEvent());
await new Promise(setImmediate);

expect(setNewCodePeriod).toHaveBeenCalledWith({
project: component,
type: 'SPECIFIC_ANALYSIS',
value: 'analysis572893',
branch: 'branchname'
});
});

it('should disable the save button when saving', () => {
const wrapper = shallowRender();

wrapper.setState({ saving: true });

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

it('should disable the save button when date is invalid', () => {
const wrapper = shallowRender();

wrapper.setState({ days: 'asdf' });

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

describe('getSettingValue', () => {
const wrapper = shallowRender();
wrapper.setState({ analysis: 'analysis1', days: '35' });

it('should work for Days', () => {
wrapper.setState({ selected: 'NUMBER_OF_DAYS' });
expect(wrapper.instance().getSettingValue()).toBe('35');
});

it('should work for Analysis', () => {
wrapper.setState({ selected: 'SPECIFIC_ANALYSIS' });
expect(wrapper.instance().getSettingValue()).toBe('analysis1');
});

it('should work for Previous version', () => {
wrapper.setState({ selected: 'PREVIOUS_VERSION' });
expect(wrapper.instance().getSettingValue()).toBeNull();
});
});

function shallowRender(props: Partial<BranchBaselineSettingModal['props']> = {}) {
return shallow<BranchBaselineSettingModal>(
<BranchBaselineSettingModal
branch={mockMainBranch()}
component="compKey"
onClose={jest.fn()}
{...props}
/>
);
}

+ 117
- 0
server/sonar-web/src/main/js/apps/projectBaseline/__tests__/BranchList-test.tsx Просмотреть файл

@@ -0,0 +1,117 @@
/*
* 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 { listBranchesNewCodePeriod, resetNewCodePeriod } from '../../../api/newCodePeriod';
import {
mockComponent,
mockLongLivingBranch,
mockMainBranch,
mockPullRequest,
mockShortLivingBranch
} from '../../../helpers/testMocks';
import BranchList from '../components/BranchList';

jest.mock('../../../api/newCodePeriod', () => ({
listBranchesNewCodePeriod: jest.fn().mockResolvedValue({ newCodePeriods: [] }),
resetNewCodePeriod: jest.fn().mockResolvedValue(null)
}));

it('should render correctly', async () => {
(listBranchesNewCodePeriod as jest.Mock).mockResolvedValueOnce({
newCodePeriods: [
{
projectKey: '',
branchKey: 'master',
type: 'NUMBER_OF_DAYS',
value: '27'
}
]
});
const wrapper = shallowRender({
branchLikes: [
mockMainBranch(),
mockLongLivingBranch(),
mockShortLivingBranch(),
mockPullRequest()
]
});
await waitAndUpdate(wrapper);
expect(wrapper.state('branches')).toHaveLength(2);
expect(wrapper).toMatchSnapshot();
});

it('should handle reset', () => {
const component = mockComponent();
const wrapper = shallowRender({ component });

wrapper.instance().resetToDefault('master');

expect(resetNewCodePeriod).toBeCalledWith({
project: component.key,
branch: 'master'
});
});

it('should toggle popup', async () => {
const wrapper = shallowRender({ branchLikes: [mockMainBranch(), mockLongLivingBranch()] });

wrapper.setState({ editedBranch: mockMainBranch() });

await waitAndUpdate(wrapper);

const nodes = wrapper.find('BranchBaselineSettingModal');
expect(nodes).toHaveLength(1);
expect(nodes.first().prop('branch')).toEqual(mockMainBranch());

wrapper.instance().closeEditModal('master', { type: 'NUMBER_OF_DAYS', value: '23' });

expect(wrapper.find('BranchBaselineSettingModal')).toHaveLength(0);
expect(wrapper.state('branches').find(b => b.name === 'master')).toEqual({
analysisDate: '2018-01-01',
isMain: true,
name: 'master',
newCodePeriod: {
type: 'NUMBER_OF_DAYS',
value: '23'
}
});
});

it('should render the right setting label', () => {
const wrapper = shallowRender();

expect(
wrapper.instance().renderNewCodePeriodSetting({ type: 'NUMBER_OF_DAYS', value: '21' })
).toBe('baseline.number_days: 21');
expect(
wrapper.instance().renderNewCodePeriodSetting({ type: 'PREVIOUS_VERSION', value: null })
).toBe('baseline.previous_version');
expect(
wrapper.instance().renderNewCodePeriodSetting({ type: 'SPECIFIC_ANALYSIS', value: 'A85835' })
).toBe('baseline.specific_analysis: A85835');
});

function shallowRender(props: Partial<BranchList['props']> = {}) {
return shallow<BranchList>(
<BranchList branchLikes={[]} component={mockComponent()} {...props} />
);
}

+ 10
- 6
server/sonar-web/src/main/js/apps/projectBaseline/__tests__/__snapshots__/App-test.tsx.snap Просмотреть файл

@@ -20,11 +20,13 @@ exports[`should render correctly 1`] = `
id="project_baseline.page.description"
values={
Object {
"link": <a
href="/documentation/user-guide/fixing-the-water-leak/"
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
to="/documentation/user-guide/fixing-the-water-leak/"
>
project_baseline.page.description.link
</a>,
</Link>,
}
}
/>
@@ -34,11 +36,13 @@ exports[`should render correctly 1`] = `
id="project_baseline.page.description2"
values={
Object {
"link": <a
href="/admin/settings?category=new_code_period"
"link": <Link
onlyActiveOnIndex={false}
style={Object {}}
to="/admin/settings?category=new_code_period"
>
project_baseline.page.description2.link
</a>,
</Link>,
}
}
/>

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

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

exports[`should render correctly 1`] = `
<RadioCard
onClick={[Function]}
selected={true}
title="baseline.specific_analysis"
>
<p
className="big-spacer-bottom"
>
baseline.specific_analysis.description
</p>
</RadioCard>
`;

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

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

exports[`should render correctly 1`] = `
<Fragment>
<div
className="spacer-bottom"
>
baseline.analysis_from
<Select
autoBlur={true}
className="input-medium spacer-left"
clearable={false}
onChange={[Function]}
options={
Array [
Object {
"label": "baseline.branch_analyses.ranges.30days",
"value": 30,
},
Object {
"label": "baseline.branch_analyses.ranges.allTime",
"value": 0,
},
]
}
searchable={false}
value={0}
/>
</div>
<div
className="branch-analysis-list-wrapper"
>
<div
className="bordered branch-analysis-list"
onScroll={[Function]}
>
<div
className="big-spacer-top big-spacer-bottom strong"
>
baseline.no_analyses
</div>
</div>
</div>
</Fragment>
`;

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

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

exports[`should render correctly 1`] = `
<Modal
contentLabel="baseline.new_code_period_for_branch_x.master"
onRequestClose={[Function]}
size="large"
>
<header
className="modal-head"
>
<h2>
baseline.new_code_period_for_branch_x.master
</h2>
</header>
<form
onSubmit={[Function]}
>
<div
className="modal-body branch-baseline-setting-modal"
>
<div
className="display-flex-row huge-spacer-bottom"
role="radiogroup"
>
<BaselineSettingPreviousVersion
isDefault={false}
onSelect={[Function]}
selected={false}
/>
<BaselineSettingDays
days="30"
isChanged={false}
isValid={false}
onChangeDays={[Function]}
onSelect={[Function]}
selected={false}
/>
<BaselineSettingAnalysis
onSelect={[Function]}
selected={false}
/>
</div>
</div>
<footer
className="modal-foot"
>
<DeferredSpinner
className="spacer-right"
loading={false}
timeout={100}
/>
<SubmitButton
disabled={true}
>
save
</SubmitButton>
<ResetButtonLink
onClick={[MockFunction]}
>
cancel
</ResetButtonLink>
</footer>
</form>
</Modal>
`;

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

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

exports[`should render correctly 1`] = `
<Fragment>
<table
className="data zebra"
>
<thead>
<tr>
<th>
branch_list.branch
</th>
<th
className="thin nowrap huge-spacer-right"
>
branch_list.current_setting
</th>
<th
className="thin nowrap"
>
branch_list.edit_settings
</th>
</tr>
</thead>
<tbody>
<tr
key="master"
>
<td
className="nowrap"
>
<BranchIcon
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": true,
"name": "master",
"newCodePeriod": Object {
"type": "NUMBER_OF_DAYS",
"value": "27",
},
}
}
className="little-spacer-right"
/>
master
<div
className="badge spacer-left"
>
branches.main_branch
</div>
</td>
<td
className="huge-spacer-right nowrap"
>
baseline.number_days: 27
</td>
<td
className="text-right"
>
<ActionsDropdown>
<ActionsDropdownItem
onClick={[Function]}
>
edit
</ActionsDropdownItem>
<ActionsDropdownItem
onClick={[Function]}
>
reset_to_default
</ActionsDropdownItem>
</ActionsDropdown>
</td>
</tr>
<tr
key="branch-6.7"
>
<td
className="nowrap"
>
<BranchIcon
branchLike={
Object {
"analysisDate": "2018-01-01",
"isMain": false,
"name": "branch-6.7",
"type": "LONG",
}
}
className="little-spacer-right"
/>
branch-6.7
</td>
<td
className="huge-spacer-right nowrap"
>
<span
className="badge badge-info"
>
default
</span>
</td>
<td
className="text-right"
>
<ActionsDropdown>
<ActionsDropdownItem
onClick={[Function]}
>
edit
</ActionsDropdownItem>
</ActionsDropdown>
</td>
</tr>
</tbody>
</table>
</Fragment>
`;

+ 9
- 4
server/sonar-web/src/main/js/apps/projectBaseline/components/App.tsx Просмотреть файл

@@ -19,14 +19,17 @@
*/
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router';
import { Button } from 'sonar-ui-common/components/controls/buttons';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { getNewCodePeriod, resetNewCodePeriod, setNewCodePeriod } from '../../../api/newCodePeriod';
import '../styles.css';
import BranchList from './BranchList';
import ProjectBaselineSelector from './ProjectBaselineSelector';

interface Props {
branchLikes: T.BranchLike[];
canAdmin?: boolean;
component: T.Component;
}
@@ -156,9 +159,9 @@ export default class App extends React.PureComponent<Props, State> {
id="project_baseline.page.description"
values={{
link: (
<a href="/documentation/user-guide/fixing-the-water-leak/">
<Link to="/documentation/user-guide/fixing-the-water-leak/">
{translate('project_baseline.page.description.link')}
</a>
</Link>
)
}}
/>
@@ -169,9 +172,9 @@ export default class App extends React.PureComponent<Props, State> {
id="project_baseline.page.description2"
values={{
link: (
<a href="/admin/settings?category=new_code_period">
<Link to="/admin/settings?category=new_code_period">
{translate('project_baseline.page.description2.link')}
</a>
</Link>
)
}}
/>
@@ -242,6 +245,8 @@ export default class App extends React.PureComponent<Props, State> {
saving={saving}
selected={selected}
/>

<BranchList branchLikes={this.props.branchLikes} component={this.props.component} />
</div>
)}
</div>

server/sonar-web/src/main/js/api/baseline.ts → server/sonar-web/src/main/js/apps/projectBaseline/components/BaselineSettingAnalysis.tsx Просмотреть файл

@@ -17,18 +17,22 @@
* 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 } from 'sonar-ui-common/helpers/request';
import throwGlobalError from '../app/utils/throwGlobalError';
import * as React from 'react';
import RadioCard from 'sonar-ui-common/components/controls/RadioCard';
import { translate } from 'sonar-ui-common/helpers/l10n';

export function getBranchAnalyses(
data: {
project: string;
category?: string;
from?: string;
to?: string;
p?: number;
ps?: number;
} & T.BranchParameters
) {
return getJSON('/api/project_analyses/search', data).catch(throwGlobalError);
export interface Props {
onSelect: (selection: T.NewCodePeriodSettingType) => void;
selected: boolean;
}

export default function BaselineSettingAnalysis({ onSelect, selected }: Props) {
return (
<RadioCard
onClick={() => onSelect('SPECIFIC_ANALYSIS')}
selected={selected}
title={translate('baseline.specific_analysis')}>
<p className="big-spacer-bottom">{translate('baseline.specific_analysis.description')}</p>
</RadioCard>
);
}

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

@@ -0,0 +1,265 @@
/*
* 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 classNames from 'classnames';
import { subDays } from 'date-fns';
import { throttle } from 'lodash';
import * as React from 'react';
import Select from 'sonar-ui-common/components/controls/Select';
import Tooltip from 'sonar-ui-common/components/controls/Tooltip';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { parseDate, toShortNotSoISOString } from 'sonar-ui-common/helpers/dates';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { getProjectActivity } from '../../../api/projectActivity';
import DateFormatter from '../../../components/intl/DateFormatter';
import TimeFormatter from '../../../components/intl/TimeFormatter';
import Events from '../../projectActivity/components/Events';
import { getAnalysesByVersionByDay, ParsedAnalysis } from '../../projectActivity/utils';

interface Props {
analysis: string;
branch: string;
component: string;
onSelectAnalysis: (analysis: string) => void;
}

interface State {
analyses: ParsedAnalysis[];
loading: boolean;
range: number;
scroll: number;
}

export default class BranchAnalysisList extends React.PureComponent<Props, State> {
mounted = false;
badges: T.Dict<HTMLDivElement> = {};
state: State = {
analyses: [],
loading: true,
range: 30,
scroll: 0
};

constructor(props: Props) {
super(props);
this.updateScroll = throttle(this.updateScroll, 20);
}

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

componentWillUnmount() {
this.mounted = false;
}

fetchAnalyses(initial = false) {
const { analysis, branch, component } = this.props;
const { range } = this.state;
this.setState({ loading: true });

return getProjectActivity({
branch,
project: component,
from: range ? toShortNotSoISOString(subDays(new Date(), range)) : undefined
}).then((result: { analyses: T.Analysis[] }) => {
// If the selected analysis wasn't found in the default 30 days range, redo the search
if (initial && analysis && !result.analyses.find(a => a.key === analysis)) {
this.handleRangeChange({ value: 0 });
return;
}

this.setState({
analyses: result.analyses.map(analysis => ({
...analysis,
date: parseDate(analysis.date)
})) as ParsedAnalysis[],
loading: false
});
});
}

handleScroll = (e: React.SyntheticEvent<HTMLDivElement>) => {
if (e.currentTarget) {
this.updateScroll(e.currentTarget.scrollTop);
}
};

updateScroll = (scroll: number) => {
this.setState({ scroll });
};

registerBadgeNode = (version: string) => (el: HTMLDivElement) => {
if (el) {
if (!el.getAttribute('originOffsetTop')) {
el.setAttribute('originOffsetTop', String(el.offsetTop));
}
this.badges[version] = el;
}
};

shouldStick = (version: string, index: number) => {
const badge = this.badges[version];
return (
badge && Number(badge.getAttribute('originOffsetTop')) < this.state.scroll + 18 + index * 2
);
};

getRangeOptions() {
return [
{
label: translate('baseline.branch_analyses.ranges.30days'),
value: 30
},
{
label: translate('baseline.branch_analyses.ranges.allTime'),
value: 0
}
];
}

handleRangeChange = ({ value }: { value: number }) => {
this.setState({ range: value }, () => this.fetchAnalyses());
};

render() {
const { analyses, loading, range } = this.state;

const byVersionByDay = getAnalysesByVersionByDay(analyses, {
category: '',
customMetrics: [],
graph: '',
project: this.props.component
});

const hasFilteredData =
byVersionByDay.length > 1 ||
(byVersionByDay.length === 1 && Object.keys(byVersionByDay[0].byDay).length > 0);

return (
<>
<div className="spacer-bottom">
{translate('baseline.analysis_from')}
<Select
autoBlur={true}
className="input-medium spacer-left"
clearable={false}
onChange={this.handleRangeChange}
options={this.getRangeOptions()}
searchable={false}
value={range}
/>
</div>
<div className="branch-analysis-list-wrapper">
<div className="bordered branch-analysis-list" onScroll={this.handleScroll}>
{loading && <DeferredSpinner className="big-spacer-top" />}

{!loading && !hasFilteredData ? (
<div className="big-spacer-top big-spacer-bottom strong">
{translate('baseline.no_analyses')}
</div>
) : (
<ul>
{byVersionByDay.map((version, idx) => {
const days = Object.keys(version.byDay);
if (days.length <= 0) {
return null;
}
return (
<li key={version.key || 'noversion'}>
{version.version && (
<div
className={classNames('branch-analysis-version-badge', {
first: idx === 0,
sticky: this.shouldStick(version.version, idx)
})}
ref={this.registerBadgeNode(version.version)}>
<Tooltip
mouseEnterDelay={0.5}
overlay={`${translate('version')} ${version.version}`}>
<span className="badge">{version.version}</span>
</Tooltip>
</div>
)}
<ul className="branch-analysis-days-list">
{days.map(day => (
<li
className="branch-analysis-day"
data-day={toShortNotSoISOString(Number(day))}
key={day}>
<div className="branch-analysis-date">
<DateFormatter date={Number(day)} long={true} />
</div>
<ul className="branch-analysis-analyses-list">
{version.byDay[day] != null &&
version.byDay[day].map(analysis => (
<li
className={classNames('branch-analysis', {
selected: false
})}
data-date={parseDate(analysis.date).valueOf()}
key={analysis.key}
onClick={() => this.props.onSelectAnalysis(analysis.key)}>
<div className="branch-analysis-time spacer-right">
<TimeFormatter date={parseDate(analysis.date)} long={false}>
{formattedTime => (
<time
className="text-middle"
dateTime={parseDate(analysis.date).toISOString()}>
{formattedTime}
</time>
)}
</TimeFormatter>
</div>

{analysis.events.length > 0 && (
<Events
analysis={analysis.key}
changeEvent={() => Promise.resolve()}
deleteEvent={() => Promise.resolve()}
events={analysis.events}
isFirst={analyses[0].key === analysis.key}
/>
)}

<div className="analysis-selection-button">
<i
className={classNames('icon-radio', {
'is-checked': analysis.key === this.props.analysis
})}
/>
</div>
</li>
))}
</ul>
</li>
))}
</ul>
</li>
);
})}
</ul>
)}
</div>
</div>
</>
);
}
}

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

@@ -0,0 +1,191 @@
/*
* 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 { ResetButtonLink, SubmitButton } from 'sonar-ui-common/components/controls/buttons';
import Modal from 'sonar-ui-common/components/controls/Modal';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
import { setNewCodePeriod } from '../../../api/newCodePeriod';
import { validateDays } from '../utils';
import BaselineSettingAnalysis from './BaselineSettingAnalysis';
import BaselineSettingDays from './BaselineSettingDays';
import BaselineSettingPreviousVersion from './BaselineSettingPreviousVersion';
import BranchAnalysisList from './BranchAnalysisList';

interface Props {
branch: T.BranchWithNewCodePeriod;
component: string;
onClose: (
branch?: string,
newSetting?: { type: T.NewCodePeriodSettingType; value: string | null }
) => void;
}

interface State {
analysis: string;
days: string;
saving: boolean;
selected?: T.NewCodePeriodSettingType;
}

export default class BranchBaselineSettingModal extends React.PureComponent<Props, State> {
mounted = false;

constructor(props: Props) {
super(props);

this.state = {
analysis: this.getValueFromProps('SPECIFIC_ANALYSIS') || '',
days: this.getValueFromProps('NUMBER_OF_DAYS') || '30',
saving: false,
selected: this.props.branch.newCodePeriod && this.props.branch.newCodePeriod.type
};
}

componentDidMount() {
this.mounted = true;
}

componentWillUnmount() {
this.mounted = false;
}

getValueFromProps(type: T.NewCodePeriodSettingType) {
return this.props.branch.newCodePeriod && this.props.branch.newCodePeriod.type === type
? this.props.branch.newCodePeriod.value
: null;
}

getSettingValue() {
switch (this.state.selected) {
case 'NUMBER_OF_DAYS':
return this.state.days;
case 'SPECIFIC_ANALYSIS':
return this.state.analysis;
default:
return null;
}
}

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

const { branch, component } = this.props;
const { selected: type } = this.state;

const value = this.getSettingValue();

if (type) {
this.setState({ saving: true });
setNewCodePeriod({
project: component,
type,
value,
branch: branch.name
}).then(
() => {
this.setState({
saving: false
});
this.props.onClose(branch.name, { type, value });
},
() => {
this.setState({
saving: false
});
}
);
}
};

requestClose = () => this.props.onClose();

handleSelectAnalysis = (analysis: string) => this.setState({ analysis });

handleSelectDays = (days: string) => this.setState({ days });

handleSelectSetting = (selected: T.NewCodePeriodSettingType) => this.setState({ selected });

render() {
const { branch } = this.props;
const { analysis, days, saving, selected } = this.state;

const currentSetting = branch.newCodePeriod && branch.newCodePeriod.type;
const currentSettingValue = branch.newCodePeriod && branch.newCodePeriod.value;

const header = translateWithParameters('baseline.new_code_period_for_branch_x', branch.name);

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

const isValid =
selected === 'PREVIOUS_VERSION' ||
(selected === 'SPECIFIC_ANALYSIS' && analysis.length > 0) ||
(selected === 'NUMBER_OF_DAYS' && validateDays(days));

return (
<Modal contentLabel={header} onRequestClose={this.requestClose} size="large">
<header className="modal-head">
<h2>{header}</h2>
</header>
<form onSubmit={this.handleSubmit}>
<div className="modal-body branch-baseline-setting-modal">
<div className="display-flex-row huge-spacer-bottom" role="radiogroup">
<BaselineSettingPreviousVersion
isDefault={false}
onSelect={this.handleSelectSetting}
selected={selected === 'PREVIOUS_VERSION'}
/>
<BaselineSettingDays
days={days}
isChanged={isChanged}
isValid={isValid}
onChangeDays={this.handleSelectDays}
onSelect={this.handleSelectSetting}
selected={selected === 'NUMBER_OF_DAYS'}
/>
<BaselineSettingAnalysis
onSelect={this.handleSelectSetting}
selected={selected === 'SPECIFIC_ANALYSIS'}
/>
</div>
{selected === 'SPECIFIC_ANALYSIS' && (
<BranchAnalysisList
analysis={analysis}
branch={branch.name}
component={this.props.component}
onSelectAnalysis={this.handleSelectAnalysis}
/>
)}
</div>
<footer className="modal-foot">
<DeferredSpinner className="spacer-right" loading={saving} />
<SubmitButton disabled={!isChanged || saving || !isValid}>
{translate('save')}
</SubmitButton>
<ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
</footer>
</form>
</Modal>
);
}
}

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

@@ -0,0 +1,218 @@
/*
* 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 ActionsDropdown, {
ActionsDropdownItem
} from 'sonar-ui-common/components/controls/ActionsDropdown';
import DeferredSpinner from 'sonar-ui-common/components/ui/DeferredSpinner';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { listBranchesNewCodePeriod, resetNewCodePeriod } from '../../../api/newCodePeriod';
import BranchIcon from '../../../components/icons-components/BranchIcon';
import { isLongLivingBranch, isMainBranch, sortBranchesAsTree } from '../../../helpers/branches';
import BranchBaselineSettingModal from './BranchBaselineSettingModal';

interface Props {
branchLikes: T.BranchLike[];
component: T.Component;
}

interface State {
branches: T.BranchWithNewCodePeriod[];
editedBranch?: T.BranchWithNewCodePeriod;
loading: boolean;
}

export default class BranchList extends React.PureComponent<Props, State> {
mounted = false;
state: State = {
branches: [],
loading: true
};

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

componentWillUnmount() {
this.mounted = false;
}

sortAndFilterBranches(branchLikes: T.BranchLike[] = []) {
return sortBranchesAsTree(
branchLikes.filter(b => isMainBranch(b) || isLongLivingBranch(b))
) as T.Branch[];
}

fetchBranches() {
const project = this.props.component.key;
this.setState({ loading: true });

const sortedBranches = this.sortAndFilterBranches(this.props.branchLikes);

listBranchesNewCodePeriod({ project }).then(
branchSettings => {
const newCodePeriods = branchSettings.newCodePeriods
? branchSettings.newCodePeriods.filter(ncp => !ncp.inherited)
: [];

const branchesWithBaseline = sortedBranches.map(b => {
const newCodePeriod = newCodePeriods.find(ncp => ncp.branchKey === b.name);
if (!newCodePeriod) {
return b;
}
const { type = 'PREVIOUS_VERSION', value = null } = newCodePeriod;
return {
...b,
newCodePeriod: { type, value }
};
});

this.setState({ branches: branchesWithBaseline, loading: false });
},
() => {
this.setState({ loading: false });
}
);
}

updateBranchNewCodePeriod = (
branch: string,
newSetting: { type: T.NewCodePeriodSettingType; value: string | null } | undefined
) => {
const { branches } = this.state;

const updated = branches.find(b => b.name === branch);
if (updated) {
updated.newCodePeriod = newSetting;
}
return branches.slice(0);
};

openEditModal = (branch: T.BranchWithNewCodePeriod) => {
this.setState({ editedBranch: branch });
};

closeEditModal = (
branch?: string,
newSetting?: { type: T.NewCodePeriodSettingType; value: string | null }
) => {
if (branch) {
this.setState({
branches: this.updateBranchNewCodePeriod(branch, newSetting),
editedBranch: undefined
});
} else {
this.setState({ editedBranch: undefined });
}
};

resetToDefault(branch: string) {
return resetNewCodePeriod({
project: this.props.component.key,
branch
}).then(() => {
this.setState({ branches: this.updateBranchNewCodePeriod(branch, undefined) });
});
}

renderNewCodePeriodSetting(newCodePeriod: {
type: T.NewCodePeriodSettingType;
value: string | null;
}) {
switch (newCodePeriod.type) {
case 'SPECIFIC_ANALYSIS':
return `${translate('baseline.specific_analysis')}: ${newCodePeriod.value}`;
case 'NUMBER_OF_DAYS':
return `${translate('baseline.number_days')}: ${newCodePeriod.value}`;
case 'PREVIOUS_VERSION':
return translate('baseline.previous_version');
default:
return newCodePeriod.type;
}
}

render() {
const { branches, editedBranch, loading } = this.state;

if (branches.length < 1) {
return null;
}

if (loading) {
return <DeferredSpinner />;
}

return (
<>
<table className="data zebra">
<thead>
<tr>
<th>{translate('branch_list.branch')}</th>
<th className="thin nowrap huge-spacer-right">
{translate('branch_list.current_setting')}
</th>
<th className="thin nowrap">{translate('branch_list.edit_settings')}</th>
</tr>
</thead>
<tbody>
{branches.map(branch => (
<tr key={branch.name}>
<td className="nowrap">
<BranchIcon branchLike={branch} className="little-spacer-right" />
{branch.name}
{branch.isMain && (
<div className="badge spacer-left">{translate('branches.main_branch')}</div>
)}
</td>
<td className="huge-spacer-right nowrap">
{branch.newCodePeriod ? (
this.renderNewCodePeriodSetting(branch.newCodePeriod)
) : (
<span className="badge badge-info">{translate('default')}</span>
)}
</td>
<td className="text-right">
<ActionsDropdown>
<ActionsDropdownItem onClick={() => this.openEditModal(branch)}>
{translate('edit')}
</ActionsDropdownItem>
{branch.newCodePeriod && (
<ActionsDropdownItem onClick={() => this.resetToDefault(branch.name)}>
{translate('reset_to_default')}
</ActionsDropdownItem>
)}
</ActionsDropdown>
</td>
</tr>
))}
</tbody>
</table>
{editedBranch && (
<BranchBaselineSettingModal
branch={editedBranch}
component={this.props.component.key}
onClose={this.closeEditModal}
/>
)}
</>
);
}
}

+ 7
- 7
server/sonar-web/src/main/js/apps/projectBaseline/styles.css Просмотреть файл

@@ -54,15 +54,15 @@
}

.branch-analysis-day {
margin-top: 8px;
margin-bottom: 24px;
margin-top: var(--gridSize);
margin-bottom: calc(3 * var(--gridSize));
}

.branch-analysis {
display: flex;
justify-content: space-between;
cursor: pointer;
padding: 8px;
padding: var(--gridSize);
border-top: 1px solid var(--barBorderColor);
border-bottom: 1px solid var(--barBorderColor);
}
@@ -85,18 +85,18 @@

.branch-analysis-version-badge {
margin-left: -12px;
padding-top: 8px;
padding-bottom: 8px;
padding-top: var(--gridSize);
padding-bottom: var(--gridSize);
background-color: white;
}

.branch-analysis-version-badge.sticky,
.branch-analysis-version-badge.first {
position: absolute;
top: 25px;
top: 1px;
left: 13px;
right: 16px;
padding-top: 24px;
padding-top: calc(3 * var(--gridSize));
z-index: var(--belowNormalZIndex);
}


+ 1
- 3
server/sonar-web/src/main/js/apps/settings/components/NewCodePeriod.tsx Просмотреть файл

@@ -28,8 +28,6 @@ import BaselineSettingDays from '../../projectBaseline/components/BaselineSettin
import BaselineSettingPreviousVersion from '../../projectBaseline/components/BaselineSettingPreviousVersion';
import { validateDays } from '../../projectBaseline/utils';

interface Props {}

interface State {
currentSetting?: T.NewCodePeriodSettingType;
days: string;
@@ -42,7 +40,7 @@ interface State {

const DEFAULT_SETTING = 'PREVIOUS_VERSION';

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

+ 11
- 1
server/sonar-web/src/main/js/helpers/testMocks.ts Просмотреть файл

@@ -51,9 +51,19 @@ export function mockAlmOrganization(overrides: Partial<T.AlmOrganization> = {}):
};
}

export function mockAnalysis(overrides: Partial<T.Analysis> = {}): T.Analysis {
return {
date: '2017-03-01T09:36:01+0100',
events: [],
key: 'foo',
projectVersion: '1.0',
...overrides
};
}

export function mockParsedAnalysis(overrides: Partial<ParsedAnalysis> = {}): ParsedAnalysis {
return {
date: new Date('2017-03-01T09:36:01+0100'),
date: new Date('2017-03-01T09:37:01+0100'),
events: [],
key: 'foo',
projectVersion: '1.0',

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

@@ -545,6 +545,8 @@ 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
project_baseline.general_setting=General setting
project_baseline.reset_to_general=Reset to general setting

baseline.previous_version=Previous version
baseline.previous_version.description=The New Code Period will begin with the analysis following the previous version.
@@ -563,6 +565,7 @@ branch_list.branch=Branch
branch_list.current_setting=Current setting
branch_list.current_baseline=Current Baseline
branch_list.edit_settings=Edit settings
branch_list.default_setting=Project setting

baseline.new_code_period_for_branch_x=New Code Period for {0}


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