aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src
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 /server/sonar-web/src
parente53211f03c6d95428b387cc3beddf5378579769a (diff)
downloadsonarqube-4621ff0206176da5af9fc8c1c704a2ca5aded1ad.tar.gz
sonarqube-4621ff0206176da5af9fc8c1c704a2ca5aded1ad.zip
SONAR-7191 Update the background tasks page to reflect latest WS changes
Diffstat (limited to 'server/sonar-web/src')
-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
35 files changed, 1487 insertions, 770 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";