import {
getCodeMetrics,
loadMoreChildren,
+ mostCommonPrefix,
retrieveComponent,
retrieveComponentChildren
} from '../utils';
expect(getComponentBreadcrumbs).toHaveBeenCalledWith('key');
});
});
+
+describe('#mostCommonPrefix', () => {
+ it('should correctly find the common path prefix', () => {
+ expect(mostCommonPrefix(['src/main/ts/tests', 'src/main/java/tests'])).toEqual('src/main/');
+ expect(mostCommonPrefix(['src/main/ts/app', 'src/main/ts/app'])).toEqual('src/main/ts/');
+ expect(mostCommonPrefix(['src/main/ts', 'lib/main/ts'])).toEqual('');
+ });
+});
isProject
} from '../../../types/component';
import { ComponentMeasure } from '../../../types/types';
+import { mostCommonPrefix } from '../utils';
export function getTooltip(component: ComponentMeasure) {
const isFile = component.qualifier === 'FIL' || component.qualifier === 'UTS';
return [component.name, component.key, component.branch].filter(s => !!s).join('\n\n');
}
-export function mostCommonPrefix(strings: string[]) {
- const sortedStrings = strings.slice(0).sort();
- const firstString = sortedStrings[0];
- const firstStringLength = firstString.length;
- const lastString = sortedStrings[sortedStrings.length - 1];
- let i = 0;
- while (i < firstStringLength && firstString.charAt(i) === lastString.charAt(i)) {
- i++;
- }
- const prefix = firstString.substr(0, i);
- const prefixTokens = prefix.split(/[\s\\/]/);
- const lastPrefixPart = prefixTokens[prefixTokens.length - 1];
- return prefix.substr(0, prefix.length - lastPrefixPart.length);
-}
-
export interface Props {
branchLike?: BranchLike;
canBrowse?: boolean;
previous,
canBrowse = false
}: Props) {
- const areBothDirs = component.qualifier === 'DIR' && previous && previous.qualifier === 'DIR';
- const prefix =
- areBothDirs && previous !== undefined
- ? mostCommonPrefix([component.name + '/', previous.name + '/'])
- : '';
- const name = prefix ? (
- <span>
- <span style={{ color: colors.secondFontColor }}>{prefix}</span>
- <span>{component.name.substr(prefix.length)}</span>
+ const ariaLabel = unclickable ? translate('code.parent_folder') : undefined;
+
+ if (
+ [ComponentQualifier.Application, ComponentQualifier.Portfolio].includes(
+ rootComponent.qualifier as ComponentQualifier
+ ) &&
+ [ComponentQualifier.Application, ComponentQualifier.Project].includes(
+ component.qualifier as ComponentQualifier
+ )
+ ) {
+ return (
+ <span className="max-width-100 display-inline-flex-center">
+ <span className="text-ellipsis" title={getTooltip(component)} aria-label={ariaLabel}>
+ {renderNameWithIcon(
+ branchLike,
+ component,
+ previous,
+ rootComponent,
+ unclickable,
+ canBrowse
+ )}
+ </span>
+ {component.branch ? (
+ <span className="text-ellipsis spacer-left">
+ <BranchIcon className="little-spacer-right" />
+ <span className="note">{component.branch}</span>
+ </span>
+ ) : (
+ <span className="spacer-left badge flex-1">{translate('branches.main_branch')}</span>
+ )}
+ </span>
+ );
+ }
+ return (
+ <span
+ className="max-width-100 display-inline-block text-ellipsis"
+ title={getTooltip(component)}
+ aria-label={ariaLabel}>
+ {renderNameWithIcon(branchLike, component, previous, rootComponent, unclickable, canBrowse)}
</span>
- ) : (
- component.name
);
+}
- let inner = null;
+function renderNameWithIcon(
+ branchLike: BranchLike | undefined,
+ component: ComponentMeasure,
+ previous: ComponentMeasure | undefined,
+ rootComponent: ComponentMeasure,
+ unclickable = false,
+ canBrowse = false
+) {
+ const name = renderName(component, previous);
if (
!unclickable &&
)
? component.branch
: undefined;
- inner = (
+ return (
<Link
className="link-with-icon"
to={getComponentOverviewUrl(component.refKey || component.key, component.qualifier, {
if (component.key !== rootComponent.key) {
Object.assign(query, { selected: component.key });
}
- inner = (
+ return (
<Link className="link-with-icon" to={{ pathname: '/code', query }}>
<QualifierIcon qualifier={component.qualifier} /> <span>{name}</span>
</Link>
);
- } else {
- inner = (
- <span>
- <QualifierIcon qualifier={component.qualifier} /> {name}
- </span>
- );
}
+ return (
+ <span>
+ <QualifierIcon qualifier={component.qualifier} /> {name}
+ </span>
+ );
+}
- if (
- [ComponentQualifier.Application, ComponentQualifier.Portfolio].includes(
- rootComponent.qualifier as ComponentQualifier
- ) &&
- [ComponentQualifier.Application, ComponentQualifier.Project].includes(
- component.qualifier as ComponentQualifier
- )
- ) {
- return (
- <span className="max-width-100 display-inline-flex-center">
- <span className="text-ellipsis" title={getTooltip(component)}>
- {inner}
- </span>
- {component.branch ? (
- <span className="text-ellipsis spacer-left">
- <BranchIcon className="little-spacer-right" />
- <span className="note">{component.branch}</span>
- </span>
- ) : (
- <span className="spacer-left badge flex-1">{translate('branches.main_branch')}</span>
- )}
- </span>
- );
- } else {
- return (
- <span
- className="max-width-100 display-inline-block text-ellipsis"
- title={getTooltip(component)}>
- {inner}
- </span>
- );
- }
+function renderName(component: ComponentMeasure, previous: ComponentMeasure | undefined) {
+ const areBothDirs = component.qualifier === 'DIR' && previous && previous.qualifier === 'DIR';
+ const prefix =
+ areBothDirs && previous !== undefined
+ ? mostCommonPrefix([component.name + '/', previous.name + '/'])
+ : '';
+ return prefix ? (
+ <span>
+ <span style={{ color: colors.secondFontColor }}>{prefix}</span>
+ <span>{component.name.slice(prefix.length)}</span>
+ </span>
+ ) : (
+ component.name
+ );
}
import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
import { mockComponentMeasure } from '../../../../helpers/mocks/component';
import { ComponentQualifier } from '../../../../types/component';
-import ComponentName, { getTooltip, mostCommonPrefix, Props } from '../ComponentName';
+import ComponentName, { getTooltip, Props } from '../ComponentName';
describe('#getTooltip', () => {
it('should correctly format component information', () => {
});
});
-describe('#mostCommonPrefix', () => {
- it('should correctly find the common path prefix', () => {
- expect(mostCommonPrefix(['src/main/ts/tests', 'src/main/java/tests'])).toEqual('src/main/');
- expect(mostCommonPrefix(['src/main/ts/app', 'src/main/ts/app'])).toEqual('src/main/ts/');
- expect(mostCommonPrefix(['src/main/ts', 'lib/main/ts'])).toEqual('');
- });
-});
-
describe('#ComponentName', () => {
it('should render correctly for files', () => {
expect(shallowRender()).toMatchSnapshot();
exports[`#ComponentName should render breadcrumb correctly for APP 1`] = `
<span
+ aria-label="code.parent_folder"
className="max-width-100 display-inline-block text-ellipsis"
title="Foo
exports[`#ComponentName should render breadcrumb correctly for SVW 1`] = `
<span
+ aria-label="code.parent_folder"
className="max-width-100 display-inline-block text-ellipsis"
title="Foo
exports[`#ComponentName should render breadcrumb correctly for TRK 1`] = `
<span
+ aria-label="code.parent_folder"
className="max-width-100 display-inline-block text-ellipsis"
title="Foo
exports[`#ComponentName should render breadcrumb correctly for TRK 2`] = `
<span
+ aria-label="code.parent_folder"
className="max-width-100 display-inline-block text-ellipsis"
title="Foo
exports[`#ComponentName should render breadcrumb correctly for VW 1`] = `
<span
+ aria-label="code.parent_folder"
className="max-width-100 display-inline-block text-ellipsis"
title="Foo
return r;
});
}
+
+export function mostCommonPrefix(strings: string[]) {
+ const sortedStrings = strings.slice(0).sort();
+ const firstString = sortedStrings[0];
+ const firstStringLength = firstString.length;
+ const lastString = sortedStrings[sortedStrings.length - 1];
+ let i = 0;
+ while (i < firstStringLength && firstString.charAt(i) === lastString.charAt(i)) {
+ i++;
+ }
+ const prefix = firstString.slice(0, i);
+ const prefixTokens = prefix.split(/[\s\\/]/);
+ const lastPrefixPart = prefixTokens[prefixTokens.length - 1];
+ return prefix.slice(0, prefix.length - lastPrefixPart.length);
+}
className?: string;
fill?: string;
size?: number;
+ ariaLabel?: string;
}
interface Props extends React.AriaAttributes {
className?: string;
size?: number;
style?: React.CSSProperties;
+ ariaLabel?: string;
// try to avoid using these:
width?: number;
height = size,
width = size,
viewBox = '0 0 16 16',
+ ariaLabel,
...iconProps
}: Props) {
return (
<svg
className={className}
+ aria-label={ariaLabel}
height={height}
style={{
fillRule: 'evenodd',
*/
import * as React from 'react';
import { colors } from '../../app/theme';
+import { translate } from '../../helpers/l10n';
import { Dict } from '../../types/types';
import Icon, { IconProps } from './Icon';
const qualifier = props.qualifier.toLowerCase();
const FoundIcon = qualifierIcons[qualifier];
- return FoundIcon ? <FoundIcon className={props.className} fill={props.fill} /> : null;
+ const ariaLabel = props.qualifier != null ? translate(`qualifier.${props.qualifier}`) : undefined;
+ return FoundIcon ? (
+ <FoundIcon className={props.className} fill={props.fill} ariaLabel={ariaLabel} />
+ ) : null;
}
-function ApplicationIcon({ fill, ...iconProps }: IconProps) {
+function ApplicationIcon({ fill, ariaLabel, ...iconProps }: IconProps) {
return (
- <Icon {...iconProps}>
+ <Icon {...iconProps} ariaLabel={ariaLabel}>
<path
d="M3.014 10.986a2 2 0 1 1-.001 4.001 2 2 0 0 1 .001-4.001zm9.984 0a2 2 0 1 1-.001 4.001 2 2 0 0 1 .001-4.001zm-5.004-.021c1.103 0 2 .896 2 2s-.897 2-2 2a2 2 0 0 1 0-4zm-4.98 1.021a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm9.984 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm-5.004-.021a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM2.984 6a2 2 0 1 1-.001 4.001A2 2 0 0 1 2.984 6zm9.984 0a2 2 0 1 1-.001 4.001A2 2 0 0 1 12.968 6zm-5.004-.021c1.103 0 2 .897 2 2a2 2 0 1 1-2-2zM2.984 7a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm9.984 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm-5.004-.021a1.001 1.001 0 0 1 0 2 1 1 0 0 1 0-2zM3 1.025a2 2 0 1 1-.001 4.001A2 2 0 0 1 3 1.025zm9.984 0a2 2 0 1 1-.001 4.001 2 2 0 0 1 .001-4.001zM7.98 1.004c1.103 0 2 .896 2 2s-.897 2-2 2a2 2 0 0 1 0-4zM3 2.025a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm9.984 0a1 1 0 1 1 0 2 1 1 0 0 1 0-2zM7.98 2.004a1.001 1.001 0 0 1 0 2 1 1 0 0 1 0-2z"
style={{ fill: fill || colors.blue }}
);
}
-function DeveloperIcon({ fill, ...iconProps }: IconProps) {
+function DeveloperIcon({ fill, ariaLabel, ...iconProps }: IconProps) {
return (
- <Icon {...iconProps}>
+ <Icon {...iconProps} ariaLabel={ariaLabel}>
<path
d="M7.974 8.02a3.5 3.5 0 0 1-2.482-1.017 3.428 3.428 0 0 1-1.028-2.455c0-.927.365-1.8 1.028-2.455a3.505 3.505 0 0 1 2.482-1.017 3.5 3.5 0 0 1 2.482 1.017 3.434 3.434 0 0 1 1.027 2.455c0 .928-.365 1.8-1.027 2.455A3.504 3.504 0 0 1 7.974 8.02zm0-5.778c-1.286 0-2.332 1.034-2.332 2.306s1.046 2.307 2.332 2.307c1.285 0 2.332-1.035 2.332-2.307S9.258 2.242 7.974 2.242zm3.534 6.418c.127.016.243.045.348.086.17.066.302.146.406.246.132.124.253.282.36.47.126.218.226.442.3.668.08.253.15.535.206.838.056.313.095.604.113.867.02.28.03.57.03.862 0 .532-.174.758-.306.882-.142.132-.397.31-.973.31H3.948c-.233 0-.437-.03-.606-.09-.14-.05-.26-.123-.366-.222-.13-.123-.306-.35-.306-.88 0-.294.01-.584.03-.863.018-.263.056-.554.112-.867a6.5 6.5 0 0 1 .207-.838c.073-.226.173-.45.298-.667.108-.19.23-.347.36-.47.106-.1.238-.18.407-.247.105-.04.22-.07.348-.086.202.13.432.277.683.435.342.217.756.4 1.265.564.523.166 1.06.25 1.59.25a5.25 5.25 0 0 0 1.592-.25c.51-.164.923-.348 1.266-.565.25-.158.48-.304.682-.435l-.002.002zm-.244-1.18c-.055 0-.184.066-.387.196-.202.13-.43.276-.685.437-.255.16-.586.307-.994.437-.408.13-.818.196-1.23.196-.41 0-.82-.065-1.228-.196a4.303 4.303 0 0 1-.993-.437c-.255-.16-.484-.306-.686-.437-.202-.13-.33-.196-.386-.196-.374 0-.716.06-1.026.183-.31.12-.572.283-.787.487a3.28 3.28 0 0 0-.57.737 4.662 4.662 0 0 0-.395.888c-.098.303-.18.633-.244.988a9.652 9.652 0 0 0-.128.992c-.02.306-.032.62-.032.942 0 .73.224 1.304.672 1.726.448.42 1.043.632 1.785.632h8.044c.743 0 1.34-.21 1.787-.633.447-.42.67-.996.67-1.725 0-.32-.01-.635-.03-.942a9.159 9.159 0 0 0-.374-1.98c-.098-.304-.23-.6-.395-.888a3.23 3.23 0 0 0-.57-.737 2.404 2.404 0 0 0-.788-.487 2.779 2.779 0 0 0-1.026-.183h-.004z"
style={{ fill: fill || colors.blue }}
);
}
-function DirectoryIcon({ fill, ...iconProps }: IconProps) {
+function DirectoryIcon({ fill, ariaLabel, ...iconProps }: IconProps) {
return (
- <Icon {...iconProps}>
+ <Icon {...iconProps} ariaLabel={ariaLabel}>
<path
d="M14 12.286V5.703a.673.673 0 0 0-.195-.5.644.644 0 0 0-.49-.203H6.704a.686.686 0 0 1-.5-.214.707.707 0 0 1-.203-.51v-.57c0-.2-.07-.363-.207-.502A.679.679 0 0 0 5.29 3H2.707a.672.672 0 0 0-.5.204.683.683 0 0 0-.206.5v8.582c0 .2.07.367.206.506.137.14.304.208.5.208h10.61a.66.66 0 0 0 .49-.208.685.685 0 0 0 .194-.506H14zm1-6.598v6.65c0 .458-.152.83-.475 1.16-.324.326-.7.502-1.15.502H2.647c-.452 0-.84-.175-1.162-.503a1.572 1.572 0 0 1-.486-1.158V3.654a1.6 1.6 0 0 1 .486-1.17A1.578 1.578 0 0 1 2.648 2h2.7c.45 0 .84.157 1.164.485.324.328.488.714.488 1.17V4h6.373c.452 0 .83.174 1.152.5.323.33.475.73.475 1.187v.001z"
style={{ fill: fill || colors.orange }}
);
}
-function FileIcon({ fill, ...iconProps }: IconProps) {
+function FileIcon({ fill, ariaLabel, ...iconProps }: IconProps) {
return (
- <Icon {...iconProps}>
+ <Icon {...iconProps} ariaLabel={ariaLabel}>
<path
d="M14 15H2V1l7.997.02c1 .034 1.759.758 2.428 1.42.667.663 1.561 1.605 1.574 2.555H14V15zM9 2H3v12h10V6H9V2zm3 10H4v-1h8v1zm0-2H4V9h8v1zm-1.988-5h3.008c-.012-.674-.714-1.443-1.204-1.937-.488-.495-1.039-1.058-1.816-1.055v2.96l.012.032z"
style={{ fill: fill || colors.blue }}
);
}
-function LibraryIcon({ fill, ...iconProps }: IconProps) {
+function LibraryIcon({ fill, ariaLabel, ...iconProps }: IconProps) {
return (
- <Icon {...iconProps}>
+ <Icon {...iconProps} ariaLabel={ariaLabel}>
<path
d="M1 13h4V3H1v10zm3-1H2v-2h2v2zM2 4h2v4H2V4zm4 9h4V3H6v10zm3-1H7v-2h2v2zM7 4h2v4H7V4zm4 9h4V3h-4v10zm3-1h-2v-2h2v2zm-2-8h2v4h-2V4z"
style={{ fill: fill || colors.blue }}
);
}
-function PortfolioIcon({ fill, ...iconProps }: IconProps) {
+function PortfolioIcon({ fill, ariaLabel, ...iconProps }: IconProps) {
return (
- <Icon {...iconProps}>
+ <Icon {...iconProps} ariaLabel={ariaLabel}>
<path
d="M14.97 14.97H1.016V1.015H14.97V14.97zm-1-12.955H2.015V13.97H13.97V2.015zm-.973 10.982H9V9h3.997v3.997zM7 12.996H3.004V9H7v3.996zM11.997 10H10v1.997h1.997V10zM6 10H4.004v1.996H6V10zm1-3H3.006V3.006H7V7zm5.985 0H9V3.015h3.985V7zM6 4.006H4.006V6H6V4.006zm5.985.009H10V6h1.985V4.015z"
style={{ fill: fill || colors.blue }}
);
}
-function ProjectIcon({ fill, ...iconProps }: IconProps) {
+function ProjectIcon({ fill, ariaLabel, ...iconProps }: IconProps) {
return (
- <Icon {...iconProps}>
+ <Icon {...iconProps} ariaLabel={ariaLabel}>
<path
d="M14.985 13.988L1 14.005 1.02 5h13.966v8.988h-.001zM1.998 5.995l.006 7.02L14.022 13 14 6.004l-12.002-.01v.001zM3 4.5V4h9.996l.004.5h1l-.005-1.497-11.98.003L2 4.5h1zm1-2v-.504h8.002L12 2.5h1l-.004-1.495H3.003L3 2.5h1z"
style={{ fill: fill || colors.blue }}
);
}
-function SubPortfolioIcon({ fill, ...iconProps }: IconProps) {
+function SubPortfolioIcon({ fill, ariaLabel, ...iconProps }: IconProps) {
return (
- <Icon {...iconProps}>
+ <Icon {...iconProps} ariaLabel={ariaLabel}>
<path
d="M14 7h2v9H7v-2H0V0h14v7zM8 8v7h7V8H8zm3 6H9v-2h2v2zm3 0h-2v-2h2v2zm-1-7V1H1v12h6V7h6zm-7 5H2V8h4v4zm5-1H9V9h2v2zm3 0h-2V9h2v2zM5 9H3v2h2V9zm1-3H2V2h4v4zm6 0H8V2h4v4zM5 3H3v2h2V3zm6 0H9v2h2V3z"
style={{ fill: fill || colors.blue }}
);
}
-function SubProjectIcon({ fill, ...iconProps }: IconProps) {
+function SubProjectIcon({ fill, ariaLabel, ...iconProps }: IconProps) {
return (
- <Icon {...iconProps}>
+ <Icon {...iconProps} ariaLabel={ariaLabel}>
<path
d="M8 9V8h6v1h1v1h1v6H6v-6h1V9h1zm7 2H7v4h8v-4zm-1-7v3h-1V5H1v7h4v1H0V4h14zm-1-2v1.5h-1V3H2v.5H1V2h12zm-1-2v1.5h-1V1H3v.5H2V0h10z"
style={{ fill: fill || colors.blue }}
);
}
-function UnitTestIcon({ fill, ...iconProps }: IconProps) {
+function UnitTestIcon({ fill, ariaLabel, ...iconProps }: IconProps) {
return (
- <Icon {...iconProps}>
+ <Icon {...iconProps} ariaLabel={ariaLabel}>
<path
d="M14 15H2V1l7.997.02c1.013-.03 1.57.893 2.239 1.555.667.663 1.75 1.47 1.763 2.42H14V15zM9 2H3v12h10V6H9V2zM7 8l-3 2.5L7 13V8zm1 5l3-2.5L8 8v5zm2.012-8h3.008c-.012-.674-.78-1.258-1.27-1.752-.488-.495-.973-1.243-1.75-1.24v2.96l.012.032z"
style={{ fill: fill || colors.blue }}
code.open_component_page=Open Component's Page
code.search_placeholder=Search for files...
code.search_placeholder.portfolio=Search for projects and sub-portfolios...
+code.parent_folder=Parent folder
#------------------------------------------------------------------------------