diff options
author | David Cho-Lerat <david.cho-lerat@sonarsource.com> | 2023-06-06 15:03:08 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-06-09 20:03:10 +0000 |
commit | 967bf884a9d329be91b0f9ee9e3b7a73229ec542 (patch) | |
tree | bd0b84a675acc08c7603449c2ebb08cd4c8a8864 /server | |
parent | 96490eade960eee1b4696cec5125e69b1de0e224 (diff) | |
download | sonarqube-967bf884a9d329be91b0f9ee9e3b7a73229ec542.tar.gz sonarqube-967bf884a9d329be91b0f9ee9e3b7a73229ec542.zip |
SONAR-19474 The code viewer header adopts the new UI
Diffstat (limited to 'server')
11 files changed, 203 insertions, 868 deletions
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<ListItemProps, 'innerRef'> & - Pick<LinkProps, 'disabled' | 'icon' | 'onClick' | 'to'> & { + Pick<LinkProps, 'disabled' | 'icon' | 'isExternal' | 'onClick' | 'to'> & { innerRef?: React.Ref<HTMLAnchorElement>; }; 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 ( <li {...liProps}> <ItemLinkStyled className={classNames(className, { disabled })} disabled={disabled} icon={icon} + isExternal={isExternal} onClick={onClick} ref={innerRef} role="menuitem" diff --git a/server/sonar-web/design-system/src/components/Link.tsx b/server/sonar-web/design-system/src/components/Link.tsx index c1176015223..0234c858928 100644 --- a/server/sonar-web/design-system/src/components/Link.tsx +++ b/server/sonar-web/design-system/src/components/Link.tsx @@ -32,6 +32,7 @@ export interface LinkProps extends RouterLinkProps { disabled?: boolean; forceExternal?: boolean; icon?: React.ReactNode; + isExternal?: boolean; onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void; preventDefault?: boolean; showExternalIcon?: boolean; @@ -45,6 +46,7 @@ function BaseLinkWithRef(props: LinkProps, ref: React.ForwardedRef<HTMLAnchorEle blurAfterClick, disabled, icon, + isExternal: isExternalProp = false, onClick, preventDefault, showExternalIcon = !icon, @@ -53,7 +55,12 @@ function BaseLinkWithRef(props: LinkProps, ref: React.ForwardedRef<HTMLAnchorEle to, ...rest } = props; - const isExternal = typeof to === 'string' && to.startsWith('http'); + + const toAsString = + typeof to === 'string' ? to : `${to.pathname ?? ''}${to.search ?? ''}${to.hash ?? ''}`; + + const isExternal = isExternalProp || toAsString.startsWith('http'); + const handleClick = React.useCallback( (event: React.MouseEvent<HTMLAnchorElement>) => { if (blurAfterClick) { @@ -75,20 +82,24 @@ function BaseLinkWithRef(props: LinkProps, ref: React.ForwardedRef<HTMLAnchorEle [onClick, blurAfterClick, preventDefault, stopPropagation, disabled] ); - return isExternal ? ( - <a - {...rest} - href={to} - onClick={handleClick} - ref={ref} - rel="noopener noreferrer" - target={target} - > - {icon} - {children} - {showExternalIcon && <OpenNewTabIcon className="sw-ml-1" />} - </a> - ) : ( + if (isExternal) { + return ( + <a + {...rest} + href={toAsString} + onClick={handleClick} + ref={ref} + rel="noopener noreferrer" + target={target} + > + {icon} + {children} + {showExternalIcon && <OpenNewTabIcon className="sw-ml-1" />} + </a> + ); + } + + return ( <RouterLink ref={ref} {...rest} onClick={handleClick} to={to}> {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<Props, State> { const { plugin } = this.props; return ( - <div className="js-actions"> + <div className="it__js-actions"> {isAvailablePlugin(plugin) && ( <div> <p className="little-spacer-bottom"> @@ -120,7 +120,7 @@ export default class PluginActions extends React.PureComponent<Props, State> { const { loading } = this.state; return ( - <div className="js-actions"> + <div className="it__js-actions"> {isAvailablePlugin(plugin) && plugin.termsAndConditionsUrl && ( <p className="little-spacer-bottom"> <Checkbox diff --git a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginActions-test.tsx.snap index 676c7005b7a..f0cbba99385 100644 --- a/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginActions-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/marketplace/components/__tests__/__snapshots__/PluginActions-test.tsx.snap @@ -2,7 +2,7 @@ exports[`should render available plugin correctly 1`] = ` <div - className="js-actions" + className="it__js-actions" > <p className="little-spacer-bottom" @@ -46,7 +46,7 @@ exports[`should render available plugin correctly 1`] = ` exports[`should render available plugin correctly 2`] = ` <div - className="js-actions" + className="it__js-actions" > <div> <p @@ -60,7 +60,7 @@ exports[`should render available plugin correctly 2`] = ` exports[`should render installed plugin correctly 1`] = ` <div - className="js-actions" + className="it__js-actions" > <PluginUpdateButton disabled={false} @@ -89,7 +89,7 @@ exports[`should render installed plugin correctly 1`] = ` exports[`should render installed plugin correctly 2`] = ` <div - className="js-actions" + className="it__js-actions" > <p> <CheckIcon diff --git a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx index c1ed0d9dafb..ce3f530e3c0 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/SourceViewerHeader.tsx @@ -17,13 +17,27 @@ * 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 { + ClipboardIconButton, + DrilldownLink, + Dropdown, + InteractiveIcon, + ItemButton, + ItemLink, + Link, + MenuIcon, + Note, + PopupPlacement, + PopupZLevel, + ProjectIcon, + QualifierIcon, + themeBorder, + themeColor, +} from 'design-system'; import * as React from 'react'; -import { ButtonIcon } from '../../components/controls/buttons'; -import { ClipboardIconButton } from '../../components/controls/clipboard'; -import Dropdown from '../../components/controls/Dropdown'; -import ListIcon from '../../components/icons/ListIcon'; -import QualifierIcon from '../../components/icons/QualifierIcon'; -import { PopupPlacement } from '../../components/ui/popups'; + import { getBranchLikeQuery } from '../../helpers/branch-like'; import { ISSUE_TYPES } from '../../helpers/constants'; import { ISSUETYPE_METRIC_KEYS_MAP } from '../../helpers/issues'; @@ -39,13 +53,14 @@ import { getComponentSecurityHotspotsUrl, getPathUrlAsString, } from '../../helpers/urls'; -import { BranchLike } from '../../types/branch-like'; + import { ComponentQualifier } from '../../types/component'; import { IssueType } from '../../types/issues'; -import { Measure, SourceViewerFile } from '../../types/types'; -import Link from '../common/Link'; -import { WorkspaceContextShape } from '../workspace/context'; -import MeasuresOverlay from './components/MeasuresOverlay'; +import { MetricKey, MetricType } from '../../types/metrics'; + +import type { BranchLike } from '../../types/branch-like'; +import type { Measure, SourceViewerFile } from '../../types/types'; +import type { WorkspaceContextShape } from '../workspace/context'; interface Props { branchLike: BranchLike | undefined; @@ -55,64 +70,54 @@ interface Props { sourceViewerFile: SourceViewerFile; } -interface State { - measuresOverlay: boolean; -} - -export default class SourceViewerHeader extends React.PureComponent<Props, State> { - state: State = { measuresOverlay: false }; - - handleShowMeasuresClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); - this.setState({ measuresOverlay: true }); - }; - - handleMeasuresOverlayClose = () => { - this.setState({ measuresOverlay: false }); - }; - - openInWorkspace = (event: React.SyntheticEvent<HTMLAnchorElement>) => { - event.preventDefault(); +export default class SourceViewerHeader extends React.PureComponent<Props> { + 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 && ( <> - <div className="source-viewer-header-measure-separator" /> - - {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 ( - <div className="source-viewer-header-measure" key={type}> - <span className="source-viewer-header-measure-label"> - {translate('issue.type', type)} - </span> - <span className="source-viewer-header-measure-value"> - <Link to={linkUrl}>{formatMeasure((measure && measure.value) || 0, 'INT')}</Link> - </span> - </div> - ); - })} + <StyledVerticalSeparator className="sw-h-8 sw-mx-6" /> + + <div className="sw-flex sw-gap-6"> + {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 ( + <div className="sw-flex sw-flex-col sw-gap-1" key={type}> + <Note className="it__source-viewer-header-measure-label sw-body-lg"> + {translate('issue.type', type)} + </Note> + + <span> + <StyledDrilldownLink className="sw-body-md" to={linkUrl}> + {formatMeasure(measure?.value ?? 0, MetricType.Integer)} + </StyledDrilldownLink> + </span> + </div> + ); + })} + </div> </> ) ); @@ -121,80 +126,81 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State render() { const { showMeasures } = this.props; const { key, measures, path, project, projectName, q } = this.props.sourceViewerFile; - const unitTestsOrLines = q === ComponentQualifier.TestFile ? 'tests' : 'lines'; - const workspace = false; + const unitTestsOrLines = q === ComponentQualifier.TestFile ? MetricKey.tests : MetricKey.lines; + const query = new URLSearchParams( omitNil({ key, ...getBranchLikeQuery(this.props.branchLike) }) ).toString(); + const rawSourcesLink = `${getBaseUrl()}/api/sources/raw?${query}`; - // TODO favorite return ( - <div className="source-viewer-header display-flex-center"> - <div className="flex-1 little-spacer-top"> - <div className="component-name"> - <div className="component-name-parent"> - <a - className="link-no-underline" - href={getPathUrlAsString(getBranchLikeUrl(project, this.props.branchLike))} - > - <QualifierIcon qualifier={ComponentQualifier.Project} /> <span>{projectName}</span> - </a> - </div> - - <div className="component-name-path"> - <QualifierIcon qualifier={q} /> <span>{collapsedDirFromPath(path)}</span> - <span className="component-name-file">{fileFromPath(path)}</span> - <span className="nudged-up spacer-left"> - <ClipboardIconButton - aria-label={translate('component_viewer.copy_path_to_clipboard')} - className="button-link link-no-underline" - copyValue={path} - /> - </span> - </div> + <StyledHeaderContainer + className={ + 'it__source-viewer-header sw-body-sm sw-flex sw-items-center sw-px-4 sw-py-3 ' + + 'sw-relative' + } + > + <div className="sw-flex sw-flex-1 sw-flex-col sw-gap-1 sw-mr-5 sw-my-1"> + <div className="sw-flex sw-gap-1 sw-items-center"> + <Link + icon={<ProjectIcon />} + to={getPathUrlAsString(getBranchLikeUrl(project, this.props.branchLike))} + > + {projectName} + </Link> </div> - </div> - {this.state.measuresOverlay && ( - <MeasuresOverlay - branchLike={this.props.branchLike} - onClose={this.handleMeasuresOverlayClose} - sourceViewerFile={this.props.sourceViewerFile} - /> - )} + <div className="sw-flex sw-gap-1 sw-items-center"> + <QualifierIcon qualifier={q} /> + + {collapsedDirFromPath(path)} + + {fileFromPath(path)} + + <span className="sw-ml-1"> + <ClipboardIconButton + aria-label={translate('component_viewer.copy_path_to_clipboard')} + copyValue={path} + /> + </span> + </div> + </div> {showMeasures && ( - <div className="display-flex-center"> + <div className="sw-flex sw-gap-6 sw-items-center"> {measures[unitTestsOrLines] && ( - <div className="source-viewer-header-measure"> - <span className="source-viewer-header-measure-label"> + <div className="sw-flex sw-flex-col sw-gap-1"> + <Note className="it__source-viewer-header-measure-label sw-body-lg"> {translate(`metric.${unitTestsOrLines}.name`)} - </span> - <span className="source-viewer-header-measure-value"> - {formatMeasure(measures[unitTestsOrLines], 'SHORT_INT')} + </Note> + + <span className="sw-body-lg"> + {formatMeasure(measures[unitTestsOrLines], MetricType.ShortInteger)} </span> </div> )} {measures.coverage !== undefined && ( - <div className="source-viewer-header-measure"> - <span className="source-viewer-header-measure-label"> + <div className="sw-flex sw-flex-col sw-gap-1"> + <Note className="it__source-viewer-header-measure-label sw-body-lg"> {translate('metric.coverage.name')} - </span> - <span className="source-viewer-header-measure-value"> - {formatMeasure(measures.coverage, 'PERCENT')} + </Note> + + <span className="sw-body-lg"> + {formatMeasure(measures.coverage, MetricType.Percent)} </span> </div> )} {measures.duplicationDensity !== undefined && ( - <div className="source-viewer-header-measure"> - <span className="source-viewer-header-measure-label"> + <div className="sw-flex sw-flex-col sw-gap-1"> + <Note className="it__source-viewer-header-measure-label sw-body-lg"> {translate('duplications')} - </span> - <span className="source-viewer-header-measure-value"> - {formatMeasure(measures.duplicationDensity, 'PERCENT')} + </Note> + + <span className="sw-body-lg"> + {formatMeasure(measures.duplicationDensity, MetricType.Percent)} </span> </div> )} @@ -204,50 +210,58 @@ export default class SourceViewerHeader extends React.PureComponent<Props, State )} <Dropdown - className="source-viewer-header-actions flex-0" + id="source-viewer-header-actions" overlay={ - <ul className="menu"> - <li> - <a className="js-measures" href="#" onClick={this.handleShowMeasuresClick}> - {translate('component_viewer.show_details')} - </a> - </li> - <li> - <Link - className="js-new-window" - rel="noopener noreferrer" - target="_blank" - to={getCodeUrl(this.props.sourceViewerFile.project, this.props.branchLike, key)} - > - {translate('component_viewer.new_window')} - </Link> - </li> - {!workspace && ( - <li> - <a className="js-workspace" href="#" onClick={this.openInWorkspace}> - {translate('component_viewer.open_in_workspace')} - </a> - </li> - )} - <li> - <a - className="js-raw-source" - href={rawSourcesLink} - rel="noopener noreferrer" - target="_blank" - > - {translate('component_viewer.show_raw_source')} - </a> - </li> - </ul> + <> + <ItemLink + isExternal + to={getCodeUrl(this.props.sourceViewerFile.project, this.props.branchLike, key)} + > + {translate('component_viewer.new_window')} + </ItemLink> + + <ItemButton className="it__js-workspace" onClick={this.openInWorkspace}> + {translate('component_viewer.open_in_workspace')} + </ItemButton> + + <ItemLink isExternal to={rawSourcesLink}> + {translate('component_viewer.show_raw_source')} + </ItemLink> + </> } - overlayPlacement={PopupPlacement.BottomRight} + placement={PopupPlacement.BottomRight} + zLevel={PopupZLevel.Global} > - <ButtonIcon className="js-actions" aria-label={translate('component_viewer.action_menu')}> - <ListIcon /> - </ButtonIcon> + <InteractiveIcon + aria-label={translate('component_viewer.action_menu')} + className="it__js-actions sw-flex-0 sw-ml-4 sw-px-3 sw-py-2" + Icon={MenuIcon} + /> </Dropdown> - </div> + </StyledHeaderContainer> ); } } + +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<SourceViewerHeader['props']> = {}) { - return shallow( - <SourceViewerHeader - branchLike={mockMainBranch()} - openComponent={jest.fn()} - sourceViewerFile={mockSourceViewerFile()} - {...props} - /> - ); -} 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`] = ` -<div - className="source-viewer-header display-flex-center" -> - <div - className="flex-1 little-spacer-top" - > - <div - className="component-name" - > - <div - className="component-name-parent" - > - <a - className="link-no-underline" - href="/dashboard?id=project" - > - <QualifierIcon - qualifier="TRK" - /> - - <span> - MyProject - </span> - </a> - </div> - <div - className="component-name-path" - > - <QualifierIcon - qualifier="FIL" - /> - - <span> - foo/ - </span> - <span - className="component-name-file" - > - bar.ts - </span> - <span - className="nudged-up spacer-left" - > - <ClipboardIconButton - aria-label="component_viewer.copy_path_to_clipboard" - className="button-link link-no-underline" - copyValue="foo/bar.ts" - /> - </span> - </div> - </div> - </div> - <Dropdown - className="source-viewer-header-actions flex-0" - overlay={ - <ul - className="menu" - > - <li> - <a - className="js-measures" - href="#" - onClick={[Function]} - > - component_viewer.show_details - </a> - </li> - <li> - <ForwardRef(Link) - className="js-new-window" - rel="noopener noreferrer" - target="_blank" - to={ - { - "pathname": "/code", - "search": "?id=project&selected=project%3Afoo%2Fbar.ts", - } - } - > - component_viewer.new_window - </ForwardRef(Link)> - </li> - <li> - <a - className="js-workspace" - href="#" - onClick={[Function]} - > - component_viewer.open_in_workspace - </a> - </li> - <li> - <a - className="js-raw-source" - href="/api/sources/raw?key=project%3Afoo%2Fbar.ts" - rel="noopener noreferrer" - target="_blank" - > - component_viewer.show_raw_source - </a> - </li> - </ul> - } - overlayPlacement="bottom-right" - > - <ButtonIcon - aria-label="component_viewer.action_menu" - className="js-actions" - > - <ListIcon /> - </ButtonIcon> - </Dropdown> -</div> -`; - -exports[`should render correctly for a unit test 1`] = ` -<div - className="source-viewer-header display-flex-center" -> - <div - className="flex-1 little-spacer-top" - > - <div - className="component-name" - > - <div - className="component-name-parent" - > - <a - className="link-no-underline" - href="/dashboard?id=my-project" - > - <QualifierIcon - qualifier="TRK" - /> - - <span> - MyProject - </span> - </a> - </div> - <div - className="component-name-path" - > - <QualifierIcon - qualifier="UTS" - /> - - <span> - foo/ - </span> - <span - className="component-name-file" - > - bar.ts - </span> - <span - className="nudged-up spacer-left" - > - <ClipboardIconButton - aria-label="component_viewer.copy_path_to_clipboard" - className="button-link link-no-underline" - copyValue="foo/bar.ts" - /> - </span> - </div> - </div> - </div> - <div - className="display-flex-center" - > - <div - className="source-viewer-header-measure" - > - <span - className="source-viewer-header-measure-label" - > - metric.tests.name - </span> - <span - className="source-viewer-header-measure-value" - > - 12 - </span> - </div> - </div> - <Dropdown - className="source-viewer-header-actions flex-0" - overlay={ - <ul - className="menu" - > - <li> - <a - className="js-measures" - href="#" - onClick={[Function]} - > - component_viewer.show_details - </a> - </li> - <li> - <ForwardRef(Link) - className="js-new-window" - rel="noopener noreferrer" - target="_blank" - to={ - { - "pathname": "/code", - "search": "?id=my-project&selected=my-project%3Afoo%2Fbar.ts", - } - } - > - component_viewer.new_window - </ForwardRef(Link)> - </li> - <li> - <a - className="js-workspace" - href="#" - onClick={[Function]} - > - component_viewer.open_in_workspace - </a> - </li> - <li> - <a - className="js-raw-source" - href="/api/sources/raw?key=my-project%3Afoo%2Fbar.ts" - rel="noopener noreferrer" - target="_blank" - > - component_viewer.show_raw_source - </a> - </li> - </ul> - } - overlayPlacement="bottom-right" - > - <ButtonIcon - aria-label="component_viewer.action_menu" - className="js-actions" - > - <ListIcon /> - </ButtonIcon> - </Dropdown> -</div> -`; - -exports[`should render correctly if issue details are passed 1`] = ` -<div - className="source-viewer-header display-flex-center" -> - <div - className="flex-1 little-spacer-top" - > - <div - className="component-name" - > - <div - className="component-name-parent" - > - <a - className="link-no-underline" - href="/dashboard?id=project" - > - <QualifierIcon - qualifier="TRK" - /> - - <span> - MyProject - </span> - </a> - </div> - <div - className="component-name-path" - > - <QualifierIcon - qualifier="FIL" - /> - - <span> - foo/ - </span> - <span - className="component-name-file" - > - bar.ts - </span> - <span - className="nudged-up spacer-left" - > - <ClipboardIconButton - aria-label="component_viewer.copy_path_to_clipboard" - className="button-link link-no-underline" - copyValue="foo/bar.ts" - /> - </span> - </div> - </div> - </div> - <div - className="display-flex-center" - > - <div - className="source-viewer-header-measure" - > - <span - className="source-viewer-header-measure-label" - > - metric.lines.name - </span> - <span - className="source-viewer-header-measure-value" - > - 56 - </span> - </div> - <div - className="source-viewer-header-measure" - > - <span - className="source-viewer-header-measure-label" - > - metric.coverage.name - </span> - <span - className="source-viewer-header-measure-value" - > - 85.2% - </span> - </div> - <div - className="source-viewer-header-measure" - > - <span - className="source-viewer-header-measure-label" - > - duplications - </span> - <span - className="source-viewer-header-measure-value" - > - 1.0% - </span> - </div> - <div - className="source-viewer-header-measure-separator" - /> - <div - className="source-viewer-header-measure" - key="BUG" - > - <span - className="source-viewer-header-measure-label" - > - issue.type.BUG - </span> - <span - className="source-viewer-header-measure-value" - > - <ForwardRef(Link) - to={ - { - "hash": "", - "pathname": "/project/issues", - "search": "?files=foo%2Fbar.ts&resolved=false&types=BUG&id=project", - } - } - > - 0 - </ForwardRef(Link)> - </span> - </div> - <div - className="source-viewer-header-measure" - key="VULNERABILITY" - > - <span - className="source-viewer-header-measure-label" - > - issue.type.VULNERABILITY - </span> - <span - className="source-viewer-header-measure-value" - > - <ForwardRef(Link) - to={ - { - "hash": "", - "pathname": "/project/issues", - "search": "?files=foo%2Fbar.ts&resolved=false&types=VULNERABILITY&id=project", - } - } - > - 2 - </ForwardRef(Link)> - </span> - </div> - <div - className="source-viewer-header-measure" - key="CODE_SMELL" - > - <span - className="source-viewer-header-measure-label" - > - issue.type.CODE_SMELL - </span> - <span - className="source-viewer-header-measure-value" - > - <ForwardRef(Link) - to={ - { - "hash": "", - "pathname": "/project/issues", - "search": "?files=foo%2Fbar.ts&resolved=false&types=CODE_SMELL&id=project", - } - } - > - 1 - </ForwardRef(Link)> - </span> - </div> - <div - className="source-viewer-header-measure" - key="SECURITY_HOTSPOT" - > - <span - className="source-viewer-header-measure-label" - > - issue.type.SECURITY_HOTSPOT - </span> - <span - className="source-viewer-header-measure-value" - > - <ForwardRef(Link) - to={ - { - "hash": "", - "pathname": "/security_hotspots", - "search": "?id=project&files=foo%2Fbar.ts", - } - } - > - 2 - </ForwardRef(Link)> - </span> - </div> - </div> - <Dropdown - className="source-viewer-header-actions flex-0" - overlay={ - <ul - className="menu" - > - <li> - <a - className="js-measures" - href="#" - onClick={[Function]} - > - component_viewer.show_details - </a> - </li> - <li> - <ForwardRef(Link) - className="js-new-window" - rel="noopener noreferrer" - target="_blank" - to={ - { - "pathname": "/code", - "search": "?id=project&selected=project%3Afoo%2Fbar.ts", - } - } - > - component_viewer.new_window - </ForwardRef(Link)> - </li> - <li> - <a - className="js-workspace" - href="#" - onClick={[Function]} - > - component_viewer.open_in_workspace - </a> - </li> - <li> - <a - className="js-raw-source" - href="/api/sources/raw?key=project%3Afoo%2Fbar.ts" - rel="noopener noreferrer" - target="_blank" - > - component_viewer.show_raw_source - </a> - </li> - </ul> - } - overlayPlacement="bottom-right" - > - <ButtonIcon - aria-label="component_viewer.action_menu" - className="js-actions" - > - <ListIcon /> - </ButtonIcon> - </Dropdown> -</div> -`; 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; } |