From: David Cho-Lerat Date: Tue, 6 Jun 2023 13:03:08 +0000 (+0200) Subject: SONAR-19474 The code viewer header adopts the new UI X-Git-Tag: 10.1.0.73491~111 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=967bf884a9d329be91b0f9ee9e3b7a73229ec542;p=sonarqube.git SONAR-19474 The code viewer header adopts the new UI --- diff --git a/server/sonar-web/design-system/src/components/DropdownMenu.tsx b/server/sonar-web/design-system/src/components/DropdownMenu.tsx index d822cf68437..74c41581703 100644 --- a/server/sonar-web/design-system/src/components/DropdownMenu.tsx +++ b/server/sonar-web/design-system/src/components/DropdownMenu.tsx @@ -71,18 +71,20 @@ interface ListItemProps { } type ItemLinkProps = Omit & - Pick & { + Pick & { innerRef?: React.Ref; }; export function ItemLink(props: ItemLinkProps) { - const { children, className, disabled, icon, onClick, innerRef, to, ...liProps } = props; + const { children, className, disabled, icon, isExternal, onClick, innerRef, to, ...liProps } = + props; return (
  • ) => void; preventDefault?: boolean; showExternalIcon?: boolean; @@ -45,6 +46,7 @@ function BaseLinkWithRef(props: LinkProps, ref: React.ForwardedRef) => { if (blurAfterClick) { @@ -75,20 +82,24 @@ function BaseLinkWithRef(props: LinkProps, ref: React.ForwardedRef - {icon} - {children} - {showExternalIcon && } - - ) : ( + if (isExternal) { + return ( + + {icon} + {children} + {showExternalIcon && } + + ); + } + + return ( {icon} {children} diff --git a/server/sonar-web/design-system/src/components/icons/index.ts b/server/sonar-web/design-system/src/components/icons/index.ts index a65e5e893fa..f6a20a74a7c 100644 --- a/server/sonar-web/design-system/src/components/icons/index.ts +++ b/server/sonar-web/design-system/src/components/icons/index.ts @@ -47,6 +47,7 @@ export { LinkIcon } from './LinkIcon'; export { LockIcon } from './LockIcon'; export { MainBranchIcon } from './MainBranchIcon'; export { MenuHelpIcon } from './MenuHelpIcon'; +export { MenuIcon } from './MenuIcon'; export { MenuSearchIcon } from './MenuSearchIcon'; export { NoDataIcon } from './NoDataIcon'; export { OpenCloseIndicator } from './OpenCloseIndicator'; diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.css b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.css deleted file mode 100644 index 6146efe91b5..00000000000 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.css +++ /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. - */ -.issue-source-viewer-header { - padding: 4px 10px; - border: 1px solid var(--gray80); - background-color: var(--barBackgroundColor); - align-items: center; - min-height: 25px; - position: sticky; - z-index: 100; - top: 0; - margin-top: 8px; - margin-bottom: -1px; -} - -.issue-source-viewer-header:first-child { - margin-top: 0; -} diff --git a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx index e51e0e58fb2..5115e878787 100644 --- a/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.tsx +++ b/server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/IssueSourceViewerHeader.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 styled from '@emotion/styled'; import classNames from 'classnames'; import { @@ -42,7 +43,6 @@ import { getBranchLikeUrl, getComponentIssuesUrl, getPathUrlAsString } from '../ import { BranchLike } from '../../../types/branch-like'; import { ComponentQualifier } from '../../../types/component'; import { SourceViewerFile } from '../../../types/types'; -import './IssueSourceViewerHeader.css'; export const INTERACTIVE_TOOLTIP_DELAY = 0.5; diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx index 186609b0b54..487b50dabe1 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx +++ b/server/sonar-web/src/main/js/apps/marketplace/components/PluginActions.tsx @@ -20,12 +20,12 @@ import * as React from 'react'; import { installPlugin, uninstallPlugin, updatePlugin } from '../../../api/plugins'; import Link from '../../../components/common/Link'; -import { Button } from '../../../components/controls/buttons'; import Checkbox from '../../../components/controls/Checkbox'; import Tooltip from '../../../components/controls/Tooltip'; +import { Button } from '../../../components/controls/buttons'; import CheckIcon from '../../../components/icons/CheckIcon'; import { translate } from '../../../helpers/l10n'; -import { isAvailablePlugin, isInstalledPlugin, Plugin } from '../../../types/plugins'; +import { Plugin, isAvailablePlugin, isInstalledPlugin } from '../../../types/plugins'; import PluginUpdateButton from './PluginUpdateButton'; interface Props { @@ -76,7 +76,7 @@ export default class PluginActions extends React.PureComponent { const { plugin } = this.props; return ( -
    +
    {isAvailablePlugin(plugin) && (

    @@ -120,7 +120,7 @@ export default class PluginActions extends React.PureComponent { const { loading } = this.state; return ( -

    +
    {isAvailablePlugin(plugin) && plugin.termsAndConditionsUrl && (

    { - state: State = { measuresOverlay: false }; - - handleShowMeasuresClick = (event: React.SyntheticEvent) => { - event.preventDefault(); - this.setState({ measuresOverlay: true }); - }; - - handleMeasuresOverlayClose = () => { - this.setState({ measuresOverlay: false }); - }; - - openInWorkspace = (event: React.SyntheticEvent) => { - event.preventDefault(); +export default class SourceViewerHeader extends React.PureComponent { + openInWorkspace = () => { const { key } = this.props.sourceViewerFile; this.props.openComponent({ branchLike: this.props.branchLike, key }); }; renderIssueMeasures = () => { const { branchLike, componentMeasures, sourceViewerFile } = this.props; + return ( componentMeasures && componentMeasures.length > 0 && ( <> -

    - - {ISSUE_TYPES.map((type: IssueType) => { - const params = { - ...getBranchLikeQuery(branchLike), - files: sourceViewerFile.path, - resolved: 'false', - types: type, - }; - - const measure = componentMeasures.find( - (m) => m.metric === ISSUETYPE_METRIC_KEYS_MAP[type].metric - ); - - const linkUrl = - type === IssueType.SecurityHotspot - ? getComponentSecurityHotspotsUrl(sourceViewerFile.project, params) - : getComponentIssuesUrl(sourceViewerFile.project, params); - - return ( -
    - - {translate('issue.type', type)} - - - {formatMeasure((measure && measure.value) || 0, 'INT')} - -
    - ); - })} + + +
    + {ISSUE_TYPES.map((type: IssueType) => { + const params = { + ...getBranchLikeQuery(branchLike), + files: sourceViewerFile.path, + resolved: 'false', + types: type, + }; + + const measure = componentMeasures.find( + (m) => m.metric === ISSUETYPE_METRIC_KEYS_MAP[type].metric + ); + + const linkUrl = + type === IssueType.SecurityHotspot + ? getComponentSecurityHotspotsUrl(sourceViewerFile.project, params) + : getComponentIssuesUrl(sourceViewerFile.project, params); + + return ( +
    + + {translate('issue.type', type)} + + + + + {formatMeasure(measure?.value ?? 0, MetricType.Integer)} + + +
    + ); + })} +
    ) ); @@ -121,80 +126,81 @@ export default class SourceViewerHeader extends React.PureComponent -
    -
    - - -
    - {collapsedDirFromPath(path)} - {fileFromPath(path)} - - - -
    + +
    +
    + } + to={getPathUrlAsString(getBranchLikeUrl(project, this.props.branchLike))} + > + {projectName} +
    -
    - {this.state.measuresOverlay && ( - - )} +
    + + + {collapsedDirFromPath(path)} + + {fileFromPath(path)} + + + + +
    +
    {showMeasures && ( -
    +
    {measures[unitTestsOrLines] && ( -
    - +
    + {translate(`metric.${unitTestsOrLines}.name`)} - - - {formatMeasure(measures[unitTestsOrLines], 'SHORT_INT')} + + + + {formatMeasure(measures[unitTestsOrLines], MetricType.ShortInteger)}
    )} {measures.coverage !== undefined && ( -
    - +
    + {translate('metric.coverage.name')} - - - {formatMeasure(measures.coverage, 'PERCENT')} + + + + {formatMeasure(measures.coverage, MetricType.Percent)}
    )} {measures.duplicationDensity !== undefined && ( -
    - +
    + {translate('duplications')} - - - {formatMeasure(measures.duplicationDensity, 'PERCENT')} + + + + {formatMeasure(measures.duplicationDensity, MetricType.Percent)}
    )} @@ -204,50 +210,58 @@ export default class SourceViewerHeader extends React.PureComponent -
  • - - {translate('component_viewer.show_details')} - -
  • -
  • - - {translate('component_viewer.new_window')} - -
  • - {!workspace && ( -
  • - - {translate('component_viewer.open_in_workspace')} - -
  • - )} -
  • - - {translate('component_viewer.show_raw_source')} - -
  • - + <> + + {translate('component_viewer.new_window')} + + + + {translate('component_viewer.open_in_workspace')} + + + + {translate('component_viewer.show_raw_source')} + + } - overlayPlacement={PopupPlacement.BottomRight} + placement={PopupPlacement.BottomRight} + zLevel={PopupZLevel.Global} > - - - + - + ); } } + +const StyledDrilldownLink = styled(DrilldownLink)` + color: ${themeColor('linkDefault')}; + + &:visited { + color: ${themeColor('linkDefault')}; + } + + &:active, + &:focus, + &:hover { + color: ${themeColor('linkActive')}; + } +`; + +const StyledHeaderContainer = styled.div` + background-color: ${themeColor('backgroundSecondary')}; + border-bottom: ${themeBorder('default', 'codeLineBorder')}; +`; + +const StyledVerticalSeparator = styled.div` + border-right: ${themeBorder('default', 'codeLineBorder')}; +`; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeader-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeader-test.tsx deleted file mode 100644 index 2bf7768283a..00000000000 --- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeader-test.tsx +++ /dev/null @@ -1,79 +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 { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockMainBranch } from '../../../helpers/mocks/branch-like'; -import { mockSourceViewerFile } from '../../../helpers/mocks/sources'; -import { ComponentQualifier } from '../../../types/component'; -import { MetricKey } from '../../../types/metrics'; -import { Measure } from '../../../types/types'; -import SourceViewerHeader from '../SourceViewerHeader'; - -it('should render correctly for a regular file', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should render correctly for a unit test', () => { - expect( - shallowRender({ - showMeasures: true, - sourceViewerFile: mockSourceViewerFile('foo/bar.ts', 'my-project', { - q: ComponentQualifier.TestFile, - measures: { tests: '12' }, - }), - }) - ).toMatchSnapshot(); -}); - -it('should render correctly if issue details are passed', () => { - const componentMeasures: Measure[] = [ - { metric: MetricKey.code_smells, value: '1' }, - { metric: MetricKey.file_complexity_distribution, value: '42' }, // unused, should be ignored - { metric: MetricKey.security_hotspots, value: '2' }, - { metric: MetricKey.vulnerabilities, value: '2' }, - ]; - - expect( - shallowRender({ - componentMeasures, - showMeasures: true, - }) - ).toMatchSnapshot(); - - expect( - shallowRender({ - componentMeasures, - showMeasures: false, - }) - .find('.source-viewer-header-measure') - .exists() - ).toBe(false); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap deleted file mode 100644 index abfac0ec143..00000000000 --- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap +++ /dev/null @@ -1,516 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly for a regular file 1`] = ` -
    -
    -
    - -
    - - - - foo/ - - - bar.ts - - - - -
    -
    -
    - -
  • - - component_viewer.show_details - -
  • -
  • - - component_viewer.new_window - -
  • -
  • - - component_viewer.open_in_workspace - -
  • -
  • - - component_viewer.show_raw_source - -
  • - - } - overlayPlacement="bottom-right" - > - - - -
    -
    -`; - -exports[`should render correctly for a unit test 1`] = ` -
    -
    -
    - -
    - - - - foo/ - - - bar.ts - - - - -
    -
    -
    -
    -
    - - metric.tests.name - - - 12 - -
    -
    - -
  • - - component_viewer.show_details - -
  • -
  • - - component_viewer.new_window - -
  • -
  • - - component_viewer.open_in_workspace - -
  • -
  • - - component_viewer.show_raw_source - -
  • - - } - overlayPlacement="bottom-right" - > - - - -
    -
    -`; - -exports[`should render correctly if issue details are passed 1`] = ` -
    -
    -
    - -
    - - - - foo/ - - - bar.ts - - - - -
    -
    -
    -
    -
    - - metric.lines.name - - - 56 - -
    -
    - - metric.coverage.name - - - 85.2% - -
    -
    - - duplications - - - 1.0% - -
    -
    -
    - - issue.type.BUG - - - - 0 - - -
    -
    - - issue.type.VULNERABILITY - - - - 2 - - -
    -
    - - issue.type.CODE_SMELL - - - - 1 - - -
    -
    - - issue.type.SECURITY_HOTSPOT - - - - 2 - - -
    -
    - -
  • - - component_viewer.show_details - -
  • -
  • - - component_viewer.new_window - -
  • -
  • - - component_viewer.open_in_workspace - -
  • -
  • - - component_viewer.show_raw_source - -
  • - - } - overlayPlacement="bottom-right" - > - - - -
    -
    -`; diff --git a/server/sonar-web/src/main/js/components/SourceViewer/styles.css b/server/sonar-web/src/main/js/components/SourceViewer/styles.css index 63cef19ca70..5915806fda5 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/styles.css +++ b/server/sonar-web/src/main/js/components/SourceViewer/styles.css @@ -31,69 +31,6 @@ border-collapse: collapse; } -.source-viewer-header { - position: relative; - padding: 2px 10px 4px; - border-bottom: 1px solid var(--barBorderColor); - background-color: var(--barBackgroundColor); -} - -.source-viewer-header-measure { - vertical-align: middle; - font-size: var(--baseFontSize); -} - -.source-viewer-header-measure .rating { - font-size: 18px; -} - -.source-viewer-header-measure-separator { - margin: 0 calc(3 * var(--gridSize)); - height: 30px; - border-right: 1px solid var(--gray80); -} - -.source-viewer-header-measure + .source-viewer-header-measure { - margin-left: calc(3 * var(--gridSize)); -} - -.source-viewer-header-measure-label { - display: block; - line-height: var(--smallFontSize); - color: var(--secondFontColor); - font-size: var(--smallFontSize); -} - -.source-viewer-header-measure-value { - display: block; - margin-top: 2px; - line-height: 18px; - color: var(--baseFontColor); - font-size: var(--bigFontSize); -} - -.source-viewer-header-actions { - display: block; - margin-left: calc(3 * var(--gridSize)); - padding: var(--gridSize) calc(var(--gridSize) / 2); -} - -.source-viewer-header-actions svg { - margin-top: 2px; -} - -.source-viewer-header-more-actions { - position: absolute; - z-index: var(--dropdownMenuZIndex); - right: 0; - top: 100%; - padding: 10px; - border: 1px solid var(--barBorderColor); - border-right: none; - background-color: #fff; - line-height: 1.8; -} - .source-viewer-code { overflow-x: auto; }