@@ -22,7 +22,9 @@ import { screen, waitFor, within } from '@testing-library/react'; | |||
import userEvent from '@testing-library/user-event'; | |||
import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup'; | |||
import ComputeEngineServiceMock from '../../../api/mocks/ComputeEngineServiceMock'; | |||
import { renderAppWithAdminContext } from '../../../helpers/testReactTestingUtils'; | |||
import { mockAppState } from '../../../helpers/testMocks'; | |||
import { renderAppWithAdminContext, RenderContext } from '../../../helpers/testReactTestingUtils'; | |||
import { EditionKey } from '../../../types/editions'; | |||
import { TaskStatuses, TaskTypes } from '../../../types/tasks'; | |||
import routes from '../routes'; | |||
@@ -141,6 +143,36 @@ describe('The Global background task page', () => { | |||
expect(await screen.findAllByRole('row')).toHaveLength(2); | |||
}); | |||
it.each([[EditionKey.community], [EditionKey.developer], [EditionKey.enterprise]])( | |||
'Editions %s should not display node name', | |||
async (editionKey: EditionKey) => { | |||
givenOneTaskWithoutNodeNameAndOneWithNodeName(); | |||
renderGlobalBackgroundTasksApp({ appState: mockAppState({ edition: editionKey }) }); | |||
await waitFor(() => { | |||
expect(screen.getAllByRole('row')).toHaveLength(3); // including header | |||
}); | |||
expect(screen.queryByText('background_tasks.table.nodeName')).not.toBeInTheDocument(); | |||
expect(screen.queryByText('best_node_ever')).not.toBeInTheDocument(); | |||
} | |||
); | |||
it('Node name should be shown in DCE edition', async () => { | |||
givenOneTaskWithoutNodeNameAndOneWithNodeName(); | |||
renderGlobalBackgroundTasksApp({ appState: mockAppState({ edition: EditionKey.datacenter }) }); | |||
await waitFor(async () => { | |||
expect(await screen.findByText('background_tasks.table.nodeName')).toBeInTheDocument(); | |||
}); | |||
expect( | |||
within(await screen.getAllByRole('row')[1]).getByText('best_node_ever') | |||
).toBeInTheDocument(); | |||
}); | |||
it('should handle task pagination', async () => { | |||
const user = userEvent.setup(); | |||
@@ -167,11 +199,30 @@ describe('The Global background task page', () => { | |||
*/ | |||
}); | |||
function givenOneTaskWithoutNodeNameAndOneWithNodeName() { | |||
computeEngineServiceMock.clearTasks(); | |||
computeEngineServiceMock.addTask({ | |||
executedAt: '2022-02-03T11:45:36+0200', | |||
submittedAt: '2022-02-03T11:45:35+0200', | |||
executionTimeMs: 167, | |||
status: TaskStatuses.InProgress, | |||
type: TaskTypes.IssueSync, | |||
nodeName: 'best_node_ever', | |||
}); | |||
computeEngineServiceMock.addTask({ | |||
executedAt: '2022-02-03T11:45:35+0200', | |||
submittedAt: '2022-02-03T11:45:34+0200', | |||
executionTimeMs: 167, | |||
status: TaskStatuses.InProgress, | |||
type: TaskTypes.IssueSync, | |||
}); | |||
} | |||
async function changeTaskFilter(user: UserEvent, fieldLabel: string, value: string) { | |||
await user.click(screen.getByLabelText(fieldLabel, { selector: 'input' })); | |||
await user.click(screen.getByText(value)); | |||
} | |||
function renderGlobalBackgroundTasksApp() { | |||
renderAppWithAdminContext('admin/background_tasks', routes, {}); | |||
function renderGlobalBackgroundTasksApp(context: RenderContext = {}) { | |||
renderAppWithAdminContext('admin/background_tasks', routes, context); | |||
} |
@@ -18,6 +18,8 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { AppState } from '../../../types/appstate'; | |||
import { EditionKey } from '../../../types/editions'; | |||
import { Task as ITask } from '../../../types/tasks'; | |||
import TaskActions from './TaskActions'; | |||
import TaskComponent from './TaskComponent'; | |||
@@ -25,6 +27,7 @@ import TaskDate from './TaskDate'; | |||
import TaskDay from './TaskDay'; | |||
import TaskExecutionTime from './TaskExecutionTime'; | |||
import TaskId from './TaskId'; | |||
import TaskNodeName from './TaskNodeName'; | |||
import TaskStatus from './TaskStatus'; | |||
import TaskSubmitter from './TaskSubmitter'; | |||
@@ -34,10 +37,11 @@ interface Props { | |||
onFilterTask: (task: ITask) => void; | |||
task: ITask; | |||
previousTask?: ITask; | |||
appState: AppState; | |||
} | |||
export default function Task(props: Props) { | |||
const { task, component, onCancelTask, onFilterTask, previousTask } = props; | |||
const { task, component, onCancelTask, onFilterTask, previousTask, appState } = props; | |||
return ( | |||
<tr> | |||
@@ -45,6 +49,7 @@ export default function Task(props: Props) { | |||
<TaskComponent task={task} /> | |||
<TaskId id={task.id} /> | |||
<TaskSubmitter submitter={task.submitterLogin} /> | |||
{appState?.edition === EditionKey.datacenter && <TaskNodeName nodeName={task.nodeName} />} | |||
<TaskDay | |||
prevSubmittedAt={previousTask && previousTask.submittedAt} | |||
submittedAt={task.submittedAt} |
@@ -0,0 +1,32 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2022 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'; | |||
interface Props { | |||
nodeName?: string; | |||
} | |||
export default function TaskNodeName({ nodeName }: Props) { | |||
return ( | |||
<td className="thin"> | |||
<div className="note">{nodeName}</div> | |||
</td> | |||
); | |||
} |
@@ -19,7 +19,10 @@ | |||
*/ | |||
import classNames from 'classnames'; | |||
import * as React from 'react'; | |||
import withAppStateContext from '../../../app/components/app-state/withAppStateContext'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { AppState } from '../../../types/appstate'; | |||
import { EditionKey } from '../../../types/editions'; | |||
import { Task as ITask } from '../../../types/tasks'; | |||
import Task from './Task'; | |||
@@ -29,9 +32,10 @@ interface Props { | |||
loading: boolean; | |||
onCancelTask: (task: ITask) => Promise<void>; | |||
onFilterTask: (task: ITask) => void; | |||
appState: AppState; | |||
} | |||
export default function Tasks({ tasks, component, loading, onCancelTask, onFilterTask }: Props) { | |||
export function Tasks({ tasks, component, loading, onCancelTask, onFilterTask, appState }: Props) { | |||
const className = classNames('data zebra zebra-hover background-tasks', { | |||
'new-loading': loading, | |||
}); | |||
@@ -45,6 +49,9 @@ export default function Tasks({ tasks, component, loading, onCancelTask, onFilte | |||
<th>{translate('background_tasks.table.task')}</th> | |||
<th>{translate('background_tasks.table.id')}</th> | |||
<th>{translate('background_tasks.table.submitter')}</th> | |||
{appState?.edition === EditionKey.datacenter && ( | |||
<th>{translate('background_tasks.table.nodeName')}</th> | |||
)} | |||
<th> </th> | |||
<th className="text-right">{translate('background_tasks.table.submitted')}</th> | |||
<th className="text-right">{translate('background_tasks.table.started')}</th> | |||
@@ -62,6 +69,7 @@ export default function Tasks({ tasks, component, loading, onCancelTask, onFilte | |||
onFilterTask={onFilterTask} | |||
previousTask={index > 0 ? tasks[index - 1] : undefined} | |||
task={task} | |||
appState={appState} | |||
/> | |||
))} | |||
</tbody> | |||
@@ -69,3 +77,5 @@ export default function Tasks({ tasks, component, loading, onCancelTask, onFilte | |||
</div> | |||
); | |||
} | |||
export default withAppStateContext(Tasks); |
@@ -33,6 +33,7 @@ jest.mock('../../../../api/ce', () => ({ | |||
{ | |||
id: 'AWkGcOThOiAPiP5AE-kM', | |||
type: 'VIEW_REFRESH', | |||
nodeName: 'node_CE_server_1', | |||
componentId: 'AWBLZYhGOUrjxRA-u6ex', | |||
componentKey: 'sonar-csharp', | |||
componentName: 'SonarC#', |
@@ -116,7 +116,7 @@ exports[`should render correctly: loaded 1`] = ` | |||
} | |||
} | |||
/> | |||
<Tasks | |||
<withAppStateContext(Tasks) | |||
component={ | |||
{ | |||
"breadcrumbs": [], | |||
@@ -156,6 +156,7 @@ exports[`should render correctly: loaded 1`] = ` | |||
"hasScannerContext": false, | |||
"id": "AWkGcOThOiAPiP5AE-kM", | |||
"logs": false, | |||
"nodeName": "node_CE_server_1", | |||
"startedAt": "2019-02-19T16:47:36+0100", | |||
"status": "FAILED", | |||
"submittedAt": "2019-02-19T16:47:35+0100", |
@@ -39,6 +39,7 @@ export enum TaskStatuses { | |||
export interface Task { | |||
analysisId?: string; | |||
branch?: string; | |||
nodeName?: string; | |||
componentKey?: string; | |||
componentName?: string; | |||
componentQualifier?: string; |
@@ -3109,6 +3109,7 @@ background_tasks.table.submitter=Submitter | |||
background_tasks.table.started=Started | |||
background_tasks.table.finished=Finished | |||
background_tasks.table.duration=Duration | |||
background_tasks.table.nodeName=Node | |||
background_tasks.filter_by_component_x=Filter by Component "{0}" | |||
background_tasks.cancel_task=Cancel Task |