@@ -21,11 +21,13 @@ import { getJSON, post, RequestData } from '../helpers/request'; | |||
import throwGlobalError from '../app/utils/throwGlobalError'; | |||
import { Task } from '../app/types'; | |||
export function getActivity(data: RequestData): Promise<any> { | |||
export function getActivity(data: RequestData): Promise<{ tasks: Task[] }> { | |||
return getJSON('/api/ce/activity', data); | |||
} | |||
export function getStatus(componentId?: string): Promise<any> { | |||
export function getStatus( | |||
componentId?: string | |||
): Promise<{ failing: number; inProgress: number; pending: number }> { | |||
const data = {}; | |||
if (componentId) { | |||
Object.assign(data, { componentId }); | |||
@@ -51,7 +53,7 @@ export function getTasksForComponent( | |||
return getJSON('/api/ce/component', { componentKey }).catch(throwGlobalError); | |||
} | |||
export function getTypes(): Promise<any> { | |||
export function getTypes(): Promise<string[]> { | |||
return getJSON('/api/ce/task_types').then(r => r.taskTypes); | |||
} | |||
@@ -17,13 +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 React from 'react'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import Stats from '../components/Stats'; | |||
import Search from '../components/Search'; | |||
import { STATUSES, CURRENTS, DEBOUNCE_DELAY, DEFAULT_FILTERS } from '../constants'; | |||
import { formatDuration } from '../utils'; | |||
import { change, click } from '../../../helpers/testUtils'; | |||
import { click } from '../../../helpers/testUtils'; | |||
const stub = jest.fn(); | |||
@@ -38,12 +38,14 @@ describe('Constants', () => { | |||
}); | |||
describe('Search', () => { | |||
const defaultProps = { | |||
const defaultProps: Search['props'] = { | |||
...DEFAULT_FILTERS, | |||
loading: false, | |||
types: [], | |||
onFilterUpdate: () => true, | |||
onReload: () => true | |||
onReload: () => true, | |||
maxExecutedAt: undefined, | |||
minSubmittedAt: undefined | |||
}; | |||
it('should render search form', () => { | |||
@@ -60,7 +62,7 @@ describe('Search', () => { | |||
const searchSpy = jest.fn(); | |||
const component = shallow(<Search {...defaultProps} onFilterUpdate={searchSpy} />); | |||
const searchInput = component.find('SearchBox'); | |||
searchInput.prop('onChange')('some search query'); | |||
searchInput.prop<Function>('onChange')('some search query'); | |||
setTimeout(() => { | |||
expect(searchSpy).toBeCalledWith({ query: 'some search query' }); | |||
done(); |
@@ -17,12 +17,12 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
// @flow | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import Helmet from 'react-helmet'; | |||
import { debounce, uniq } from 'lodash'; | |||
import { connect } from 'react-redux'; | |||
import { InjectedRouter } from 'react-router'; | |||
import { Location } from 'history'; | |||
import Header from './Header'; | |||
import Footer from './Footer'; | |||
import StatsContainer from './StatsContainer'; | |||
@@ -37,67 +37,59 @@ import { | |||
cancelAllTasks, | |||
cancelTask as cancelTaskAPI | |||
} from '../../../api/ce'; | |||
import { updateTask, mapFiltersToParameters } from '../utils'; | |||
import { updateTask, mapFiltersToParameters, Query } from '../utils'; | |||
import { fetchOrganizations } from '../../../store/rootActions'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { parseAsDate } from '../../../helpers/query'; | |||
import { toShortNotSoISOString } from '../../../helpers/dates'; | |||
import '../background-tasks.css'; | |||
import { Task } from '../../../app/types'; | |||
/*:: | |||
type Props = { | |||
component: Object, | |||
location: Object, | |||
fetchOrganizations: (Array<string>) => string | |||
}; | |||
*/ | |||
/*:: | |||
type State = { | |||
loading: boolean, | |||
tasks: Array<*>, | |||
types?: Array<*>, | |||
query: string, | |||
pendingCount: number, | |||
failingCount: number | |||
}; | |||
*/ | |||
class BackgroundTasksApp extends React.PureComponent { | |||
/*:: loadTasksDebounced: Function; */ | |||
/*:: mounted: boolean; */ | |||
/*:: props: Props; */ | |||
static contextTypes = { | |||
router: PropTypes.object.isRequired | |||
}; | |||
state /*: State */ = { | |||
loading: true, | |||
tasks: [], | |||
// filters | |||
query: '', | |||
interface Props { | |||
component?: { id: string }; | |||
fetchOrganizations: (keys: string[]) => void; | |||
location: Location; | |||
router: Pick<InjectedRouter, 'push'>; | |||
} | |||
// stats | |||
pendingCount: 0, | |||
failingCount: 0 | |||
}; | |||
interface State { | |||
loading: boolean; | |||
tasks: Task[]; | |||
types?: string[]; | |||
query: string; | |||
pendingCount: number; | |||
failingCount: number; | |||
} | |||
componentWillMount() { | |||
this.loadTasksDebounced = debounce(this.loadTasks.bind(this), DEBOUNCE_DELAY); | |||
class BackgroundTasksApp extends React.PureComponent<Props, State> { | |||
loadTasksDebounced: () => void; | |||
mounted = false; | |||
constructor(props: Props) { | |||
super(props); | |||
this.state = { | |||
failingCount: 0, | |||
loading: true, | |||
pendingCount: 0, | |||
query: '', | |||
tasks: [] | |||
}; | |||
this.loadTasksDebounced = debounce(this.loadTasks, DEBOUNCE_DELAY); | |||
} | |||
componentDidMount() { | |||
this.mounted = true; | |||
getTypes().then(types => { | |||
this.setState({ types }); | |||
this.loadTasks(); | |||
}); | |||
getTypes().then( | |||
types => { | |||
this.setState({ types }); | |||
this.loadTasks(); | |||
}, | |||
() => {} | |||
); | |||
} | |||
componentDidUpdate(prevProps /*: Props */) { | |||
componentDidUpdate(prevProps: Props) { | |||
if ( | |||
prevProps.component !== this.props.component || | |||
prevProps.location !== this.props.location | |||
@@ -110,7 +102,13 @@ class BackgroundTasksApp extends React.PureComponent { | |||
this.mounted = false; | |||
} | |||
loadTasks() { | |||
stopLoading = () => { | |||
if (this.mounted) { | |||
this.setState({ loading: false }); | |||
} | |||
}; | |||
loadTasks = () => { | |||
this.setState({ loading: true }); | |||
const status = this.props.location.query.status || DEFAULT_FILTERS.status; | |||
@@ -131,7 +129,7 @@ class BackgroundTasksApp extends React.PureComponent { | |||
Promise.all([getActivity(parameters), getStatus(parameters.componentId)]).then(responses => { | |||
if (this.mounted) { | |||
const [activity, status] = responses; | |||
const tasks = activity.tasks; | |||
const { tasks } = activity; | |||
const pendingCount = status.pending; | |||
const failingCount = status.failing; | |||
@@ -146,14 +144,14 @@ class BackgroundTasksApp extends React.PureComponent { | |||
loading: false | |||
}); | |||
} | |||
}); | |||
} | |||
}, this.stopLoading); | |||
}; | |||
handleFilterUpdate(nextState /*: Object */) { | |||
handleFilterUpdate = (nextState: Partial<Query>) => { | |||
const nextQuery = { ...this.props.location.query, ...nextState }; | |||
// remove defaults | |||
Object.keys(DEFAULT_FILTERS).forEach(key => { | |||
Object.keys(DEFAULT_FILTERS).forEach((key: keyof typeof DEFAULT_FILTERS) => { | |||
if (nextQuery[key] === DEFAULT_FILTERS[key]) { | |||
delete nextQuery[key]; | |||
} | |||
@@ -167,26 +165,28 @@ class BackgroundTasksApp extends React.PureComponent { | |||
nextQuery.maxExecutedAt = toShortNotSoISOString(nextQuery.maxExecutedAt); | |||
} | |||
this.context.router.push({ | |||
this.props.router.push({ | |||
pathname: this.props.location.pathname, | |||
query: nextQuery | |||
}); | |||
} | |||
}; | |||
handleCancelTask(task) { | |||
handleCancelTask = (task: Task) => { | |||
this.setState({ loading: true }); | |||
cancelTaskAPI(task.id).then(nextTask => { | |||
if (this.mounted) { | |||
const tasks = updateTask(this.state.tasks, nextTask); | |||
this.setState({ tasks, loading: false }); | |||
this.setState(state => ({ | |||
tasks: updateTask(state.tasks, nextTask), | |||
loading: false | |||
})); | |||
} | |||
}); | |||
} | |||
}, this.stopLoading); | |||
}; | |||
handleFilterTask(task) { | |||
handleFilterTask = (task: Task) => { | |||
this.handleFilterUpdate({ query: task.componentKey }); | |||
} | |||
}; | |||
handleShowFailing() { | |||
this.handleFilterUpdate({ | |||
@@ -203,7 +203,7 @@ class BackgroundTasksApp extends React.PureComponent { | |||
if (this.mounted) { | |||
this.loadTasks(); | |||
} | |||
}); | |||
}, this.stopLoading); | |||
} | |||
render() { | |||
@@ -234,8 +234,8 @@ class BackgroundTasksApp extends React.PureComponent { | |||
<StatsContainer | |||
component={component} | |||
failingCount={failingCount} | |||
onCancelAllPending={this.handleCancelAllPending.bind(this)} | |||
onShowFailing={this.handleShowFailing.bind(this)} | |||
onCancelAllPending={this.handleCancelAllPending} | |||
onShowFailing={this.handleShowFailing} | |||
pendingCount={pendingCount} | |||
/> | |||
@@ -245,7 +245,7 @@ class BackgroundTasksApp extends React.PureComponent { | |||
loading={loading} | |||
maxExecutedAt={maxExecutedAt} | |||
minSubmittedAt={minSubmittedAt} | |||
onFilterUpdate={this.handleFilterUpdate.bind(this)} | |||
onFilterUpdate={this.handleFilterUpdate} | |||
onReload={this.loadTasksDebounced} | |||
query={query} | |||
status={status} | |||
@@ -256,8 +256,8 @@ class BackgroundTasksApp extends React.PureComponent { | |||
<Tasks | |||
component={component} | |||
loading={loading} | |||
onCancelTask={this.handleCancelTask.bind(this)} | |||
onFilterTask={this.handleFilterTask.bind(this)} | |||
onCancelTask={this.handleCancelTask} | |||
onFilterTask={this.handleFilterTask} | |||
tasks={tasks} | |||
/> | |||
@@ -17,9 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
/* @flow */ | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import * as React from 'react'; | |||
import StatusFilter from './StatusFilter'; | |||
import TypesFilter from './TypesFilter'; | |||
import CurrentsFilter from './CurrentsFilter'; | |||
@@ -28,35 +26,40 @@ import { DEFAULT_FILTERS } from '../constants'; | |||
import SearchBox from '../../../components/controls/SearchBox'; | |||
import { Button } from '../../../components/ui/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { Query } from '../utils'; | |||
export default class Search extends React.PureComponent { | |||
static propTypes = { | |||
loading: PropTypes.bool.isRequired, | |||
status: PropTypes.any.isRequired, | |||
taskType: PropTypes.any.isRequired, | |||
currents: PropTypes.any.isRequired, | |||
query: PropTypes.string.isRequired, | |||
onFilterUpdate: PropTypes.func.isRequired, | |||
onReload: PropTypes.func.isRequired | |||
}; | |||
interface Props { | |||
component?: unknown; | |||
currents: string; | |||
loading: boolean; | |||
onFilterUpdate: (changes: Partial<Query>) => void; | |||
onReload: () => void; | |||
query: string; | |||
status: string; | |||
taskType: string; | |||
maxExecutedAt: Date | undefined; | |||
minSubmittedAt: Date | undefined; | |||
types: string[]; | |||
} | |||
handleStatusChange = (status /*: string */) => { | |||
export default class Search extends React.PureComponent<Props> { | |||
handleStatusChange = (status: string) => { | |||
this.props.onFilterUpdate({ status }); | |||
}; | |||
handleTypeChange = (taskType /*: string */) => { | |||
handleTypeChange = (taskType: string) => { | |||
this.props.onFilterUpdate({ taskType }); | |||
}; | |||
handleCurrentsChange = (currents /*: string */) => { | |||
handleCurrentsChange = (currents: string) => { | |||
this.props.onFilterUpdate({ currents }); | |||
}; | |||
handleDateChange = (date /*: { maxExecutedAt?: Date; minSubmittedAt?: Date } */) => { | |||
handleDateChange = (date: { maxExecutedAt?: Date; minSubmittedAt?: Date }) => { | |||
this.props.onFilterUpdate(date); | |||
}; | |||
handleQueryChange = (query /*: string */) => { | |||
handleQueryChange = (query: string) => { | |||
this.props.onFilterUpdate({ query }); | |||
}; | |||
@@ -17,38 +17,29 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
/* @flow */ | |||
import React from 'react'; | |||
import * as React from 'react'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { DeleteButton } from '../../../components/ui/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
failingCount: number, | |||
isSystemAdmin?: boolean, | |||
pendingCount: number, | |||
onShowFailing: () => void, | |||
onCancelAllPending: () => void | |||
}; | |||
*/ | |||
/*:: | |||
type State = Object; | |||
*/ | |||
export default class Stats extends React.PureComponent { | |||
/*:: props: Props; */ | |||
/*:: state: State; */ | |||
interface Props { | |||
component?: unknown; | |||
failingCount?: number; | |||
isSystemAdmin?: boolean; | |||
pendingCount?: number; | |||
onShowFailing: () => void; | |||
onCancelAllPending: () => void; | |||
} | |||
handleShowFailing = (event /*: Object */) => { | |||
export default class Stats extends React.PureComponent<Props> { | |||
handleShowFailing = (event: React.MouseEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
event.currentTarget.blur(); | |||
this.props.onShowFailing(); | |||
}; | |||
renderPending() { | |||
if (this.props.pendingCount == null) { | |||
if (this.props.pendingCount === undefined) { | |||
return null; | |||
} | |||
if (this.props.pendingCount > 0) { | |||
@@ -79,7 +70,7 @@ export default class Stats extends React.PureComponent { | |||
} | |||
renderFailures() { | |||
if (this.props.failingCount == null) { | |||
if (this.props.failingCount === undefined) { | |||
return null; | |||
} | |||
@@ -25,4 +25,4 @@ const mapStateToProps = (state: Store) => ({ | |||
isSystemAdmin: !!getAppState(state).canAdmin | |||
}); | |||
export default connect(mapStateToProps)(Stats as any); | |||
export default connect(mapStateToProps)(Stats); |
@@ -29,7 +29,7 @@ import TaskSubmitter from './TaskSubmitter'; | |||
import { Task as TaskType } from '../../../app/types'; | |||
interface Props { | |||
component?: {}; | |||
component?: unknown; | |||
onCancelTask: (task: TaskType) => void; | |||
onFilterTask: (task: TaskType) => void; | |||
task: TaskType; |
@@ -32,7 +32,7 @@ const AnalysisWarningsModal = lazyLoad( | |||
); | |||
interface Props { | |||
component?: {}; | |||
component?: unknown; | |||
onCancelTask: (task: Task) => void; | |||
onFilterTask: (task: Task) => void; | |||
task: Task; |
@@ -1,85 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
/* @flow */ | |||
import React from 'react'; | |||
import classNames from 'classnames'; | |||
import Task from './Task'; | |||
import { translate } from '../../../helpers/l10n'; | |||
/*:: | |||
type Props = { | |||
tasks: Array<*>, | |||
component: Object, | |||
loading: boolean, | |||
onCancelTask: Function, | |||
onFilterTask: Function | |||
}; | |||
*/ | |||
/*:: | |||
type State = Object; | |||
*/ | |||
export default class Tasks extends React.PureComponent { | |||
/*:: props: Props; */ | |||
/*:: state: State; */ | |||
render() { | |||
const { tasks, component, loading, onCancelTask, onFilterTask } = this.props; | |||
const className = classNames('data zebra zebra-hover background-tasks', { | |||
'new-loading': loading | |||
}); | |||
return ( | |||
<div className="boxed-group boxed-group-inner"> | |||
<table className={className}> | |||
<thead> | |||
<tr> | |||
<th>{translate('background_tasks.table.status')}</th> | |||
<th>{translate('background_tasks.table.task')}</th> | |||
<th>{translate('background_tasks.table.id')}</th> | |||
<th>{translate('background_tasks.table.submitter')}</th> | |||
<th> </th> | |||
<th className="text-right">{translate('background_tasks.table.submitted')}</th> | |||
<th className="text-right">{translate('background_tasks.table.started')}</th> | |||
<th className="text-right">{translate('background_tasks.table.finished')}</th> | |||
<th className="text-right">{translate('background_tasks.table.duration')}</th> | |||
<th> </th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{tasks.map((task, index, tasks) => ( | |||
<Task | |||
component={component} | |||
key={task.id} | |||
onCancelTask={onCancelTask} | |||
onFilterTask={onFilterTask} | |||
previousTask={index > 0 ? tasks[index - 1] : undefined} | |||
task={task} | |||
tasks={tasks} | |||
/> | |||
))} | |||
</tbody> | |||
</table> | |||
</div> | |||
); | |||
} | |||
} |
@@ -0,0 +1,71 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import Task from './Task'; | |||
import { Task as TaskType } from '../../../app/types'; | |||
import { translate } from '../../../helpers/l10n'; | |||
interface Props { | |||
tasks: TaskType[]; | |||
component?: unknown; | |||
loading: boolean; | |||
onCancelTask: (task: TaskType) => void; | |||
onFilterTask: (task: TaskType) => void; | |||
} | |||
export default function Tasks({ tasks, component, loading, onCancelTask, onFilterTask }: Props) { | |||
const className = classNames('data zebra zebra-hover background-tasks', { | |||
'new-loading': loading | |||
}); | |||
return ( | |||
<div className="boxed-group boxed-group-inner"> | |||
<table className={className}> | |||
<thead> | |||
<tr> | |||
<th>{translate('background_tasks.table.status')}</th> | |||
<th>{translate('background_tasks.table.task')}</th> | |||
<th>{translate('background_tasks.table.id')}</th> | |||
<th>{translate('background_tasks.table.submitter')}</th> | |||
<th> </th> | |||
<th className="text-right">{translate('background_tasks.table.submitted')}</th> | |||
<th className="text-right">{translate('background_tasks.table.started')}</th> | |||
<th className="text-right">{translate('background_tasks.table.finished')}</th> | |||
<th className="text-right">{translate('background_tasks.table.duration')}</th> | |||
<th> </th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{tasks.map((task, index, tasks) => ( | |||
<Task | |||
component={component} | |||
key={task.id} | |||
onCancelTask={onCancelTask} | |||
onFilterTask={onFilterTask} | |||
previousTask={index > 0 ? tasks[index - 1] : undefined} | |||
task={task} | |||
/> | |||
))} | |||
</tbody> | |||
</table> | |||
</div> | |||
); | |||
} |
@@ -1,3 +1,5 @@ | |||
import { Query } from './utils'; | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 SonarSource SA | |||
@@ -17,7 +19,6 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
/* @flow */ | |||
export const STATUSES = { | |||
ALL: '__ALL__', | |||
ALL_EXCEPT_PENDING: '__ALL_EXCEPT_PENDING__', | |||
@@ -41,7 +42,7 @@ export const DATE = { | |||
CUSTOM: 'CUSTOM' | |||
}; | |||
export const DEFAULT_FILTERS = { | |||
export const DEFAULT_FILTERS: Query = { | |||
status: STATUSES.ALL_EXCEPT_PENDING, | |||
taskType: ALL_TYPES, | |||
currents: CURRENTS.ALL, |
@@ -17,15 +17,25 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { STATUSES, ALL_TYPES, CURRENTS } from './constants'; | |||
import { STATUSES, CURRENTS, ALL_TYPES } from './constants'; | |||
import { toShortNotSoISOString } from '../../helpers/dates'; | |||
import { Task } from '../../app/types'; | |||
export function updateTask(tasks, newTask) { | |||
export interface Query { | |||
currents: string; | |||
maxExecutedAt?: Date; | |||
minSubmittedAt?: Date; | |||
query: string; | |||
status: string; | |||
taskType: string; | |||
} | |||
export function updateTask(tasks: Task[], newTask: Task) { | |||
return tasks.map(task => (task.id === newTask.id ? newTask : task)); | |||
} | |||
export function mapFiltersToParameters(filters /*: Object */ = {}) { | |||
const parameters = {}; | |||
export function mapFiltersToParameters(filters: Partial<Query> = {}) { | |||
const parameters: any = {}; | |||
if (filters.status === STATUSES.ALL) { | |||
parameters.status = [ | |||
@@ -66,10 +76,6 @@ export function mapFiltersToParameters(filters /*: Object */ = {}) { | |||
parameters.componentQuery = filters.query; | |||
} | |||
if (filters.lastPage !== 1) { | |||
parameters.p = filters.lastPage; | |||
} | |||
return parameters; | |||
} | |||
@@ -77,11 +83,11 @@ const ONE_SECOND = 1000; | |||
const ONE_MINUTE = 60 * ONE_SECOND; | |||
const ONE_HOUR = 60 * ONE_MINUTE; | |||
function format(int, suffix) { | |||
function format(int: number, suffix: string) { | |||
return `${int}${suffix}`; | |||
} | |||
export function formatDuration(value /*: ?number */) { | |||
export function formatDuration(value: number | undefined) { | |||
if (!value) { | |||
return ''; | |||
} |