Browse Source

SONAR-9867 Add button promoting governance on background tasks page

tags/6.7-RC1
Stas Vilchik 6 years ago
parent
commit
fed59c4f46

+ 1
- 1
server/sonar-web/src/main/js/api/ce.ts View File

@@ -81,7 +81,7 @@ export function getTypes(): Promise<any> {
return getJSON('/api/ce/task_types').then(r => r.taskTypes);
}

export function getWorkers(): Promise<{ canSetWorkerCount: boolean; value: number } | Response> {
export function getWorkers(): Promise<{ canSetWorkerCount: boolean; value: number }> {
return getJSON('/api/ce/worker_count').catch(throwGlobalError);
}


+ 1
- 1
server/sonar-web/src/main/js/app/components/nav/component/NoBranchSupportPopup.tsx View File

@@ -33,7 +33,7 @@ export default function NoBranchSupportPopup(props: Props) {
<p className="big-spacer-bottom markdown">{translate('branches.no_support.header.text')}</p>
<p>
<a href="https://redirect.sonarsource.com/doc/branches.html" target="_blank">
{translate('branches.learn_more')}
{translate('learn_more')}
</a>
<a
className="button spacer-left"

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

@@ -22,7 +22,7 @@ exports[`renders 1`] = `
href="https://redirect.sonarsource.com/doc/branches.html"
target="_blank"
>
branches.learn_more
learn_more
</a>
<a
className="button spacer-left"

server/sonar-web/src/main/js/apps/background-tasks/components/Header.js → server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx View File

@@ -17,18 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/* @flow */
import React from 'react';
import * as React from 'react';
import Workers from './Workers';
import { translate } from '../../../helpers/l10n';

/*::
type Props = {
component?: Object
};
*/
interface Props {
component?: any;
}

export default function Header(props /*: Props */) {
export default function Header(props: Props) {
return (
<header className="page-header">
<h1 className="page-title">{translate('background_tasks.page')}</h1>

+ 44
- 0
server/sonar-web/src/main/js/apps/background-tasks/components/NoWorkersSupportPopup.tsx View File

@@ -0,0 +1,44 @@
/*
* SonarQube
* Copyright (C) 2009-2016 SonarSource SA
* mailto:contact 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 BubblePopup from '../../../components/common/BubblePopup';
import { translate } from '../../../helpers/l10n';

interface Props {
popupPosition?: any;
}

export default function NoWorkersSupportPopup(props: Props) {
return (
<BubblePopup position={props.popupPosition} customClass="bubble-popup-bottom-right">
<div className="abs-width-400">
<h6 className="spacer-bottom">{translate('background_tasks.add_more_workers')}</h6>
<p className="big-spacer-bottom markdown">
{translate('background_tasks.add_more_workers.text')}
</p>
<p>
<a href="https://redirect.sonarsource.com/plugins/governance.html" target="_blank">
{translate('learn_more')}
</a>
</p>
</div>
</BubblePopup>
);
}

server/sonar-web/src/main/js/apps/background-tasks/components/Workers.js → server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx View File

@@ -17,28 +17,30 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import WorkersForm from './WorkersForm';
import NoWorkersSupportPopup from './NoWorkersSupportPopup';
import Tooltip from '../../../components/controls/Tooltip';
import { getWorkers } from '../../../api/ce';
import { translate } from '../../../helpers/l10n';
import HelpIcon from '../../../components/icons-components/HelpIcon';
import BubblePopupHelper from '../../../components/common/BubblePopupHelper';

/*::
type State = {
canSetWorkerCount: boolean,
formOpen: boolean,
loading: boolean,
workerCount: number
};
*/

export default class Workers extends React.PureComponent {
/*:: mounted: boolean; */
state /*: State */ = {
interface State {
canSetWorkerCount: boolean;
formOpen: boolean;
loading: boolean;
noSupportPopup: boolean;
workerCount: number;
}

export default class Workers extends React.PureComponent<{}, State> {
mounted: boolean;
state: State = {
canSetWorkerCount: false,
formOpen: false,
loading: true,
noSupportPopup: false,
workerCount: 1
};

@@ -64,16 +66,30 @@ export default class Workers extends React.PureComponent {
});
};

closeForm = (newWorkerCount /*: ?number */) =>
closeForm = (newWorkerCount?: number) =>
newWorkerCount
? this.setState({ formOpen: false, workerCount: newWorkerCount })
: this.setState({ formOpen: false });

handleChangeClick = (event /*: Event */) => {
handleChangeClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault();
this.setState({ formOpen: true });
};

handleHelpClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.stopPropagation();
this.toggleNoSupportPopup();
};

toggleNoSupportPopup = (show?: boolean) => {
if (show != undefined) {
this.setState({ noSupportPopup: show });
} else {
this.setState(state => ({ noSupportPopup: !state.noSupportPopup }));
}
};

render() {
const { canSetWorkerCount, formOpen, loading, workerCount } = this.state;

@@ -101,6 +117,21 @@ export default class Workers extends React.PureComponent {
</Tooltip>
)}

{!loading &&
!canSetWorkerCount && (
<span className="spacer-left">
<a className="link-no-underline" href="#" onClick={this.handleHelpClick}>
<HelpIcon className="text-text-bottom" fill="#cdcdcd" />
</a>
<BubblePopupHelper
isOpen={this.state.noSupportPopup}
position="bottomright"
popup={<NoWorkersSupportPopup />}
togglePopup={this.toggleNoSupportPopup}
/>
</span>
)}

{formOpen && <WorkersForm onClose={this.closeForm} workerCount={this.state.workerCount} />}
</div>
);

server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.js → server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.tsx View File

@@ -17,36 +17,29 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import Modal from 'react-modal';
import Select from 'react-select';
import * as Select from 'react-select';
import { times } from 'lodash';
import { setWorkerCount } from '../../../api/ce';
import { translate } from '../../../helpers/l10n';

const MAX_WORKERS = 10;

/*::
type Props = {
onClose: (newWorkerCount?: number) => void,
workerCount: number
};
*/
interface Props {
onClose: (newWorkerCount?: number) => void;
workerCount: number;
}

/*::
type State = {
newWorkerCount: number,
submitting: boolean
};
*/
interface State {
newWorkerCount: number;
submitting: boolean;
}

export default class WorkersForm extends React.PureComponent {
/*:: mounted: boolean; */
/*:: props: Props; */
/*:: state: State; */
export default class WorkersForm extends React.PureComponent<Props, State> {
mounted: boolean;

constructor(props /*: Props */) {
constructor(props: Props) {
super(props);
this.state = {
newWorkerCount: props.workerCount,
@@ -64,10 +57,10 @@ export default class WorkersForm extends React.PureComponent {

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

handleWorkerCountChange = (option /*: { value: number } */) =>
handleWorkerCountChange = (option: { value: number }) =>
this.setState({ newWorkerCount: option.value });

handleSubmit = (event /*: Event */) => {
handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
event.preventDefault();
this.setState({ submitting: true });
const { newWorkerCount } = this.state;
@@ -86,7 +79,7 @@ export default class WorkersForm extends React.PureComponent {
};

render() {
const options = times(MAX_WORKERS).map((_, i) => ({ label: i + 1, value: i + 1 }));
const options = times(MAX_WORKERS).map((_, i) => ({ label: String(i + 1), value: i + 1 }));

return (
<Modal

server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/Workers-test.js → server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/Workers-test.tsx View File

@@ -17,8 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import * as React from 'react';
import { shallow } from 'enzyme';
import Workers from '../Workers';
import { click } from '../../../../helpers/testUtils';
@@ -34,10 +33,10 @@ it('renders', () => {
});
expect(wrapper).toMatchSnapshot();

wrapper.setState({ canSetWorkerCount: false });
wrapper.setState({ workerCount: 2 });
expect(wrapper).toMatchSnapshot();

wrapper.setState({ workerCount: 2 });
wrapper.setState({ canSetWorkerCount: false });
expect(wrapper).toMatchSnapshot();
});

@@ -66,7 +65,7 @@ it('updates worker count', () => {
});
expect(wrapper).toMatchSnapshot();

wrapper.find('WorkersForm').prop('onClose')(7);
wrapper.find('WorkersForm').prop<Function>('onClose')(7);
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/WorkersForm-test.js → server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/WorkersForm-test.tsx View File

@@ -17,36 +17,33 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
// @flow
import React from 'react';
import { shallow } from 'enzyme';
import WorkersForm from '../WorkersForm';
import { submit, doAsync } from '../../../../helpers/testUtils';

jest.mock('../../../../api/ce', () => ({
setWorkerCount: () => Promise.resolve()
}));

import * as React from 'react';
import { shallow } from 'enzyme';
import WorkersForm from '../WorkersForm';
import { submit } from '../../../../helpers/testUtils';

it('changes select', () => {
const wrapper = shallow(<WorkersForm onClose={jest.fn()} workerCount={1} />);
expect(wrapper).toMatchSnapshot();

wrapper.find('Select').prop('onChange')({ value: 7 });
wrapper.find('Select').prop<Function>('onChange')({ value: 7 });
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

it('returns new worker count', () => {
it('returns new worker count', async () => {
const onClose = jest.fn();
const wrapper = shallow(<WorkersForm onClose={onClose} workerCount={1} />);
// $FlowFixMe
wrapper.instance().mounted = true;
wrapper.find('Select').prop('onChange')({ value: 7 });
(wrapper.instance() as WorkersForm).mounted = true;
wrapper.find('Select').prop<Function>('onChange')({ value: 7 });

wrapper.update();
submit(wrapper.find('form'));

return doAsync(() => {
expect(onClose).toBeCalled();
});
await new Promise(setImmediate);
expect(onClose).toBeCalled();
});

server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Workers-test.js.snap → server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Workers-test.tsx.snap View File

@@ -78,12 +78,30 @@ exports[`renders 2`] = `

exports[`renders 3`] = `
<div>
<Tooltip
overlay="background_tasks.number_of_workers.warning"
placement="bottom"
>
<i
className="icon-alert-warn little-spacer-right bt-workers-warning-icon"
/>
</Tooltip>
background_tasks.number_of_workers
<strong
className="little-spacer-left"
>
1
2
</strong>
<Tooltip
overlay="background_tasks.change_number_of_workers"
placement="bottom"
>
<a
className="icon-edit spacer-left"
href="#"
onClick={[Function]}
/>
</Tooltip>
</div>
`;

@@ -103,6 +121,26 @@ exports[`renders 4`] = `
>
2
</strong>
<span
className="spacer-left"
>
<a
className="link-no-underline"
href="#"
onClick={[Function]}
>
<HelpIcon
className="text-text-bottom"
fill="#cdcdcd"
/>
</a>
<BubblePopupHelper
isOpen={false}
popup={<NoWorkersSupportPopup />}
position="bottomright"
togglePopup={[Function]}
/>
</span>
</div>
`;


server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/WorkersForm-test.js.snap → server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/WorkersForm-test.tsx.snap View File

@@ -62,43 +62,43 @@ exports[`changes select 1`] = `
options={
Array [
Object {
"label": 1,
"label": "1",
"value": 1,
},
Object {
"label": 2,
"label": "2",
"value": 2,
},
Object {
"label": 3,
"label": "3",
"value": 3,
},
Object {
"label": 4,
"label": "4",
"value": 4,
},
Object {
"label": 5,
"label": "5",
"value": 5,
},
Object {
"label": 6,
"label": "6",
"value": 6,
},
Object {
"label": 7,
"label": "7",
"value": 7,
},
Object {
"label": 8,
"label": "8",
"value": 8,
},
Object {
"label": 9,
"label": "9",
"value": 9,
},
Object {
"label": 10,
"label": "10",
"value": 10,
},
]
@@ -205,43 +205,43 @@ exports[`changes select 2`] = `
options={
Array [
Object {
"label": 1,
"label": "1",
"value": 1,
},
Object {
"label": 2,
"label": "2",
"value": 2,
},
Object {
"label": 3,
"label": "3",
"value": 3,
},
Object {
"label": 4,
"label": "4",
"value": 4,
},
Object {
"label": 5,
"label": "5",
"value": 5,
},
Object {
"label": 6,
"label": "6",
"value": 6,
},
Object {
"label": 7,
"label": "7",
"value": 7,
},
Object {
"label": 8,
"label": "8",
"value": 8,
},
Object {
"label": 9,
"label": "9",
"value": 9,
},
Object {
"label": 10,
"label": "10",
"value": 10,
},
]

+ 1
- 3
server/sonar-web/src/main/less/components/bubble-popup.less View File

@@ -90,13 +90,11 @@
.bubble-popup-bottom-right {
.bubble-popup-bottom;
margin-left: 0;
margin-right: -@popupArrowSize;
margin-right: -16px;

.bubble-popup-arrow {
left: auto;
right: 15px;
border-right-width: 0;
border-left-color: barBorderColor;
}
}


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

@@ -76,6 +76,7 @@ issues=Issues
inheritance=Inheritance
key=Key
language=Language
learn_more=Learn More
library=Library
line_number=Line Number
links=Links
@@ -2211,7 +2212,8 @@ background_tasks.number_of_workers=Number of Workers:
background_tasks.number_of_workers.warning=Configuring additional workers without first vertically scaling your server could have negative performance impacts.
background_tasks.change_number_of_workers=Edit CE Workers
background_tasks.change_number_of_workers.hint=If your queue backs up behind the analysis reports from large projects, increasing the number of Compute Engine workers will allow you to take full advantage of having configured increased Compute Engine memory on a multi-core server (vertical scaling).
background_tasks.add_more_with_governance=Add more with Governance
background_tasks.add_more_workers=Speed up your analysis by adding more Workers
background_tasks.add_more_workers.text=Increase the number of Compute Engine Workers with the Governance product. Available in our commercial editions.
background_tasks.search_by_task_or_component=Search by Task or Component
background_tasks.failing_count=Count of projects where processing of most recent analysis report failed

@@ -2580,7 +2582,6 @@ branches.set_leak_period=Set Leak Period
branches.last_analysis_date=Last Analysis Date
branches.no_support.header=Get the most out of SonarQube with branches analysis
branches.no_support.header.text=Analyze each branch of your project separately with our Developer Pack.
branches.learn_more=Learn More
branches.buy_developer_pack=Buy Developer Pack



Loading…
Cancel
Save