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