]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7191 Update the background tasks page to reflect latest WS changes
authorStas Vilchik <vilchiks@gmail.com>
Thu, 11 Feb 2016 17:57:39 +0000 (18:57 +0100)
committerStas Vilchik <vilchiks@gmail.com>
Tue, 16 Feb 2016 13:44:59 +0000 (14:44 +0100)
39 files changed:
server/sonar-web/src/main/js/api/ce.js
server/sonar-web/src/main/js/apps/background-tasks/app.js
server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/Task.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/TaskCancelButton.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/TaskLogsLink.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/header.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/search.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/stats.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/components/tasks.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/constants.js
server/sonar-web/src/main/js/apps/background-tasks/containers/BackgroundTasksAppContainer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/containers/ListFooterContainer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/containers/SearchContainer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/containers/StatsContainer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/containers/TasksContainer.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/header.js [deleted file]
server/sonar-web/src/main/js/apps/background-tasks/main.js [deleted file]
server/sonar-web/src/main/js/apps/background-tasks/search.js [deleted file]
server/sonar-web/src/main/js/apps/background-tasks/stats.js [deleted file]
server/sonar-web/src/main/js/apps/background-tasks/store/actions.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/store/reducers.js [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/styles/background-tasks.css [new file with mode: 0644]
server/sonar-web/src/main/js/apps/background-tasks/tasks.js [deleted file]
server/sonar-web/src/main/js/components/store/configureStore.js [new file with mode: 0644]
server/sonar-web/src/main/less/init/forms.less
server/sonar-web/src/main/less/pages/analysis-reports.less [deleted file]
server/sonar-web/src/main/less/sonar.less
server/sonar-web/tests/apps/background-tasks-test.js
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index c31db7b0dbc9ea69287b88ecf5d576f83c60f996..dc36ed444a3cf155a77365dc346dd79b575b7f0c 100644 (file)
@@ -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);
+}
index 23a39dd0423b5a76d136096a0c42a81f96d08fc1..d1626102c8f8356adb8b0545b333404f9815f71a 100644 (file)
  */
 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 (file)
index 0000000..9f2192a
--- /dev/null
@@ -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 (file)
index 0000000..bcbfd7f
--- /dev/null
@@ -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 (file)
index 0000000..b5969a7
--- /dev/null
@@ -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 (file)
index 0000000..14a7495
--- /dev/null
@@ -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 (file)
index 0000000..58daa47
--- /dev/null
@@ -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 (file)
index 0000000..f5b793d
--- /dev/null
@@ -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 (file)
index 0000000..e6a0ca3
--- /dev/null
@@ -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/components/TaskDate.js b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.js
new file mode 100644 (file)
index 0000000..5c8759c
--- /dev/null
@@ -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 moment from 'moment';
+import React from 'react';
+
+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 (file)
index 0000000..663cffa
--- /dev/null
@@ -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 (file)
index 0000000..07e830d
--- /dev/null
@@ -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 (file)
index 0000000..2f5893e
--- /dev/null
@@ -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 (file)
index 0000000..060e397
--- /dev/null
@@ -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 (file)
index 0000000..5e6696a
--- /dev/null
@@ -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 (file)
index 0000000..3fbf337
--- /dev/null
@@ -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 (file)
index 0000000..349b62a
--- /dev/null
@@ -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/components/stats.js b/server/sonar-web/src/main/js/apps/background-tasks/components/stats.js
new file mode 100644 (file)
index 0000000..d59e40b
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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, { Component } from 'react';
+
+import { formatDuration } from './../helpers';
+import { translate } from '../../../helpers/l10n';
+
+
+export default class Stats extends Component {
+  handleCancelAllPending (e) {
+    e.preventDefault();
+    this.props.onCancelAllPending();
+  }
+
+  handleShowFailing (e) {
+    e.preventDefault();
+    this.props.onShowFailing();
+  }
+
+  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">
+            {formatDuration(this.props.inProgressDuration)}
+          </span>
+        </span>
+    );
+  }
+
+  renderPending () {
+    if (this.props.pendingCount == null) {
+      return null;
+    }
+    if (this.props.pendingCount > 0) {
+      return (
+          <span>
+            <span ref="pendingCount" className="emphasised-measure">{this.props.pendingCount}</span>
+            &nbsp;
+            {translate('background_tasks.pending')}
+            <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 {
+      return (
+          <span>
+            <span ref="pendingCount" className="emphasised-measure">{this.props.pendingCount}</span>
+            &nbsp;
+            {translate('background_tasks.pending')}
+          </span>
+      );
+    }
+  }
+
+  renderFailures () {
+    if (this.props.failingCount == null) {
+      return null;
+    }
+
+    if (this.props.options && this.props.options.component) {
+      return null;
+    }
+
+    if (this.props.failingCount > 0) {
+      return (
+          <span>
+            <a ref="failureCount"
+               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.failingCount}</a>
+            &nbsp;
+            {translate('background_tasks.failures')}
+          </span>
+      );
+    } else {
+      return (
+          <span>
+            <span ref="failureCount" className="emphasised-measure" data-toggle="tooltip"
+                  title="Count of projects where processing of most recent analysis report failed">
+              {this.props.failingCount}
+            </span>
+            &nbsp;
+            {translate('background_tasks.failures')}
+          </span>
+      );
+    }
+  }
+
+  render () {
+    return (
+        <section className="big-spacer-top big-spacer-bottom">
+          <span>
+            {this.renderPending()}
+          </span>
+          <span className="huge-spacer-left">
+            {this.renderFailures()}
+          </span>
+          {this.renderInProgressDuration()}
+        </section>
+
+    );
+  }
+}
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 (file)
index 0000000..85b29bd
--- /dev/null
@@ -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>
+  );
+}
index 31e1993edc01560fdacfbc98a47b3a1e8afe525a..be338fb716856738a61860eab55c33250da0078d 100644 (file)
@@ -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 (file)
index 0000000..7549613
--- /dev/null
@@ -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 (file)
index 0000000..048068f
--- /dev/null
@@ -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 (file)
index 0000000..ccae847
--- /dev/null
@@ -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 (file)
index 0000000..f182892
--- /dev/null
@@ -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 (file)
index 0000000..0fc2989
--- /dev/null
@@ -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/header.js b/server/sonar-web/src/main/js/apps/background-tasks/header.js
deleted file mode 100644 (file)
index bc84cae..0000000
+++ /dev/null
@@ -1,32 +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 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>
-    );
-  }
-});
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 (file)
index bc52ddf..0000000
+++ /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 (file)
index cc06fc2..0000000
+++ /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/stats.js b/server/sonar-web/src/main/js/apps/background-tasks/stats.js
deleted file mode 100644 (file)
index 95ac963..0000000
+++ /dev/null
@@ -1,130 +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 React from 'react';
-
-import { formatDuration } from './helpers';
-import { TooltipsMixin } from '../../components/mixins/tooltips-mixin';
-import { translate } from '../../helpers/l10n';
-
-
-export default React.createClass({
-  mixins: [TooltipsMixin],
-
-  onPendingCanceled(e) {
-    e.preventDefault();
-    this.props.cancelPending();
-  },
-
-  onFailuresClick(e) {
-    e.preventDefault();
-    this.props.showFailures();
-  },
-
-  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">
-            {formatDuration(this.props.inProgressDuration)}
-          </span>
-        </span>
-    );
-  },
-
-  renderPending() {
-    if (this.props.pendingCount == null) {
-      return null;
-    }
-    if (this.props.pendingCount > 0) {
-      return (
-          <span>
-            <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>
-          </span>
-      );
-    } else {
-      return (
-          <span>
-            <span ref="pendingCount" className="emphasised-measure">{this.props.pendingCount}</span>
-            &nbsp;
-            {translate('background_tasks.pending')}
-          </span>
-      );
-    }
-  },
-
-  renderFailures() {
-    if (this.props.failuresCount == null) {
-      return null;
-    }
-
-    if (this.props.options && this.props.options.component) {
-      return null;
-    }
-
-    if (this.props.failuresCount > 0) {
-      return (
-          <span>
-            <a ref="failureCount"
-               onClick={this.onFailuresClick}
-               className="emphasised-measure"
-               data-toggle="tooltip"
-               title="Count of projects where processing of most recent analysis report failed"
-               href="#">{this.props.failuresCount}</a>
-            &nbsp;
-            {translate('background_tasks.failures')}
-          </span>
-      );
-    } else {
-      return (
-          <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}
-            </span>
-            &nbsp;
-            {translate('background_tasks.failures')}
-          </span>
-      );
-    }
-  },
-
-  render() {
-    return (
-        <section className="big-spacer-top big-spacer-bottom">
-          <span>
-            {this.renderPending()}
-          </span>
-          <span className="huge-spacer-left">
-            {this.renderFailures()}
-          </span>
-          {this.renderInProgressDuration()}
-        </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 (file)
index 0000000..511af5c
--- /dev/null
@@ -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 (file)
index 0000000..5c00567
--- /dev/null
@@ -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 (file)
index 0000000..2bad2ab
--- /dev/null
@@ -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 (file)
index ee5920f..0000000
+++ /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 (file)
index 0000000..fee2f60
--- /dev/null
@@ -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);
+}
index 9a832f9679e16e83ac323088d238aa50b87e8995..d0cd92873ebd93c1a646b66e26c7b33a165d0887 100644 (file)
@@ -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 (file)
index 4e3bef5..0000000
+++ /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;
-}
index 5188f62907b1e348eb863dba60fc90b89113904c..b1b5e033e2947ac778cf9c4a6eed1001dffbeac0 100644 (file)
@@ -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";
index 9bc486c7d633d9c13b8f8677704a24d2ee1ab895..0d70aa18311d226135daa3b549421ffb349bab70 100644 (file)
@@ -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';
 
index ce0758fb4b8737ed4159c3fafb7f1a07649ee7d2..c6811c5c5234f3910715db6d481a3e346bb27650 100644 (file)
@@ -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.