@@ -146,11 +146,6 @@ mark { | |||
font-weight: bold; | |||
} | |||
.emphasised-measure { | |||
font-size: 24px; | |||
font-weight: 300; | |||
} | |||
blockquote { | |||
border-left: 3px solid var(--barBorderColor); | |||
padding: 0 8px; |
@@ -19,14 +19,11 @@ | |||
*/ | |||
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 { click } from '../../../helpers/testUtils'; | |||
const stub = jest.fn(); | |||
describe('Constants', () => { | |||
it('should have STATUSES', () => { | |||
expect(Object.keys(STATUSES).length).toBe(7); | |||
@@ -79,98 +76,6 @@ describe('Search', () => { | |||
}); | |||
}); | |||
describe('Stats', () => { | |||
describe('Pending', () => { | |||
it('should show zero pending', () => { | |||
const result = shallow( | |||
<Stats onCancelAllPending={stub} onShowFailing={stub} pendingCount={0} /> | |||
); | |||
expect(result.find('.js-pending-count').text()).toContain('0'); | |||
}); | |||
it('should show 5 pending', () => { | |||
const result = shallow( | |||
<Stats onCancelAllPending={stub} onShowFailing={stub} pendingCount={5} /> | |||
); | |||
expect(result.find('.js-pending-count').text()).toContain('5'); | |||
}); | |||
it('should not show cancel pending button', () => { | |||
const result = shallow( | |||
<Stats onCancelAllPending={stub} onShowFailing={stub} pendingCount={0} /> | |||
); | |||
expect(result.find('[data-test="cancel-pending"]').length).toBe(0); | |||
}); | |||
it('should show cancel pending button', () => { | |||
const result = shallow( | |||
<Stats | |||
isSystemAdmin={true} | |||
onCancelAllPending={stub} | |||
onShowFailing={stub} | |||
pendingCount={5} | |||
/> | |||
); | |||
expect(result.find('[data-test="cancel-pending"]').length).toBe(1); | |||
}); | |||
it('should trigger cancelling pending', () => { | |||
const spy = jest.fn(); | |||
const result = shallow( | |||
<Stats | |||
isSystemAdmin={true} | |||
onCancelAllPending={spy} | |||
onShowFailing={stub} | |||
pendingCount={5} | |||
/> | |||
); | |||
expect(spy).not.toBeCalled(); | |||
result.find('[data-test="cancel-pending"]').prop<Function>('onConfirm')(); | |||
expect(spy).toBeCalled(); | |||
}); | |||
}); | |||
describe('Failures', () => { | |||
it('should show zero failures', () => { | |||
const result = shallow( | |||
<Stats failingCount={0} onCancelAllPending={stub} onShowFailing={stub} /> | |||
); | |||
expect(result.find('.js-failures-count').text()).toContain('0'); | |||
}); | |||
it('should show 5 failures', () => { | |||
const result = shallow( | |||
<Stats failingCount={5} onCancelAllPending={stub} onShowFailing={stub} /> | |||
); | |||
expect(result.find('.js-failures-count').text()).toContain('5'); | |||
}); | |||
it('should not show link to failures', () => { | |||
const result = shallow( | |||
<Stats failingCount={0} onCancelAllPending={stub} onShowFailing={stub} /> | |||
); | |||
expect(result.find('.js-failures-count').is('a')).toBeFalsy(); | |||
}); | |||
it('should show link to failures', () => { | |||
const result = shallow( | |||
<Stats failingCount={5} onCancelAllPending={stub} onShowFailing={stub} /> | |||
); | |||
expect(result.find('.js-failures-count').is('a')).toBeTruthy(); | |||
}); | |||
it('should trigger filtering failures', () => { | |||
const spy = jest.fn(); | |||
const result = shallow( | |||
<Stats failingCount={5} onCancelAllPending={stub} onShowFailing={spy} /> | |||
); | |||
expect(spy).not.toBeCalled(); | |||
click(result.find('.js-failures-count')); | |||
expect(spy).toBeCalled(); | |||
}); | |||
}); | |||
}); | |||
describe('Helpers', () => { | |||
describe('#formatDuration()', () => { | |||
it('should format 173ms', () => { |
@@ -41,3 +41,8 @@ | |||
.bt-workers-warning-icon { | |||
margin-top: 5px; | |||
} | |||
.emphasised-measure { | |||
font-size: var(--hugeFontSize); | |||
font-weight: 300; | |||
} |
@@ -21,15 +21,14 @@ 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'; | |||
import Stats from './Stats'; | |||
import Search from './Search'; | |||
import Tasks from './Tasks'; | |||
import { DEFAULT_FILTERS, DEBOUNCE_DELAY, STATUSES, CURRENTS } from '../constants'; | |||
import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; | |||
import { Location, Router } from '../../../components/hoc/withRouter'; | |||
import { | |||
getTypes, | |||
getActivity, | |||
@@ -45,34 +44,28 @@ import { toShortNotSoISOString } from '../../../helpers/dates'; | |||
import '../background-tasks.css'; | |||
interface Props { | |||
component?: { id: string }; | |||
component?: Pick<T.Component, 'key'> & { id: string }; // id should be removed when api/ce/activity accept a component key instead of an id | |||
fetchOrganizations: (keys: string[]) => void; | |||
location: Location; | |||
router: Pick<InjectedRouter, 'push'>; | |||
router: Pick<Router, 'push'>; | |||
} | |||
interface State { | |||
failingCount: number; | |||
loading: boolean; | |||
pendingCount: number; | |||
pendingTime?: number; | |||
tasks: T.Task[]; | |||
types?: string[]; | |||
query: string; | |||
pendingCount: number; | |||
failingCount: number; | |||
} | |||
class BackgroundTasksApp extends React.PureComponent<Props, State> { | |||
export 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.state = { failingCount: 0, loading: true, pendingCount: 0, tasks: [] }; | |||
this.loadTasksDebounced = debounce(this.loadTasks, DEBOUNCE_DELAY); | |||
} | |||
@@ -125,25 +118,23 @@ class BackgroundTasksApp extends React.PureComponent<Props, State> { | |||
parameters.componentId = this.props.component.id; | |||
} | |||
Promise.all([getActivity(parameters), getStatus(parameters.componentId)]).then(responses => { | |||
if (this.mounted) { | |||
const [activity, status] = responses; | |||
const { tasks } = activity; | |||
const pendingCount = status.pending; | |||
const failingCount = status.failing; | |||
const organizations = uniq(tasks.map(task => task.organization).filter(o => o)); | |||
this.props.fetchOrganizations(organizations); | |||
this.setState({ | |||
tasks, | |||
pendingCount, | |||
failingCount, | |||
loading: false | |||
}); | |||
} | |||
}, this.stopLoading); | |||
Promise.all([getActivity(parameters), getStatus(parameters.componentId)]).then( | |||
([{ tasks }, status]) => { | |||
if (this.mounted) { | |||
const organizations = uniq(tasks.map(task => task.organization).filter(o => o)); | |||
this.props.fetchOrganizations(organizations); | |||
this.setState({ | |||
failingCount: status.failing, | |||
loading: false, | |||
pendingCount: status.pending, | |||
pendingTime: status.pendingTime, | |||
tasks | |||
}); | |||
} | |||
}, | |||
this.stopLoading | |||
); | |||
}; | |||
handleFilterUpdate = (nextState: Partial<Query>) => { | |||
@@ -187,13 +178,13 @@ class BackgroundTasksApp extends React.PureComponent<Props, State> { | |||
this.handleFilterUpdate({ query: task.componentKey }); | |||
}; | |||
handleShowFailing() { | |||
handleShowFailing = () => { | |||
this.handleFilterUpdate({ | |||
...DEFAULT_FILTERS, | |||
status: STATUSES.FAILED, | |||
currents: CURRENTS.ONLY_CURRENTS | |||
}); | |||
} | |||
}; | |||
handleCancelAllPending = () => { | |||
this.setState({ loading: true }); | |||
@@ -207,7 +198,7 @@ class BackgroundTasksApp extends React.PureComponent<Props, State> { | |||
render() { | |||
const { component } = this.props; | |||
const { loading, types, tasks, pendingCount, failingCount } = this.state; | |||
const { loading, types, tasks } = this.state; | |||
if (!types) { | |||
return ( | |||
@@ -230,12 +221,13 @@ class BackgroundTasksApp extends React.PureComponent<Props, State> { | |||
<Helmet title={translate('background_tasks.page')} /> | |||
<Header component={component} /> | |||
<StatsContainer | |||
<Stats | |||
component={component} | |||
failingCount={failingCount} | |||
failingCount={this.state.failingCount} | |||
onCancelAllPending={this.handleCancelAllPending} | |||
onShowFailing={this.handleShowFailing} | |||
pendingCount={pendingCount} | |||
pendingCount={this.state.pendingCount} | |||
pendingTime={this.state.pendingTime} | |||
/> | |||
<Search |
@@ -0,0 +1,69 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 { connect } from 'react-redux'; | |||
import * as theme from '../../../app/theme'; | |||
import { ClearButton } from '../../../components/ui/buttons'; | |||
import ConfirmButton from '../../../components/controls/ConfirmButton'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { getAppState, Store } from '../../../store/rootReducer'; | |||
export interface Props { | |||
isSystemAdmin?: boolean; | |||
onCancelAllPending: () => void; | |||
pendingCount?: number; | |||
} | |||
export function StatPendingCount({ isSystemAdmin, onCancelAllPending, pendingCount }: Props) { | |||
if (pendingCount === undefined) { | |||
return null; | |||
} | |||
return ( | |||
<span> | |||
<span className="emphasised-measure">{pendingCount}</span> | |||
<span className="little-spacer-left display-inline-flex-center"> | |||
{translate('background_tasks.pending')} | |||
{isSystemAdmin && pendingCount > 0 && ( | |||
<ConfirmButton | |||
cancelButtonText={translate('close')} | |||
confirmButtonText={translate('background_tasks.cancel_all_tasks.submit')} | |||
isDestructive={true} | |||
modalBody={translate('background_tasks.cancel_all_tasks.text')} | |||
modalHeader={translate('background_tasks.cancel_all_tasks')} | |||
onConfirm={onCancelAllPending}> | |||
{({ onClick }) => ( | |||
<Tooltip overlay={translate('background_tasks.cancel_all_tasks')}> | |||
<ClearButton className="little-spacer-left" color={theme.red} onClick={onClick} /> | |||
</Tooltip> | |||
)} | |||
</ConfirmButton> | |||
)} | |||
</span> | |||
</span> | |||
); | |||
} | |||
const mapStateToProps = (state: Store) => ({ | |||
isSystemAdmin: getAppState(state).canAdmin | |||
}); | |||
export default connect(mapStateToProps)(StatPendingCount); |
@@ -0,0 +1,49 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 HelpTooltip from '../../../components/controls/HelpTooltip'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { formatMeasure } from '../../../helpers/measures'; | |||
// Do not display the pending time for values smaller than this threshold (in ms) | |||
const MIN_PENDING_TIME_THRESHOLD = 1000; | |||
export interface Props { | |||
className?: string; | |||
component?: Pick<T.Component, 'key'>; | |||
pendingCount?: number; | |||
pendingTime?: number; | |||
} | |||
export default function StatPendingTime({ className, pendingCount, pendingTime }: Props) { | |||
if (!pendingTime || !pendingCount || pendingTime < MIN_PENDING_TIME_THRESHOLD) { | |||
return null; | |||
} | |||
return ( | |||
<span className={className}> | |||
<span className="emphasised-measure">{formatMeasure(pendingTime, 'MILLISEC')}</span> | |||
<span className="little-spacer-left">{translate('background_tasks.pending_time')}</span> | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay={translate('background_tasks.pending_time.description')} | |||
/> | |||
</span> | |||
); | |||
} |
@@ -0,0 +1,52 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 { ButtonLink } from '../../../components/ui/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import HelpTooltip from '../../../components/controls/HelpTooltip'; | |||
export interface Props { | |||
className?: string; | |||
failingCount?: number; | |||
onShowFailing: () => void; | |||
} | |||
export default function StatStillFailing({ className, failingCount, onShowFailing }: Props) { | |||
if (failingCount === undefined) { | |||
return null; | |||
} | |||
return ( | |||
<span className={className}> | |||
{failingCount > 0 ? ( | |||
<ButtonLink className="emphasised-measure text-baseline" onClick={onShowFailing}> | |||
{failingCount} | |||
</ButtonLink> | |||
) : ( | |||
<span className="emphasised-measure">{failingCount}</span> | |||
)} | |||
<span className="little-spacer-left">{translate('background_tasks.failures')}</span> | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay={translate('background_tasks.failing_count')} | |||
/> | |||
</span> | |||
); | |||
} |
@@ -18,112 +18,37 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { DeleteButton } from '../../../components/ui/buttons'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import ConfirmButton from '../../../components/controls/ConfirmButton'; | |||
import StatPendingCount from './StatPendingCount'; | |||
import StatPendingTime from './StatPendingTime'; | |||
import StatStillFailing from './StatStillFailing'; | |||
interface Props { | |||
component?: unknown; | |||
export interface Props { | |||
component?: Pick<T.Component, 'key'>; | |||
failingCount?: number; | |||
isSystemAdmin?: boolean; | |||
pendingCount?: number; | |||
onShowFailing: () => void; | |||
onCancelAllPending: () => void; | |||
onShowFailing: () => void; | |||
pendingCount?: number; | |||
pendingTime?: number; | |||
} | |||
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 === undefined) { | |||
return null; | |||
} | |||
if (this.props.pendingCount > 0) { | |||
return ( | |||
<span> | |||
<span className="js-pending-count emphasised-measure">{this.props.pendingCount}</span> | |||
<span className="display-inline-flex-center little-spacer-left"> | |||
{translate('background_tasks.pending')} | |||
{this.props.isSystemAdmin && ( | |||
<ConfirmButton | |||
cancelButtonText={translate('close')} | |||
confirmButtonText={translate('background_tasks.cancel_all_tasks.submit')} | |||
data-test="cancel-pending" | |||
isDestructive={true} | |||
modalBody={translate('background_tasks.cancel_all_tasks.text')} | |||
modalHeader={translate('background_tasks.cancel_all_tasks')} | |||
onConfirm={this.props.onCancelAllPending}> | |||
{({ onClick }) => ( | |||
<Tooltip overlay={translate('background_tasks.cancel_all_tasks')}> | |||
<DeleteButton | |||
className="js-cancel-pending little-spacer-left" | |||
onClick={onClick} | |||
/> | |||
</Tooltip> | |||
)} | |||
</ConfirmButton> | |||
)} | |||
</span> | |||
</span> | |||
); | |||
} else { | |||
return ( | |||
<span> | |||
<span className="js-pending-count emphasised-measure">{this.props.pendingCount}</span> | |||
| |||
{translate('background_tasks.pending')} | |||
</span> | |||
); | |||
} | |||
} | |||
renderFailures() { | |||
if (this.props.failingCount === undefined) { | |||
return null; | |||
} | |||
if (this.props.component) { | |||
return null; | |||
} | |||
if (this.props.failingCount > 0) { | |||
return ( | |||
<span> | |||
<Tooltip overlay={translate('background_tasks.failing_count')}> | |||
<a | |||
className="js-failures-count emphasised-measure" | |||
href="#" | |||
onClick={this.handleShowFailing}> | |||
{this.props.failingCount} | |||
</a> | |||
</Tooltip> | |||
| |||
{translate('background_tasks.failures')} | |||
</span> | |||
); | |||
} else { | |||
return ( | |||
<span> | |||
<Tooltip overlay={translate('background_tasks.failing_count')}> | |||
<span className="js-failures-count emphasised-measure">{this.props.failingCount}</span> | |||
</Tooltip> | |||
<span className="little-spacer-left">{translate('background_tasks.failures')}</span> | |||
</span> | |||
); | |||
} | |||
} | |||
render() { | |||
return ( | |||
<section className="big-spacer-top big-spacer-bottom"> | |||
<span>{this.renderPending()}</span> | |||
<span className="huge-spacer-left">{this.renderFailures()}</span> | |||
</section> | |||
); | |||
} | |||
export default function Stats({ component, pendingCount, pendingTime, ...props }: Props) { | |||
return ( | |||
<section className="big-spacer-top big-spacer-bottom"> | |||
<StatPendingCount onCancelAllPending={props.onCancelAllPending} pendingCount={pendingCount} /> | |||
{!component && ( | |||
<StatPendingTime | |||
className="huge-spacer-left" | |||
pendingCount={pendingCount} | |||
pendingTime={pendingTime} | |||
/> | |||
)} | |||
{!component && ( | |||
<StatStillFailing | |||
className="huge-spacer-left" | |||
failingCount={props.failingCount} | |||
onShowFailing={props.onShowFailing} | |||
/> | |||
)} | |||
</section> | |||
); | |||
} |
@@ -0,0 +1,100 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme'; | |||
import { BackgroundTasksApp } from '../BackgroundTasksApp'; | |||
import { mockLocation, mockRouter } from '../../../../helpers/testMocks'; | |||
import { waitAndUpdate } from '../../../../helpers/testUtils'; | |||
jest.mock('../../../../api/ce', () => ({ | |||
getTypes: jest.fn().mockResolvedValue({ | |||
taskTypes: ['REPORT', 'PROJECT_EXPORT', 'PROJECT_IMPORT', 'VIEW_REFRESH'] | |||
}), | |||
getActivity: jest.fn().mockResolvedValue({ | |||
tasks: [ | |||
{ | |||
id: 'AWkGcOThOiAPiP5AE-kM', | |||
type: 'VIEW_REFRESH', | |||
componentId: 'AWBLZYhGOUrjxRA-u6ex', | |||
componentKey: 'sonar-csharp', | |||
componentName: 'SonarC#', | |||
componentQualifier: 'APP', | |||
status: 'FAILED', | |||
submittedAt: '2019-02-19T16:47:35+0100', | |||
startedAt: '2019-02-19T16:47:36+0100', | |||
executedAt: '2019-02-19T16:47:36+0100', | |||
executionTimeMs: 16, | |||
logs: false, | |||
errorMessage: | |||
'Analyses suspended. Please set a valid license for the Edition you installed.', | |||
hasScannerContext: false, | |||
organization: 'default-organization', | |||
errorType: 'LICENSING', | |||
warningCount: 0, | |||
warnings: [] | |||
}, | |||
{ | |||
id: 'AWkGcOThOiAPiP5AE-kL', | |||
type: 'VIEW_REFRESH', | |||
componentId: 'AV2ZaHs1Wa2znA6pDz1l', | |||
componentKey: 'c-cpp-build-wrapper', | |||
componentName: 'C/C++ Build Wrapper', | |||
componentQualifier: 'APP', | |||
status: 'SUCCESS', | |||
submittedAt: '2019-02-19T16:47:35+0100', | |||
startedAt: '2019-02-19T16:47:36+0100', | |||
executedAt: '2019-02-19T16:47:36+0100', | |||
executionTimeMs: 19, | |||
logs: false, | |||
hasScannerContext: false, | |||
organization: 'default-organization', | |||
warningCount: 0, | |||
warnings: [] | |||
} | |||
] | |||
}), | |||
getStatus: jest.fn().mockResolvedValue({ pending: 0, failing: 15, inProgress: 0 }), | |||
cancelAllTasks: jest.fn().mockResolvedValue({}), | |||
cancelTask: jest.fn().mockResolvedValue({}) | |||
})); | |||
beforeEach(() => { | |||
jest.clearAllMocks(); | |||
}); | |||
it('should render correctly', async () => { | |||
const wrapper = shallowRender(); | |||
expect(wrapper).toMatchSnapshot(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props: Partial<BackgroundTasksApp['props']> = {}) { | |||
return shallow( | |||
<BackgroundTasksApp | |||
component={{ key: 'foo', id: '564' }} | |||
fetchOrganizations={jest.fn()} | |||
location={mockLocation()} | |||
router={mockRouter()} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,62 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme'; | |||
import { StatPendingCount, Props } from '../StatPendingCount'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should not render', () => { | |||
expect(shallowRender({ pendingCount: undefined }).type()).toBeNull(); | |||
}); | |||
it('should not show cancel pending button', () => { | |||
expect( | |||
shallowRender({ pendingCount: 0 }) | |||
.find('ConfirmButton') | |||
.exists() | |||
).toBe(false); | |||
expect( | |||
shallowRender({ isSystemAdmin: false }) | |||
.find('ConfirmButton') | |||
.exists() | |||
).toBe(false); | |||
}); | |||
it('should trigger cancelling pending', () => { | |||
const onCancelAllPending = jest.fn(); | |||
const result = shallowRender({ onCancelAllPending }); | |||
expect(onCancelAllPending).not.toBeCalled(); | |||
result.find('ConfirmButton').prop<Function>('onConfirm')(); | |||
expect(onCancelAllPending).toBeCalled(); | |||
}); | |||
function shallowRender(props: Partial<Props> = {}) { | |||
return shallow( | |||
<StatPendingCount | |||
isSystemAdmin={true} | |||
onCancelAllPending={jest.fn()} | |||
pendingCount={5} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,49 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme'; | |||
import StatPendingTime, { Props } from '../StatPendingTime'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should not render', () => { | |||
expect(shallowRender({ pendingCount: undefined }).type()).toBeNull(); | |||
expect(shallowRender({ pendingCount: 0 }).type()).toBeNull(); | |||
expect(shallowRender({ pendingTime: undefined }).type()).toBeNull(); | |||
}); | |||
it('should not render when pending time is too small', () => { | |||
expect( | |||
shallowRender({ pendingTime: 0 }) | |||
.find('.emphasised-measure') | |||
.exists() | |||
).toBe(false); | |||
expect( | |||
shallowRender({ pendingTime: 900 }) | |||
.find('.emphasised-measure') | |||
.exists() | |||
).toBe(false); | |||
}); | |||
function shallowRender(props: Partial<Props> = {}) { | |||
return shallow(<StatPendingTime pendingCount={5} pendingTime={15420} {...props} />); | |||
} |
@@ -0,0 +1,47 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2019 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 { shallow } from 'enzyme'; | |||
import StatStillFailing, { Props } from '../StatStillFailing'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should not render', () => { | |||
expect(shallowRender({ failingCount: undefined }).type()).toBeNull(); | |||
}); | |||
it('should render without the filter link', () => { | |||
expect(shallowRender({ failingCount: 0 })).toMatchSnapshot(); | |||
}); | |||
it('should trigger filtering failures', () => { | |||
const onShowFailing = jest.fn(); | |||
const result = shallowRender({ onShowFailing }); | |||
expect(onShowFailing).not.toBeCalled(); | |||
click(result.find('ButtonLink')); | |||
expect(onShowFailing).toBeCalled(); | |||
}); | |||
function shallowRender(props: Partial<Props> = {}) { | |||
return shallow(<StatStillFailing failingCount={5} onShowFailing={jest.fn()} {...props} />); | |||
} |
@@ -17,12 +17,28 @@ | |||
* 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 './Stats'; | |||
import { getAppState, Store } from '../../../store/rootReducer'; | |||
import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import Stats, { Props } from '../Stats'; | |||
import { mockComponent } from '../../../../helpers/testMocks'; | |||
const mapStateToProps = (state: Store) => ({ | |||
isSystemAdmin: !!getAppState(state).canAdmin | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
export default connect(mapStateToProps)(Stats); | |||
it('should render correctly for a component', () => { | |||
expect(shallowRender({ component: mockComponent() })).toMatchSnapshot(); | |||
}); | |||
function shallowRender(props: Partial<Props> = {}) { | |||
return shallow( | |||
<Stats | |||
failingCount={4} | |||
onCancelAllPending={jest.fn()} | |||
onShowFailing={jest.fn()} | |||
pendingCount={2} | |||
pendingTime={110545} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -0,0 +1,168 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="page page-limited" | |||
> | |||
<i | |||
className="spinner" | |||
/> | |||
</div> | |||
`; | |||
exports[`should render correctly 2`] = ` | |||
<div | |||
className="page page-limited" | |||
> | |||
<Suggestions | |||
suggestions="background_tasks" | |||
/> | |||
<HelmetWrapper | |||
defer={true} | |||
encodeSpecialCharacters={true} | |||
title="background_tasks.page" | |||
/> | |||
<Header | |||
component={ | |||
Object { | |||
"id": "564", | |||
"key": "foo", | |||
} | |||
} | |||
/> | |||
<Stats | |||
component={ | |||
Object { | |||
"id": "564", | |||
"key": "foo", | |||
} | |||
} | |||
failingCount={15} | |||
onCancelAllPending={[Function]} | |||
onShowFailing={[Function]} | |||
pendingCount={0} | |||
/> | |||
<Search | |||
component={ | |||
Object { | |||
"id": "564", | |||
"key": "foo", | |||
} | |||
} | |||
currents="__ALL__" | |||
loading={false} | |||
onFilterUpdate={[Function]} | |||
onReload={[Function]} | |||
query="" | |||
status="__ALL_EXCEPT_PENDING__" | |||
taskType="ALL_TYPES" | |||
types={ | |||
Object { | |||
"taskTypes": Array [ | |||
"REPORT", | |||
"PROJECT_EXPORT", | |||
"PROJECT_IMPORT", | |||
"VIEW_REFRESH", | |||
], | |||
} | |||
} | |||
/> | |||
<Tasks | |||
component={ | |||
Object { | |||
"id": "564", | |||
"key": "foo", | |||
} | |||
} | |||
loading={false} | |||
onCancelTask={[Function]} | |||
onFilterTask={[Function]} | |||
tasks={ | |||
Array [ | |||
Object { | |||
"componentId": "AWBLZYhGOUrjxRA-u6ex", | |||
"componentKey": "sonar-csharp", | |||
"componentName": "SonarC#", | |||
"componentQualifier": "APP", | |||
"errorMessage": "Analyses suspended. Please set a valid license for the Edition you installed.", | |||
"errorType": "LICENSING", | |||
"executedAt": "2019-02-19T16:47:36+0100", | |||
"executionTimeMs": 16, | |||
"hasScannerContext": false, | |||
"id": "AWkGcOThOiAPiP5AE-kM", | |||
"logs": false, | |||
"organization": "default-organization", | |||
"startedAt": "2019-02-19T16:47:36+0100", | |||
"status": "FAILED", | |||
"submittedAt": "2019-02-19T16:47:35+0100", | |||
"type": "VIEW_REFRESH", | |||
"warningCount": 0, | |||
"warnings": Array [], | |||
}, | |||
Object { | |||
"componentId": "AV2ZaHs1Wa2znA6pDz1l", | |||
"componentKey": "c-cpp-build-wrapper", | |||
"componentName": "C/C++ Build Wrapper", | |||
"componentQualifier": "APP", | |||
"executedAt": "2019-02-19T16:47:36+0100", | |||
"executionTimeMs": 19, | |||
"hasScannerContext": false, | |||
"id": "AWkGcOThOiAPiP5AE-kL", | |||
"logs": false, | |||
"organization": "default-organization", | |||
"startedAt": "2019-02-19T16:47:36+0100", | |||
"status": "SUCCESS", | |||
"submittedAt": "2019-02-19T16:47:35+0100", | |||
"type": "VIEW_REFRESH", | |||
"warningCount": 0, | |||
"warnings": Array [], | |||
}, | |||
] | |||
} | |||
/> | |||
<Footer | |||
tasks={ | |||
Array [ | |||
Object { | |||
"componentId": "AWBLZYhGOUrjxRA-u6ex", | |||
"componentKey": "sonar-csharp", | |||
"componentName": "SonarC#", | |||
"componentQualifier": "APP", | |||
"errorMessage": "Analyses suspended. Please set a valid license for the Edition you installed.", | |||
"errorType": "LICENSING", | |||
"executedAt": "2019-02-19T16:47:36+0100", | |||
"executionTimeMs": 16, | |||
"hasScannerContext": false, | |||
"id": "AWkGcOThOiAPiP5AE-kM", | |||
"logs": false, | |||
"organization": "default-organization", | |||
"startedAt": "2019-02-19T16:47:36+0100", | |||
"status": "FAILED", | |||
"submittedAt": "2019-02-19T16:47:35+0100", | |||
"type": "VIEW_REFRESH", | |||
"warningCount": 0, | |||
"warnings": Array [], | |||
}, | |||
Object { | |||
"componentId": "AV2ZaHs1Wa2znA6pDz1l", | |||
"componentKey": "c-cpp-build-wrapper", | |||
"componentName": "C/C++ Build Wrapper", | |||
"componentQualifier": "APP", | |||
"executedAt": "2019-02-19T16:47:36+0100", | |||
"executionTimeMs": 19, | |||
"hasScannerContext": false, | |||
"id": "AWkGcOThOiAPiP5AE-kL", | |||
"logs": false, | |||
"organization": "default-organization", | |||
"startedAt": "2019-02-19T16:47:36+0100", | |||
"status": "SUCCESS", | |||
"submittedAt": "2019-02-19T16:47:35+0100", | |||
"type": "VIEW_REFRESH", | |||
"warningCount": 0, | |||
"warnings": Array [], | |||
}, | |||
] | |||
} | |||
/> | |||
</div> | |||
`; |
@@ -0,0 +1,26 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<span> | |||
<span | |||
className="emphasised-measure" | |||
> | |||
5 | |||
</span> | |||
<span | |||
className="little-spacer-left display-inline-flex-center" | |||
> | |||
background_tasks.pending | |||
<ConfirmButton | |||
cancelButtonText="close" | |||
confirmButtonText="background_tasks.cancel_all_tasks.submit" | |||
isDestructive={true} | |||
modalBody="background_tasks.cancel_all_tasks.text" | |||
modalHeader="background_tasks.cancel_all_tasks" | |||
onConfirm={[MockFunction]} | |||
> | |||
<Component /> | |||
</ConfirmButton> | |||
</span> | |||
</span> | |||
`; |
@@ -0,0 +1,20 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<span> | |||
<span | |||
className="emphasised-measure" | |||
> | |||
15s | |||
</span> | |||
<span | |||
className="little-spacer-left" | |||
> | |||
background_tasks.pending_time | |||
</span> | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay="background_tasks.pending_time.description" | |||
/> | |||
</span> | |||
`; |
@@ -0,0 +1,40 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<span> | |||
<ButtonLink | |||
className="emphasised-measure text-baseline" | |||
onClick={[MockFunction]} | |||
> | |||
5 | |||
</ButtonLink> | |||
<span | |||
className="little-spacer-left" | |||
> | |||
background_tasks.failures | |||
</span> | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay="background_tasks.failing_count" | |||
/> | |||
</span> | |||
`; | |||
exports[`should render without the filter link 1`] = ` | |||
<span> | |||
<span | |||
className="emphasised-measure" | |||
> | |||
0 | |||
</span> | |||
<span | |||
className="little-spacer-left" | |||
> | |||
background_tasks.failures | |||
</span> | |||
<HelpTooltip | |||
className="little-spacer-left" | |||
overlay="background_tasks.failing_count" | |||
/> | |||
</span> | |||
`; |
@@ -0,0 +1,33 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<section | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
<Connect(StatPendingCount) | |||
onCancelAllPending={[MockFunction]} | |||
pendingCount={2} | |||
/> | |||
<StatPendingTime | |||
className="huge-spacer-left" | |||
pendingCount={2} | |||
pendingTime={110545} | |||
/> | |||
<StatStillFailing | |||
className="huge-spacer-left" | |||
failingCount={4} | |||
onShowFailing={[MockFunction]} | |||
/> | |||
</section> | |||
`; | |||
exports[`should render correctly for a component 1`] = ` | |||
<section | |||
className="big-spacer-top big-spacer-bottom" | |||
> | |||
<Connect(StatPendingCount) | |||
onCancelAllPending={[MockFunction]} | |||
pendingCount={2} | |||
/> | |||
</section> | |||
`; |
@@ -2356,6 +2356,8 @@ background_tasks.show_warnings=Show Warnings | |||
background_tasks.error_message=Error Message | |||
background_tasks.error_stacktrace=Error Details | |||
background_tasks.pending=pending | |||
background_tasks.pending_time=pending time | |||
background_tasks.pending_time.description=Pending time of the oldest background task waiting to be processed. | |||
background_tasks.failures=still failing | |||
background_tasks.number_of_workers=Number of Workers: |