aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStas Vilchik <vilchiks@gmail.com>2016-02-11 18:57:39 +0100
committerStas Vilchik <vilchiks@gmail.com>2016-02-16 14:44:59 +0100
commit4621ff0206176da5af9fc8c1c704a2ca5aded1ad (patch)
treeb2b6bbcb5ae6812c591f8c71e454454566f3d493
parente53211f03c6d95428b387cc3beddf5378579769a (diff)
downloadsonarqube-4621ff0206176da5af9fc8c1c704a2ca5aded1ad.tar.gz
sonarqube-4621ff0206176da5af9fc8c1c704a2ca5aded1ad.zip
SONAR-7191 Update the background tasks page to reflect latest WS changes
-rw-r--r--server/sonar-web/src/main/js/api/ce.js23
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/app.js19
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js76
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js42
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js89
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.js45
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Task.js80
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskCancelButton.js37
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.js44
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js (renamed from server/sonar-web/src/main/js/apps/background-tasks/header.js)19
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js35
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.js29
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskLogsLink.js33
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js50
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.js48
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/header.js35
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/search.js118
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/stats.js (renamed from server/sonar-web/src/main/js/apps/background-tasks/stats.js)72
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/tasks.js53
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/constants.js11
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/containers/BackgroundTasksAppContainer.js38
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/containers/ListFooterContainer.js34
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/containers/SearchContainer.js51
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/containers/StatsContainer.js48
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/containers/TasksContainer.js42
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/main.js241
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/search.js168
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/store/actions.js210
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/store/reducers.js100
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/styles/background-tasks.css20
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/tasks.js189
-rw-r--r--server/sonar-web/src/main/js/components/store/configureStore.js34
-rw-r--r--server/sonar-web/src/main/less/init/forms.less6
-rw-r--r--server/sonar-web/src/main/less/pages/analysis-reports.less117
-rw-r--r--server/sonar-web/src/main/less/sonar.less1
-rw-r--r--server/sonar-web/tests/apps/background-tasks-test.js8
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties6
37 files changed, 1497 insertions, 774 deletions
diff --git a/server/sonar-web/src/main/js/api/ce.js b/server/sonar-web/src/main/js/api/ce.js
index c31db7b0dbc..dc36ed444a3 100644
--- a/server/sonar-web/src/main/js/api/ce.js
+++ b/server/sonar-web/src/main/js/api/ce.js
@@ -18,6 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import $ from 'jquery';
+import { getJSON, post } from '../helpers/request.js';
export function getQueue (data) {
const url = baseUrl + '/api/ce/queue';
@@ -30,22 +31,30 @@ export function getActivity (data) {
}
export function getTask (id) {
- const url = baseUrl + '/api/ce/task';
- return $.get(url, { id });
+ const url = window.baseUrl + '/api/ce/task';
+ return getJSON(url, { id }).then(r => r.task);
}
export function cancelTask (id) {
- const url = baseUrl + '/api/ce/cancel';
- return $.post(url, { id }).then(getTask.bind(null, id));
+ const url = window.baseUrl + '/api/ce/cancel';
+ return post(url, { id }).then(
+ getTask.bind(null, id),
+ getTask.bind(null, id)
+ );
}
export function cancelAllTasks () {
- const url = baseUrl + '/api/ce/cancel_all';
- return $.post(url);
+ const url = window.baseUrl + '/api/ce/cancel_all';
+ return post(url);
}
-export function getTasksForComponent(componentId) {
+export function getTasksForComponent (componentId) {
const url = baseUrl + '/api/ce/component';
const data = { componentId };
return new Promise((resolve) => $.get(url, data).done(resolve));
}
+
+export function getTypes () {
+ const url = window.baseUrl + '/api/ce/task_types';
+ return getJSON(url).then(r => r.taskTypes);
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/app.js b/server/sonar-web/src/main/js/apps/background-tasks/app.js
index 23a39dd0423..d1626102c8f 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/app.js
+++ b/server/sonar-web/src/main/js/apps/background-tasks/app.js
@@ -19,9 +19,22 @@
*/
import React from 'react';
import ReactDOM from 'react-dom';
-import Main from './main';
+import { Provider } from 'react-redux';
+
+import BackgroundTasksAppContainer from './containers/BackgroundTasksAppContainer';
+import rootReducer from './store/reducers';
+import configureStore from '../../components/store/configureStore';
+
+import './styles/background-tasks.css';
window.sonarqube.appStarted.then(options => {
- let el = document.querySelector(options.el);
- ReactDOM.render(<Main options={options}/>, el);
+ const el = document.querySelector(options.el);
+
+ const store = configureStore(rootReducer);
+
+ ReactDOM.render((
+ <Provider store={store}>
+ <BackgroundTasksAppContainer options={options}/>
+ </Provider>
+ ), el);
});
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
new file mode 100644
index 00000000000..9f2192ab629
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js
@@ -0,0 +1,76 @@
+/*
+ * 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 moment from 'moment';
+import React, { Component } from 'react';
+
+import { DATE } from './../constants';
+import Header from './Header';
+import StatsContainer from '../containers/StatsContainer';
+import SearchContainer from '../containers/SearchContainer';
+import TasksContainer from '../containers/TasksContainer';
+import ListFooterContainer from '../containers/ListFooterContainer';
+
+
+export default class BackgroundTasksApp extends Component {
+ componentDidMount () {
+ this.props.initApp();
+ }
+
+ getComponentFilter () {
+ if (this.props.options.component) {
+ return { componentId: this.props.options.component.id };
+ } else {
+ return {};
+ }
+ }
+
+ getDateFilter () {
+ const DATE_FORMAT = 'YYYY-MM-DD';
+ let filter = {};
+ switch (this.state.dateFilter) {
+ case DATE.TODAY:
+ filter.minSubmittedAt = moment().startOf('day').format(DATE_FORMAT);
+ break;
+ case DATE.CUSTOM:
+ if (this.state.minDate) {
+ filter.minSubmittedAt = moment(this.state.minDate).format(DATE_FORMAT);
+ }
+ if (this.state.maxDate) {
+ filter.maxExecutedAt = moment(this.state.maxDate).format(DATE_FORMAT);
+ }
+ break;
+ default:
+ // do nothing
+ }
+ return filter;
+ }
+
+ render () {
+ return (
+ <div className="page">
+ <Header/>
+ <StatsContainer/>
+ <SearchContainer/>
+ <TasksContainer/>
+ <ListFooterContainer/>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js b/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js
new file mode 100644
index 00000000000..bcbfd7f92d7
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js
@@ -0,0 +1,42 @@
+/*
+ * 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 React from 'react';
+
+import Checkbox from '../../../components/shared/checkbox';
+import { CURRENTS } from '../constants';
+
+export default function CurrentsFilter ({ value, onChange }) {
+ function handleChange (value) {
+ const newValue = value ? CURRENTS.ONLY_CURRENTS : CURRENTS.ALL;
+ onChange(newValue);
+ }
+
+ const checked = value === CURRENTS.ONLY_CURRENTS;
+
+ return (
+ <div className="bt-search-form-field">
+ <Checkbox
+ initiallyChecked={checked}
+ onCheck={handleChange}/>
+ &nbsp;
+ <label>Yes</label>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js b/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js
new file mode 100644
index 00000000000..b5969a75ceb
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js
@@ -0,0 +1,89 @@
+/*
+ * 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 $ from 'jquery';
+import moment from 'moment';
+import React, { Component } from 'react';
+
+import { DATE_FORMAT } from '../constants';
+
+export default class DateFilter extends Component {
+ componentDidMount () {
+ this.attachDatePicker();
+ }
+
+ componentDidUpdate () {
+ this.attachDatePicker();
+ }
+
+ attachDatePicker () {
+ let opts = {
+ dateFormat: 'yy-mm-dd',
+ changeMonth: true,
+ changeYear: true,
+ onSelect: this.handleChange.bind(this)
+ };
+ if ($.fn && $.fn.datepicker) {
+ $(this.refs.minDate).datepicker(opts);
+ $(this.refs.maxDate).datepicker(opts);
+ }
+ }
+
+ handleChange () {
+ const date = {};
+ const minDateRaw = this.refs.minDate.value;
+ const maxDateRaw = this.refs.maxDate.value;
+ const minDate = moment(minDateRaw, DATE_FORMAT, true);
+ const maxDate = moment(maxDateRaw, DATE_FORMAT, true);
+
+ if (minDate.isValid()) {
+ date.minSubmittedAt = minDate.format(DATE_FORMAT);
+ }
+
+ if (maxDate.isValid()) {
+ date.maxExecutedAt = maxDate.format(DATE_FORMAT);
+ }
+
+ this.props.onChange(date);
+ }
+
+ render () {
+ const { minSubmittedAt, maxExecutedAt } = this.props.value;
+
+ return (
+ <div>
+ <input
+ className="input-small"
+ value={minSubmittedAt}
+ onChange={this.handleChange.bind(this)}
+ ref="minDate"
+ type="text"
+ placeholder="From"/>
+ {' '}
+ <input
+ className="input-small"
+ value={maxExecutedAt}
+ onChange={this.handleChange.bind(this)}
+ ref="maxDate"
+ type="text"
+ placeholder="To"/>
+ </div>
+ );
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.js b/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.js
new file mode 100644
index 00000000000..14a7495099b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.js
@@ -0,0 +1,45 @@
+/*
+ * 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 React from 'react';
+import Select from 'react-select';
+
+import { STATUSES } from '../constants';
+import { translate } from '../../../helpers/l10n';
+
+export default function StatusFilter ({ value, onChange }) {
+ const options = [
+ { value: STATUSES.ALL, label: translate('background_task.status.ALL') },
+ { value: STATUSES.PENDING, label: translate('background_task.status.PENDING') },
+ { value: STATUSES.IN_PROGRESS, label: translate('background_task.status.IN_PROGRESS') },
+ { value: STATUSES.SUCCESS, label: translate('background_task.status.SUCCESS') },
+ { value: STATUSES.FAILED, label: translate('background_task.status.FAILED') },
+ { value: STATUSES.CANCELED, label: translate('background_task.status.CANCELED') }
+ ];
+
+ return (
+ <Select
+ value={value}
+ onChange={option => onChange(option.value)}
+ className="input-small"
+ options={options}
+ clearable={false}
+ searchable={false}/>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Task.js b/server/sonar-web/src/main/js/apps/background-tasks/components/Task.js
new file mode 100644
index 00000000000..58daa47f6bb
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Task.js
@@ -0,0 +1,80 @@
+/*
+ * 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 React from 'react';
+
+import TaskStatus from './TaskStatus';
+import TaskComponent from './TaskComponent';
+import TaskDay from './TaskDay';
+import TaskDate from './TaskDate';
+import TaskExecutionTime from './TaskExecutionTime';
+import TaskCancelButton from './TaskCancelButton';
+import TaskLogsLink from './TaskLogsLink';
+
+import { STATUSES } from './../constants';
+
+
+function renderFilter (task) {
+ // if (this.props.options && this.props.options.component) {
+ // return null;
+ // }
+ return <td className="thin nowrap">
+ <a onClick={this.handleFilter.bind(this, task)} className="icon-filter icon-half-transparent spacer-left" href="#"
+ title={`Show only "${task.componentName}" tasks`} data-toggle="tooltip"/>
+ </td>;
+}
+
+export default function Task ({ task, index, tasks, onCancelTask, onFilterTask }) {
+ function handleFilterTask (task, e) {
+ e.preventDefault();
+ onFilterTask(task);
+ }
+
+ const prevTask = index > 0 ? tasks[index - 1] : null;
+
+ return (
+ <tr>
+ <TaskStatus task={task}/>
+ <TaskComponent task={task}/>
+ <TaskDay task={task} prevTask={prevTask}/>
+ <TaskDate date={task.submittedAt} format="LTS"/>
+ <TaskDate date={task.startedAt} format="LTS"/>
+ <TaskDate date={task.executedAt} format="LTS"/>
+ <TaskExecutionTime task={task}/>
+
+ <td className="thin nowrap text-right">
+ {task.logs && (
+ <TaskLogsLink task={task}/>
+ )}
+ {task.status === STATUSES.PENDING && (
+ <TaskCancelButton task={task} onCancelTask={onCancelTask}/>
+ )}
+ </td>
+
+ <td className="thin nowrap">
+ <a
+ onClick={handleFilterTask.bind(this, task)}
+ className="icon-filter icon-half-transparent spacer-left"
+ href="#"
+ title={`Show only "${task.componentName}" tasks`}
+ data-toggle="tooltip"/>
+ </td>
+ </tr>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskCancelButton.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskCancelButton.js
new file mode 100644
index 00000000000..f5b793db0fb
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskCancelButton.js
@@ -0,0 +1,37 @@
+/*
+ * 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 React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default function TaskCancelButton ({ task, onCancelTask }) {
+ function handleClick (e) {
+ e.preventDefault();
+ onCancelTask(task);
+ }
+
+ return (
+ <a
+ onClick={handleClick}
+ className="icon-delete"
+ title={translate('background_tasks.cancel_task')}
+ data-toggle="tooltip"
+ href="#"/>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.js
new file mode 100644
index 00000000000..e6a0ca329f8
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.js
@@ -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 React from 'react';
+
+import { getComponentUrl } from '../../../helpers/urls';
+import QualifierIcon from '../../../components/shared/qualifier-icon';
+
+export default function TaskComponent ({ task }) {
+ if (!task.componentKey) {
+ return (
+ <td>
+ <span className="note">{task.id}</span>
+ </td>
+ );
+ }
+
+ return (
+ <td>
+ <a className="link-with-icon" href={getComponentUrl(task.componentKey)}>
+ <span className="little-spacer-right">
+ <QualifierIcon qualifier={task.componentQualifier}/>
+ </span>
+ <span>{task.componentName}</span>
+ </a>
+ </td>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/header.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js
index bc84cae82d6..5c8759cfa9e 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/header.js
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js
@@ -17,16 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import moment from 'moment';
import React from 'react';
-import { translate } from '../../helpers/l10n';
-export default React.createClass({
- render() {
- return (
- <header className="page-header">
- <h1 className="page-title">{translate('background_tasks.page')}</h1>
- <p className="page-description">{translate('background_tasks.page.description')}</p>
- </header>
- );
- }
-});
+export default function TaskDate ({ date, format }) {
+ return (
+ <td className="thin nowrap text-right">
+ {date ? moment(date).format(format) : ''}
+ </td>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js
new file mode 100644
index 00000000000..663cffa59be
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js
@@ -0,0 +1,35 @@
+/*
+ * 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 moment from 'moment';
+import React from 'react';
+
+function isAnotherDay (a, b) {
+ return !moment(a).isSame(moment(b), 'day');
+}
+
+export default function TaskDay ({ task, prevTask }) {
+ const shouldDisplay = !prevTask || isAnotherDay(task.submittedAt, prevTask.submittedAt);
+
+ return (
+ <td className="thin nowrap text-right">
+ {shouldDisplay ? moment(task.submittedAt).format('LL') : ''}
+ </td>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.js
new file mode 100644
index 00000000000..07e830d148a
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.js
@@ -0,0 +1,29 @@
+/*
+ * 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 React from 'react';
+import { formatDuration } from './../helpers';
+
+export default function TaskExecutionTime ({ task }) {
+ return (
+ <td className="thin nowrap text-right">
+ {formatDuration(task.executionTimeMs)}
+ </td>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskLogsLink.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskLogsLink.js
new file mode 100644
index 00000000000..2f5893e8bed
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskLogsLink.js
@@ -0,0 +1,33 @@
+/*
+ * 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 React from 'react';
+import { translate } from '../../../helpers/l10n';
+
+export default function TaskLogsLink ({ task }) {
+ const url = `${window.baseUrl}/api/ce/logs?taskId=${task.id}`;
+
+ return (
+ <a
+ target="_blank"
+ href={url}>
+ {translate('background_tasks.logs')}
+ </a>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js
new file mode 100644
index 00000000000..060e397a137
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js
@@ -0,0 +1,50 @@
+/*
+ * 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 React from 'react';
+
+import { STATUSES } from './../constants';
+import PendingIcon from '../../../components/shared/pending-icon';
+import { translate } from '../../../helpers/l10n';
+
+export default function TaskStatus ({ task }) {
+ let inner;
+
+ switch (task.status) {
+ case STATUSES.PENDING:
+ inner = <PendingIcon/>;
+ break;
+ case STATUSES.IN_PROGRESS:
+ inner = <i className="spinner"/>;
+ break;
+ case STATUSES.SUCCESS:
+ inner = <span className="badge badge-success">{translate('background_task.status.SUCCESS')}</span>;
+ break;
+ case STATUSES.FAILED:
+ inner = <span className="badge badge-danger">{translate('background_task.status.FAILED')}</span>;
+ break;
+ case STATUSES.CANCELED:
+ inner = <span className="badge badge-muted">{translate('background_task.status.CANCELED')}</span>;
+ break;
+ default:
+ inner = '';
+ }
+
+ return <td className="thin spacer-right">{inner}</td>;
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.js
new file mode 100644
index 00000000000..5e6696a468b
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.js
@@ -0,0 +1,48 @@
+/*
+ * 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 React from 'react';
+import Select from 'react-select';
+
+import { ALL_TYPES } from '../constants';
+import { translate } from '../../../helpers/l10n';
+
+export default function TypesFilter ({ value, onChange, types }) {
+ const options = types.map(t => {
+ return {
+ value: t,
+ label: translate('background_task.type', t)
+ };
+ });
+
+ const allOptions = [
+ { value: ALL_TYPES, label: translate('background_task.type.ALL') },
+ ...options
+ ];
+
+ return (
+ <Select
+ value={value}
+ onChange={option => onChange(option.value)}
+ className="input-medium"
+ options={allOptions}
+ clearable={false}
+ searchable={false}/>
+ );
+}
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
new file mode 100644
index 00000000000..3fbf337976e
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/header.js
@@ -0,0 +1,35 @@
+/*
+ * 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 React from 'react';
+
+import { translate } from '../../../helpers/l10n';
+
+export default function Header () {
+ return (
+ <header className="page-header">
+ <h1 className="page-title">
+ {translate('background_tasks.page')}
+ </h1>
+ <p className="page-description">
+ {translate('background_tasks.page.description')}
+ </p>
+ </header>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/search.js b/server/sonar-web/src/main/js/apps/background-tasks/components/search.js
new file mode 100644
index 00000000000..349b62ad9c3
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/search.js
@@ -0,0 +1,118 @@
+/*
+ * 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 React from 'react';
+
+import StatusFilter from './StatusFilter';
+import TypesFilter from './TypesFilter';
+import CurrentsFilter from './CurrentsFilter';
+import DateFilter from './DateFilter';
+import { translate } from '../../../helpers/l10n';
+
+export default React.createClass({
+
+ onSearchFormSubmit(e) {
+ e.preventDefault();
+ this.onSearch();
+ },
+
+ onSearch() {
+ let searchInput = this.refs.searchInput;
+ let query = searchInput.value;
+ this.props.onSearch(query);
+ },
+
+ renderSearchBox() {
+ if (this.props.options && this.props.options.component) {
+ // do not render search form on the project-level page
+ return null;
+ }
+ return (
+ <input onChange={this.onSearch}
+ value={this.props.query}
+ ref="searchInput"
+ className="input-large"
+ type="search"
+ placeholder="Search"/>
+ );
+ },
+
+ refresh(e) {
+ e.preventDefault();
+ this.props.onRefresh();
+ },
+
+ render() {
+ return (
+ <section className="big-spacer-top big-spacer-bottom">
+ <ul className="bt-search-form">
+ <li>
+ <h6 className="bt-search-form-label">
+ Status
+ </h6>
+ <StatusFilter
+ value={this.props.status}
+ onChange={this.props.onStatusChange}/>
+ </li>
+ {this.props.types.length > 1 && (
+ <li>
+ <h6 className="bt-search-form-label">
+ Type
+ </h6>
+ <TypesFilter
+ value={this.props.taskType}
+ onChange={this.props.onTypeChange}
+ types={this.props.types}/>
+ </li>
+ )}
+ <li>
+ <h6 className="bt-search-form-label">
+ Only Latest Analysis
+ </h6>
+ <CurrentsFilter
+ value={this.props.currents}
+ onChange={this.props.onCurrentsChange}/>
+ </li>
+ <li>
+ <h6 className="bt-search-form-label">
+ Date
+ </h6>
+ <DateFilter
+ value={this.props.date}
+ onChange={this.props.onDateChange}/>
+ </li>
+ <li>
+ <h6 className="bt-search-form-label">
+ Component
+ </h6>
+ {this.renderSearchBox()}
+ </li>
+ <li className="bt-search-form-right">
+ <button
+ ref="reloadButton"
+ onClick={this.refresh}
+ disabled={this.props.fetching}>
+ {translate('reload')}
+ </button>
+ </li>
+ </ul>
+ </section>
+ );
+ }
+});
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/stats.js b/server/sonar-web/src/main/js/apps/background-tasks/components/stats.js
index 95ac963376e..d59e40bf0ef 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/stats.js
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/stats.js
@@ -17,42 +17,45 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import React from 'react';
+import React, { Component } from 'react';
-import { formatDuration } from './helpers';
-import { TooltipsMixin } from '../../components/mixins/tooltips-mixin';
-import { translate } from '../../helpers/l10n';
+import { formatDuration } from './../helpers';
+import { translate } from '../../../helpers/l10n';
-export default React.createClass({
- mixins: [TooltipsMixin],
-
- onPendingCanceled(e) {
+export default class Stats extends Component {
+ handleCancelAllPending (e) {
e.preventDefault();
- this.props.cancelPending();
- },
+ this.props.onCancelAllPending();
+ }
- onFailuresClick(e) {
+ handleShowFailing (e) {
e.preventDefault();
- this.props.showFailures();
- },
+ this.props.onShowFailing();
+ }
- renderInProgressDuration() {
+ renderInProgressDuration () {
if (!this.props.inProgressDuration) {
return null;
}
return (
- <span className="huge-spacer-left" title={translate('background_tasks.in_progress_duration')}
- data-toggle="tooltip">
- <i className="spinner spacer-right" style={{ verticalAlign: 'text-top' }}/>
- <span ref="inProgressDuration" className="emphasised-measure">
+ <span
+ className="huge-spacer-left"
+ title={translate('background_tasks.in_progress_duration')}
+ data-toggle="tooltip">
+ <i
+ className="spinner spacer-right"
+ style={{ verticalAlign: 'text-top' }}/>
+ <span
+ ref="inProgressDuration"
+ className="emphasised-measure">
{formatDuration(this.props.inProgressDuration)}
</span>
</span>
);
- },
+ }
- renderPending() {
+ renderPending () {
if (this.props.pendingCount == null) {
return null;
}
@@ -62,8 +65,13 @@ export default React.createClass({
<span ref="pendingCount" className="emphasised-measure">{this.props.pendingCount}</span>
&nbsp;
{translate('background_tasks.pending')}
- <a ref="cancelPending" onClick={this.onPendingCanceled} className="icon-delete spacer-left"
- title={translate('background_tasks.cancel_all_tasks')} data-toggle="tooltip" href="#"></a>
+ <a
+ ref="cancelPending"
+ onClick={this.handleCancelAllPending.bind(this)}
+ className="icon-delete spacer-left"
+ title={translate('background_tasks.cancel_all_tasks')}
+ data-toggle="tooltip"
+ href="#"/>
</span>
);
} else {
@@ -75,10 +83,10 @@ export default React.createClass({
</span>
);
}
- },
+ }
- renderFailures() {
- if (this.props.failuresCount == null) {
+ renderFailures () {
+ if (this.props.failingCount == null) {
return null;
}
@@ -86,15 +94,15 @@ export default React.createClass({
return null;
}
- if (this.props.failuresCount > 0) {
+ if (this.props.failingCount > 0) {
return (
<span>
<a ref="failureCount"
- onClick={this.onFailuresClick}
+ onClick={this.handleShowFailing.bind(this)}
className="emphasised-measure"
data-toggle="tooltip"
title="Count of projects where processing of most recent analysis report failed"
- href="#">{this.props.failuresCount}</a>
+ href="#">{this.props.failingCount}</a>
&nbsp;
{translate('background_tasks.failures')}
</span>
@@ -104,16 +112,16 @@ export default React.createClass({
<span>
<span ref="failureCount" className="emphasised-measure" data-toggle="tooltip"
title="Count of projects where processing of most recent analysis report failed">
- {this.props.failuresCount}
+ {this.props.failingCount}
</span>
&nbsp;
{translate('background_tasks.failures')}
</span>
);
}
- },
+ }
- render() {
+ render () {
return (
<section className="big-spacer-top big-spacer-bottom">
<span>
@@ -127,4 +135,4 @@ export default React.createClass({
);
}
-});
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/tasks.js b/server/sonar-web/src/main/js/apps/background-tasks/components/tasks.js
new file mode 100644
index 00000000000..85b29bd53a1
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/tasks.js
@@ -0,0 +1,53 @@
+/*
+ * 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 React from 'react';
+
+import Task from './Task';
+import { translate } from '../../../helpers/l10n';
+
+export default function Tasks ({ tasks, onCancelTask, onFilterTask }) {
+ return (
+ <table className="data zebra zebra-hover background-tasks">
+ <thead>
+ <tr>
+ <th>&nbsp;</th>
+ <th>&nbsp;</th>
+ <th>&nbsp;</th>
+ <th>{translate('background_tasks.table.submitted')}</th>
+ <th>{translate('background_tasks.table.started')}</th>
+ <th>{translate('background_tasks.table.finished')}</th>
+ <th>{translate('background_tasks.table.duration')}</th>
+ <th>&nbsp;</th>
+ </tr>
+ </thead>
+ <tbody>
+ {tasks.map((task, index, tasks) => (
+ <Task
+ key={task.id}
+ task={task}
+ index={index}
+ tasks={tasks}
+ onCancelTask={onCancelTask}
+ onFilterTask={onFilterTask}/>
+ ))}
+ </tbody>
+ </table>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/constants.js b/server/sonar-web/src/main/js/apps/background-tasks/constants.js
index 31e1993edc0..be338fb7168 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/constants.js
+++ b/server/sonar-web/src/main/js/apps/background-tasks/constants.js
@@ -27,6 +27,9 @@ export const STATUSES = {
};
+export const ALL_TYPES = 'ALL_TYPES';
+
+
export const CURRENTS = {
ALL: '__ALL__',
ONLY_CURRENTS: 'CURRENTS'
@@ -39,6 +42,14 @@ export const DATE = {
CUSTOM: 'CUSTOM'
};
+export const DEFAULT_FILTERS = {
+ status: STATUSES.ALL,
+ taskType: ALL_TYPES,
+ currents: CURRENTS.ALL,
+ date: {},
+ query: ''
+};
+
export const DATE_FORMAT = 'YYYY-MM-DD';
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/containers/BackgroundTasksAppContainer.js b/server/sonar-web/src/main/js/apps/background-tasks/containers/BackgroundTasksAppContainer.js
new file mode 100644
index 00000000000..75496136964
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/containers/BackgroundTasksAppContainer.js
@@ -0,0 +1,38 @@
+/*
+ * 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 { connect } from 'react-redux';
+
+import Main from '../components/BackgroundTasksApp';
+import { initApp } from '../store/actions';
+
+function mapStateToProps () {
+ return {};
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ initApp: () => dispatch(initApp())
+ };
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Main);
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/containers/ListFooterContainer.js b/server/sonar-web/src/main/js/apps/background-tasks/containers/ListFooterContainer.js
new file mode 100644
index 00000000000..048068f63f5
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/containers/ListFooterContainer.js
@@ -0,0 +1,34 @@
+/*
+ * 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 { connect } from 'react-redux';
+
+import ListFooter from '../../../components/shared/list-footer';
+
+function mapStateToProps (state) {
+ return {
+ ready: !state.fetching,
+ total: state.total,
+ count: state.tasks.length
+ };
+}
+
+export default connect(
+ mapStateToProps
+)(ListFooter);
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/containers/SearchContainer.js b/server/sonar-web/src/main/js/apps/background-tasks/containers/SearchContainer.js
new file mode 100644
index 00000000000..ccae8470dd2
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/containers/SearchContainer.js
@@ -0,0 +1,51 @@
+/*
+ * 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 { connect } from 'react-redux';
+
+import Search from '../components/Search';
+import { filterTasks, search } from '../store/actions';
+
+function mapStateToProps (state) {
+ return {
+ fetching: state.fetching,
+ status: state.status,
+ currents: state.currents,
+ date: state.date,
+ query: state.query,
+ taskType: state.taskType,
+ types: state.types
+ };
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ onRefresh: () => dispatch(filterTasks()),
+ onStatusChange: (status) => dispatch(filterTasks({ status })),
+ onTypeChange: (taskType) => dispatch(filterTasks({ taskType })),
+ onCurrentsChange: (currents) => dispatch(filterTasks({ currents })),
+ onDateChange: (date) => dispatch(filterTasks({ date })),
+ onSearch: (query) => dispatch(search(query))
+ };
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Search);
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/containers/StatsContainer.js b/server/sonar-web/src/main/js/apps/background-tasks/containers/StatsContainer.js
new file mode 100644
index 00000000000..f1828926628
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/containers/StatsContainer.js
@@ -0,0 +1,48 @@
+/*
+ * 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 { connect } from 'react-redux';
+
+import Stats from '../components/Stats';
+import { filterTasks, cancelAllPending } from '../store/actions';
+import { STATUSES, CURRENTS, DEFAULT_FILTERS } from '../constants';
+
+function mapStateToProps (state) {
+ return {
+ pendingCount: state.pendingCount,
+ failingCount: state.failingCount,
+ inProgressDuration: state.inProgressDuration
+ };
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ onShowFailing: () => dispatch(filterTasks({
+ ...DEFAULT_FILTERS,
+ status: STATUSES.FAILED,
+ currents: CURRENTS.ONLY_CURRENTS
+ })),
+ onCancelAllPending: () => dispatch(cancelAllPending())
+ };
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Stats);
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/containers/TasksContainer.js b/server/sonar-web/src/main/js/apps/background-tasks/containers/TasksContainer.js
new file mode 100644
index 00000000000..0fc2989b98c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/containers/TasksContainer.js
@@ -0,0 +1,42 @@
+/*
+ * 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 { connect } from 'react-redux';
+
+import Tasks from '../components/Tasks';
+import { cancelTask, filterTasks } from '../store/actions';
+
+function mapStateToProps (state) {
+ return {
+ fetching: state.fetching,
+ tasks: state.tasks
+ };
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ onCancelTask: (task) => dispatch(cancelTask(task)),
+ onFilterTask: (task) => dispatch(filterTasks({ query: task.componentKey }))
+ };
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Tasks);
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/main.js b/server/sonar-web/src/main/js/apps/background-tasks/main.js
deleted file mode 100644
index bc52ddf9236..00000000000
--- a/server/sonar-web/src/main/js/apps/background-tasks/main.js
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * 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 _ from 'underscore';
-import moment from 'moment';
-import React from 'react';
-
-import { getQueue, getActivity, cancelTask, cancelAllTasks } from '../../api/ce';
-import { STATUSES, CURRENTS, DATE, DEBOUNCE_DELAY } from './constants';
-import Header from './header';
-import Stats from './stats';
-import Search from './search';
-import Tasks from './tasks';
-import ListFooter from '../../components/shared/list-footer';
-
-
-const PAGE_SIZE = 200;
-
-
-export default React.createClass({
- getInitialState() {
- return {
- queue: [],
- activity: [],
- activityTotal: 0,
- activityPage: 1,
- statusFilter: STATUSES.ALL,
- currentsFilter: CURRENTS.ALL,
- dateFilter: DATE.ANY,
- searchQuery: ''
- };
- },
-
- componentDidMount() {
- this.requestData();
- this.requestData = _.debounce(this.requestData, DEBOUNCE_DELAY);
- },
-
- getComponentFilter() {
- if (this.props.options.component) {
- return { componentId: this.props.options.component.id };
- } else {
- return {};
- }
- },
-
- getDateFilter() {
- const DATE_FORMAT = 'YYYY-MM-DD';
- let filter = {};
- switch (this.state.dateFilter) {
- case DATE.TODAY:
- filter.minSubmittedAt = moment().startOf('day').format(DATE_FORMAT);
- break;
- case DATE.CUSTOM:
- if (this.state.minDate) {
- filter.minSubmittedAt = moment(this.state.minDate).format(DATE_FORMAT);
- }
- if (this.state.maxDate) {
- filter.maxExecutedAt = moment(this.state.maxDate).format(DATE_FORMAT);
- }
- break;
- default:
- // do nothing
- }
- return filter;
- },
-
- getCurrentFilters() {
- let filters = {};
- if (this.state.statusFilter !== STATUSES.ALL) {
- filters.status = this.state.statusFilter;
- }
- if (this.state.currentsFilter !== STATUSES.ALL) {
- filters.onlyCurrents = true;
- }
- if (this.state.dateFilter !== DATE.ANY) {
- _.extend(filters, this.getDateFilter());
- }
- if (this.state.searchQuery) {
- _.extend(filters, { componentQuery: this.state.searchQuery });
- }
- return filters;
- },
-
- requestData() {
- this.requestQueue();
- this.requestActivity();
- this.requestFailures();
- },
-
- requestQueue() {
- let filters = this.getComponentFilter();
- if (!Object.keys(this.getCurrentFilters()).length) {
- getQueue(filters).done(queue => {
- let tasks = queue.tasks;
- this.setState({
- queue: this.orderTasks(tasks),
- pendingCount: this.countPending(tasks),
- inProgressDuration: this.getInProgressDuration(tasks)
- });
- });
- } else {
- this.setState({ queue: [] });
- }
- },
-
- requestActivity() {
- let filters = _.extend(
- this.getCurrentFilters(),
- this.getComponentFilter(),
- { p: this.state.activityPage, ps: PAGE_SIZE });
- getActivity(filters).done(activity => {
- let newActivity = activity.paging.pageIndex === 1 ?
- activity.tasks : [].concat(this.state.activity, activity.tasks);
- this.setState({
- activity: this.orderTasks(newActivity),
- activityTotal: activity.paging.total,
- activityPage: activity.paging.pageIndex
- });
- });
- },
-
- requestFailures() {
- let filters = _.extend(
- this.getComponentFilter(),
- { ps: 1, onlyCurrents: true, status: STATUSES.FAILED });
- getActivity(filters).done(failures => {
- this.setState({ failuresCount: failures.paging.total });
- });
- },
-
- countPending(tasks) {
- return _.where(tasks, { status: STATUSES.PENDING }).length;
- },
-
- orderTasks(tasks) {
- return _.sortBy(tasks, task => {
- return -moment(task.submittedAt).unix();
- });
- },
-
- getInProgressDuration(tasks) {
- let taskInProgress = _.findWhere(tasks, { status: STATUSES.IN_PROGRESS });
- return taskInProgress ? taskInProgress.executionTimeMs : null;
- },
-
- onStatusChange(newStatus) {
- this.setState({ statusFilter: newStatus, activityPage: 1 }, this.requestData);
- },
-
- onCurrentsChange(newCurrents) {
- this.setState({ currentsFilter: newCurrents, activityPage: 1 }, this.requestData);
- },
-
- onDateChange(newDate, minDate, maxDate) {
- this.setState({
- dateFilter: newDate,
- minDate: minDate,
- maxDate: maxDate,
- activityPage: 1
- }, this.requestData);
- },
-
- onSearch(query) {
- this.setState({ searchQuery: query }, this.requestData);
- },
-
- loadMore() {
- this.setState({ activityPage: this.state.activityPage + 1 }, this.requestActivity);
- },
-
- showFailures() {
- this.setState({
- statusFilter: STATUSES.FAILED,
- currentsFilter: CURRENTS.ONLY_CURRENTS,
- activityPage: 1
- }, this.requestActivity);
- },
-
- onTaskCanceled(task) {
- cancelTask(task.id).then(this.requestData);
- },
-
- cancelPending() {
- cancelAllTasks().then(this.requestData);
- },
-
- handleFilter(task) {
- this.onSearch(task.componentKey);
- },
-
- render() {
- return (
- <div className="page">
- <Header/>
-
- <Stats
- {...this.props}
- {...this.state}
- cancelPending={this.cancelPending}
- showFailures={this.showFailures}/>
-
- <Search
- {...this.props}
- {...this.state}
- refresh={this.requestData}
- onStatusChange={this.onStatusChange}
- onCurrentsChange={this.onCurrentsChange}
- onDateChange={this.onDateChange}
- onSearch={this.onSearch}/>
-
- <Tasks
- {...this.props}
- tasks={[].concat(this.state.queue, this.state.activity)}
- onTaskCanceled={this.onTaskCanceled}
- onFilter={this.handleFilter}/>
-
- <ListFooter
- count={this.state.queue.length + this.state.activity.length}
- total={this.state.queue.length + this.state.activityTotal}
- loadMore={this.loadMore}/>
- </div>
- );
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/search.js b/server/sonar-web/src/main/js/apps/background-tasks/search.js
deleted file mode 100644
index cc06fc2417e..00000000000
--- a/server/sonar-web/src/main/js/apps/background-tasks/search.js
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * 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 $ from 'jquery';
-import moment from 'moment';
-import React from 'react';
-import RadioToggle from '../../components/shared/radio-toggle';
-import { STATUSES, CURRENTS, DATE, DATE_FORMAT } from './constants';
-import { translate } from '../../helpers/l10n';
-
-export default React.createClass({
- componentDidMount() {
- this.attachDatePicker();
- },
-
- componentDidUpdate() {
- this.attachDatePicker();
- },
-
- getCurrentsOptions() {
- return [
- { value: CURRENTS.ALL, label: translate('background_tasks.currents_filter.ALL') },
- { value: CURRENTS.ONLY_CURRENTS, label: translate('background_tasks.currents_filter.ONLY_CURRENTS') }
- ];
- },
-
- getStatusOptions() {
- return [
- { value: STATUSES.ALL, label: translate('background_task.status.ALL') },
- { value: STATUSES.SUCCESS, label: translate('background_task.status.SUCCESS') },
- { value: STATUSES.FAILED, label: translate('background_task.status.FAILED') },
- { value: STATUSES.CANCELED, label: translate('background_task.status.CANCELED') }
- ];
- },
-
- getDateOptions() {
- return [
- { value: DATE.ANY, label: translate('background_tasks.date_filter.ALL') },
- { value: DATE.TODAY, label: translate('background_tasks.date_filter.TODAY') },
- { value: DATE.CUSTOM, label: translate('background_tasks.date_filter.CUSTOM') }
- ];
- },
-
- onDateChange(newDate) {
- if (newDate === DATE.CUSTOM) {
- let minDateRaw = this.refs.minDate.value;
- let maxDateRaw = this.refs.maxDate.value;
- let minDate = moment(minDateRaw, DATE_FORMAT, true);
- let maxDate = moment(maxDateRaw, DATE_FORMAT, true);
- this.props.onDateChange(newDate,
- minDate.isValid() ? minDate : null,
- maxDate.isValid() ? maxDate : null);
- } else {
- this.props.onDateChange(newDate);
- }
- },
-
- onDateInputChange() {
- this.onDateChange(DATE.CUSTOM);
- },
-
- attachDatePicker() {
- let opts = {
- dateFormat: 'yy-mm-dd',
- changeMonth: true,
- changeYear: true,
- onSelect: this.onDateInputChange
- };
- if ($.fn && $.fn.datepicker) {
- $(this.refs.minDate).datepicker(opts);
- $(this.refs.maxDate).datepicker(opts);
- }
- },
-
- renderCustomDateInput() {
- let shouldBeVisible = this.props.dateFilter === DATE.CUSTOM;
- let className = shouldBeVisible ? 'spacer-top' : 'spacer-top hidden';
- return (
- <div className={className}>
- from&nbsp;
- <input onChange={this.onDateInputChange} ref="minDate" type="text"/>
- &nbsp;to&nbsp;
- <input onChange={this.onDateInputChange} ref="maxDate" type="text"/>
- </div>
- );
- },
-
- onSearchFormSubmit(e) {
- e.preventDefault();
- this.onSearch();
- },
-
- onSearch() {
- let searchInput = this.refs.searchInput;
- let query = searchInput.value;
- this.props.onSearch(query);
- },
-
- renderSearchBox() {
- if (this.props.options && this.props.options.component) {
- // do not render search form on the project-level page
- return null;
- }
- return (
- <form onSubmit={this.onSearchFormSubmit} className="search-box">
- <button className="search-box-submit button-clean">
- <i className="icon-search"></i>
- </button>
- <input onChange={this.onSearch}
- value={this.props.searchQuery}
- ref="searchInput"
- className="search-box-input"
- type="search"
- placeholder="Search"/>
- </form>
- );
- },
-
- refresh(e) {
- e.preventDefault();
- this.props.refresh();
- let btn = e.target;
- btn.disabled = true;
- setTimeout(() => btn.disabled = false, 500);
- },
-
- render() {
- return (
- <section className="big-spacer-top big-spacer-bottom">
- <ul className="list-inline">
- <li>
- <RadioToggle options={this.getStatusOptions()} value={this.props.statusFilter}
- name="background-task-status" onCheck={this.props.onStatusChange}/>
- </li>
- <li>
- <RadioToggle options={this.getCurrentsOptions()} value={this.props.currentsFilter}
- name="background-task-currents" onCheck={this.props.onCurrentsChange}/>
- </li>
- <li>
- <RadioToggle options={this.getDateOptions()} value={this.props.dateFilter}
- name="background-task-date" onCheck={this.onDateChange}/>
- {this.renderCustomDateInput()}
- </li>
- <li>{this.renderSearchBox()}</li>
- <li className="pull-right">
- <button onClick={this.refresh} ref="reloadButton">{translate('reload')}</button>
- </li>
- </ul>
- </section>
- );
- }
-});
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/store/actions.js b/server/sonar-web/src/main/js/apps/background-tasks/store/actions.js
new file mode 100644
index 00000000000..511af5cdb29
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/store/actions.js
@@ -0,0 +1,210 @@
+/*
+ * 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 _ from 'underscore';
+import { getTypes, getActivity, cancelAllTasks, cancelTask as cancelTaskAPI } from '../../../api/ce';
+
+import { STATUSES, ALL_TYPES, CURRENTS, DEBOUNCE_DELAY } from '../constants';
+
+const PAGE_SIZE = 1000;
+
+export const INIT = 'INIT';
+export const REQUEST_TASKS = 'REQUEST_TASKS';
+export const RECEIVE_TASKS = 'RECEIVE_TASKS';
+export const UPDATE_QUERY = 'UPDATE_QUERY';
+export const RECEIVE_STATS = 'RECEIVE_STATS';
+export const CANCEL_ALL_PENDING = 'CANCEL_ALL_PENDING';
+export const CANCEL_TASK = 'CANCEL_TASK';
+export const FINISH_CANCEL_TASK = 'FINISH_CANCEL_TASK';
+
+export function init (types) {
+ return {
+ type: INIT,
+ types
+ };
+}
+
+export function requestTasks (filters) {
+ return {
+ type: REQUEST_TASKS,
+ ...filters
+ };
+}
+
+export function receiveTasks (tasks, total) {
+ return {
+ type: RECEIVE_TASKS,
+ tasks,
+ total
+ };
+}
+
+export function updateQuery (query) {
+ return {
+ type: UPDATE_QUERY,
+ query
+ };
+}
+
+export function receiveStats ({ pendingCount, failingCount, inProgressDuration }) {
+ return {
+ type: RECEIVE_STATS,
+ pendingCount,
+ failingCount,
+ inProgressDuration
+ };
+}
+
+export function cancelAllPendingAction () {
+ return {
+ type: CANCEL_ALL_PENDING
+ };
+}
+
+export function cancelTaskAction (task) {
+ return {
+ type: CANCEL_TASK,
+ task
+ };
+}
+
+export function finishCancelTaskAction (task) {
+ return {
+ type: FINISH_CANCEL_TASK,
+ task
+ };
+}
+
+function mapFiltersToParameters (filters = {}) {
+ const parameters = {};
+
+ if (filters.status !== STATUSES.ALL) {
+ parameters.status = filters.status;
+ } else {
+ parameters.status = [
+ STATUSES.PENDING,
+ STATUSES.IN_PROGRESS,
+ STATUSES.SUCCESS,
+ STATUSES.FAILED,
+ STATUSES.CANCELED
+ ].join();
+ }
+
+ if (filters.taskType !== ALL_TYPES) {
+ parameters.type = filters.taskType;
+ }
+
+ if (filters.currents !== CURRENTS.ALL) {
+ parameters.onlyCurrents = true;
+ }
+
+ if (filters.date.minSubmittedAt) {
+ parameters.minSubmittedAt = filters.date.minSubmittedAt;
+ }
+
+ if (filters.date.maxExecutedAt) {
+ parameters.maxExecutedAt = filters.date.maxExecutedAt;
+ }
+
+ if (filters.query) {
+ parameters.componentQuery = filters.query;
+ }
+
+ if (filters.lastPage !== 1) {
+ parameters.p = filters.lastPage;
+ }
+
+ return parameters;
+}
+
+function getInProgressDuration (tasks) {
+ return tasks.length ? tasks[0].executionTimeMs : null;
+}
+
+function fetchTasks (filters) {
+ return dispatch => {
+ const parameters = mapFiltersToParameters(filters);
+ parameters.ps = PAGE_SIZE;
+
+ dispatch(requestTasks(filters));
+
+ return Promise.all([
+ getActivity(parameters),
+ getActivity({ ps: 1, onlyCurrents: true, status: STATUSES.FAILED }),
+ getActivity({ ps: 1, status: STATUSES.PENDING }),
+ getActivity({ ps: 1, status: STATUSES.IN_PROGRESS })
+ ]).then(responses => {
+ const [activity, failingActivity, pendingActivity, inProgressActivity] = responses;
+ const tasks = activity.tasks;
+ const total = activity.paging.total;
+
+ dispatch(receiveTasks(tasks, total));
+
+ const pendingCount = pendingActivity.paging.total;
+ const inProgressDuration = getInProgressDuration(inProgressActivity.tasks);
+ const failingCount = failingActivity.paging.total;
+
+ dispatch(receiveStats({ pendingCount, failingCount, inProgressDuration }));
+ });
+ };
+}
+
+export function filterTasks (overriddenFilters) {
+ return (dispatch, getState) => {
+ const { status, taskType, currents, date, query } = getState();
+ const filters = { status, taskType, currents, date, query };
+ const finalFilters = { ...filters, ...overriddenFilters };
+
+ dispatch(fetchTasks(finalFilters));
+ };
+}
+
+const debouncedFilter = _.debounce((dispatch, overriddenFilters) => {
+ dispatch(filterTasks(overriddenFilters));
+}, DEBOUNCE_DELAY);
+
+export function search (query) {
+ return dispatch => {
+ dispatch(updateQuery(query));
+ debouncedFilter(dispatch, { query });
+ };
+}
+
+export function cancelAllPending () {
+ return dispatch => {
+ dispatch(cancelAllPendingAction());
+ cancelAllTasks().then(() => dispatch(filterTasks()));
+ };
+}
+
+export function cancelTask (task) {
+ return dispatch => {
+ dispatch(cancelTaskAction(task));
+ cancelTaskAPI(task.id).then(nextTask => dispatch(finishCancelTaskAction(nextTask)));
+ };
+}
+
+export function initApp () {
+ return dispatch => {
+ getTypes().then(types => {
+ dispatch(init(types));
+ dispatch(filterTasks());
+ });
+ };
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/store/reducers.js b/server/sonar-web/src/main/js/apps/background-tasks/store/reducers.js
new file mode 100644
index 00000000000..5c00567011d
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/store/reducers.js
@@ -0,0 +1,100 @@
+/*
+ * 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 {
+ INIT,
+ REQUEST_TASKS,
+ RECEIVE_TASKS,
+ UPDATE_QUERY,
+ RECEIVE_STATS,
+ CANCEL_ALL_PENDING,
+ FINISH_CANCEL_TASK
+} from './actions';
+import { DEFAULT_FILTERS } from '../constants';
+
+export const initialState = {
+ fetching: false,
+ tasks: [],
+ total: 0,
+
+ types: [],
+
+ // filters
+ ...DEFAULT_FILTERS,
+
+ // stats
+ pendingCount: 0,
+ failingCount: 0,
+ inProgressDuration: null
+};
+
+function updateTask (tasks, newTask) {
+ return tasks.map(task => task.id === newTask.id ? newTask : task);
+}
+
+export default function (state = initialState, action) {
+ switch (action.type) {
+ case INIT:
+ return {
+ ...state,
+ types: action.types
+ };
+ case REQUEST_TASKS:
+ return {
+ ...state,
+ fetching: true,
+ status: action.status,
+ currents: action.currents,
+ date: action.date,
+ query: action.query,
+ taskType: action.taskType
+ };
+ case RECEIVE_TASKS:
+ return {
+ ...state,
+ fetching: false,
+ tasks: action.tasks,
+ total: action.total
+ };
+ case UPDATE_QUERY:
+ return {
+ ...state,
+ query: action.query
+ };
+ case RECEIVE_STATS:
+ return {
+ ...state,
+ pendingCount: action.pendingCount,
+ failingCount: action.failingCount,
+ inProgressDuration: action.inProgressDuration
+ };
+ case CANCEL_ALL_PENDING:
+ return {
+ ...state,
+ fetching: true
+ };
+ case FINISH_CANCEL_TASK:
+ return {
+ ...state,
+ tasks: updateTask(state.tasks, action.task)
+ };
+ default:
+ return state;
+ }
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/styles/background-tasks.css b/server/sonar-web/src/main/js/apps/background-tasks/styles/background-tasks.css
new file mode 100644
index 00000000000..2bad2aba5a0
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/background-tasks/styles/background-tasks.css
@@ -0,0 +1,20 @@
+.bt-search-form {
+ display: flex;
+ align-items: flex-start;
+}
+
+.bt-search-form > li + li {
+ margin-left: 40px;
+}
+
+.bt-search-form-label {
+ margin-bottom: 4px;
+}
+
+.bt-search-form-field {
+ padding: 4px 0;
+}
+
+.bt-search-form-right {
+ margin-left: auto !important;
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/tasks.js b/server/sonar-web/src/main/js/apps/background-tasks/tasks.js
deleted file mode 100644
index ee5920f944a..00000000000
--- a/server/sonar-web/src/main/js/apps/background-tasks/tasks.js
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * 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 moment from 'moment';
-import React from 'react';
-
-import { getComponentUrl } from '../../helpers/urls';
-import QualifierIcon from '../../components/shared/qualifier-icon';
-import PendingIcon from '../../components/shared/pending-icon';
-import { STATUSES } from './constants';
-import { formatDuration } from './helpers';
-import { TooltipsMixin } from '../../components/mixins/tooltips-mixin';
-import { translate } from '../../helpers/l10n';
-
-
-export default React.createClass({
- propTypes: {
- tasks: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
- },
-
- mixins: [TooltipsMixin],
-
- onTaskCanceled (task, e) {
- e.preventDefault();
- this.props.onTaskCanceled(task);
- },
-
- handleFilter (task, e) {
- e.preventDefault();
- this.props.onFilter(task);
- },
-
- renderTaskStatus(task) {
- let inner;
- switch (task.status) {
- case STATUSES.PENDING:
- inner = <PendingIcon/>;
- break;
- case STATUSES.IN_PROGRESS:
- inner = <i className="spinner"/>;
- break;
- case STATUSES.SUCCESS:
- inner = <span className="badge badge-success">{translate('background_task.status.SUCCESS')}</span>;
- break;
- case STATUSES.FAILED:
- inner = <span className="badge badge-danger">{translate('background_task.status.FAILED')}</span>;
- break;
- case STATUSES.CANCELED:
- inner = <span className="badge badge-muted">{translate('background_task.status.CANCELED')}</span>;
- break;
- default:
- inner = '';
- }
- return <td className="thin spacer-right">{inner}</td>;
- },
-
- renderTaskComponent(task) {
- if (!task.componentKey) {
- return <td><span className="note">{task.id}</span></td>;
- }
-
- return (
- <td>
- <a className="link-with-icon" href={getComponentUrl(task.componentKey)}>
- <span className="little-spacer-right">
- <QualifierIcon qualifier={task.componentQualifier}/>
- </span>
- <span>{task.componentName}</span>
- </a>
- </td>
- );
- },
-
- renderTaskDate(task, field, format = 'LLL') {
- let date = task[field];
- return (
- <td className="thin nowrap text-right">
- {date ? moment(date).format(format) : ''}
- </td>
- );
- },
-
- renderTaskDay(task, previousTask) {
- let shouldDisplay = !previousTask || this.isAnotherDay(task.submittedAt, previousTask.submittedAt);
- return (
- <td className="thin nowrap text-right">
- {shouldDisplay ? moment(task.submittedAt).format('LL') : ''}
- </td>
- );
- },
-
- renderTaskExecutionTime(task) {
- return <td className="thin nowrap text-right">{formatDuration(task.executionTimeMs)}</td>;
- },
-
- isAnotherDay(a, b) {
- return !moment(a).isSame(moment(b), 'day');
- },
-
- renderFilter(task) {
- if (this.props.options && this.props.options.component) {
- return null;
- }
- return <td className="thin nowrap">
- <a onClick={this.handleFilter.bind(this, task)} className="icon-filter icon-half-transparent spacer-left" href="#"
- title={`Show only "${task.componentName}" tasks`} data-toggle="tooltip"/>
- </td>;
- },
-
- renderCancelButton(task) {
- if (task.status === STATUSES.PENDING) {
- return (
- <a onClick={this.onTaskCanceled.bind(this, task)} className="icon-delete"
- title={translate('background_tasks.cancel_task')} data-toggle="tooltip" href="#"></a>
- );
- } else {
- return null;
- }
- },
-
- renderLogsLink(task) {
- if (task.logs) {
- let url = `${window.baseUrl}/api/ce/logs?taskId=${task.id}`;
- return <a target="_blank" href={url}>{translate('background_tasks.logs')}</a>;
- } else {
- return null;
- }
- },
-
- renderTask(task, index, tasks) {
- let previousTask = index > 0 ? tasks[index - 1] : null;
- return (
- <tr key={task.id}>
- {this.renderTaskStatus(task)}
- {this.renderTaskComponent(task)}
- {this.renderTaskDay(task, previousTask)}
- {this.renderTaskDate(task, 'submittedAt', 'LTS')}
- {this.renderTaskDate(task, 'startedAt', 'LTS')}
- {this.renderTaskDate(task, 'executedAt', 'LTS')}
- {this.renderTaskExecutionTime(task)}
- <td className="thin nowrap text-right">
- {this.renderLogsLink(task)}
- {this.renderCancelButton(task)}
- </td>
- {this.renderFilter(task)}
- </tr>
- );
- },
-
- render() {
- if (!this.props.tasks.length) {
- return null;
- }
- let tasks = this.props.tasks.map(this.renderTask);
- return (
- <table className="data zebra zebra-hover background-tasks">
- <thead>
- <tr>
- <th>&nbsp;</th>
- <th>&nbsp;</th>
- <th>&nbsp;</th>
- <th>{translate('background_tasks.table.submitted')}</th>
- <th>{translate('background_tasks.table.started')}</th>
- <th>{translate('background_tasks.table.finished')}</th>
- <th>{translate('background_tasks.table.duration')}</th>
- <th>&nbsp;</th>
- </tr>
- </thead>
- <tbody>{tasks}</tbody>
- </table>
- );
- }
-});
diff --git a/server/sonar-web/src/main/js/components/store/configureStore.js b/server/sonar-web/src/main/js/components/store/configureStore.js
new file mode 100644
index 00000000000..fee2f603bf2
--- /dev/null
+++ b/server/sonar-web/src/main/js/components/store/configureStore.js
@@ -0,0 +1,34 @@
+/*
+ * 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 { createStore, applyMiddleware } from 'redux';
+import thunk from 'redux-thunk';
+
+const middlewares = [thunk];
+
+if (process.env.NODE_ENV !== 'production') {
+ const createLogger = require('redux-logger');
+ middlewares.push(createLogger());
+}
+
+const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
+
+export default function configureStore (rootReducer) {
+ return createStoreWithMiddleware(rootReducer);
+}
diff --git a/server/sonar-web/src/main/less/init/forms.less b/server/sonar-web/src/main/less/init/forms.less
index 9a832f9679e..d0cd92873eb 100644
--- a/server/sonar-web/src/main/less/init/forms.less
+++ b/server/sonar-web/src/main/less/init/forms.less
@@ -58,7 +58,11 @@ input[type=date] {
padding: 0 6px;
}
-input[type=search] {
+input[type="search"] {
+ -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
diff --git a/server/sonar-web/src/main/less/pages/analysis-reports.less b/server/sonar-web/src/main/less/pages/analysis-reports.less
deleted file mode 100644
index 4e3bef56f27..00000000000
--- a/server/sonar-web/src/main/less/pages/analysis-reports.less
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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 (reference) "../mixins";
-@import (reference) "../variables";
-@import (reference) "../components/ui";
-@import (reference) "../components/navigator/base";
-@import (reference) "../init/type";
-
-
-@pendingColor: #fdfce2;
-@workingColor: #ecf9fc;
-@doneColor: #ecfced;
-@cancelledColor: #fcecec;
-@failedColor: #fcecec;
-
-
-.analysis-reports-actions {
- margin-bottom: 10px;
-}
-
-.analysis-reports-total {
- float: right;
- margin-top: -20px;
-}
-
-.analysis-reports-results .navigator-results-list > li {
- cursor: default;
-}
-
-.analysis-reports-no-results {
- border: none !important;
-}
-
-.analysis-reports-report-pending {
- background-color: @pendingColor !important;
-}
-
-.analysis-reports-report-working {
- background-color: @workingColor !important;
-}
-
-.analysis-reports-report-done {
- background-color: @doneColor !important;
-}
-
-.analysis-reports-report-cancelled {
- background-color: @cancelledColor !important;
-}
-
-.analysis-reports-report-failed {
- background-color: @failedColor !important;
-
- .analysis-reports-report-id { color: darken(@failedColor, 60%); }
-}
-
-.analysis-reports-project {
- display: inline-block;
- vertical-align: middle;
- width: 30%;
- .text-ellipsis;
-}
-
-.analysis-reports-timestamp {
- display: inline-block;
- vertical-align: middle;
- width: 220px;
- margin-left: 15px;
-}
-
-.analysis-reports-report-id {
- position: absolute;
- top: 6px;
- right: 6px;
- opacity: 0.3;
- font-size: @bigFontSize;
-
- &:before { content: '#'; }
-}
-
-.analysis-reports-spinner {
- width: 200px;
- height: 200px;
- margin-top: 20px;
-}
-
-.analysis-reports-spinner,
-.analysis-reports-spinner:before
-.analysis-reports-spinner:after {
- animation-duration: 6s;
-}
-
-.analysis-reports-timestamp-spinner {
- margin-left: 10px;
-}
-
-.analysis-reports-timestamp-spinner,
-.analysis-reports-timestamp-spinner:before,
-.analysis-reports-timestamp-spinner:after {
- animation-duration: 2s;
-}
diff --git a/server/sonar-web/src/main/less/sonar.less b/server/sonar-web/src/main/less/sonar.less
index 5188f62907b..b1b5e033e29 100644
--- a/server/sonar-web/src/main/less/sonar.less
+++ b/server/sonar-web/src/main/less/sonar.less
@@ -58,7 +58,6 @@
@import "components/pills";
@import "components/react-select";
-@import "pages/analysis-reports";
@import "pages/coding-rules";
@import "pages/dashboard";
@import "pages/issues";
diff --git a/server/sonar-web/tests/apps/background-tasks-test.js b/server/sonar-web/tests/apps/background-tasks-test.js
index 9bc486c7d63..0d70aa18311 100644
--- a/server/sonar-web/tests/apps/background-tasks-test.js
+++ b/server/sonar-web/tests/apps/background-tasks-test.js
@@ -3,10 +3,10 @@ import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
-import Header from '../../src/main/js/apps/background-tasks/header';
-import Stats from '../../src/main/js/apps/background-tasks/stats';
-import Search from '../../src/main/js/apps/background-tasks/search';
-import Tasks from '../../src/main/js/apps/background-tasks/tasks';
+import Header from '../../src/main/js/apps/background-tasks/components/Header';
+import Stats from '../../src/main/js/apps/background-tasks/components/Stats';
+import Search from '../../src/main/js/apps/background-tasks/components/Search';
+import Tasks from '../../src/main/js/apps/background-tasks/components/Tasks';
import {STATUSES, CURRENTS, DEBOUNCE_DELAY} from '../../src/main/js/apps/background-tasks/constants';
import {formatDuration} from '../../src/main/js/apps/background-tasks/helpers';
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index ce0758fb4b8..c6811c5c523 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -3082,6 +3082,12 @@ background_task.status.SUCCESS=Success
background_task.status.FAILED=Failed
background_task.status.CANCELED=Canceled
+background_task.type.ALL=All
+background_task.type.REPORT=Project Analysis
+background_task.type.DEV_REFRESH=Developer Analysis
+background_task.type.DEV_PURGE=Developer Cleaning
+background_task.type.VIEW_REFRESH=View Analysis
+
background_tasks.page=Background Tasks
background_tasks.page.description=This page allows monitoring of the queue of tasks running asynchronously on the server. It also gives access to the history of finished tasks, their status and logs. Analysis report processing is the most common kind of background task.