aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeremy Davis <jeremy.davis@sonarsource.com>2023-12-01 17:47:29 +0100
committersonartech <sonartech@sonarsource.com>2023-12-04 20:03:22 +0000
commitcdda3dbcdd4e4b5f4a2e7471a37fa66877e26aad (patch)
tree9586bf172e5b2dc763fda6c5a90774790a98e95b
parente347b7fd97c898781bd3eefe606155c12d22abe9 (diff)
downloadsonarqube-cdda3dbcdd4e4b5f4a2e7471a37fa66877e26aad.tar.gz
sonarqube-cdda3dbcdd4e4b5f4a2e7471a37fa66877e26aad.zip
SONAR-21069 Background tasks adopt the new UI
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalContainer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx81
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css44
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx110
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.tsx20
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/NoWorkersSupportPopup.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/ScannerContext.tsx49
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Search.tsx130
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Stacktrace.tsx68
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx52
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingTime.tsx18
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/StatStillFailing.tsx30
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Stats.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Task.tsx28
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx36
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx108
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.tsx26
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.tsx39
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskId.tsx32
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskNodeName.tsx9
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.tsx78
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskSubmitter.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TaskType.tsx35
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.tsx78
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx12
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx59
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.tsx58
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties4
32 files changed, 646 insertions, 693 deletions
diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
index 7fbe8586840..da3dd405fb4 100644
--- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
+++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx
@@ -70,6 +70,8 @@ const TEMP_PAGELIST_WITH_NEW_BACKGROUND_WHITE = [
'/project_roles',
'/admin/permissions',
'/admin/permission_templates',
+ '/project/background_tasks',
+ '/admin/background_tasks',
];
export default function GlobalContainer() {
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx b/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx
index cdc0ed40273..8844b0974b9 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx
@@ -19,15 +19,18 @@
*/
import { screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import selectEvent from 'react-select-event';
import ComputeEngineServiceMock from '../../../api/mocks/ComputeEngineServiceMock';
import { parseDate } from '../../../helpers/dates';
import { mockAppState } from '../../../helpers/testMocks';
+import { RenderContext, renderAppWithAdminContext } from '../../../helpers/testReactTestingUtils';
import {
- RenderContext,
- dateInputEvent,
- renderAppWithAdminContext,
-} from '../../../helpers/testReactTestingUtils';
-import { byLabelText, byPlaceholderText, byRole, byText } from '../../../helpers/testSelector';
+ byLabelText,
+ byPlaceholderText,
+ byRole,
+ byTestId,
+ byText,
+} from '../../../helpers/testSelector';
import { EditionKey } from '../../../types/editions';
import { TaskStatuses, TaskTypes } from '../../../types/tasks';
import routes from '../routes';
@@ -47,7 +50,7 @@ describe('The Global background task page', () => {
renderGlobalBackgroundTasksApp();
await ui.appLoaded();
- expect(ui.numberOfWorkers(2).get()).toBeInTheDocument();
+ expect(ui.numberOfWorkers().get()).toHaveTextContent('2');
const editWorkersButton = screen.getByRole('button', {
name: 'background_tasks.change_number_of_workers',
@@ -72,7 +75,7 @@ describe('The Global background task page', () => {
await user.click(within(modal).getByRole('button', { name: 'save' }));
- expect(ui.numberOfWorkers(4).get()).toBeInTheDocument();
+ expect(ui.numberOfWorkers().get()).toHaveTextContent('4');
});
it('should display the list of tasks', async () => {
@@ -311,18 +314,22 @@ function getPageObject() {
const selectors = {
loading: byLabelText('loading'),
pageHeading: byRole('heading', { name: 'background_tasks.page' }),
- numberOfWorkers: (count: number) =>
- byText('background_tasks.number_of_workers').byText(`${count}`),
- onlyLatestAnalysis: byRole('checkbox', { name: 'yes' }),
+ numberOfWorkers: () => byLabelText(`background_tasks.number_of_workers`),
+ onlyLatestAnalysis: byRole('switch', {
+ name: 'background_tasks.currents_filter.ALL',
+ }),
search: byPlaceholderText('background_tasks.search_by_task_or_component'),
- fromDateInput: byRole('textbox', { name: 'start_date' }),
- toDateInput: byRole('textbox', { name: 'end_date' }),
+ fromDateInput: byLabelText('start_date'),
+ toDateInput: byLabelText('end_date'),
resetFilters: byRole('button', { name: 'reset_verb' }),
showMoreButton: byRole('button', { name: 'show_more' }),
reloadButton: byRole('button', { name: 'reload' }),
cancelAllButton: byRole('button', { description: 'background_tasks.cancel_all_tasks' }),
cancelAllButtonConfirm: byText('background_tasks.cancel_all_tasks.submit'),
row: byRole('row'),
+ startDateInput: byPlaceholderText('start_date'),
+ monthSelector: byTestId('month-select'),
+ yearSelector: byTestId('year-select'),
};
const ui = {
@@ -339,26 +346,64 @@ function getPageObject() {
},
async changeTaskFilter(fieldLabel: string, value: string) {
- await user.click(screen.getByLabelText(fieldLabel, { selector: 'input' }));
- await user.click(screen.getByText(value));
+ await selectEvent.select(screen.getByRole('combobox', { name: fieldLabel }), [value]);
+ expect(await screen.findByRole('button', { name: 'reload' })).toBeEnabled();
},
async setDateRange(from?: string, to?: string) {
- const dateInput = dateInputEvent(user);
if (from) {
- await dateInput.pickDate(ui.fromDateInput.get(), parseDate(from));
+ await this.selectDate(from, ui.fromDateInput.get());
}
if (to) {
- await dateInput.pickDate(ui.toDateInput.get(), parseDate(to));
+ await this.selectDate(to, ui.toDateInput.get());
}
+ expect(await screen.findByRole('button', { name: 'reload' })).toBeEnabled();
+ },
+
+ async selectDate(date: string, datePickerSelector: HTMLElement) {
+ const monthMap = [
+ 'Jan',
+ 'Feb',
+ 'Mar',
+ 'Apr',
+ 'May',
+ 'Jun',
+ 'Jul',
+ 'Aug',
+ 'Sep',
+ 'Oct',
+ 'Nov',
+ 'Dec',
+ ];
+ const parsedDate = parseDate(date);
+ await user.click(datePickerSelector);
+ const monthSelector = within(ui.monthSelector.get()).getByRole('combobox');
+
+ await user.click(monthSelector);
+ const selectedMonthElements = within(ui.monthSelector.get()).getAllByText(
+ monthMap[parseDate(parsedDate).getMonth()],
+ );
+ await user.click(selectedMonthElements[selectedMonthElements.length - 1]);
+
+ const yearSelector = within(ui.yearSelector.get()).getByRole('combobox');
+
+ await user.click(yearSelector);
+ const selectedYearElements = within(ui.yearSelector.get()).getAllByText(
+ parseDate(parsedDate).getFullYear(),
+ );
+ await user.click(selectedYearElements[selectedYearElements.length - 1]);
+
+ await user.click(
+ screen.getByText(parseDate(parsedDate).getDate().toString(), { selector: 'button' }),
+ );
},
async clickOnTaskAction(rowIndex: number, label: string) {
const row = ui.getAllRows()[rowIndex];
expect(row).toBeVisible();
await user.click(within(row).getByRole('button', { name: 'background_tasks.show_actions' }));
- await user.click(within(row).getByRole('button', { name: label }));
+ await user.click(within(row).getByRole('menuitem', { name: label }));
},
};
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css b/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css
deleted file mode 100644
index d88eee5073c..00000000000
--- a/server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.
- */
-.bt-search-form {
- display: flex;
- align-items: flex-end;
-}
-
-.bt-search-form > li + li {
- margin-left: 16px;
-}
-
-.bt-search-form-field {
- padding: 4px 0;
-}
-
-.bt-search-form-large {
- flex: 1;
-}
-
-.bt-workers-warning-icon {
- margin-top: 5px;
-}
-
-.emphasised-measure {
- font-size: var(--hugeFontSize);
- font-weight: 300;
-}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx
index 70b0e5291af..ad0705a64e9 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/BackgroundTasksApp.tsx
@@ -17,6 +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.
*/
+import { LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
import { debounce } from 'lodash';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
@@ -37,7 +38,6 @@ import { translate } from '../../../helpers/l10n';
import { parseAsDate } from '../../../helpers/query';
import { Task, TaskStatuses } from '../../../types/tasks';
import { Component, Paging, RawQuery } from '../../../types/types';
-import '../background-tasks.css';
import { CURRENTS, DEBOUNCE_DELAY, DEFAULT_FILTERS, PAGE_SIZE } from '../constants';
import { Query, mapFiltersToParameters, updateTask } from '../utils';
import Header from './Header';
@@ -192,7 +192,9 @@ export class BackgroundTasksApp extends React.PureComponent<Props, State> {
this.handleFilterUpdate({ query: task.componentKey });
};
- handleShowFailing = () => {
+ handleShowFailing = (e: React.SyntheticEvent<HTMLAnchorElement>) => {
+ e.preventDefault();
+
this.handleFilterUpdate({
...DEFAULT_FILTERS,
status: TaskStatuses.Failed,
@@ -211,7 +213,7 @@ export class BackgroundTasksApp extends React.PureComponent<Props, State> {
};
render() {
- const { component } = this.props;
+ const { component, location } = this.props;
const { loading, pagination, types, tasks } = this.state;
if (!types) {
@@ -223,58 +225,60 @@ export class BackgroundTasksApp extends React.PureComponent<Props, State> {
);
}
- const status = this.props.location.query.status || DEFAULT_FILTERS.status;
- const taskType = this.props.location.query.taskType || DEFAULT_FILTERS.taskType;
- const currents = this.props.location.query.currents || DEFAULT_FILTERS.currents;
- const minSubmittedAt = parseAsDate(this.props.location.query.minSubmittedAt);
- const maxExecutedAt = parseAsDate(this.props.location.query.maxExecutedAt);
- const query = this.props.location.query.query || '';
+ const status = location.query.status || DEFAULT_FILTERS.status;
+ const taskType = location.query.taskType || DEFAULT_FILTERS.taskType;
+ const currents = location.query.currents || DEFAULT_FILTERS.currents;
+ const minSubmittedAt = parseAsDate(location.query.minSubmittedAt);
+ const maxExecutedAt = parseAsDate(location.query.maxExecutedAt);
+ const query = location.query.query ?? '';
return (
- <main className="page page-limited">
- <Suggestions suggestions="background_tasks" />
- <Helmet defer={false} title={translate('background_tasks.page')} />
- <Header component={component} />
-
- <Stats
- component={component}
- failingCount={this.state.failingCount}
- onCancelAllPending={this.handleCancelAllPending}
- onShowFailing={this.handleShowFailing}
- pendingCount={this.state.pendingCount}
- pendingTime={this.state.pendingTime}
- />
-
- <Search
- component={component}
- currents={currents}
- loading={loading}
- maxExecutedAt={maxExecutedAt}
- minSubmittedAt={minSubmittedAt}
- onFilterUpdate={this.handleFilterUpdate}
- onReload={this.loadTasksDebounced}
- query={query}
- status={status}
- taskType={taskType}
- types={types}
- />
-
- <Tasks
- component={component}
- loading={loading}
- onCancelTask={this.handleCancelTask}
- onFilterTask={this.handleFilterTask}
- tasks={tasks}
- />
-
- <ListFooter
- count={tasks.length}
- loadMore={this.loadMoreTasks}
- loading={loading}
- pageSize={pagination.pageSize}
- total={pagination.total}
- />
- </main>
+ <LargeCenteredLayout id="background-tasks">
+ <PageContentFontWrapper className="sw-my-8 sw-body-sm">
+ <Suggestions suggestions="background_tasks" />
+ <Helmet defer={false} title={translate('background_tasks.page')} />
+ <Header component={component} />
+
+ <Stats
+ component={component}
+ failingCount={this.state.failingCount}
+ onCancelAllPending={this.handleCancelAllPending}
+ onShowFailing={this.handleShowFailing}
+ pendingCount={this.state.pendingCount}
+ pendingTime={this.state.pendingTime}
+ />
+
+ <Search
+ component={component}
+ currents={currents}
+ loading={loading}
+ maxExecutedAt={maxExecutedAt}
+ minSubmittedAt={minSubmittedAt}
+ onFilterUpdate={this.handleFilterUpdate}
+ onReload={this.loadTasksDebounced}
+ query={query}
+ status={status}
+ taskType={taskType}
+ types={types}
+ />
+
+ <Tasks
+ component={component}
+ onCancelTask={this.handleCancelTask}
+ onFilterTask={this.handleFilterTask}
+ tasks={tasks}
+ />
+
+ <ListFooter
+ count={tasks.length}
+ loadMore={this.loadMoreTasks}
+ loading={loading}
+ pageSize={pagination.pageSize}
+ total={pagination.total}
+ useMIUIButtons
+ />
+ </PageContentFontWrapper>
+ </LargeCenteredLayout>
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.tsx
index f4f5c9dd87b..816b86685f1 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/CurrentsFilter.tsx
@@ -17,19 +17,18 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Switch } from 'design-system';
import * as React from 'react';
-import Checkbox from '../../../components/controls/Checkbox';
import { translate } from '../../../helpers/l10n';
import { CURRENTS } from '../constants';
interface CurrentsFilterProps {
value?: string;
- id: string;
onChange: (value: string) => void;
}
-export default function CurrentsFilter(props: CurrentsFilterProps) {
- const { id, value, onChange } = props;
+export default function CurrentsFilter(props: Readonly<CurrentsFilterProps>) {
+ const { value, onChange } = props;
const checked = value === CURRENTS.ONLY_CURRENTS;
const handleChange = React.useCallback(
@@ -41,10 +40,13 @@ export default function CurrentsFilter(props: CurrentsFilterProps) {
);
return (
- <div className="bt-search-form-field">
- <Checkbox id={id} checked={checked} onCheck={handleChange}>
- <span className="little-spacer-left">{translate('yes')}</span>
- </Checkbox>
- </div>
+ <Switch
+ value={checked}
+ onChange={handleChange}
+ labels={{
+ on: translate('background_tasks.currents_filter.ONLY_CURRENTS'),
+ off: translate('background_tasks.currents_filter.ALL'),
+ }}
+ />
);
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.tsx
index b89b722d23d..ae6113d98be 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/DateFilter.tsx
@@ -17,8 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { DateRangePicker } from 'design-system';
import * as React from 'react';
-import DageRangeInput from '../../../components/controls/DateRangeInput';
+import { translate } from '../../../helpers/l10n';
interface Props {
maxExecutedAt: Date | undefined;
@@ -34,9 +35,14 @@ export default class DateFilter extends React.PureComponent<Props> {
render() {
const dateRange = { from: this.props.minSubmittedAt, to: this.props.maxExecutedAt };
return (
- <div className="nowrap">
- <DageRangeInput onChange={this.handleDateRangeChange} value={dateRange} />
- </div>
+ <DateRangePicker
+ clearButtonLabel={translate('clear')}
+ fromLabel={translate('start_date')}
+ toLabel={translate('end_date')}
+ onChange={this.handleDateRangeChange}
+ inputSize="small"
+ value={dateRange}
+ />
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx
index 0fc59090648..87ee9e72e16 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Header.tsx
@@ -17,30 +17,34 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Title } from 'design-system';
import * as React from 'react';
-import DocLink from '../../../components/common/DocLink';
+import DocumentationLink from '../../../components/common/DocumentationLink';
import { translate } from '../../../helpers/l10n';
+import { Component } from '../../../types/types';
import Workers from './Workers';
interface Props {
- component?: any;
+ component?: Component;
}
-export default function Header(props: Props) {
+export default function Header(props: Readonly<Props>) {
return (
- <header className="page-header">
- <h1 className="page-title">{translate('background_tasks.page')}</h1>
+ <header className="sw-mb-12 sw-flex sw-justify-between">
+ <div className="sw-flex-1">
+ <Title className="sw-mb-4">{translate('background_tasks.page')}</Title>
+ <p className="sw-max-w-3/4">
+ {translate('background_tasks.page.description')}
+ <DocumentationLink className="spacer-left" to="/analyzing-source-code/background-tasks/">
+ {translate('learn_more')}
+ </DocumentationLink>
+ </p>
+ </div>
{!props.component && (
- <div className="page-actions">
+ <div>
<Workers />
</div>
)}
- <p className="page-description">
- {translate('background_tasks.page.description')}
- <DocLink className="spacer-left" to="/analyzing-source-code/background-tasks/">
- {translate('learn_more')}
- </DocLink>
- </p>
</header>
);
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/NoWorkersSupportPopup.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/NoWorkersSupportPopup.tsx
index 08cd492f109..0806ceceb66 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/NoWorkersSupportPopup.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/NoWorkersSupportPopup.tsx
@@ -17,24 +17,19 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Link } from 'design-system';
import * as React from 'react';
-import Link from '../../../components/common/Link';
import { translate } from '../../../helpers/l10n';
export default function NoWorkersSupportPopup() {
return (
<>
- <p className="spacer-bottom">
+ <p className="sw-mb-2">
<strong>{translate('background_tasks.add_more_workers')}</strong>
</p>
- <p className="big-spacer-bottom markdown">
- {translate('background_tasks.add_more_workers.text')}
- </p>
+ <p className="sw-mb-4 markdown">{translate('background_tasks.add_more_workers.text')}</p>
<p>
- <Link
- to="https://www.sonarsource.com/plans-and-pricing/enterprise/?referrer=sonarqube-background-tasks"
- target="_blank"
- >
+ <Link to="https://www.sonarsource.com/plans-and-pricing/enterprise/?referrer=sonarqube-background-tasks">
{translate('learn_more')}
</Link>
</p>
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/ScannerContext.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/ScannerContext.tsx
index a63cd07032e..da971eac10b 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/ScannerContext.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/ScannerContext.tsx
@@ -17,10 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Modal, Spinner } from 'design-system';
+import { noop } from 'lodash';
import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
import { getTask } from '../../../api/ce';
-import Modal from '../../../components/controls/Modal';
-import { ResetButtonLink } from '../../../components/controls/buttons';
import { translate } from '../../../helpers/l10n';
import { Task } from '../../../types/tasks';
@@ -51,7 +52,7 @@ export default class ScannerContext extends React.PureComponent<Props, State> {
if (this.mounted) {
this.setState({ scannerContext: task.scannerContext });
}
- });
+ }, noop);
}
render() {
@@ -59,30 +60,26 @@ export default class ScannerContext extends React.PureComponent<Props, State> {
const { scannerContext } = this.state;
return (
- <Modal contentLabel="scanner context" onRequestClose={this.props.onClose} size="large">
- <div className="modal-head">
- <h2>
- {translate('background_tasks.scanner_context')}
- {': '}
- {task.componentName}
- {' ['}
- {translate('background_task.type', task.type)}
- {']'}
- </h2>
- </div>
-
- <div className="modal-body modal-container">
- {scannerContext != null ? (
+ <Modal
+ onClose={this.props.onClose}
+ isLarge
+ isScrollable
+ headerTitle={
+ <FormattedMessage
+ id="background_tasks.error_stacktrace.title"
+ values={{
+ project: task.componentName,
+ type: translate('background_task.type', task.type),
+ }}
+ />
+ }
+ body={
+ <Spinner loading={scannerContext == null}>
<pre className="js-task-scanner-context">{scannerContext}</pre>
- ) : (
- <i className="spinner" />
- )}
- </div>
-
- <div className="modal-foot">
- <ResetButtonLink onClick={this.props.onClose}>{translate('close')}</ResetButtonLink>
- </div>
- </Modal>
+ </Spinner>
+ }
+ secondaryButtonLabel={translate('close')}
+ />
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Search.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Search.tsx
index 0164436a3f6..358a48179b9 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/Search.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Search.tsx
@@ -17,9 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonSecondary, InputSearch } from 'design-system';
import * as React from 'react';
-import { Button } from '../../../components/controls/buttons';
-import SearchBox from '../../../components/controls/SearchBox';
import { translate } from '../../../helpers/l10n';
import { DEFAULT_FILTERS } from '../constants';
import { Query } from '../utils';
@@ -67,78 +66,61 @@ export default class Search extends React.PureComponent<Props> {
this.props.onFilterUpdate(DEFAULT_FILTERS);
};
- renderSearchBox() {
- const { component, query } = this.props;
-
- if (component) {
- // do not render search form on the project-level page
- return null;
- }
-
- return (
- <li className="bt-search-form-large">
- <SearchBox
- onChange={this.handleQueryChange}
- placeholder={translate('background_tasks.search_by_task_or_component')}
- value={query}
- />
- </li>
- );
- }
-
render() {
- const { loading, component, types, status, taskType, currents, minSubmittedAt, maxExecutedAt } =
- this.props;
+ const {
+ loading,
+ component,
+ query,
+ types,
+ status,
+ taskType,
+ currents,
+ minSubmittedAt,
+ maxExecutedAt,
+ } = this.props;
return (
- <section className="big-spacer-top big-spacer-bottom">
- <ul className="bt-search-form">
+ <section className="sw-my-4">
+ <ul className="sw-flex sw-items-center sw-flex-wrap sw-gap-4">
<li>
- <div className="display-flex-column">
- <label
- id="background-task-status-filter-label"
- className="text-bold little-spacer-bottom"
- htmlFor="status-filter"
- >
- {translate('status')}
- </label>
- <StatusFilter id="status-filter" onChange={this.handleStatusChange} value={status} />
- </div>
+ <label
+ id="background-task-status-filter-label"
+ className="sw-body-sm-highlight sw-mr-2"
+ htmlFor="status-filter"
+ >
+ {translate('status')}
+ </label>
+ <StatusFilter id="status-filter" onChange={this.handleStatusChange} value={status} />
</li>
{types.length > 1 && (
<li>
- <div className="display-flex-column">
- <label
- id="background-task-type-filter-label"
- className="text-bold little-spacer-bottom"
- htmlFor="types-filter"
- >
- {translate('type')}
- </label>
- <TypesFilter
- id="types-filter"
- onChange={this.handleTypeChange}
- types={types}
- value={taskType}
- />
- </div>
+ <label
+ id="background-task-type-filter-label"
+ className="sw-body-sm-highlight sw-mr-2"
+ htmlFor="types-filter"
+ >
+ {translate('type')}
+ </label>
+ <TypesFilter
+ id="types-filter"
+ onChange={this.handleTypeChange}
+ types={types}
+ value={taskType}
+ />
</li>
)}
{!component && (
- <li>
- <div className="display-flex-column">
- <label className="text-bold little-spacer-bottom" htmlFor="currents-filter">
- {translate('background_tasks.currents_filter.ONLY_CURRENTS')}
- </label>
- <CurrentsFilter
- id="currents-filter"
- onChange={this.handleCurrentsChange}
- value={currents}
- />
- </div>
+ <li className="sw-flex sw-items-center">
+ <label className="sw-body-sm-highlight sw-mr-2">
+ {translate('background_tasks.currents_filter.ONLY_CURRENTS')}
+ </label>
+ <CurrentsFilter onChange={this.handleCurrentsChange} value={currents} />
</li>
)}
- <li>
+ <li className="sw-flex sw-items-center">
+ <label className="sw-body-sm-highlight sw-mr-2">
+ {translate('background_tasks.date_filter')}
+ </label>
<DateFilter
maxExecutedAt={maxExecutedAt}
minSubmittedAt={minSubmittedAt}
@@ -146,15 +128,27 @@ export default class Search extends React.PureComponent<Props> {
/>
</li>
- {this.renderSearchBox()}
+ {!component && (
+ <li>
+ <InputSearch
+ onChange={this.handleQueryChange}
+ placeholder={translate('background_tasks.search_by_task_or_component')}
+ value={query}
+ />
+ </li>
+ )}
- <li className="nowrap">
- <Button className="js-reload" disabled={loading} onClick={this.props.onReload}>
+ <li>
+ <ButtonSecondary
+ className="js-reload sw-mr-2"
+ disabled={loading}
+ onClick={this.props.onReload}
+ >
{translate('reload')}
- </Button>{' '}
- <Button disabled={loading} onClick={this.handleReset}>
+ </ButtonSecondary>
+ <ButtonSecondary disabled={loading} onClick={this.handleReset}>
{translate('reset_verb')}
- </Button>
+ </ButtonSecondary>
</li>
</ul>
</section>
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Stacktrace.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Stacktrace.tsx
index 92938ac6c89..c0e8b6912e5 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/Stacktrace.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Stacktrace.tsx
@@ -17,10 +17,10 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Modal, Spinner } from 'design-system';
import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
import { getTask } from '../../../api/ce';
-import Modal from '../../../components/controls/Modal';
-import { ResetButtonLink } from '../../../components/controls/buttons';
import { translate } from '../../../helpers/l10n';
import { Task } from '../../../types/tasks';
@@ -67,40 +67,36 @@ export default class Stacktrace extends React.PureComponent<Props, State> {
const { loading, stacktrace } = this.state;
return (
- <Modal contentLabel="stacktrace" onRequestClose={this.props.onClose} size="large">
- <div className="modal-head">
- <h2>
- {translate('background_tasks.error_stacktrace')}
- {': '}
- {task.componentName}
- {' ['}
- {translate('background_task.type', task.type)}
- {']'}
- </h2>
- </div>
-
- <div className="modal-body modal-container">
- {loading ? (
- <i className="spinner" />
- ) : stacktrace ? (
- <div>
- <h4 className="spacer-bottom">{translate('background_tasks.error_stacktrace')}</h4>
- <pre className="js-task-stacktrace">{stacktrace}</pre>
- </div>
- ) : (
- <div>
- <h4 className="spacer-bottom">{translate('background_tasks.error_message')}</h4>
- <pre className="js-task-error-message">{task.errorMessage}</pre>
- </div>
- )}
- </div>
-
- <div className="modal-foot">
- <ResetButtonLink className="js-modal-close" onClick={this.props.onClose}>
- {translate('close')}
- </ResetButtonLink>
- </div>
- </Modal>
+ <Modal
+ onClose={this.props.onClose}
+ isLarge
+ isScrollable
+ headerTitle={
+ <FormattedMessage
+ id="background_tasks.error_stacktrace.title"
+ values={{
+ project: task.componentName,
+ type: translate('background_task.type', task.type),
+ }}
+ />
+ }
+ body={
+ <Spinner loading={loading}>
+ {stacktrace ? (
+ <div>
+ <h4 className="sw-mb-2">{translate('background_tasks.error_stacktrace')}</h4>
+ <pre className="js-task-stacktrace">{stacktrace}</pre>
+ </div>
+ ) : (
+ <div>
+ <h4 className="sw-mb-2">{translate('background_tasks.error_message')}</h4>
+ <pre className="js-task-error-message">{task.errorMessage}</pre>
+ </div>
+ )}
+ </Spinner>
+ }
+ secondaryButtonLabel={translate('close')}
+ />
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx
index 48be363a391..4abe690af8d 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingCount.tsx
@@ -17,12 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { DestructiveIcon, TrashIcon } from 'design-system';
import * as React from 'react';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
-import { colors } from '../../../app/theme';
import ConfirmButton from '../../../components/controls/ConfirmButton';
import Tooltip from '../../../components/controls/Tooltip';
-import { ClearButton } from '../../../components/controls/buttons';
import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/appstate';
@@ -32,34 +31,37 @@ export interface Props {
pendingCount?: number;
}
-function StatPendingCount({ appState, onCancelAllPending, pendingCount }: Props) {
+function StatPendingCount({ appState, onCancelAllPending, pendingCount }: Readonly<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')}
- {appState.canAdmin && pendingCount > 0 && (
- <ConfirmButton
- cancelButtonText={translate('close')}
- confirmButtonText={translate('background_tasks.cancel_all_tasks.submit')}
- isDestructive
- 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={colors.red} onClick={onClick} />
- </Tooltip>
- )}
- </ConfirmButton>
- )}
- </span>
- </span>
+ <div className="sw-flex sw-items-center">
+ <span className="sw-body-md-highlight sw-mr-1">{pendingCount}</span>
+ {translate('background_tasks.pending')}
+ {appState.canAdmin && pendingCount > 0 && (
+ <ConfirmButton
+ cancelButtonText={translate('close')}
+ confirmButtonText={translate('background_tasks.cancel_all_tasks.submit')}
+ isDestructive
+ 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')}>
+ <DestructiveIcon
+ aria-label={translate('background_tasks.cancel_all_tasks')}
+ className="sw-ml-1"
+ Icon={TrashIcon}
+ onClick={onClick}
+ />
+ </Tooltip>
+ )}
+ </ConfirmButton>
+ )}
+ </div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingTime.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingTime.tsx
index c8a7d86f0c0..21533525b97 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingTime.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/StatPendingTime.tsx
@@ -17,6 +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.
*/
+import { HelperHintIcon } from 'design-system';
import * as React from 'react';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { translate } from '../../../helpers/l10n';
@@ -26,23 +27,24 @@ import { formatMeasure } from '../../../helpers/measures';
const MIN_PENDING_TIME_THRESHOLD = 1000;
export interface Props {
- className?: string;
pendingCount?: number;
pendingTime?: number;
}
-export default function StatPendingTime({ className, pendingCount, pendingTime }: Props) {
+export default function StatPendingTime({ pendingCount, pendingTime }: Readonly<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>
+ <div className="sw-flex sw-items-center">
+ <span className="sw-body-md-highlight sw-mr-1">{formatMeasure(pendingTime, 'MILLISEC')}</span>
+ {translate('background_tasks.pending_time')}
<HelpTooltip
- className="little-spacer-left"
+ className="sw-ml-1"
overlay={translate('background_tasks.pending_time.description')}
- />
- </span>
+ >
+ <HelperHintIcon />
+ </HelpTooltip>
+ </div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/StatStillFailing.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/StatStillFailing.tsx
index 407050341ab..36369fbdddc 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/StatStillFailing.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/StatStillFailing.tsx
@@ -17,36 +17,38 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { HelperHintIcon, StandoutLink } from 'design-system';
import * as React from 'react';
-import { ButtonLink } from '../../../components/controls/buttons';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import { translate } from '../../../helpers/l10n';
export interface Props {
- className?: string;
failingCount?: number;
- onShowFailing: () => void;
+ onShowFailing: (e: React.SyntheticEvent<HTMLAnchorElement>) => void;
}
-export default function StatStillFailing({ className, failingCount, onShowFailing }: Props) {
+export default function StatStillFailing({ failingCount, onShowFailing }: Readonly<Props>) {
if (failingCount === undefined) {
return null;
}
return (
- <span className={className}>
+ <div className="sw-flex sw-items-center ">
{failingCount > 0 ? (
- <ButtonLink className="emphasised-measure text-baseline" onClick={onShowFailing}>
+ <StandoutLink
+ className="sw-body-md-highlight sw-align-baseline"
+ to="#"
+ onClick={onShowFailing}
+ >
{failingCount}
- </ButtonLink>
+ </StandoutLink>
) : (
- <span className="emphasised-measure">{failingCount}</span>
+ <span className="sw-body-md-highlight">{failingCount}</span>
)}
- <span className="little-spacer-left">{translate('background_tasks.failures')}</span>
- <HelpTooltip
- className="little-spacer-left"
- overlay={translate('background_tasks.failing_count')}
- />
- </span>
+ <span className="sw-ml-1">{translate('background_tasks.failures')}</span>
+ <HelpTooltip className="sw-ml-1" overlay={translate('background_tasks.failing_count')}>
+ <HelperHintIcon />
+ </HelpTooltip>
+ </div>
);
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Stats.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Stats.tsx
index b5cf7b32fc1..0f7a011d752 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/Stats.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Stats.tsx
@@ -27,28 +27,20 @@ export interface Props {
component?: Pick<Component, 'key'>;
failingCount?: number;
onCancelAllPending: () => void;
- onShowFailing: () => void;
+ onShowFailing: (e: React.SyntheticEvent<HTMLAnchorElement>) => void;
pendingCount?: number;
pendingTime?: number;
}
-export default function Stats({ component, pendingCount, pendingTime, ...props }: Props) {
+export default function Stats({ component, pendingCount, pendingTime, ...props }: Readonly<Props>) {
return (
- <section className="big-spacer-top big-spacer-bottom">
+ <section className="sw-flex sw-items-center sw-my-4 sw-gap-8 sw-body-md">
<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}
- />
+ <>
+ <StatPendingTime pendingCount={pendingCount} pendingTime={pendingTime} />
+ <StatStillFailing failingCount={props.failingCount} onShowFailing={props.onShowFailing} />
+ </>
)}
</section>
);
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx
index cf09c8f331f..5a7c6c9a24f 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/StatusFilter.tsx
@@ -17,8 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { InputSelect, LabelValueSelectOption } from 'design-system';
import * as React from 'react';
-import Select, { LabelValueSelectOption } from '../../../components/controls/Select';
import { translate } from '../../../helpers/l10n';
import { TaskStatuses } from '../../../types/tasks';
import { STATUSES } from '../constants';
@@ -29,10 +29,10 @@ interface StatusFilterProps {
onChange: (value?: string) => void;
}
-export default function StatusFilter(props: StatusFilterProps) {
+export default function StatusFilter(props: Readonly<StatusFilterProps>) {
const { id, value, onChange } = props;
- const options: LabelValueSelectOption[] = [
+ const options: LabelValueSelectOption<string>[] = [
{ value: STATUSES.ALL, label: translate('background_task.status.ALL') },
{
value: STATUSES.ALL_EXCEPT_PENDING,
@@ -46,21 +46,21 @@ export default function StatusFilter(props: StatusFilterProps) {
];
const handleChange = React.useCallback(
- ({ value }: LabelValueSelectOption) => {
+ ({ value }: LabelValueSelectOption<string>) => {
onChange(value);
},
[onChange],
);
return (
- <Select
+ <InputSelect
aria-labelledby="background-task-status-filter-label"
- className="input-medium"
+ className="sw-w-abs-200"
id={id}
onChange={handleChange}
options={options}
+ size="medium"
value={options.find((o) => o.value === value)}
- isSearchable={false}
/>
);
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Task.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Task.tsx
index 27c538d38bb..c6a2bdad84e 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/Task.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Task.tsx
@@ -17,16 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { TableRow } from 'design-system';
import * as React from 'react';
-import { AppState } from '../../../types/appstate';
+import { AppStateContext } from '../../../app/components/app-state/AppStateContext';
import { EditionKey } from '../../../types/editions';
import { Task as ITask } from '../../../types/tasks';
import TaskActions from './TaskActions';
import TaskComponent from './TaskComponent';
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';
@@ -36,25 +35,20 @@ interface Props {
onCancelTask: (task: ITask) => Promise<void>;
onFilterTask: (task: ITask) => void;
task: ITask;
- previousTask?: ITask;
- appState: AppState;
}
-export default function Task(props: Props) {
- const { task, component, onCancelTask, onFilterTask, previousTask, appState } = props;
+export default function Task(props: Readonly<Props>) {
+ const { task, component, onCancelTask, onFilterTask } = props;
+
+ const appState = React.useContext(AppStateContext);
+ const isDataCenter = appState.edition === EditionKey.datacenter;
return (
- <tr>
+ <TableRow>
<TaskStatus status={task.status} />
<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}
- />
- <TaskDate date={task.submittedAt} />
+ {isDataCenter && <TaskNodeName nodeName={task.nodeName} />}
+ <TaskSubmitter submittedAt={task.submittedAt} submitter={task.submitterLogin} />
<TaskDate baseDate={task.submittedAt} date={task.startedAt} />
<TaskDate baseDate={task.submittedAt} date={task.executedAt} />
<TaskExecutionTime ms={task.executionTimeMs} />
@@ -64,6 +58,6 @@ export default function Task(props: Props) {
onFilterTask={onFilterTask}
task={task}
/>
- </tr>
+ </TableRow>
);
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx
index 39b30634b87..ec6464f1df9 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskActions.tsx
@@ -17,8 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ActionCell, ActionsDropdown, ItemButton, ItemDangerButton } from 'design-system';
import * as React from 'react';
-import ActionsDropdown, { ActionsDropdownItem } from '../../../components/controls/ActionsDropdown';
import ConfirmModal from '../../../components/controls/ConfirmModal';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Task, TaskStatuses } from '../../../types/tasks';
@@ -103,51 +103,45 @@ export default class TaskActions extends React.PureComponent<Props, State> {
}
return (
- <td className="thin nowrap">
+ <ActionCell>
<ActionsDropdown
- label={translate('background_tasks.show_actions')}
+ id={`task-${task.id}-actions`}
+ ariaLabel={translate('background_tasks.show_actions')}
className="js-task-action"
>
{canFilter && task.componentName && (
- <ActionsDropdownItem className="js-task-filter" onClick={this.handleFilterClick}>
+ <ItemButton className="js-task-filter" onClick={this.handleFilterClick}>
{translateWithParameters(
'background_tasks.filter_by_component_x',
task.componentName,
)}
- </ActionsDropdownItem>
+ </ItemButton>
)}
{canCancel && (
- <ActionsDropdownItem
- className="js-task-cancel"
- destructive
- onClick={this.handleCancelClick}
- >
+ <ItemDangerButton className="js-task-cancel" onClick={this.handleCancelClick}>
{translate('background_tasks.cancel_task')}
- </ActionsDropdownItem>
+ </ItemDangerButton>
)}
{task.hasScannerContext && (
- <ActionsDropdownItem
+ <ItemButton
className="js-task-show-scanner-context"
onClick={this.handleShowScannerContextClick}
>
{translate('background_tasks.show_scanner_context')}
- </ActionsDropdownItem>
+ </ItemButton>
)}
{canShowStacktrace && (
- <ActionsDropdownItem
+ <ItemButton
className="js-task-show-stacktrace"
onClick={this.handleShowStacktraceClick}
>
{translate('background_tasks.show_stacktrace')}
- </ActionsDropdownItem>
+ </ItemButton>
)}
{canShowWarnings && (
- <ActionsDropdownItem
- className="js-task-show-warnings"
- onClick={this.handleShowWarningsClick}
- >
+ <ItemButton className="js-task-show-warnings" onClick={this.handleShowWarningsClick}>
{translate('background_tasks.show_warnings')}
- </ActionsDropdownItem>
+ </ItemButton>
)}
</ActionsDropdown>
@@ -177,7 +171,7 @@ export default class TaskActions extends React.PureComponent<Props, State> {
taskId={task.id}
/>
)}
- </td>
+ </ActionCell>
);
}
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx
index 6e248875d59..bdcf3d16be6 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskComponent.tsx
@@ -17,11 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import styled from '@emotion/styled';
+import {
+ BranchIcon,
+ ContentCell,
+ Note,
+ PullRequestIcon,
+ QualifierIcon,
+ StandoutLink,
+} from 'design-system';
import * as React from 'react';
-import Link from '../../../components/common/Link';
-import BranchIcon from '../../../components/icons/BranchIcon';
-import PullRequestIcon from '../../../components/icons/PullRequestIcon';
-import QualifierIcon from '../../../components/icons/QualifierIcon';
+import { translate } from '../../../helpers/l10n';
import {
getBranchUrl,
getPortfolioUrl,
@@ -30,57 +36,50 @@ import {
} from '../../../helpers/urls';
import { isPortfolioLike } from '../../../types/component';
import { Task } from '../../../types/tasks';
-import TaskType from './TaskType';
interface Props {
task: Task;
}
-export default function TaskComponent({ task }: Props) {
- if (!task.componentKey) {
- return (
- <td>
- <span className="note">{task.id}</span>
- <TaskType type={task.type} />
- </td>
- );
- }
-
+export default function TaskComponent({ task }: Readonly<Props>) {
return (
- <td>
- {task.branch !== undefined && <BranchIcon className="little-spacer-right" />}
- {task.pullRequest !== undefined && <PullRequestIcon className="little-spacer-right" />}
+ <ContentCell>
+ <div>
+ <p>
+ {task.componentKey && (
+ <span className="sw-mr-2">
+ <TaskComponentIndicator task={task} />
- {!task.branch && !task.pullRequest && task.componentQualifier && (
- <span className="little-spacer-right">
- <QualifierIcon qualifier={task.componentQualifier} />
- </span>
- )}
+ {task.componentName && (
+ <StandoutLink className="sw-ml-2" to={getTaskComponentUrl(task.componentKey, task)}>
+ <StyledSpan title={task.componentName}>{task.componentName}</StyledSpan>
- {task.componentName && (
- <Link className="spacer-right" to={getTaskComponentUrl(task.componentKey, task)}>
- <span className="text-limited text-text-top" title={task.componentName}>
- {task.componentName}
- </span>
+ {task.branch && (
+ <StyledSpan title={task.branch}>
+ <span className="sw-mx-1">/</span>
+ {task.branch}
+ </StyledSpan>
+ )}
- {task.branch && (
- <span className="text-limited text-text-top" title={task.branch}>
- <span style={{ marginLeft: 5, marginRight: 5 }}>/</span>
- {task.branch}
+ {task.pullRequest && (
+ <StyledSpan title={task.pullRequestTitle}>
+ <span className="sw-mx-1">/</span>
+ {task.pullRequest}
+ </StyledSpan>
+ )}
+ </StandoutLink>
+ )}
</span>
)}
- {task.pullRequest && (
- <span className="text-limited text-text-top" title={task.pullRequestTitle}>
- <span style={{ marginLeft: 5, marginRight: 5 }}>/</span>
- {task.pullRequest}
- </span>
- )}
- </Link>
- )}
+ <span>{translate('background_task.type', task.type)}</span>
+ </p>
- <TaskType type={task.type} />
- </td>
+ <Note as="div" className="sw-mt-2">
+ {translate('background_tasks.table.id')}: {task.id}
+ </Note>
+ </div>
+ </ContentCell>
);
}
@@ -94,3 +93,28 @@ function getTaskComponentUrl(componentKey: string, task: Task) {
}
return getProjectUrl(componentKey);
}
+
+const StyledSpan = styled.span`
+ display: inline-block;
+ max-width: 16vw;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ margin-bottom: -4px; /* compensate the inline-block effect on the wrapping link */
+`;
+
+function TaskComponentIndicator({ task }: Readonly<Props>) {
+ if (task.branch !== undefined) {
+ return <BranchIcon />;
+ }
+
+ if (task.pullRequest !== undefined) {
+ return <PullRequestIcon />;
+ }
+
+ if (task.componentQualifier) {
+ return <QualifierIcon qualifier={task.componentQualifier} />;
+ }
+
+ return null;
+}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.tsx
index 88ca7c10e58..f69a81a8468 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDate.tsx
@@ -17,7 +17,9 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import styled from '@emotion/styled';
import { differenceInDays } from 'date-fns';
+import { Note, NumericalCell, themeColor } from 'design-system';
import * as React from 'react';
import TimeFormatter from '../../../components/intl/TimeFormatter';
import { isValidDate, parseDate } from '../../../helpers/dates';
@@ -27,19 +29,29 @@ interface Props {
baseDate?: string;
}
-export default function TaskDate({ date, baseDate }: Props) {
- const parsedDate = date && parseDate(date);
- const parsedBaseDate = baseDate && parseDate(baseDate);
+export default function TaskDate({ date, baseDate }: Readonly<Props>) {
+ const parsedDate = date !== undefined && parseDate(date);
+ const parsedBaseDate = baseDate !== undefined && parseDate(baseDate);
const diff =
parsedDate && parsedBaseDate && isValidDate(parsedDate) && isValidDate(parsedBaseDate)
? differenceInDays(parsedDate, parsedBaseDate)
: 0;
return (
- <td className="thin nowrap text-right">
- {diff > 0 && <span className="text-warning little-spacer-right">{`(+${diff}d)`}</span>}
+ <NumericalCell className="sw-px-2">
+ {diff > 0 && <StyledWarningText className="sw-mr-1">{`(+${diff}d)`}</StyledWarningText>}
- {parsedDate && isValidDate(parsedDate) ? <TimeFormatter date={parsedDate} long /> : ''}
- </td>
+ {parsedDate && isValidDate(parsedDate) ? (
+ <span className="sw-whitespace-nowrap">
+ <TimeFormatter date={parsedDate} long />
+ </span>
+ ) : (
+ ''
+ )}
+ </NumericalCell>
);
}
+
+const StyledWarningText = styled(Note)`
+ color: ${themeColor('warningText')};
+`;
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.tsx
deleted file mode 100644
index 1d076a32b96..00000000000
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { isSameDay } from 'date-fns';
-import * as React from 'react';
-import DateFormatter from '../../../components/intl/DateFormatter';
-import { parseDate } from '../../../helpers/dates';
-
-interface Props {
- submittedAt: string;
- prevSubmittedAt?: string;
-}
-
-export default function TaskDay({ submittedAt, prevSubmittedAt }: Props) {
- const shouldDisplay =
- !prevSubmittedAt || !isSameDay(parseDate(submittedAt), parseDate(prevSubmittedAt));
-
- return (
- <td className="thin nowrap text-right small">
- {shouldDisplay ? <DateFormatter date={submittedAt} long /> : ''}
- </td>
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.tsx
index b7cddbf0e1b..b7a2733ebc2 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskExecutionTime.tsx
@@ -17,6 +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.
*/
+import { NumericalCell } from 'design-system/lib';
import * as React from 'react';
import { formatDuration } from '../utils';
@@ -24,6 +25,10 @@ interface Props {
ms?: number;
}
-export default function TaskExecutionTime({ ms }: Props) {
- return <td className="thin nowrap text-right">{ms && formatDuration(ms)}</td>;
+export default function TaskExecutionTime({ ms }: Readonly<Props>) {
+ return (
+ <NumericalCell className="sw-whitespace-nowrap">
+ {ms !== undefined && formatDuration(ms)}
+ </NumericalCell>
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskId.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskId.tsx
deleted file mode 100644
index b70dfd204ee..00000000000
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskId.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 {
- id: string;
-}
-
-export default function TaskId({ id }: Props) {
- return (
- <td className="thin nowrap">
- <div className="note">{id}</div>
- </td>
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskNodeName.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskNodeName.tsx
index a8fdfd7e2a7..3d616c93e36 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskNodeName.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskNodeName.tsx
@@ -17,16 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ContentCell, Note } from 'design-system';
import * as React from 'react';
interface Props {
nodeName?: string;
}
-export default function TaskNodeName({ nodeName }: Props) {
+export default function TaskNodeName({ nodeName }: Readonly<Props>) {
return (
- <td className="thin">
- <div className="note">{nodeName}</div>
- </td>
+ <ContentCell>
+ <Note>{nodeName}</Note>
+ </ContentCell>
);
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.tsx
index 3d2fe6a250f..5b2e6f3f489 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskStatus.tsx
@@ -17,8 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import {
+ ClockIcon,
+ ContentCell,
+ FlagErrorIcon,
+ FlagSuccessIcon,
+ FlagWarningIcon,
+ Spinner,
+} from 'design-system';
import * as React from 'react';
-import PendingIcon from '../../../components/icons/PendingIcon';
import { translate } from '../../../helpers/l10n';
import { TaskStatuses } from '../../../types/tasks';
@@ -26,32 +33,51 @@ interface Props {
status: string;
}
-export default function TaskStatus({ status }: Props) {
- let inner;
+interface StatusDataDictionnary {
+ [key: string]: StatusDataType;
+}
+
+interface StatusDataType {
+ iconComponent: React.ReactElement;
+ textKey: string;
+}
+
+const STATUS_ENUM: StatusDataDictionnary = {
+ [TaskStatuses.Pending]: {
+ iconComponent: <ClockIcon />,
+ textKey: 'background_task.status.PENDING',
+ },
+ [TaskStatuses.InProgress]: {
+ iconComponent: <Spinner />,
+ textKey: 'background_task.status.IN_PROGRESS',
+ },
+ [TaskStatuses.Success]: {
+ iconComponent: <FlagSuccessIcon />,
+ textKey: 'background_task.status.SUCCESS',
+ },
+ [TaskStatuses.Failed]: {
+ iconComponent: <FlagErrorIcon />,
+ textKey: 'background_task.status.FAILED',
+ },
+ [TaskStatuses.Canceled]: {
+ iconComponent: <FlagWarningIcon />,
+ textKey: 'background_task.status.CANCELED',
+ },
+};
+
+export default function TaskStatus({ status }: Readonly<Props>) {
+ const statusData = STATUS_ENUM[status];
- switch (status) {
- case TaskStatuses.Pending:
- inner = <PendingIcon />;
- break;
- case TaskStatuses.InProgress:
- inner = <i className="spinner" />;
- break;
- case TaskStatuses.Success:
- inner = (
- <span className="badge badge-success">{translate('background_task.status.SUCCESS')}</span>
- );
- break;
- case TaskStatuses.Failed:
- inner = (
- <span className="badge badge-error">{translate('background_task.status.FAILED')}</span>
- );
- break;
- case TaskStatuses.Canceled:
- inner = <span className="badge">{translate('background_task.status.CANCELED')}</span>;
- break;
- default:
- inner = '';
+ if (!statusData) {
+ return <ContentCell />;
}
- return <td className="thin spacer-right">{inner}</td>;
+ return (
+ <ContentCell>
+ <div className="sw-flex sw-gap-1 sw-items-center">
+ {statusData.iconComponent}
+ {translate(statusData.textKey)}
+ </div>
+ </ContentCell>
+ );
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskSubmitter.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskSubmitter.tsx
index 67c5c5f1b50..bfb5085fe4b 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskSubmitter.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskSubmitter.tsx
@@ -17,17 +17,42 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ContentCell, Note } from 'design-system';
import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
+import DateFormatter from '../../../components/intl/DateFormatter';
+import TimeFormatter from '../../../components/intl/TimeFormatter';
+import { isValidDate, parseDate } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
interface Props {
+ submittedAt: string;
submitter?: string;
}
-export default function TaskSubmitter({ submitter }: Props) {
+export default function TaskSubmitter(props: Readonly<Props>) {
+ const { submitter = translate('anonymous'), submittedAt } = props;
+
return (
- <td className="thin note">
- <span className="text-limited-small text-bottom">{submitter || translate('anonymous')}</span>
- </td>
+ <ContentCell>
+ <div>
+ <div className="sw-whitespace-nowrap">
+ {isValidDate(parseDate(submittedAt)) ? (
+ <FormattedMessage
+ id="background_tasks.date_and_time"
+ values={{
+ date: <DateFormatter date={submittedAt} long />,
+ time: <TimeFormatter date={submittedAt} long />,
+ }}
+ />
+ ) : (
+ <DateFormatter date={submittedAt} long />
+ )}
+ </div>
+ <Note>
+ <FormattedMessage id="background_tasks.submitted_by_x" values={{ submitter }} />
+ </Note>
+ </div>
+ </ContentCell>
);
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskType.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TaskType.tsx
deleted file mode 100644
index cf7a9763d89..00000000000
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TaskType.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { translate } from '../../../helpers/l10n';
-
-interface Props {
- type: string;
-}
-
-export default function TaskType({ type }: Props) {
- return (
- <span className="display-inline-block note">
- {'['}
- {translate('background_task.type', type)}
- {']'}
- </span>
- );
-}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.tsx
index 76114c6af1f..9099f90e0c1 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Tasks.tsx
@@ -17,11 +17,11 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
+import { ContentCell, NumericalCell, Table, TableRow } from 'design-system';
import * as React from 'react';
+import { AppStateContext } from '../../../app/components/app-state/AppStateContext';
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,52 +29,46 @@ import Task from './Task';
interface Props {
tasks: ITask[];
component?: unknown;
- loading: boolean;
onCancelTask: (task: ITask) => Promise<void>;
onFilterTask: (task: ITask) => void;
- appState: AppState;
}
-export function Tasks({ tasks, component, loading, onCancelTask, onFilterTask, appState }: Props) {
- const className = classNames('data zebra zebra-hover background-tasks', {
- 'new-loading': loading,
- });
+const COLUMN_WIDTHS = [0, 'auto', 'auto', 0, 0, 0, 0];
+const COLUMN_WIDTHS_WITH_NODES = [0, 'auto', 'auto', 0, 0, 0, 0, 0];
+
+export function Tasks({ tasks, component, onCancelTask, onFilterTask }: Readonly<Props>) {
+ const appState = React.useContext(AppStateContext);
+ const isDataCenter = appState.edition === EditionKey.datacenter;
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>
- {appState?.edition === EditionKey.datacenter && (
- <th>{translate('background_tasks.table.nodeName')}</th>
- )}
- <th>&nbsp;</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>&nbsp;</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}
- appState={appState}
- />
- ))}
- </tbody>
- </table>
- </div>
+ <Table
+ columnCount={isDataCenter ? COLUMN_WIDTHS_WITH_NODES.length : COLUMN_WIDTHS.length}
+ columnWidths={isDataCenter ? COLUMN_WIDTHS_WITH_NODES : COLUMN_WIDTHS}
+ header={
+ <TableRow>
+ <ContentCell>{translate('background_tasks.table.status')}</ContentCell>
+ <ContentCell>{translate('background_tasks.table.task')}</ContentCell>
+ {isDataCenter && (
+ <ContentCell>{translate('background_tasks.table.nodeName')}</ContentCell>
+ )}
+ <ContentCell>{translate('background_tasks.table.submitted')}</ContentCell>
+ <NumericalCell>{translate('background_tasks.table.started')}</NumericalCell>
+ <NumericalCell>{translate('background_tasks.table.finished')}</NumericalCell>
+ <NumericalCell>{translate('background_tasks.table.duration')}</NumericalCell>
+ <ContentCell />
+ </TableRow>
+ }
+ >
+ {tasks.map((task) => (
+ <Task
+ component={component}
+ key={task.id}
+ onCancelTask={onCancelTask}
+ onFilterTask={onFilterTask}
+ task={task}
+ />
+ ))}
+ </Table>
);
}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx
index c5fe7304c50..40ad55b7a5e 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/TypesFilter.tsx
@@ -17,8 +17,8 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { InputSelect, LabelValueSelectOption } from 'design-system';
import * as React from 'react';
-import Select, { LabelValueSelectOption } from '../../../components/controls/Select';
import { translate } from '../../../helpers/l10n';
import { ALL_TYPES } from '../constants';
@@ -30,7 +30,7 @@ interface Props {
}
export default class TypesFilter extends React.PureComponent<Props> {
- handleChange = ({ value }: LabelValueSelectOption) => {
+ handleChange = ({ value }: LabelValueSelectOption<string>) => {
this.props.onChange(value);
};
@@ -43,20 +43,20 @@ export default class TypesFilter extends React.PureComponent<Props> {
};
});
- const allOptions: LabelValueSelectOption[] = [
+ const allOptions: LabelValueSelectOption<string>[] = [
{ value: ALL_TYPES, label: translate('background_task.type.ALL') },
...options,
];
return (
- <Select
+ <InputSelect
aria-labelledby="background-task-type-filter-label"
- className="input-large"
+ className="sw-w-abs-200"
id={id}
isClearable={false}
+ size="medium"
onChange={this.handleChange}
options={allOptions}
- isSearchable={false}
value={allOptions.find((o) => o.value === value)}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx
index cbde95ea0d1..88ba5ff3e78 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/Workers.tsx
@@ -17,14 +17,17 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import {
+ FlagWarningIcon,
+ HelperHintIcon,
+ InteractiveIcon,
+ PencilIcon,
+ Spinner,
+} from 'design-system';
import * as React from 'react';
import { getWorkers } from '../../../api/ce';
-import { colors } from '../../../app/theme';
-import { EditButton } from '../../../components/controls/buttons';
import HelpTooltip from '../../../components/controls/HelpTooltip';
import Tooltip from '../../../components/controls/Tooltip';
-import AlertWarnIcon from '../../../components/icons/AlertWarnIcon';
-import PlusCircleIcon from '../../../components/icons/PlusCircleIcon';
import { translate } from '../../../helpers/l10n';
import NoWorkersSupportPopup from './NoWorkersSupportPopup';
import WorkersForm from './WorkersForm';
@@ -33,7 +36,6 @@ interface State {
canSetWorkerCount: boolean;
formOpen: boolean;
loading: boolean;
- noSupportPopup: boolean;
workerCount: number;
}
@@ -43,7 +45,6 @@ export default class Workers extends React.PureComponent<{}, State> {
canSetWorkerCount: false,
formOpen: false,
loading: true,
- noSupportPopup: false,
workerCount: 1,
};
@@ -85,57 +86,41 @@ export default class Workers extends React.PureComponent<{}, State> {
this.setState({ formOpen: true });
};
- handleHelpClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
- event.preventDefault();
- event.stopPropagation();
- this.toggleNoSupportPopup();
- };
-
- toggleNoSupportPopup = (show?: boolean) => {
- if (show !== undefined) {
- this.setState({ noSupportPopup: show });
- } else {
- this.setState((state) => ({ noSupportPopup: !state.noSupportPopup }));
- }
- };
-
render() {
const { canSetWorkerCount, formOpen, loading, workerCount } = this.state;
return (
- <div className="display-flex-center">
+ <div className="sw-flex sw-items-center">
{!loading && workerCount > 1 && (
<Tooltip overlay={translate('background_tasks.number_of_workers.warning')}>
- <span className="display-inline-flex-center little-spacer-right">
- <AlertWarnIcon fill="#d3d3d3" />
- </span>
+ <div className="sw-py-1/2 sw-mr-1">
+ <FlagWarningIcon />
+ </div>
</Tooltip>
)}
- <span className="text-middle">
- {translate('background_tasks.number_of_workers')}
+ <span id="ww">{translate('background_tasks.number_of_workers')}</span>
- {loading ? (
- <i className="spinner little-spacer-left" />
- ) : (
- <strong className="little-spacer-left">{workerCount}</strong>
- )}
- </span>
+ <Spinner className="sw-ml-1" loading={loading}>
+ <strong aria-labelledby="ww" className="sw-ml-1">
+ {workerCount}
+ </strong>
+ </Spinner>
{!loading && canSetWorkerCount && (
<Tooltip overlay={translate('background_tasks.change_number_of_workers')}>
- <EditButton
+ <InteractiveIcon
+ Icon={PencilIcon}
aria-label={translate('background_tasks.change_number_of_workers')}
- className="js-edit button-small spacer-left"
+ className="js-edit sw-ml-2"
onClick={this.handleChangeClick}
- title={translate('edit')}
/>
</Tooltip>
)}
{!loading && !canSetWorkerCount && (
- <HelpTooltip className="spacer-left" overlay={<NoWorkersSupportPopup />}>
- <PlusCircleIcon fill={colors.blue} size={12} />
+ <HelpTooltip className="sw-ml-2" overlay={<NoWorkersSupportPopup />}>
+ <HelperHintIcon />
</HelpTooltip>
)}
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.tsx b/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.tsx
index 601b7d4b3a7..2c57c02b947 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/components/WorkersForm.tsx
@@ -17,15 +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 { ButtonPrimary, FlagMessage, InputSelect, Modal } from 'design-system';
import * as React from 'react';
import { setWorkerCount } from '../../../api/ce';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import Modal from '../../../components/controls/Modal';
-import Select from '../../../components/controls/Select';
-import { Alert } from '../../../components/ui/Alert';
import { translate } from '../../../helpers/l10n';
const MAX_WORKERS = 10;
+const WORKERS_FORM_ID = 'workers-form';
interface Props {
onClose: (newWorkerCount?: number) => void;
@@ -60,7 +58,7 @@ export default class WorkersForm extends React.PureComponent<Props, State> {
this.props.onClose();
};
- handleWorkerCountChange = (option: { value: number }) =>
+ handleWorkerCountChange = (option: { label: string; value: number }) =>
this.setState({ newWorkerCount: option.value });
handleSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
@@ -82,6 +80,8 @@ export default class WorkersForm extends React.PureComponent<Props, State> {
};
render() {
+ const { newWorkerCount, submitting } = this.state;
+
const options = [];
for (let i = 1; i <= MAX_WORKERS; i++) {
options.push({ label: String(i), value: i });
@@ -89,37 +89,33 @@ export default class WorkersForm extends React.PureComponent<Props, State> {
return (
<Modal
- contentLabel={translate('background_tasks.change_number_of_workers')}
- onRequestClose={this.handleClose}
- >
- <header className="modal-head">
- <h2 id="background-task-workers-label">
- {translate('background_tasks.change_number_of_workers')}
- </h2>
- </header>
- <form onSubmit={this.handleSubmit}>
- <div className="modal-body">
- <Select
- aria-labelledby="background-task-workers-label"
- className="input-tiny spacer-top"
+ headerTitle={translate('background_tasks.change_number_of_workers')}
+ onClose={this.handleClose}
+ isOverflowVisible
+ body={
+ <form id={WORKERS_FORM_ID} onSubmit={this.handleSubmit}>
+ <InputSelect
+ aria-label={translate('background_tasks.change_number_of_workers')}
+ className="sw-mt-2"
isSearchable={false}
onChange={this.handleWorkerCountChange}
options={options}
- value={options.find((o) => o.value === this.state.newWorkerCount)}
+ size="medium"
+ value={options.find((o) => o.value === newWorkerCount)}
/>
- <Alert className="big-spacer-top" variant="info">
+ <FlagMessage className="sw-mt-4" variant="info">
{translate('background_tasks.change_number_of_workers.hint')}
- </Alert>
- </div>
- <footer className="modal-foot">
- <div>
- {this.state.submitting && <i className="spinner spacer-right" />}
- <SubmitButton disabled={this.state.submitting}>{translate('save')}</SubmitButton>
- <ResetButtonLink onClick={this.handleClose}>{translate('cancel')}</ResetButtonLink>
- </div>
- </footer>
- </form>
- </Modal>
+ </FlagMessage>
+ </form>
+ }
+ primaryButton={
+ <ButtonPrimary disabled={submitting} type="submit" form={WORKERS_FORM_ID}>
+ {translate('save')}
+ </ButtonPrimary>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ loading={submitting}
+ />
);
}
}
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index b3d5b50819f..9fb52c27090 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -3646,6 +3646,7 @@ background_tasks.page.description=This page allows monitoring of the queue of ta
background_tasks.currents_filter.ALL=All
background_tasks.currents_filter.ONLY_CURRENTS=Only Latest Analysis
+background_tasks.date_filter=Date
background_tasks.date_filter.ALL=Any Date
background_tasks.date_filter.TODAY=Today
background_tasks.date_filter.CUSTOM=Custom
@@ -3672,11 +3673,14 @@ background_tasks.show_actions=Show actions
background_tasks.show_stacktrace=Show Error Details
background_tasks.show_warnings=Show Warnings
background_tasks.error_message=Error Message
+background_tasks.error_stacktrace.title=Error Details: {project} [{type}]
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.date_and_time={date} - {time}
+background_tasks.submitted_by_x=By {submitter}
background_tasks.number_of_workers=Number of Workers:
background_tasks.number_of_workers.warning=Configuring additional workers without first vertically scaling your server could have negative performance impacts.