aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src
diff options
context:
space:
mode:
authorStas Vilchik <stas.vilchik@sonarsource.com>2017-07-06 14:37:46 +0200
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-07-17 10:52:47 +0200
commit373c5888e080843bf252bb4f776e8c17c4b667e2 (patch)
treee50a97eb8355ac4cf65f1a957fe07185953d63a3 /server/sonar-web/src
parent0839c4ba9cf741764c885ce6ba544bc6b1a82410 (diff)
downloadsonarqube-373c5888e080843bf252bb4f776e8c17c4b667e2.tar.gz
sonarqube-373c5888e080843bf252bb4f776e8c17c4b667e2.zip
SONAR-9508 Display worker counter in background tasks page
Diffstat (limited to 'server/sonar-web/src')
-rw-r--r--server/sonar-web/src/main/js/api/ce.js7
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css9
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js2
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Header.js15
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Workers.js108
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.js126
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/Workers-test.js72
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/WorkersForm-test.js52
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Workers-test.js.snap175
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/WorkersForm-test.js.snap283
-rw-r--r--server/sonar-web/src/main/less/init/forms.less12
11 files changed, 856 insertions, 5 deletions
diff --git a/server/sonar-web/src/main/js/api/ce.js b/server/sonar-web/src/main/js/api/ce.js
index bc4eb2283e2..4fa75478b1d 100644
--- a/server/sonar-web/src/main/js/api/ce.js
+++ b/server/sonar-web/src/main/js/api/ce.js
@@ -19,6 +19,7 @@
*/
// @flow
import { getJSON, post } from '../helpers/request';
+import throwGlobalError from '../app/utils/throwGlobalError';
export const getActivity = (data?: Object): Promise<*> => getJSON('/api/ce/activity', data);
@@ -42,3 +43,9 @@ export const getTasksForComponent = (componentKey: string): Promise<*> =>
getJSON('/api/ce/component', { componentKey });
export const getTypes = (): Promise<*> => getJSON('/api/ce/task_types').then(r => r.taskTypes);
+
+export const getWorkers = (): Promise<{ canSetWorkerCount: boolean, value: number }> =>
+ getJSON('/api/ce/worker_count').catch(throwGlobalError);
+
+export const setWorkerCount = (count: number): Promise<void> =>
+ post('/api/ce/set_worker_count', { count }).catch(throwGlobalError);
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css b/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css
index 2bad2aba5a0..a957b5f0250 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css
+++ b/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css
@@ -18,3 +18,12 @@
.bt-search-form-right {
margin-left: auto !important;
}
+
+.bt-workers-warning-icon {
+ position: relative;
+ top: -1px;
+}
+
+.bt-workers-warning-icon::before {
+ color: #d3d3d3;
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js
index 4062eaea41a..a26112033ff 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js
@@ -214,7 +214,7 @@ class BackgroundTasksApp extends React.PureComponent {
return (
<div className="page page-limited">
<Helmet title={translate('background_tasks.page')} />
- <Header />
+ <Header component={component} />
<Stats
component={component}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Header.js b/server/sonar-web/src/main/js/apps/background-tasks/components/Header.js
index c775e76f17a..e91b1b1188b 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/Header.js
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Header.js
@@ -19,19 +19,26 @@
*/
/* @flow */
import React from 'react';
+import Workers from './Workers';
import { translate } from '../../../helpers/l10n';
-const Header = () => {
+type Props = {
+ component?: Object
+};
+
+export default function Header(props: Props) {
return (
<header className="page-header">
<h1 className="page-title">
{translate('background_tasks.page')}
</h1>
+ {!props.component &&
+ <div className="page-actions">
+ <Workers />
+ </div>}
<p className="page-description">
{translate('background_tasks.page.description')}
</p>
</header>
);
-};
-
-export default Header;
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.js b/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.js
new file mode 100644
index 00000000000..b37622a54f7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.js
@@ -0,0 +1,108 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import WorkersForm from './WorkersForm';
+import Tooltip from '../../../components/controls/Tooltip';
+import { getWorkers } from '../../../api/ce';
+import { translate } from '../../../helpers/l10n';
+
+type State = {
+ canSetWorkerCount: boolean,
+ formOpen: boolean,
+ loading: boolean,
+ workerCount: number
+};
+
+export default class Workers extends React.PureComponent {
+ mounted: boolean;
+ state: State = {
+ canSetWorkerCount: false,
+ formOpen: false,
+ loading: true,
+ workerCount: 1
+ };
+
+ componentDidMount() {
+ this.mounted = true;
+ this.loadWorkers();
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ loadWorkers = () => {
+ this.setState({ loading: true });
+ getWorkers().then(({ canSetWorkerCount, value }) => {
+ if (this.mounted) {
+ this.setState({
+ canSetWorkerCount,
+ loading: false,
+ workerCount: value
+ });
+ }
+ });
+ };
+
+ closeForm = (newWorkerCount?: number) =>
+ (newWorkerCount
+ ? this.setState({ formOpen: false, workerCount: newWorkerCount })
+ : this.setState({ formOpen: false }));
+
+ handleChangeClick = (event: Event) => {
+ event.preventDefault();
+ this.setState({ formOpen: true });
+ };
+
+ render() {
+ const { canSetWorkerCount, formOpen, loading, workerCount } = this.state;
+
+ return (
+ <div>
+ {!loading &&
+ workerCount > 1 &&
+ <Tooltip overlay={translate('background_tasks.number_of_workers.warning')}>
+ <i className="icon-alert-warn little-spacer-right bt-workers-warning-icon" />
+ </Tooltip>}
+
+ {translate('background_tasks.number_of_workers')}
+
+ {loading
+ ? <i className="spinner little-spacer-left" />
+ : <strong className="little-spacer-left">{workerCount}</strong>}
+
+ {!loading &&
+ (canSetWorkerCount
+ ? <Tooltip overlay={translate('background_tasks.change_number_of_workers')}>
+ <a className="icon-edit spacer-left" href="#" onClick={this.handleChangeClick} />
+ </Tooltip>
+ : <a
+ className="button button-promote spacer-left"
+ href="https://redirect.sonarsource.com/plugins/governance.html"
+ target="_blank">
+ {translate('background_tasks.add_more_with_governance')}
+ </a>)}
+
+ {formOpen && <WorkersForm onClose={this.closeForm} workerCount={this.state.workerCount} />}
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.js b/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.js
new file mode 100644
index 00000000000..2f66fe8ab3d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.js
@@ -0,0 +1,126 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import Modal from 'react-modal';
+import 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
+};
+
+type State = {
+ newWorkerCount: number,
+ submitting: boolean
+};
+
+export default class WorkersForm extends React.PureComponent {
+ mounted: boolean;
+ props: Props;
+ state: State;
+
+ constructor(props: Props) {
+ super(props);
+ this.state = {
+ newWorkerCount: props.workerCount,
+ submitting: false
+ };
+ }
+
+ componentDidMount() {
+ this.mounted = true;
+ }
+
+ componentWillUnmount() {
+ this.mounted = false;
+ }
+
+ handleClose = () => this.props.onClose();
+
+ handleWorkerCountChange = (option: { value: number }) =>
+ this.setState({ newWorkerCount: option.value });
+
+ handleSubmit = (event: Event) => {
+ event.preventDefault();
+ this.setState({ submitting: true });
+ const { newWorkerCount } = this.state;
+ setWorkerCount(newWorkerCount).then(
+ () => {
+ if (this.mounted) {
+ this.props.onClose(newWorkerCount);
+ }
+ },
+ () => {
+ if (this.mounted) {
+ this.setState({ submitting: false });
+ }
+ }
+ );
+ };
+
+ render() {
+ const options = times(MAX_WORKERS).map((_, i) => ({ label: i + 1, value: i + 1 }));
+
+ return (
+ <Modal
+ isOpen={true}
+ contentLabel={translate('background_tasks.change_number_of_workers')}
+ className="modal"
+ overlayClassName="modal-overlay"
+ onRequestClose={this.handleClose}>
+ <header className="modal-head">
+ <h2>{translate('background_tasks.change_number_of_workers')}</h2>
+ </header>
+ <form onSubmit={this.handleSubmit}>
+ <div className="modal-body">
+ <Select
+ className="input-tiny spacer-top"
+ clearable={false}
+ onChange={this.handleWorkerCountChange}
+ options={options}
+ searchable={false}
+ value={this.state.newWorkerCount}
+ />
+ <div className="big-spacer-top alert alert-success markdown">
+ {translate('background_tasks.change_number_of_workers.hint')}
+ </div>
+ </div>
+ <footer className="modal-foot">
+ <div>
+ {this.state.submitting && <i className="spinner spacer-right" />}
+ <button disabled={this.state.submitting} type="submit">
+ {translate('save')}
+ </button>
+ <button type="reset" className="button-link" onClick={this.handleClose}>
+ {translate('cancel')}
+ </button>
+ </div>
+ </footer>
+ </form>
+ </Modal>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/Workers-test.js b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/Workers-test.js
new file mode 100644
index 00000000000..5ad06439c4a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/Workers-test.js
@@ -0,0 +1,72 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @flow
+import React from 'react';
+import { shallow } from 'enzyme';
+import Workers from '../Workers';
+import { click } from '../../../../helpers/testUtils';
+
+it('renders', () => {
+ const wrapper = shallow(<Workers />);
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.setState({
+ canSetWorkerCount: true,
+ loading: false,
+ workerCount: 1
+ });
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.setState({ canSetWorkerCount: false });
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.setState({ workerCount: 2 });
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('opens form', () => {
+ const wrapper = shallow(<Workers />);
+
+ wrapper.setState({
+ canSetWorkerCount: true,
+ loading: false,
+ workerCount: 1
+ });
+ expect(wrapper).toMatchSnapshot();
+
+ click(wrapper.find('.icon-edit'));
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('updates worker count', () => {
+ const wrapper = shallow(<Workers />);
+
+ wrapper.setState({
+ canSetWorkerCount: true,
+ formOpen: true,
+ loading: false,
+ workerCount: 1
+ });
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.find('WorkersForm').prop('onClose')(7);
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/WorkersForm-test.js b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/WorkersForm-test.js
new file mode 100644
index 00000000000..f821ba6fb32
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/WorkersForm-test.js
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 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.
+ */
+// @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()
+}));
+
+it('changes select', () => {
+ const wrapper = shallow(<WorkersForm onClose={jest.fn()} workerCount={1} />);
+ expect(wrapper).toMatchSnapshot();
+
+ wrapper.find('Select').prop('onChange')({ value: 7 });
+ wrapper.update();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it('returns new worker count', () => {
+ 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.update();
+ submit(wrapper.find('form'));
+
+ return doAsync(() => {
+ expect(onClose).toBeCalled();
+ });
+});
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Workers-test.js.snap b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Workers-test.js.snap
new file mode 100644
index 00000000000..7951ffa70e3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/Workers-test.js.snap
@@ -0,0 +1,175 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`opens form 1`] = `
+<div>
+ background_tasks.number_of_workers
+ <strong
+ className="little-spacer-left"
+ >
+ 1
+ </strong>
+ <Tooltip
+ overlay="background_tasks.change_number_of_workers"
+ placement="bottom"
+ >
+ <a
+ className="icon-edit spacer-left"
+ href="#"
+ onClick={[Function]}
+ />
+ </Tooltip>
+</div>
+`;
+
+exports[`opens form 2`] = `
+<div>
+ background_tasks.number_of_workers
+ <strong
+ className="little-spacer-left"
+ >
+ 1
+ </strong>
+ <Tooltip
+ overlay="background_tasks.change_number_of_workers"
+ placement="bottom"
+ >
+ <a
+ className="icon-edit spacer-left"
+ href="#"
+ onClick={[Function]}
+ />
+ </Tooltip>
+ <WorkersForm
+ onClose={[Function]}
+ workerCount={1}
+ />
+</div>
+`;
+
+exports[`renders 1`] = `
+<div>
+ background_tasks.number_of_workers
+ <i
+ className="spinner little-spacer-left"
+ />
+</div>
+`;
+
+exports[`renders 2`] = `
+<div>
+ background_tasks.number_of_workers
+ <strong
+ className="little-spacer-left"
+ >
+ 1
+ </strong>
+ <Tooltip
+ overlay="background_tasks.change_number_of_workers"
+ placement="bottom"
+ >
+ <a
+ className="icon-edit spacer-left"
+ href="#"
+ onClick={[Function]}
+ />
+ </Tooltip>
+</div>
+`;
+
+exports[`renders 3`] = `
+<div>
+ background_tasks.number_of_workers
+ <strong
+ className="little-spacer-left"
+ >
+ 1
+ </strong>
+ <a
+ className="button button-promote spacer-left"
+ href="https://redirect.sonarsource.com/plugins/governance.html"
+ target="_blank"
+ >
+ background_tasks.add_more_with_governance
+ </a>
+</div>
+`;
+
+exports[`renders 4`] = `
+<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"
+ >
+ 2
+ </strong>
+ <a
+ className="button button-promote spacer-left"
+ href="https://redirect.sonarsource.com/plugins/governance.html"
+ target="_blank"
+ >
+ background_tasks.add_more_with_governance
+ </a>
+</div>
+`;
+
+exports[`updates worker count 1`] = `
+<div>
+ background_tasks.number_of_workers
+ <strong
+ className="little-spacer-left"
+ >
+ 1
+ </strong>
+ <Tooltip
+ overlay="background_tasks.change_number_of_workers"
+ placement="bottom"
+ >
+ <a
+ className="icon-edit spacer-left"
+ href="#"
+ onClick={[Function]}
+ />
+ </Tooltip>
+ <WorkersForm
+ onClose={[Function]}
+ workerCount={1}
+ />
+</div>
+`;
+
+exports[`updates worker count 2`] = `
+<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"
+ >
+ 7
+ </strong>
+ <Tooltip
+ overlay="background_tasks.change_number_of_workers"
+ placement="bottom"
+ >
+ <a
+ className="icon-edit spacer-left"
+ href="#"
+ onClick={[Function]}
+ />
+ </Tooltip>
+</div>
+`;
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/WorkersForm-test.js.snap b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/WorkersForm-test.js.snap
new file mode 100644
index 00000000000..35ba8f86128
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/__tests__/__snapshots__/WorkersForm-test.js.snap
@@ -0,0 +1,283 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`changes select 1`] = `
+<Modal
+ ariaHideApp={true}
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="background_tasks.change_number_of_workers"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ background_tasks.change_number_of_workers
+ </h2>
+ </header>
+ <form
+ onSubmit={[Function]}
+ >
+ <div
+ className="modal-body"
+ >
+ <Select
+ addLabelText="Add \\"{label}\\"?"
+ arrowRenderer={[Function]}
+ autosize={true}
+ backspaceRemoves={true}
+ backspaceToRemoveMessage="Press backspace to remove {label}"
+ className="input-tiny spacer-top"
+ clearAllText="Clear all"
+ clearValueText="Clear value"
+ clearable={false}
+ delimiter=","
+ disabled={false}
+ escapeClearsValue={true}
+ filterOptions={[Function]}
+ ignoreAccents={true}
+ ignoreCase={true}
+ inputProps={Object {}}
+ isLoading={false}
+ joinValues={false}
+ labelKey="label"
+ matchPos="any"
+ matchProp="any"
+ menuBuffer={0}
+ menuRenderer={[Function]}
+ multi={false}
+ noResultsText="No results found"
+ onBlurResetsInput={true}
+ onChange={[Function]}
+ onCloseResetsInput={true}
+ openAfterFocus={false}
+ optionComponent={[Function]}
+ options={
+ Array [
+ Object {
+ "label": 1,
+ "value": 1,
+ },
+ Object {
+ "label": 2,
+ "value": 2,
+ },
+ Object {
+ "label": 3,
+ "value": 3,
+ },
+ Object {
+ "label": 4,
+ "value": 4,
+ },
+ Object {
+ "label": 5,
+ "value": 5,
+ },
+ Object {
+ "label": 6,
+ "value": 6,
+ },
+ Object {
+ "label": 7,
+ "value": 7,
+ },
+ Object {
+ "label": 8,
+ "value": 8,
+ },
+ Object {
+ "label": 9,
+ "value": 9,
+ },
+ Object {
+ "label": 10,
+ "value": 10,
+ },
+ ]
+ }
+ pageSize={5}
+ placeholder="Select..."
+ required={false}
+ scrollMenuIntoView={true}
+ searchable={false}
+ simpleValue={false}
+ tabSelectsValue={true}
+ value={1}
+ valueComponent={[Function]}
+ valueKey="value"
+ />
+ <div
+ className="big-spacer-top alert alert-success markdown"
+ >
+ background_tasks.change_number_of_workers.hint
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <div>
+ <button
+ disabled={false}
+ type="submit"
+ >
+ save
+ </button>
+ <button
+ className="button-link"
+ onClick={[Function]}
+ type="reset"
+ >
+ cancel
+ </button>
+ </div>
+ </footer>
+ </form>
+</Modal>
+`;
+
+exports[`changes select 2`] = `
+<Modal
+ ariaHideApp={true}
+ className="modal"
+ closeTimeoutMS={0}
+ contentLabel="background_tasks.change_number_of_workers"
+ isOpen={true}
+ onRequestClose={[Function]}
+ overlayClassName="modal-overlay"
+ parentSelector={[Function]}
+ portalClassName="ReactModalPortal"
+ shouldCloseOnOverlayClick={true}
+>
+ <header
+ className="modal-head"
+ >
+ <h2>
+ background_tasks.change_number_of_workers
+ </h2>
+ </header>
+ <form
+ onSubmit={[Function]}
+ >
+ <div
+ className="modal-body"
+ >
+ <Select
+ addLabelText="Add \\"{label}\\"?"
+ arrowRenderer={[Function]}
+ autosize={true}
+ backspaceRemoves={true}
+ backspaceToRemoveMessage="Press backspace to remove {label}"
+ className="input-tiny spacer-top"
+ clearAllText="Clear all"
+ clearValueText="Clear value"
+ clearable={false}
+ delimiter=","
+ disabled={false}
+ escapeClearsValue={true}
+ filterOptions={[Function]}
+ ignoreAccents={true}
+ ignoreCase={true}
+ inputProps={Object {}}
+ isLoading={false}
+ joinValues={false}
+ labelKey="label"
+ matchPos="any"
+ matchProp="any"
+ menuBuffer={0}
+ menuRenderer={[Function]}
+ multi={false}
+ noResultsText="No results found"
+ onBlurResetsInput={true}
+ onChange={[Function]}
+ onCloseResetsInput={true}
+ openAfterFocus={false}
+ optionComponent={[Function]}
+ options={
+ Array [
+ Object {
+ "label": 1,
+ "value": 1,
+ },
+ Object {
+ "label": 2,
+ "value": 2,
+ },
+ Object {
+ "label": 3,
+ "value": 3,
+ },
+ Object {
+ "label": 4,
+ "value": 4,
+ },
+ Object {
+ "label": 5,
+ "value": 5,
+ },
+ Object {
+ "label": 6,
+ "value": 6,
+ },
+ Object {
+ "label": 7,
+ "value": 7,
+ },
+ Object {
+ "label": 8,
+ "value": 8,
+ },
+ Object {
+ "label": 9,
+ "value": 9,
+ },
+ Object {
+ "label": 10,
+ "value": 10,
+ },
+ ]
+ }
+ pageSize={5}
+ placeholder="Select..."
+ required={false}
+ scrollMenuIntoView={true}
+ searchable={false}
+ simpleValue={false}
+ tabSelectsValue={true}
+ value={7}
+ valueComponent={[Function]}
+ valueKey="value"
+ />
+ <div
+ className="big-spacer-top alert alert-success markdown"
+ >
+ background_tasks.change_number_of_workers.hint
+ </div>
+ </div>
+ <footer
+ className="modal-foot"
+ >
+ <div>
+ <button
+ disabled={false}
+ type="submit"
+ >
+ save
+ </button>
+ <button
+ className="button-link"
+ onClick={[Function]}
+ type="reset"
+ >
+ cancel
+ </button>
+ </div>
+ </footer>
+ </form>
+</Modal>
+`;
diff --git a/server/sonar-web/src/main/less/init/forms.less b/server/sonar-web/src/main/less/init/forms.less
index 7852cb8b2c7..1a0a37e5e32 100644
--- a/server/sonar-web/src/main/less/init/forms.less
+++ b/server/sonar-web/src/main/less/init/forms.less
@@ -254,6 +254,18 @@ input[type="submit"].button-grey {
padding: 0 6px;
}
+.button-promote,
+input[type="submit"].button-promote {
+ border-color: #5041d2;
+ background-color: #5041d2;
+ color: #fff;
+ transition: background-color 0.3s ease;
+
+ &:hover, &:focus, &.active {
+ background-color: darken(#5041d2, 10%);
+ }
+}
+
.button-group {
display: inline-block;
vertical-align: middle;