From cdda3dbcdd4e4b5f4a2e7471a37fa66877e26aad Mon Sep 17 00:00:00 2001 From: Jeremy Davis Date: Fri, 1 Dec 2023 17:47:29 +0100 Subject: [PATCH] SONAR-21069 Background tasks adopt the new UI --- .../js/app/components/GlobalContainer.tsx | 2 + .../__tests__/BackgroundTasks-it.tsx | 81 ++++++++--- .../background-tasks/background-tasks.css | 44 ------ .../components/BackgroundTasksApp.tsx | 110 ++++++++------- .../components/CurrentsFilter.tsx | 20 +-- .../components/DateFilter.tsx | 14 +- .../background-tasks/components/Header.tsx | 28 ++-- .../components/NoWorkersSupportPopup.tsx | 13 +- .../components/ScannerContext.tsx | 49 ++++--- .../background-tasks/components/Search.tsx | 130 +++++++++--------- .../components/Stacktrace.tsx | 68 +++++---- .../components/StatPendingCount.tsx | 52 +++---- .../components/StatPendingTime.tsx | 18 +-- .../components/StatStillFailing.tsx | 30 ++-- .../background-tasks/components/Stats.tsx | 22 +-- .../components/StatusFilter.tsx | 14 +- .../apps/background-tasks/components/Task.tsx | 28 ++-- .../components/TaskActions.tsx | 36 ++--- .../components/TaskComponent.tsx | 108 +++++++++------ .../background-tasks/components/TaskDate.tsx | 26 +++- .../background-tasks/components/TaskDay.tsx | 39 ------ .../components/TaskExecutionTime.tsx | 9 +- .../background-tasks/components/TaskId.tsx | 32 ----- .../components/TaskNodeName.tsx | 9 +- .../components/TaskStatus.tsx | 78 +++++++---- .../components/TaskSubmitter.tsx | 33 ++++- .../background-tasks/components/TaskType.tsx | 35 ----- .../background-tasks/components/Tasks.tsx | 78 +++++------ .../components/TypesFilter.tsx | 12 +- .../background-tasks/components/Workers.tsx | 59 +++----- .../components/WorkersForm.tsx | 58 ++++---- .../resources/org/sonar/l10n/core.properties | 4 + 32 files changed, 646 insertions(+), 693 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/background-tasks/background-tasks.css delete mode 100644 server/sonar-web/src/main/js/apps/background-tasks/components/TaskDay.tsx delete mode 100644 server/sonar-web/src/main/js/apps/background-tasks/components/TaskId.tsx delete mode 100644 server/sonar-web/src/main/js/apps/background-tasks/components/TaskType.tsx 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 { this.handleFilterUpdate({ query: task.componentKey }); }; - handleShowFailing = () => { + handleShowFailing = (e: React.SyntheticEvent) => { + e.preventDefault(); + this.handleFilterUpdate({ ...DEFAULT_FILTERS, status: TaskStatuses.Failed, @@ -211,7 +213,7 @@ export class BackgroundTasksApp extends React.PureComponent { }; 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 { ); } - 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 ( -
- - -
- - - - - - - - -
+ + + + +
+ + + + + + + + + + ); } } 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) { + 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 ( -
- - {translate('yes')} - -
+ ); } 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 { render() { const dateRange = { from: this.props.minSubmittedAt, to: this.props.maxExecutedAt }; return ( -
- -
+ ); } } 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) { return ( -
-

{translate('background_tasks.page')}

+
+
+ {translate('background_tasks.page')} +

+ {translate('background_tasks.page.description')} + + {translate('learn_more')} + +

+
{!props.component && ( -
+
)} -

- {translate('background_tasks.page.description')} - - {translate('learn_more')} - -

); } 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 ( <> -

+

{translate('background_tasks.add_more_workers')}

-

- {translate('background_tasks.add_more_workers.text')} -

+

{translate('background_tasks.add_more_workers.text')}

- + {translate('learn_more')}

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 { if (this.mounted) { this.setState({ scannerContext: task.scannerContext }); } - }); + }, noop); } render() { @@ -59,30 +60,26 @@ export default class ScannerContext extends React.PureComponent { const { scannerContext } = this.state; return ( - -
-

- {translate('background_tasks.scanner_context')} - {': '} - {task.componentName} - {' ['} - {translate('background_task.type', task.type)} - {']'} -

-
- -
- {scannerContext != null ? ( + + } + body={ +
{scannerContext}
- ) : ( - - )} -
- -
- {translate('close')} -
-
+ + } + 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 { 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 ( -
  • - -
  • - ); - } - 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 ( -
    -
      +
      +
      • -
        - - -
        + +
      • {types.length > 1 && (
      • -
        - - -
        + +
      • )} {!component && ( -
      • -
        - - -
        +
      • + +
      • )} -
      • +
      • + { />
      • - {this.renderSearchBox()} + {!component && ( +
      • + +
      • + )} -
      • - {' '} - +
      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 { const { loading, stacktrace } = this.state; return ( - -
      -

      - {translate('background_tasks.error_stacktrace')} - {': '} - {task.componentName} - {' ['} - {translate('background_task.type', task.type)} - {']'} -

      -
      - -
      - {loading ? ( - - ) : stacktrace ? ( -
      -

      {translate('background_tasks.error_stacktrace')}

      -
      {stacktrace}
      -
      - ) : ( -
      -

      {translate('background_tasks.error_message')}

      -
      {task.errorMessage}
      -
      - )} -
      - -
      - - {translate('close')} - -
      -
      + + } + body={ + + {stacktrace ? ( +
      +

      {translate('background_tasks.error_stacktrace')}

      +
      {stacktrace}
      +
      + ) : ( +
      +

      {translate('background_tasks.error_message')}

      +
      {task.errorMessage}
      +
      + )} +
      + } + 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) { if (pendingCount === undefined) { return null; } return ( - - {pendingCount} - - {translate('background_tasks.pending')} - {appState.canAdmin && pendingCount > 0 && ( - - {({ onClick }) => ( - - - - )} - - )} - - +
      + {pendingCount} + {translate('background_tasks.pending')} + {appState.canAdmin && pendingCount > 0 && ( + + {({ onClick }) => ( + + + + )} + + )} +
      ); } 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) { if (!pendingTime || !pendingCount || pendingTime < MIN_PENDING_TIME_THRESHOLD) { return null; } return ( - - {formatMeasure(pendingTime, 'MILLISEC')} - {translate('background_tasks.pending_time')} +
      + {formatMeasure(pendingTime, 'MILLISEC')} + {translate('background_tasks.pending_time')} - + > + + +
      ); } 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) => void; } -export default function StatStillFailing({ className, failingCount, onShowFailing }: Props) { +export default function StatStillFailing({ failingCount, onShowFailing }: Readonly) { if (failingCount === undefined) { return null; } return ( - +
      {failingCount > 0 ? ( - + {failingCount} - + ) : ( - {failingCount} + {failingCount} )} - {translate('background_tasks.failures')} - - + {translate('background_tasks.failures')} + + + +
      ); } 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; failingCount?: number; onCancelAllPending: () => void; - onShowFailing: () => void; + onShowFailing: (e: React.SyntheticEvent) => void; pendingCount?: number; pendingTime?: number; } -export default function Stats({ component, pendingCount, pendingTime, ...props }: Props) { +export default function Stats({ component, pendingCount, pendingTime, ...props }: Readonly) { return ( -
      +
      {!component && ( - - )} - {!component && ( - + <> + + + )}
      ); 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) { const { id, value, onChange } = props; - const options: LabelValueSelectOption[] = [ + const options: LabelValueSelectOption[] = [ { 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) => { onChange(value); }, [onChange], ); return ( - 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) => { - 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 ( -
      +
      {!loading && workerCount > 1 && ( - - - +
      + +
      )} - - {translate('background_tasks.number_of_workers')} + {translate('background_tasks.number_of_workers')} - {loading ? ( - - ) : ( - {workerCount} - )} - + + + {workerCount} + + {!loading && canSetWorkerCount && ( - )} {!loading && !canSetWorkerCount && ( - }> - + }> + )} 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 { this.props.onClose(); }; - handleWorkerCountChange = (option: { value: number }) => + handleWorkerCountChange = (option: { label: string; value: number }) => this.setState({ newWorkerCount: option.value }); handleSubmit = (event: React.SyntheticEvent) => { @@ -82,6 +80,8 @@ export default class WorkersForm extends React.PureComponent { }; 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 { return ( -
      -

      - {translate('background_tasks.change_number_of_workers')} -

      -
      -
      -
      -