From d66ad5b0638a6705ffb82aecd8782ca2b1bdce9d Mon Sep 17 00:00:00 2001 From: Viktor Vorona Date: Thu, 18 Jan 2024 12:48:30 +0100 Subject: [PATCH] SONAR-20223 Apply new spinner for pages already migrated to MIUI --- .../components/BackgroundTasksApp.tsx | 3 +- .../main/js/apps/code/components/Search.tsx | 3 +- .../js/apps/issues/components/IssuesApp.tsx | 2 +- .../CrossComponentSourceViewer.tsx | 2 +- .../components/Header.tsx | 9 +- .../notifications/ProjectNotifications.tsx | 5 +- .../apps/projects/components/AllProjects.tsx | 2 +- .../components/HotspotOpenInIdeButton.tsx | 2 +- .../components/HotspotSidebarHeader.tsx | 2 +- .../HotspotSnippetContainerRenderer.tsx | 3 +- .../components/HotspotViewerRenderer.tsx | 2 +- .../settings/components/NewCodeDefinition.tsx | 2 +- .../js/apps/users/components/TokensForm.tsx | 12 +- .../apps/users/components/TokensFormItem.tsx | 2 +- .../src/main/js/components/facet/FacetBox.tsx | 37 -- .../main/js/components/facet/FacetHeader.tsx | 137 ------ .../main/js/components/facet/FacetItem.tsx | 78 --- .../js/components/facet/FacetItemsList.tsx | 41 -- .../js/components/facet/ListStyleFacet.tsx | 448 ------------------ .../components/facet/ListStyleFacetFooter.tsx | 77 --- .../facet/MultipleSelectionHint.css | 35 -- .../facet/MultipleSelectionHint.tsx | 52 -- .../components/facet/__tests__/Facet-it.tsx | 126 ----- 23 files changed, 20 insertions(+), 1062 deletions(-) delete mode 100644 server/sonar-web/src/main/js/components/facet/FacetBox.tsx delete mode 100644 server/sonar-web/src/main/js/components/facet/FacetHeader.tsx delete mode 100644 server/sonar-web/src/main/js/components/facet/FacetItem.tsx delete mode 100644 server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx delete mode 100644 server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx delete mode 100644 server/sonar-web/src/main/js/components/facet/ListStyleFacetFooter.tsx delete mode 100644 server/sonar-web/src/main/js/components/facet/MultipleSelectionHint.css delete mode 100644 server/sonar-web/src/main/js/components/facet/MultipleSelectionHint.tsx delete mode 100644 server/sonar-web/src/main/js/components/facet/__tests__/Facet-it.tsx 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 6bc89531a2f..a340aae00fa 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,7 +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 { LargeCenteredLayout, PageContentFontWrapper, Spinner } from 'design-system'; import { debounce } from 'lodash'; import * as React from 'react'; import { Helmet } from 'react-helmet-async'; @@ -32,7 +32,6 @@ import withComponentContext from '../../../app/components/componentContext/withC import ListFooter from '../../../components/controls/ListFooter'; import Suggestions from '../../../components/embed-docs-modal/Suggestions'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; -import Spinner from '../../../components/ui/Spinner'; import { toShortISO8601String } from '../../../helpers/dates'; import { translate } from '../../../helpers/l10n'; import { parseAsDate } from '../../../helpers/query'; diff --git a/server/sonar-web/src/main/js/apps/code/components/Search.tsx b/server/sonar-web/src/main/js/apps/code/components/Search.tsx index 4fddfa8abd5..242e8bf0a80 100644 --- a/server/sonar-web/src/main/js/apps/code/components/Search.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/Search.tsx @@ -18,12 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import classNames from 'classnames'; -import { InputSearch, ToggleButton } from 'design-system'; +import { InputSearch, Spinner, ToggleButton } from 'design-system'; import { isEmpty, omit } from 'lodash'; import * as React from 'react'; import { getTree } from '../../../api/components'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; -import Spinner from '../../../components/ui/Spinner'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { KeyboardKeys } from '../../../helpers/keycodes'; import { translate } from '../../../helpers/l10n'; diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index 3856e8d3bdd..8d29062fc9f 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -26,6 +26,7 @@ import { LAYOUT_FOOTER_HEIGHT, LargeCenteredLayout, PageContentFontWrapper, + Spinner, ToggleButton, themeBorder, themeColor, @@ -51,7 +52,6 @@ import { Location, Router, withRouter } from '../../../components/hoc/withRouter import IssueTabViewer from '../../../components/rules/IssueTabViewer'; import '../../../components/search-navigator.css'; import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils'; -import Spinner from '../../../components/ui/Spinner'; import { fillBranchLike, getBranchLikeQuery, diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx index 972d8041e8f..2369d3edba5 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewer.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 { Spinner } from 'design-system'; import { findLastIndex, keyBy } from 'lodash'; import * as React from 'react'; import { getComponentForSourceViewer, getDuplications, getSources } from '../../../api/components'; @@ -33,7 +34,6 @@ import { issuesByComponentAndLine, } from '../../../components/SourceViewer/helpers/indexing'; import { Alert } from '../../../components/ui/Alert'; -import Spinner from '../../../components/ui/Spinner'; import { WorkspaceContext } from '../../../components/workspace/context'; import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { throwGlobalError } from '../../../helpers/error'; diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx index 8cc99731af9..154fa40466b 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/Header.tsx @@ -17,11 +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 { ButtonPrimary, FlagMessage, Title } from 'design-system'; +import { ButtonPrimary, FlagMessage, Spinner, Title } from 'design-system'; import React, { useState } from 'react'; import { createPermissionTemplate } from '../../../api/permissions'; import { Router, withRouter } from '../../../components/hoc/withRouter'; -import Spinner from '../../../components/ui/Spinner'; import { throwGlobalError } from '../../../helpers/error'; import { translate } from '../../../helpers/l10n'; import { useGithubProvisioningEnabledQuery } from '../../../queries/identity-provider/github'; @@ -60,8 +59,10 @@ function Header(props: Props) {
- {translate('permission_templates.page')} - +
+ {translate('permission_templates.page')} + +
setCreateModal(true)}>{translate('create')}
diff --git a/server/sonar-web/src/main/js/apps/projectInformation/notifications/ProjectNotifications.tsx b/server/sonar-web/src/main/js/apps/projectInformation/notifications/ProjectNotifications.tsx index 3b813508a2c..c27c1d9e0ac 100644 --- a/server/sonar-web/src/main/js/apps/projectInformation/notifications/ProjectNotifications.tsx +++ b/server/sonar-web/src/main/js/apps/projectInformation/notifications/ProjectNotifications.tsx @@ -17,13 +17,12 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { Checkbox, FlagMessage, SubTitle } from 'design-system'; +import { Checkbox, FlagMessage, Spinner, SubTitle } from 'design-system'; import * as React from 'react'; import { WithNotificationsProps, withNotifications, } from '../../../components/hoc/withNotifications'; -import Spinner from '../../../components/ui/Spinner'; import { hasMessage, translate, translateWithParameters } from '../../../helpers/l10n'; import { NotificationProjectType } from '../../../types/notifications'; import { Component } from '../../../types/types'; @@ -79,7 +78,7 @@ export function ProjectNotifications(props: WithNotificationsProps & Props) { {translate('notification.dispatcher.information')} - +

{translate('project_information.project_notifications.title')}

diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx index 30d32f20a8b..0b71813fe40 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx @@ -21,6 +21,7 @@ import styled from '@emotion/styled'; import { LargeCenteredLayout, PageContentFontWrapper, + Spinner, themeBorder, themeColor, } from 'design-system'; @@ -36,7 +37,6 @@ import ScreenPositionHelper from '../../../components/common/ScreenPositionHelpe import Suggestions from '../../../components/embed-docs-modal/Suggestions'; import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; import '../../../components/search-navigator.css'; -import Spinner from '../../../components/ui/Spinner'; import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication'; import { translate } from '../../../helpers/l10n'; import { addSideBarClass, removeSideBarClass } from '../../../helpers/pages'; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx index 1a7facfa094..acd81abda1f 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotOpenInIdeButton.tsx @@ -24,9 +24,9 @@ import { ItemButton, PopupPlacement, PopupZLevel, + Spinner, } from 'design-system'; import * as React from 'react'; -import Spinner from '../../../components/ui/Spinner'; import { addGlobalErrorMessage, addGlobalSuccessMessage } from '../../../helpers/globalMessages'; import { translate } from '../../../helpers/l10n'; import { openHotspot, probeSonarLintServers } from '../../../helpers/sonarlint'; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSidebarHeader.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSidebarHeader.tsx index 5f705e89c5f..3553fa6a9b0 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSidebarHeader.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSidebarHeader.tsx @@ -28,6 +28,7 @@ import { ItemDivider, ItemHeader, PopupZLevel, + Spinner, } from 'design-system'; import * as React from 'react'; import withComponentContext from '../../../app/components/componentContext/withComponentContext'; @@ -35,7 +36,6 @@ import withCurrentUserContext from '../../../app/components/current-user/withCur import HelpTooltip from '../../../components/controls/HelpTooltip'; import Tooltip from '../../../components/controls/Tooltip'; import Measure from '../../../components/measure/Measure'; -import Spinner from '../../../components/ui/Spinner'; import { PopupPlacement } from '../../../components/ui/popups'; import { isBranch } from '../../../helpers/branch-like'; import { translate } from '../../../helpers/l10n'; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx index e942f9b8979..b7efcfd4c82 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotSnippetContainerRenderer.tsx @@ -19,9 +19,8 @@ */ import { withTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { themeColor } from 'design-system'; +import { Spinner, themeColor } from 'design-system'; import * as React from 'react'; -import Spinner from '../../../components/ui/Spinner'; import { translate } from '../../../helpers/l10n'; import { Hotspot } from '../../../types/security-hotspots'; import { diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx index 604f748b623..1ed1a6b62ec 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerRenderer.tsx @@ -19,13 +19,13 @@ */ import * as React from 'react'; import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; -import Spinner from '../../../components/ui/Spinner'; import { fillBranchLike } from '../../../helpers/branch-like'; import { Standards } from '../../../types/security'; import { Hotspot, HotspotStatusOption } from '../../../types/security-hotspots'; import { Component } from '../../../types/types'; import { HotspotHeader } from './HotspotHeader'; +import { Spinner } from 'design-system'; import { CurrentUser } from '../../../types/users'; import { RuleDescriptionSection } from '../../coding-rules/rule'; import HotspotReviewHistoryAndComments from './HotspotReviewHistoryAndComments'; diff --git a/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx b/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx index e05d726808d..47920dde7fd 100644 --- a/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx +++ b/server/sonar-web/src/main/js/apps/settings/components/NewCodeDefinition.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import classNames from 'classnames'; +import { Spinner } from 'design-system'; import React, { useCallback, useEffect } from 'react'; import { FormattedMessage } from 'react-intl'; import DocLink from '../../../components/common/DocLink'; @@ -25,7 +26,6 @@ import { ResetButtonLink, SubmitButton } from '../../../components/controls/butt import NewCodeDefinitionDaysOption from '../../../components/new-code-definition/NewCodeDefinitionDaysOption'; import NewCodeDefinitionPreviousVersionOption from '../../../components/new-code-definition/NewCodeDefinitionPreviousVersionOption'; import { NewCodeDefinitionLevels } from '../../../components/new-code-definition/utils'; -import Spinner from '../../../components/ui/Spinner'; import { translate } from '../../../helpers/l10n'; import { getNumberOfDaysDefaultValue, diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx index c270565839b..539fc0feeed 100644 --- a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx @@ -24,6 +24,7 @@ import { GreySeparator, InputField, InputSelect, + Spinner, Table, TableRow, } from 'design-system'; @@ -32,7 +33,6 @@ import * as React from 'react'; import { getScannableProjects } from '../../../api/components'; import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; import { LabelValueSelectOption } from '../../../components/controls/Select'; -import Spinner from '../../../components/ui/Spinner'; import { translate } from '../../../helpers/l10n'; import { EXPIRATION_OPTIONS, @@ -181,14 +181,6 @@ export function TokensForm(props: Readonly) { setNewTokenExpiration(newTokenExpiration?.value as TokenExpiration); }; - const customSpinner = ( - - - - - - ); - const tableHeader = ( {translate('name')} @@ -299,7 +291,7 @@ export function TokensForm(props: Readonly) { header={tableHeader} noHeaderTopBorder > - + {tokens && tokens.length <= 0 ? ( diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx index a2b8b75ad90..84e81312d0a 100644 --- a/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx @@ -24,6 +24,7 @@ import { ContentCell, DangerButtonSecondary, FlagWarningIcon, + Spinner, TableRow, themeColor, } from 'design-system'; @@ -32,7 +33,6 @@ import { FormattedMessage } from 'react-intl'; import ConfirmButton from '../../../components/controls/ConfirmButton'; import DateFormatter from '../../../components/intl/DateFormatter'; import DateFromNow from '../../../components/intl/DateFromNow'; -import Spinner from '../../../components/ui/Spinner'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { useRevokeTokenMutation } from '../../../queries/users'; import { UserToken } from '../../../types/token'; diff --git a/server/sonar-web/src/main/js/components/facet/FacetBox.tsx b/server/sonar-web/src/main/js/components/facet/FacetBox.tsx deleted file mode 100644 index b3ee7f9168f..00000000000 --- a/server/sonar-web/src/main/js/components/facet/FacetBox.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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 classNames from 'classnames'; -import * as React from 'react'; - -export interface FacetBoxProps { - className?: string; - children: React.ReactNode; - property: string; -} - -export default function FacetBox(props: FacetBoxProps) { - const { children, className, property } = props; - - return ( -
- {children} -
- ); -} diff --git a/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx b/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx deleted file mode 100644 index 96cbd24311a..00000000000 --- a/server/sonar-web/src/main/js/components/facet/FacetHeader.tsx +++ /dev/null @@ -1,137 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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 classNames from 'classnames'; -import * as React from 'react'; -import HelpTooltip from '../../components/controls/HelpTooltip'; -import { Button, ButtonLink } from '../../components/controls/buttons'; -import OpenCloseIcon from '../../components/icons/OpenCloseIcon'; -import { translate, translateWithParameters } from '../../helpers/l10n'; -import Tooltip from '../controls/Tooltip'; -import Spinner from '../ui/Spinner'; - -interface Props { - children?: React.ReactNode; - fetching?: boolean; - helper?: string; - disabled?: boolean; - disabledHelper?: string; - name: string; - id: string; - onClear?: () => void; - onClick?: () => void; - open: boolean; - values?: string[]; -} - -export default class FacetHeader extends React.PureComponent { - handleClick = (event: React.SyntheticEvent) => { - event.preventDefault(); - event.nativeEvent.preventDefault(); - if (this.props.onClick) { - this.props.onClick(); - } - }; - - renderHelper() { - if (!this.props.helper) { - return null; - } - return ; - } - - renderValueIndicator() { - const { values } = this.props; - if (!values || !values.length) { - return null; - } - const value = - values.length === 1 ? values[0] : translateWithParameters('x_selected', values.length); - return ( - - {value} - - ); - } - - render() { - const { disabled, values, disabledHelper, name, open, children, fetching, id } = this.props; - const showClearButton = values != null && values.length > 0 && this.props.onClear != null; - const header = disabled ? ( - - - {name} - - - ) : ( - name - ); - return ( -
- {this.props.onClick ? ( - - - {this.renderHelper()} - - ) : ( - - {header} - {this.renderHelper()} - - )} - - {children} - - - {this.renderValueIndicator()} - - - {fetching && ( - - - - )} - - {showClearButton && ( - - )} -
- ); - } -} diff --git a/server/sonar-web/src/main/js/components/facet/FacetItem.tsx b/server/sonar-web/src/main/js/components/facet/FacetItem.tsx deleted file mode 100644 index fc570237ad3..00000000000 --- a/server/sonar-web/src/main/js/components/facet/FacetItem.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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 classNames from 'classnames'; -import * as React from 'react'; - -export interface Props { - active?: boolean; - className?: string; - halfWidth?: boolean; - name: React.ReactNode; - onClick: (x: string, multiple?: boolean) => void; - stat?: React.ReactNode; - /** Textual version of `name` */ - tooltip?: string; - value: string; -} - -export default class FacetItem extends React.PureComponent { - static defaultProps = { - halfWidth: false, - }; - - handleClick = (event: React.MouseEvent) => { - event.preventDefault(); - this.props.onClick(this.props.value, event.ctrlKey || event.metaKey); - }; - - renderValue() { - if (this.props.stat == null) { - return null; - } - - return {this.props.stat}; - } - - render() { - const { name, halfWidth, active, value, tooltip } = this.props; - - const className = classNames('search-navigator-facet button-link', this.props.className, { - active, - }); - - return ( - - - - ); - } -} diff --git a/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx b/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx deleted file mode 100644 index a6fe8b9c62c..00000000000 --- a/server/sonar-web/src/main/js/components/facet/FacetItemsList.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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'; - -export type FacetItemsListProps = - | { - children?: React.ReactNode; - labelledby: string; - label?: never; - } - | { - children?: React.ReactNode; - labelledby?: never; - label: string; - }; - -export default function FacetItemsList({ children, labelledby, label }: FacetItemsListProps) { - const props = labelledby ? { 'aria-labelledby': labelledby } : { 'aria-label': label }; - return ( -
- {children} -
- ); -} diff --git a/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx b/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx deleted file mode 100644 index 3179eb81a2f..00000000000 --- a/server/sonar-web/src/main/js/components/facet/ListStyleFacet.tsx +++ /dev/null @@ -1,448 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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 classNames from 'classnames'; -import { sortBy, without } from 'lodash'; -import * as React from 'react'; -import ListFooter from '../../components/controls/ListFooter'; -import SearchBox from '../../components/controls/SearchBox'; -import { Alert } from '../../components/ui/Alert'; -import { translate } from '../../helpers/l10n'; -import { formatMeasure } from '../../helpers/measures'; -import { queriesEqual } from '../../helpers/query'; -import { Dict, Paging, RawQuery } from '../../types/types'; -import FacetBox from './FacetBox'; -import FacetHeader from './FacetHeader'; -import FacetItem from './FacetItem'; -import FacetItemsList from './FacetItemsList'; -import ListStyleFacetFooter from './ListStyleFacetFooter'; -import MultipleSelectionHint from './MultipleSelectionHint'; - -interface SearchResponse { - maxResults?: boolean; - results: S[]; - paging?: Paging; -} - -export interface Props { - className?: string; - disabled?: boolean; - disabledHelper?: string; - facetHeader: string; - fetching: boolean; - getFacetItemText: (item: string) => string; - getSearchResultKey: (result: S) => string; - getSearchResultText: (result: S) => string; - loadSearchResultCount?: (result: S[]) => Promise>; - maxInitialItems: number; - maxItems: number; - minSearchLength: number; - onChange: (changes: Dict) => void; - onClear?: () => void; - onItemClick?: (itemValue: string, multiple: boolean) => void; - onSearch: (query: string, page?: number) => Promise>; - onToggle: (property: string) => void; - open: boolean; - property: string; - query?: RawQuery; - renderFacetItem: (item: string) => React.ReactNode; - renderSearchResult: (result: S, query: string) => React.ReactNode; - searchPlaceholder: string; - getSortedItems?: () => string[]; - stats: Dict | undefined; - values: string[]; - showMoreAriaLabel?: string; - showLessAriaLabel?: string; -} - -interface State { - autoFocus: boolean; - query: string; - searching: boolean; - searchMaxResults?: boolean; - searchPaging?: Paging; - searchResults?: S[]; - searchResultsCounts: Dict; - showFullList: boolean; -} - -export default class ListStyleFacet extends React.Component, State> { - mounted = false; - - static defaultProps = { - maxInitialItems: 15, - maxItems: 100, - minSearchLength: 2, - }; - - state: State = { - autoFocus: false, - query: '', - searching: false, - searchResultsCounts: {}, - showFullList: false, - }; - - componentDidMount() { - this.mounted = true; - } - - componentDidUpdate(prevProps: Props) { - if (!prevProps.open && this.props.open) { - // focus search field *only* if it was manually open - this.setState({ autoFocus: true }); - } else if ( - (prevProps.open && !this.props.open) || - !queriesEqual(prevProps.query || {}, this.props.query || {}) - ) { - // reset state when closing the facet, or when query changes - this.setState({ - query: '', - searchMaxResults: undefined, - searchResults: undefined, - searching: false, - searchResultsCounts: {}, - showFullList: false, - }); - } else if ( - prevProps.stats !== this.props.stats && - Object.keys(this.props.stats || {}).length < this.props.maxInitialItems - ) { - // show limited list if `stats` changed and there are less than 15 items - this.setState({ showFullList: false }); - } - } - - componentWillUnmount() { - this.mounted = false; - } - - handleItemClick = (itemValue: string, multiple: boolean) => { - if (this.props.onItemClick) { - this.props.onItemClick(itemValue, multiple); - } else { - const { values } = this.props; - if (multiple) { - const newValue = sortBy( - values.includes(itemValue) ? without(values, itemValue) : [...values, itemValue], - ); - this.props.onChange({ [this.props.property]: newValue }); - } else { - this.props.onChange({ - [this.props.property]: values.includes(itemValue) && values.length < 2 ? [] : [itemValue], - }); - } - } - }; - - handleHeaderClick = () => { - this.props.onToggle(this.props.property); - }; - - handleClear = () => { - if (this.props.onClear) { - this.props.onClear(); - } else { - this.props.onChange({ [this.props.property]: [] }); - } - }; - - stopSearching = () => { - if (this.mounted) { - this.setState({ searching: false }); - } - }; - - search = (query: string) => { - if (query.length >= this.props.minSearchLength) { - this.setState({ query, searching: true }); - this.props - .onSearch(query) - .then(this.loadCountsForSearchResults) - .then(({ maxResults, paging, results, stats }) => { - if (this.mounted) { - this.setState((state) => ({ - searching: false, - searchMaxResults: maxResults, - searchResults: results, - searchPaging: paging, - searchResultsCounts: { ...state.searchResultsCounts, ...stats }, - })); - } - }) - .catch(this.stopSearching); - } else { - this.setState({ query, searching: false, searchResults: [] }); - } - }; - - searchMore = () => { - const { query, searchPaging, searchResults } = this.state; - if (query && searchResults && searchPaging) { - this.setState({ searching: true }); - this.props - .onSearch(query, searchPaging.pageIndex + 1) - .then(this.loadCountsForSearchResults) - .then(({ paging, results, stats }) => { - if (this.mounted) { - this.setState((state) => ({ - searching: false, - searchResults: [...searchResults, ...results], - searchPaging: paging, - searchResultsCounts: { ...state.searchResultsCounts, ...stats }, - })); - } - }) - .catch(this.stopSearching); - } - }; - - loadCountsForSearchResults = (response: SearchResponse) => { - const { loadSearchResultCount = () => Promise.resolve({}) } = this.props; - const resultsToLoad = response.results.filter((result) => { - const key = this.props.getSearchResultKey(result); - return this.getStat(key) === undefined && this.state.searchResultsCounts[key] === undefined; - }); - if (resultsToLoad.length > 0) { - return loadSearchResultCount(resultsToLoad).then((stats) => ({ ...response, stats })); - } else { - return { ...response, stats: {} }; - } - }; - - getStat(item: string) { - const { stats } = this.props; - return stats && stats[item] !== undefined ? stats && stats[item] : undefined; - } - - getFacetHeaderId = (property: string) => { - return `facet_${property}`; - }; - - showFullList = () => { - this.setState({ showFullList: true }); - }; - - hideFullList = () => { - this.setState({ showFullList: false }); - }; - - renderList() { - const { - maxInitialItems, - maxItems, - property, - stats, - showMoreAriaLabel, - showLessAriaLabel, - values, - } = this.props; - - if (!stats) { - return null; - } - - const sortedItems = this.props.getSortedItems - ? this.props.getSortedItems() - : sortBy( - Object.keys(stats), - (key) => -stats[key], - (key) => this.props.getFacetItemText(key), - ); - - const limitedList = this.state.showFullList - ? sortedItems - : sortedItems.slice(0, maxInitialItems); - - // make sure all selected items are displayed - const selectedBelowLimit = this.state.showFullList - ? [] - : sortedItems.slice(maxInitialItems).filter((item) => values.includes(item)); - - const mightHaveMoreResults = sortedItems.length >= maxItems; - - return ( - <> - - {limitedList.map((item) => ( - - ))} - - {selectedBelowLimit.length > 0 && ( - <> -
⋯
- - {selectedBelowLimit.map((item) => ( - - ))} - - - )} - - {mightHaveMoreResults && this.state.showFullList && ( - - {translate('facet_might_have_more_results')} - - )} - - ); - } - - renderSearch() { - return ( - - ); - } - - renderSearchResults() { - const { property, showMoreAriaLabel } = this.props; - const { searching, searchMaxResults, searchResults, searchPaging } = this.state; - - if (!searching && (!searchResults || !searchResults.length)) { - return
{translate('no_results')}
; - } - - if (!searchResults) { - // initial search - return null; - } - - return ( - <> - - {searchResults.map((result) => this.renderSearchResult(result))} - - {searchMaxResults && ( - - {translate('facet_might_have_more_results')} - - )} - {searchPaging && ( - - )} - - ); - } - - renderSearchResult(result: S) { - const key = this.props.getSearchResultKey(result); - const active = this.props.values.includes(key); - const stat = this.getStat(key) || this.state.searchResultsCounts[key]; - return ( - - ); - } - - render() { - const { - className, - disabled, - disabledHelper, - facetHeader, - fetching, - open, - property, - stats = {}, - values: propsValues, - } = this.props; - const { query, searching, searchResults } = this.state; - const values = propsValues.map((item) => this.props.getFacetItemText(item)); - const loadingResults = - query !== '' && searching && (searchResults === undefined || searchResults.length === 0); - const showList = !query || loadingResults; - return ( - - - - {open && !disabled && ( - <> - {this.renderSearch()} - {showList ? this.renderList() : this.renderSearchResults()} - - - )} - - ); - } -} - -function formatFacetStat(stat: number | undefined) { - return stat && formatMeasure(stat, 'SHORT_INT'); -} diff --git a/server/sonar-web/src/main/js/components/facet/ListStyleFacetFooter.tsx b/server/sonar-web/src/main/js/components/facet/ListStyleFacetFooter.tsx deleted file mode 100644 index 64d88ca1242..00000000000 --- a/server/sonar-web/src/main/js/components/facet/ListStyleFacetFooter.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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, translateWithParameters } from '../../helpers/l10n'; -import { formatMeasure } from '../../helpers/measures'; -import { MetricType } from '../../types/metrics'; -import { ButtonLink } from '../controls/buttons'; - -interface Props { - count: number; - showMore: () => void; - showLess?: () => void; - total: number; - showMoreAriaLabel?: string; - showLessAriaLabel?: string; -} - -export default class ListStyleFacetFooter extends React.PureComponent { - handleShowMoreClick = () => { - this.props.showMore(); - }; - - handleShowLessClick = () => { - if (this.props.showLess) { - this.props.showLess(); - } - }; - - render() { - const { count, total, showMoreAriaLabel, showLessAriaLabel } = this.props; - const hasMore = total > count; - const allShown = Boolean(total && total === count); - - return ( -
- {translateWithParameters('x_show', formatMeasure(count, MetricType.Integer))} - - {hasMore && ( - - {translate('show_more')} - - )} - - {this.props.showLess && allShown && ( - - {translate('show_less')} - - )} -
- ); - } -} diff --git a/server/sonar-web/src/main/js/components/facet/MultipleSelectionHint.css b/server/sonar-web/src/main/js/components/facet/MultipleSelectionHint.css deleted file mode 100644 index c06a6b1ab6b..00000000000 --- a/server/sonar-web/src/main/js/components/facet/MultipleSelectionHint.css +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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. - */ -.multiple-selection-hint { - margin-top: var(--gridSize); - margin-bottom: var(--gridSize); - text-align: center; -} - -.multiple-selection-hint-inner { - display: inline-block; - height: var(--controlHeight); - line-height: var(--controlHeight); - border-radius: var(--controlHeight); - background-color: var(--barBorderColor); - text-align: center; - padding: 0 var(--gridSize); - font-size: var(--smallFontSize); -} diff --git a/server/sonar-web/src/main/js/components/facet/MultipleSelectionHint.tsx b/server/sonar-web/src/main/js/components/facet/MultipleSelectionHint.tsx deleted file mode 100644 index 43473f32a0d..00000000000 --- a/server/sonar-web/src/main/js/components/facet/MultipleSelectionHint.tsx +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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'; -import './MultipleSelectionHint.css'; - -export interface MultipleSelectionHintProps { - options: number; - values: number; -} - -const MAX_OPTIONS = 2; - -export default function MultipleSelectionHint({ options, values }: MultipleSelectionHintProps) { - // do not render if nothing is selected or there are less than 2 possible options - if (values === 0 || options < MAX_OPTIONS) { - return null; - } - - return ( -
-
- {translate( - isOnMac() - ? 'shortcuts.section.global.facets.multiselection.mac' - : 'shortcuts.section.global.facets.multiselection', - )} -
-
- ); -} - -function isOnMac() { - return navigator.userAgent.indexOf('Mac OS X') !== -1; -} diff --git a/server/sonar-web/src/main/js/components/facet/__tests__/Facet-it.tsx b/server/sonar-web/src/main/js/components/facet/__tests__/Facet-it.tsx deleted file mode 100644 index 878a270684a..00000000000 --- a/server/sonar-web/src/main/js/components/facet/__tests__/Facet-it.tsx +++ /dev/null @@ -1,126 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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 { screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; -import { renderComponent } from '../../../helpers/testReactTestingUtils'; -import FacetBox, { FacetBoxProps } from '../FacetBox'; -import FacetHeader from '../FacetHeader'; -import FacetItem from '../FacetItem'; -import FacetItemsList from '../FacetItemsList'; - -it('should render and function correctly', async () => { - const user = userEvent.setup(); - const onFacetClick = jest.fn(); - renderFacet(undefined, undefined, { onClick: onFacetClick }); - - // Start closed. - let facetHeader = screen.getByRole('button', { name: 'foo', expanded: false }); - expect(facetHeader).toBeInTheDocument(); - expect(screen.queryByText('Foo/Bar')).not.toBeInTheDocument(); - - // Expand. - await user.click(facetHeader); - facetHeader = screen.getByRole('button', { name: 'foo', expanded: true }); - expect(facetHeader).toBeInTheDocument(); - expect(screen.getByText('Foo/Bar')).toBeInTheDocument(); - - // Interact with facets. - const facet1 = screen.getByRole('checkbox', { name: 'Foo/Bar 10' }); - expect(facet1).toHaveClass('active'); - await user.click(facet1); - expect(onFacetClick).toHaveBeenCalledWith('bar', false); - - const facet2 = screen.getByRole('checkbox', { name: 'Foo/Baz' }); - expect(facet2).not.toHaveClass('active'); - - // Collapse again. - await user.click(facetHeader); - expect(screen.getByRole('button', { name: 'foo', expanded: false })).toBeInTheDocument(); - expect(screen.queryByText('Foo/Bar')).not.toBeInTheDocument(); -}); - -it('should correctly render a header with helper text', async () => { - renderFacet(undefined, { helper: 'Help text' }); - await expect(screen.getByRole('img', { description: 'Help text' })).toHaveATooltipWithContent( - 'Help text', - ); -}); - -it('should correctly render a header with value data', async () => { - const user = userEvent.setup(); - renderFacet(undefined, { values: ['value 1'] }); - expect(screen.getByText('value 1')).toBeInTheDocument(); - await user.click(screen.getByRole('button', { name: 'clear_x_filter.foo' })); - expect(screen.queryByText('value 1')).not.toBeInTheDocument(); -}); - -it('should correctly render a disabled header', () => { - renderFacet(undefined, { onClick: undefined }); - expect(screen.queryByRole('checkbox', { name: 'foo' })).not.toBeInTheDocument(); -}); - -function renderFacet( - facetBoxProps: Partial = {}, - facetHeaderProps: Partial = {}, - facetItemProps: Partial = {}, -) { - function Facet() { - const [open, setOpen] = React.useState(facetHeaderProps.open ?? false); - const [values, setValues] = React.useState(facetHeaderProps.values ?? undefined); - - const property = 'foo'; - const headerId = `facet_${property}`; - - return ( - - setOpen(!open)} - onClear={() => setValues(undefined)} - {...{ ...facetHeaderProps, open, values }} - /> - - {open && ( - - - - - )} - - ); - } - - return renderComponent(); -} -- 2.39.5