@@ -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); | |||
} | |||
@@ -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" |
@@ -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" |
@@ -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> |
@@ -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> | |||
); | |||
} |
@@ -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> | |||
); |
@@ -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 |
@@ -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(); | |||
}); |
@@ -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(); | |||
}); |
@@ -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> | |||
`; | |||
@@ -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, | |||
}, | |||
] |
@@ -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; | |||
} | |||
} | |||
@@ -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 | |||