}
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"
disabled?: boolean;
forceExternal?: boolean;
icon?: React.ReactNode;
+ isExternal?: boolean;
onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;
preventDefault?: boolean;
showExternalIcon?: boolean;
blurAfterClick,
disabled,
icon,
+ isExternal: isExternalProp = false,
onClick,
preventDefault,
showExternalIcon = !icon,
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) {
[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}
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';
+++ /dev/null
-/*
- * 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;
-}
* 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 {
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;
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 {
const { plugin } = this.props;
return (
- <div className="js-actions">
+ <div className="it__js-actions">
{isAvailablePlugin(plugin) && (
<div>
<p className="little-spacer-bottom">
const { loading } = this.state;
return (
- <div className="js-actions">
+ <div className="it__js-actions">
{isAvailablePlugin(plugin) && plugin.termsAndConditionsUrl && (
<p className="little-spacer-bottom">
<Checkbox
exports[`should render available plugin correctly 1`] = `
<div
- className="js-actions"
+ className="it__js-actions"
>
<p
className="little-spacer-bottom"
exports[`should render available plugin correctly 2`] = `
<div
- className="js-actions"
+ className="it__js-actions"
>
<div>
<p
exports[`should render installed plugin correctly 1`] = `
<div
- className="js-actions"
+ className="it__js-actions"
>
<PluginUpdateButton
disabled={false}
exports[`should render installed plugin correctly 2`] = `
<div
- className="js-actions"
+ className="it__js-actions"
>
<p>
<CheckIcon
* 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';
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;
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>
</>
)
);
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>
)}
)}
<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')};
+`;
+++ /dev/null
-/*
- * 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}
- />
- );
-}
+++ /dev/null
-// 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>
-`;
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;
}