--- /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 styled from '@emotion/styled';
+import tw from 'twin.macro';
+import { themeColor, themeContrast } from '../helpers/theme';
+import { ThemeColors } from '../types/theme';
+
+type BadgeVariant = 'default' | 'new' | 'deleted' | 'counter';
+
+const variantList: Record<BadgeVariant, ThemeColors> = {
+ default: 'badgeDefault',
+ new: 'badgeNew',
+ deleted: 'badgeDeleted',
+ counter: 'badgeCounter',
+};
+
+interface BadgeProps {
+ children: string | number;
+ className?: string;
+ title?: string;
+ variant?: BadgeVariant;
+}
+
+export default function Badge({ className, children, title, variant = 'default' }: BadgeProps) {
+ const commonProps = {
+ 'aria-label': title ?? children.toString(),
+ className,
+ role: 'status',
+ title,
+ };
+ if (variant === 'counter') {
+ return <StyledCounter {...commonProps}>{children}</StyledCounter>;
+ }
+ return (
+ <StyledBadge variantInfo={variantList[variant]} {...commonProps}>
+ {children}
+ </StyledBadge>
+ );
+}
+
+const StyledBadge = styled.span<{
+ variantInfo: ThemeColors;
+}>`
+ ${tw`sw-text-[0.75rem]`};
+ ${tw`sw-leading-[0.938rem]`};
+ ${tw`sw-font-semibold`};
+ ${tw`sw-inline-block`};
+ ${tw`sw-whitespace-nowrap`};
+ ${tw`sw-px-[0.125rem] sw-py-[0.03125rem]`};
+ ${tw`sw-rounded-1/2`};
+
+ color: ${({ variantInfo }) => themeContrast(variantInfo)};
+ background-color: ${({ variantInfo }) => themeColor(variantInfo)};
+ text-transform: uppercase;
+
+ &:empty {
+ ${tw`sw-hidden`}
+ }
+
+ .page-actions & {
+ ${tw`sw-my-1`};
+ ${tw`sw-mx-0`};
+ }
+`;
+
+const StyledCounter = styled.span`
+ ${tw`sw-text-[0.75rem]`};
+ ${tw`sw-font-regular`};
+ ${tw`sw-px-2`};
+ ${tw`sw-inline-flex`};
+ ${tw`sw-leading-[1.125rem]`};
+ ${tw`sw-items-center sw-justify-center`};
+ ${tw`sw-rounded-pill`};
+
+ color: ${themeContrast('badgeCounter')};
+ background-color: ${themeColor('badgeCounter')};
+
+ &:empty {
+ ${tw`sw-hidden`}
+ }
+`;
placeholder: string;
searchInputAriaLabel: string;
size?: InputSizeKeys;
- tooShortText: string;
+ tooShortText?: string;
value?: string;
}
id={id}
onMouseDown={onMouseDown}
style={{ '--inputSize': INPUT_SIZES[size] }}
- title={tooShort && isDefined(minLength) ? tooShortText : ''}
+ title={tooShort && tooShortText && isDefined(minLength) ? tooShortText : ''}
>
<StyledInputWrapper className="sw-flex sw-items-center">
<input
/>
)}
- {tooShort && isDefined(minLength) && (
+ {tooShort && tooShortText && isDefined(minLength) && (
<StyledNote className="sw-ml-1" role="note">
{tooShortText}
</StyledNote>
--- /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 { useTheme } from '@emotion/react';
+import React from 'react';
+import { theme as twTheme } from 'twin.macro';
+import { BasePlacement, PopupPlacement } from '../helpers/positioning';
+import { themeColor, themeContrast } from '../helpers/theme';
+
+const SIZE = {
+ sm: twTheme('spacing.4'),
+ md: twTheme('spacing.6'),
+ xl: twTheme('spacing.16'),
+};
+
+type QGStatus = 'ERROR' | 'OK' | 'NONE' | 'NOT_COMPUTED';
+
+interface Props {
+ ariaLabel?: string;
+ className?: string;
+ size?: keyof typeof SIZE;
+ status: QGStatus;
+ tooltipPlacement?: BasePlacement;
+ withTooltip?: boolean;
+}
+
+const RX_4 = 4;
+const RX_2 = 2;
+
+export default function QualityGateIndicator(props: Props) {
+ const {
+ className,
+ size = 'md',
+ status,
+ tooltipPlacement = PopupPlacement.Right,
+ withTooltip,
+ ariaLabel,
+ } = props;
+ const iconProps = {
+ className,
+ height: SIZE[size],
+ rx: size === 'xl' ? RX_4 : RX_2,
+ size,
+ tooltipPlacement,
+ width: SIZE[size],
+ withTooltip,
+ };
+ let StatusComponent: React.ReactNode;
+ switch (status) {
+ case 'NONE':
+ case 'NOT_COMPUTED':
+ StatusComponent = <QGNotComputed {...iconProps} />;
+ break;
+ case 'OK':
+ StatusComponent = <QGPassed {...iconProps} />;
+ break;
+ case 'ERROR':
+ StatusComponent = <QGFailed {...iconProps} />;
+ break;
+ }
+ return <div aria-label={ariaLabel}>{StatusComponent}</div>;
+}
+
+const COMMON_PROPS = {
+ fill: 'none',
+ role: 'status',
+ xmlns: 'http://www.w3.org/2000/svg',
+};
+
+interface IconProps {
+ className?: string;
+ height: string;
+ rx: number;
+ size: keyof typeof SIZE;
+ tooltipPlacement?: BasePlacement;
+ width: string;
+ withTooltip?: boolean;
+}
+
+function QGNotComputed({
+ className,
+ rx,
+ size,
+ tooltipPlacement,
+ withTooltip,
+ ...sizeProps
+}: IconProps) {
+ const theme = useTheme();
+ const contrastColor = themeContrast('qgIndicatorNotComputed')({ theme });
+ return (
+ <svg className={className} {...COMMON_PROPS} {...sizeProps}>
+ <rect fill={themeColor('qgIndicatorNotComputed')({ theme })} rx={rx} {...sizeProps} />
+ {
+ {
+ xl: <path d="M42 31v3H22v-3z" fill={contrastColor} />,
+ md: <path d="M18 12v1.5H6V12z" fill={contrastColor} />,
+ sm: <path d="M12 8v1H4V8z" fill={contrastColor} />,
+ }[size]
+ }
+ </svg>
+ );
+}
+
+function QGPassed({ className, rx, size, tooltipPlacement, withTooltip, ...sizeProps }: IconProps) {
+ const theme = useTheme();
+ const contrastColor = themeContrast('qgIndicatorPassed')({ theme });
+ return (
+ <svg className={className} {...COMMON_PROPS} {...sizeProps}>
+ <rect fill={themeColor('qgIndicatorPassed')({ theme })} rx={rx} {...sizeProps} />
+ {
+ {
+ xl: (
+ <>
+ <path d="M38.974 25 41 27.026 28.847 39.178l-2.025-2.025z" fill={contrastColor} />
+ <path d="M30.974 37.153 28.95 39.18 22 32.229l2.026-2.025z" fill={contrastColor} />
+ </>
+ ),
+ md: (
+ <>
+ <path d="m16.95 7.5 1.308 1.307-7.84 7.84-1.308-1.306z" fill={contrastColor} />
+ <path d="m11.79 15.34-1.307 1.307-4.484-4.483 1.307-1.306z" fill={contrastColor} />
+ </>
+ ),
+ sm: (
+ <>
+ <path d="m11.3 5 .871.87-5.227 5.228-.87-.871z" fill={contrastColor} />
+ <path d="m7.86 10.227-.872.871L4 8.11l.871-.871z" fill={contrastColor} />
+ </>
+ ),
+ }[size]
+ }
+ </svg>
+ );
+}
+
+function QGFailed({ className, rx, size, tooltipPlacement, withTooltip, ...sizeProps }: IconProps) {
+ const theme = useTheme();
+ const contrastColor = themeContrast('qgIndicatorFailed')({ theme });
+ return (
+ <svg className={className} {...COMMON_PROPS} {...sizeProps}>
+ <rect fill={themeColor('qgIndicatorFailed')({ theme })} rx={rx} {...sizeProps} />
+ {
+ {
+ xl: (
+ <>
+ <path d="m37.153 25 2.026 2.026-12.153 12.152L25 37.153z" fill={contrastColor} />
+ <path d="m39.178 37.153-2.025 2.026L25 27.026 27.026 25z" fill={contrastColor} />
+ </>
+ ),
+ md: (
+ <>
+ <path d="m15.34 7.5 1.307 1.307-7.84 7.84L7.5 15.34z" fill={contrastColor} />
+ <path d="m16.647 15.34-1.307 1.307-7.84-7.84L8.806 7.5z" fill={contrastColor} />
+ </>
+ ),
+ sm: (
+ <>
+ <path d="m10.227 5 .871.871-5.227 5.227L5 10.227z" fill={contrastColor} />
+ <path d="m11.098 10.227-.871.87L5 5.872 5.87 5z" fill={contrastColor} />
+ </>
+ ),
+ }[size]
+ }
+ </svg>
+ );
+}
import tw from 'twin.macro';
import { themeColor, themeContrast } from '../helpers/theme';
-interface MainTextProps {
+interface TextBoldProps {
+ className?: string;
match?: string;
name: string;
}
-export function SearchText({ match, name }: MainTextProps) {
+export function TextBold({ match, name, className }: TextBoldProps) {
return match ? (
<StyledText
+ className={className}
// Safe: comes from the search engine, that injects bold tags into component names
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: match }}
/>
) : (
- <StyledText title={name}>{name}</StyledText>
+ <StyledText className={className} title={name}>
+ {name}
+ </StyledText>
);
}
-export function TextMuted({ text }: { text: string }) {
- return <StyledMutedText title={text}>{text}</StyledMutedText>;
+export function TextMuted({ text, className }: { className?: string; text: string }) {
+ return (
+ <StyledMutedText className={className} title={text}>
+ {text}
+ </StyledMutedText>
+ );
}
export const StyledText = styled.span`
--- /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 { screen } from '@testing-library/react';
+import { render } from '../../helpers/testUtils';
+import Badge from '../Badge';
+
+it('renders badge correctly', () => {
+ render(<Badge>foo</Badge>);
+ expect(screen.getByRole('status')).toBeInTheDocument();
+});
+
+it('renders counter correctly', () => {
+ render(<Badge variant="counter">23</Badge>);
+ expect(screen.getByRole('status')).toHaveAttribute('aria-label', '23');
+});
onChange={jest.fn()}
placeholder="placeholder"
searchInputAriaLabel=""
- tooShortText=""
+ tooShortText="too short"
value="foo"
{...props}
/>
--- /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 { screen } from '@testing-library/react';
+import { render } from '../../helpers/testUtils';
+import { FCProps } from '../../types/misc';
+import QualityGateIndicator from '../QualityGateIndicator';
+
+const SIZE_VS_WIDTH = {
+ sm: '1rem',
+ md: '1.5rem',
+ xl: '4rem',
+};
+
+it.each([
+ ['OK', 'sm'],
+ ['OK', 'md'],
+ ['OK', 'xl'],
+ ['ERROR', 'sm'],
+ ['ERROR', 'md'],
+ ['ERROR', 'xl'],
+ ['NONE', 'sm'],
+ ['NONE', 'md'],
+ ['NONE', 'xl'],
+])(
+ 'render the %s status and %s size correctly',
+ (status: 'ERROR' | 'OK' | 'NONE' | 'NOT_COMPUTED', size: 'sm' | 'md' | 'xl') => {
+ setupWithProps({ status, size });
+
+ expect(screen.getByRole('status')).toHaveAttribute('width', SIZE_VS_WIDTH[size]);
+ }
+);
+
+it('should display tooltip', () => {
+ const { rerender } = setupWithProps({
+ status: 'NONE',
+ withTooltip: true,
+ ariaLabel: 'label-none',
+ });
+ expect(screen.getByLabelText('label-none')).toBeInTheDocument();
+
+ rerender(<QualityGateIndicator ariaLabel="label-ok" status="OK" withTooltip={true} />);
+ expect(screen.getByLabelText('label-ok')).toBeInTheDocument();
+
+ rerender(<QualityGateIndicator ariaLabel="label-error" status="ERROR" withTooltip={true} />);
+ expect(screen.getByLabelText('label-error')).toBeInTheDocument();
+});
+
+function setupWithProps(props: Partial<FCProps<typeof QualityGateIndicator>> = {}) {
+ return render(<QualityGateIndicator status="OK" {...props} />);
+}
import { screen } from '@testing-library/react';
import { render } from '../../helpers/testUtils';
-import { SearchText, TextMuted } from '../Text';
+import { TextBold, TextMuted } from '../Text';
it('should render SearchText', () => {
- render(<SearchText match="hi" name="hiya" />);
+ render(<TextBold match="hi" name="hiya" />);
expect(screen.getByText('hi')).toHaveStyle({
'font-weight': '600',
--- /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 { GitBranchIcon } from '@primer/octicons-react';
+import { OcticonHoc } from './Icon';
+
+export default OcticonHoc(GitBranchIcon, 'BranchIcon');
--- /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 { useTheme } from '@emotion/react';
+import { themeColor, themeContrast } from '../../helpers/theme';
+import { CustomIcon, IconProps } from './Icon';
+
+export default function HelperHintIcon({ fill = 'currentColor', ...iconProps }: IconProps) {
+ const theme = useTheme();
+ return (
+ <CustomIcon {...iconProps}>
+ <circle cx="8" cy="8" fill={themeColor('iconHelperHint')({ theme })} r="7" />
+ <path
+ d="M6.82812 10.2301h1.61506v-.1449c.00852-.83094.30682-1.21872.98012-1.62355.7969-.47301 1.3168-1.09943 1.3168-2.10085C10.7401 4.86932 9.53835 4 7.84659 4 6.29972 4 5.03835 4.80966 5 6.5142h1.73864c.02556-.6946.54119-1.06534 1.09943-1.06534.57528 0 1.03977.38353 1.03977.97586 0 .55823-.40483.92897-.92898 1.26136-.71591.4517-1.11647.90767-1.12074 2.39912v.1449Zm.83949 2.7273c.54546 0 1.01847-.456 1.02273-1.0227-.00426-.5583-.47727-1.0142-1.02273-1.0142-.5625 0-1.02698.4559-1.02272 1.0142-.00426.5667.46022 1.0227 1.02272 1.0227Z"
+ fill={themeContrast('iconHelperHint')({ theme })}
+ />
+ </CustomIcon>
+ );
+}
--- /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 { useTheme } from '@emotion/react';
+import { themeColor } from '../../helpers/theme';
+import { CustomIcon, IconProps } from './Icon';
+
+export default function MainBranchIcon({ fill = 'currentColor', ...iconProps }: IconProps) {
+ const theme = useTheme();
+ return (
+ <CustomIcon {...iconProps}>
+ <path
+ clipRule="evenodd"
+ d="M8.251 2.49932a.75003.75003 0 0 0-.75.75.75001.75001 0 1 0 .75-.75Zm-2.25.75A2.25004 2.25004 0 0 1 7.21713 1.2516a2.25 2.25 0 0 1 2.33319.16148 2.24917 2.24917 0 0 1 .76538.94287c.1639.37851.2206.79478.1639 1.20334a2.25026 2.25026 0 0 1-.48534 1.11323 2.25 2.25 0 0 1-.99326.6988v5.25598c.50069.177.92271.5252 1.1915.9832.2687.458.3669.9963.2771 1.5197a2.25092 2.25092 0 0 1-2.2186 1.8705 2.25092 2.25092 0 0 1-2.21861-1.8705 2.25115 2.25115 0 0 1 .27716-1.5197 2.2514 2.2514 0 0 1 1.19145-.9832V5.37132a2.24999 2.24999 0 0 1-1.5-2.122Zm2.25 8.74998a.74985.74985 0 0 0-.53033.2197.74987.74987 0 0 0-.21967.5303c0 .1989.07902.3897.21967.5304a.75017.75017 0 0 0 1.06066 0 .75023.75023 0 0 0 .21967-.5304.74983.74983 0 0 0-.21967-.5303.74981.74981 0 0 0-.53033-.2197Z"
+ fill={themeColor(fill)({ theme })}
+ fillRule="evenodd"
+ />
+ </CustomIcon>
+ );
+}
--- /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 { GitPullRequestIcon } from '@primer/octicons-react';
+import { OcticonHoc } from './Icon';
+
+export default OcticonHoc(GitPullRequestIcon, 'PullRequestIcon');
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+export { default as BranchIcon } from './BranchIcon';
export { default as ChevronDownIcon } from './ChevronDownIcon';
export { default as ClockIcon } from './ClockIcon';
export { FlagErrorIcon } from './FlagErrorIcon';
export { FlagInfoIcon } from './FlagInfoIcon';
export { FlagSuccessIcon } from './FlagSuccessIcon';
export { FlagWarningIcon } from './FlagWarningIcon';
+export { default as HelperHintIcon } from './HelperHintIcon';
export { default as HomeFillIcon } from './HomeFillIcon';
export { default as HomeIcon } from './HomeIcon';
+export { default as MainBranchIcon } from './MainBranchIcon';
export { default as MenuHelpIcon } from './MenuHelpIcon';
export { default as MenuSearchIcon } from './MenuSearchIcon';
export { default as OpenNewTabIcon } from './OpenNewTabIcon';
+export { default as PullRequestIcon } from './PullRequestIcon';
export { default as StarIcon } from './StarIcon';
*/
export * from './Avatar';
+export { default as Badge } from './Badge';
export * from './buttons';
export { default as DeferredSpinner } from './DeferredSpinner';
export { default as Dropdown } from './Dropdown';
export * from './icons';
export { default as InputSearch } from './InputSearch';
export * from './InteractiveIcon';
+export * from './Link';
export { default as Link } from './Link';
export * from './MainAppBar';
export * from './MainMenu';
export * from './MainMenuItem';
export * from './NavBarTabs';
export * from './popups';
+export { default as QualityGateIndicator } from './QualityGateIndicator';
export * from './SonarQubeLogo';
export * from './Text';
export { default as Tooltip } from './Tooltip';
export * from './components';
export * from './helpers';
export * from './theme';
+export * from './types/theme';
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
-import { ClockIcon, ItemLink, SearchText, TextMuted } from 'design-system';
+import { ClockIcon, ItemLink, TextBold, TextMuted } from 'design-system';
import * as React from 'react';
import FavoriteIcon from '../../../components/icons/FavoriteIcon';
import { translate } from '../../../helpers/l10n';
to={to}
>
<div className="sw-flex sw-justify-between sw-items-center sw-w-full">
- <SearchText match={component.match} name={component.name} />
+ <TextBold match={component.match} name={component.name} />
<div className="sw-ml-2">
{component.isFavorite && <FavoriteIcon favorite={true} size={16} />}
{!component.isFavorite && component.isRecentlyBrowsed && (
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { last } from 'lodash';
+import { HoverLink, TextMuted } from 'design-system';
import * as React from 'react';
-import Link from '../../../../components/common/Link';
-import QualifierIcon from '../../../../components/icons/QualifierIcon';
-import { isMainBranch } from '../../../../helpers/branch-like';
+import Favorite from '../../../../components/controls/Favorite';
import { getComponentOverviewUrl } from '../../../../helpers/urls';
-import { BranchLike } from '../../../../types/branch-like';
import { Component } from '../../../../types/types';
-import { colors } from '../../../theme';
+import { CurrentUser, isLoggedIn } from '../../../../types/users';
export interface BreadcrumbProps {
component: Component;
- currentBranchLike: BranchLike | undefined;
+ currentUser: CurrentUser;
}
export function Breadcrumb(props: BreadcrumbProps) {
- const {
- component: { breadcrumbs },
- currentBranchLike,
- } = props;
- const lastBreadcrumbElement = last(breadcrumbs);
- const isNotMainBranch = currentBranchLike && !isMainBranch(currentBranchLike);
+ const { component, currentUser } = props;
return (
- <div className="big flex-shrink display-flex-center">
- {breadcrumbs.map((breadcrumbElement, i) => {
- const isFirst = i === 0;
- const isNotLast = i < breadcrumbs.length - 1;
+ <div className="sw-text-sm sw-flex sw-justify-center">
+ {component.breadcrumbs.map((breadcrumbElement, i) => {
+ const isNotLast = i < component.breadcrumbs.length - 1;
const isLast = !isNotLast;
- const showQualifierIcon = isFirst && lastBreadcrumbElement;
-
- const name =
- isNotMainBranch || isNotLast ? (
- <>
- {showQualifierIcon && !isNotMainBranch && (
- <QualifierIcon
- className="spacer-right"
- qualifier={lastBreadcrumbElement.qualifier}
- fill={colors.neutral800}
- />
- )}
- <Link
- className="link-no-underline"
- to={getComponentOverviewUrl(breadcrumbElement.key, breadcrumbElement.qualifier)}
- >
- {showQualifierIcon && isNotMainBranch && (
- <QualifierIcon
- className="spacer-right"
- qualifier={lastBreadcrumbElement.qualifier}
- fill={colors.primary}
- />
- )}
- {breadcrumbElement.name}
- </Link>
- </>
- ) : (
- <>
- {showQualifierIcon && (
- <QualifierIcon
- className="spacer-right"
- qualifier={lastBreadcrumbElement.qualifier}
- fill={colors.neutral800}
- />
- )}
- {breadcrumbElement.name}
- </>
- );
return (
- <span className="flex-shrink display-flex-center" key={breadcrumbElement.key}>
- {isLast ? (
- <h1 className="text-ellipsis" title={breadcrumbElement.name}>
- {name}
- </h1>
- ) : (
- <span className="text-ellipsis" title={breadcrumbElement.name}>
- {name}
- </span>
+ <div key={breadcrumbElement.key} className="sw-flex">
+ {isLast && isLoggedIn(currentUser) && (
+ <Favorite
+ className="sw-mr-2"
+ component={component.key}
+ favorite={Boolean(component.isFavorite)}
+ qualifier={component.qualifier}
+ />
)}
- {isNotLast && <span className="slash-separator" />}
- </span>
+ <HoverLink
+ blurAfterClick={true}
+ className="js-project-link sw-flex"
+ key={breadcrumbElement.name}
+ title={breadcrumbElement.name}
+ to={getComponentOverviewUrl(breadcrumbElement.key, breadcrumbElement.qualifier)}
+ >
+ <TextMuted text={breadcrumbElement.name} />
+ </HoverLink>
+ {isNotLast && <span className="slash-separator sw-mx-2.5" />}
+ </div>
);
})}
</div>
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import Favorite from '../../../../components/controls/Favorite';
import { ProjectAlmBindingResponse } from '../../../../types/alm-settings';
import { BranchLike } from '../../../../types/branch-like';
import { Component } from '../../../../types/types';
-import { CurrentUser, isLoggedIn } from '../../../../types/users';
+import { CurrentUser } from '../../../../types/users';
import withCurrentUserContext from '../../current-user/withCurrentUserContext';
import BranchLikeNavigation from './branch-like/BranchLikeNavigation';
-import CurrentBranchLikeMergeInformation from './branch-like/CurrentBranchLikeMergeInformation';
import { Breadcrumb } from './Breadcrumb';
export interface HeaderProps {
const { branchLikes, component, currentBranchLike, currentUser, projectBinding } = props;
return (
- <div className="display-flex-center flex-shrink">
- <Breadcrumb component={component} currentBranchLike={currentBranchLike} />
- {isLoggedIn(currentUser) && (
- <Favorite
- className="spacer-left"
- component={component.key}
- favorite={Boolean(component.isFavorite)}
- qualifier={component.qualifier}
- />
- )}
+ <div className="sw-flex sw-flex-shrink sw-items-center">
+ <Breadcrumb component={component} currentUser={currentUser} />
{currentBranchLike && (
<>
+ <span className="slash-separator sw-ml-2" />
<BranchLikeNavigation
branchLikes={branchLikes}
component={component}
currentBranchLike={currentBranchLike}
projectBinding={projectBinding}
/>
- <CurrentBranchLikeMergeInformation currentBranchLike={currentBranchLike} />
</>
)}
</div>
+++ /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 { screen } from '@testing-library/react';
-import * as React from 'react';
-import { mockBranch, mockMainBranch } from '../../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../../helpers/mocks/component';
-import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
-import { ComponentQualifier } from '../../../../../types/component';
-import { Breadcrumb, BreadcrumbProps } from '../Breadcrumb';
-
-it('should render correctly', () => {
- renderBreadcrumb();
- expect(screen.getByRole('link', { name: 'Parent portfolio' })).toBeInTheDocument();
- expect(screen.getByRole('heading', { name: 'Child portfolio' })).toBeInTheDocument();
-});
-
-it('should render correctly when not on a main branch', () => {
- renderBreadcrumb({
- component: mockComponent({
- breadcrumbs: [
- {
- key: 'project',
- name: 'My Project',
- qualifier: ComponentQualifier.Project,
- },
- ],
- }),
- currentBranchLike: mockBranch(),
- });
- expect(
- screen.getByRole('link', { name: `qualifier.${ComponentQualifier.Project} My Project` })
- ).toBeInTheDocument();
-});
-
-function renderBreadcrumb(props: Partial<BreadcrumbProps> = {}) {
- return renderComponent(
- <Breadcrumb
- component={mockComponent({
- breadcrumbs: [
- {
- key: 'parent-portfolio',
- name: 'Parent portfolio',
- qualifier: ComponentQualifier.Portfolio,
- },
- {
- key: 'child-portfolio',
- name: 'Child portfolio',
- qualifier: ComponentQualifier.SubPortfolio,
- },
- ],
- })}
- currentBranchLike={mockMainBranch()}
- {...props}
- />
- );
-}
* 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 { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import * as React from 'react';
-import Favorite from '../../../../../components/controls/Favorite';
-import { mockSetOfBranchAndPullRequest } from '../../../../../helpers/mocks/branch-like';
+import {
+ mockMainBranch,
+ mockPullRequest,
+ mockSetOfBranchAndPullRequestForBranchSelector,
+} from '../../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../../helpers/mocks/component';
-import { mockCurrentUser } from '../../../../../helpers/testMocks';
+import { mockCurrentUser, mockLoggedInUser } from '../../../../../helpers/testMocks';
+import { renderApp } from '../../../../../helpers/testReactTestingUtils';
+import { AlmKeys } from '../../../../../types/alm-settings';
+import { Feature } from '../../../../../types/features';
+import { BranchStatusContext } from '../../../branch-status/BranchStatusContext';
import { Header, HeaderProps } from '../Header';
-it('should render correctly', () => {
- const wrapper = shallowRender({ currentUser: mockCurrentUser({ isLoggedIn: true }) });
- expect(wrapper).toMatchSnapshot();
+jest.mock('../../../../../api/favorites', () => ({
+ addFavorite: jest.fn().mockResolvedValue({}),
+ removeFavorite: jest.fn().mockResolvedValue({}),
+}));
+
+it('should render correctly when there is only 1 branch', () => {
+ renderHeader({ branchLikes: [mockMainBranch()] });
+ expect(screen.getByText('project')).toBeInTheDocument();
+ expect(screen.getByLabelText('help-tooltip')).toBeInTheDocument();
+ expect(
+ screen.getByRole('button', { name: 'branch-1 overview.quality_gate_x.OK' })
+ ).toBeDisabled();
+});
+
+it('should render correctly when there are multiple branch', async () => {
+ const user = userEvent.setup();
+ renderHeader();
+ expect(screen.getByRole('button', { name: 'branch-1 overview.quality_gate_x.OK' })).toBeEnabled();
+ expect(screen.queryByLabelText('help-tooltip')).not.toBeInTheDocument();
+
+ await user.click(screen.getByRole('button', { name: 'branch-1 overview.quality_gate_x.OK' }));
+ expect(screen.getByText('branches.main_branch')).toBeInTheDocument();
+ expect(
+ screen.getByRole('menuitem', { name: 'branch-2 overview.quality_gate_x.ERROR ERROR' })
+ ).toBeInTheDocument();
+ expect(screen.getByRole('menuitem', { name: 'branch-3' })).toBeInTheDocument();
+ expect(screen.getByRole('menuitem', { name: '1 – PR-1' })).toBeInTheDocument();
+ expect(screen.getByRole('menuitem', { name: '2 – PR-2' })).toBeInTheDocument();
+
+ await user.click(
+ screen.getByRole('menuitem', { name: 'branch-2 overview.quality_gate_x.ERROR ERROR' })
+ );
+ expect(screen.getByText('/dashboard?branch=branch-2&id=my-project')).toBeInTheDocument();
});
-it('should not render favorite button if the user is not logged in', () => {
- const wrapper = shallowRender();
- expect(wrapper.find(Favorite).exists()).toBe(false);
+it('should show manage branch and pull request button for admin', async () => {
+ const user = userEvent.setup();
+ renderHeader({
+ currentUser: mockLoggedInUser(),
+ component: mockComponent({
+ configuration: { showSettings: true },
+ breadcrumbs: [{ name: 'project', key: 'project', qualifier: 'TRK' }],
+ }),
+ });
+ await user.click(screen.getByRole('button', { name: 'branch-1 overview.quality_gate_x.OK' }));
+
+ expect(screen.getByRole('link', { name: 'branch_like_navigation.manage' })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'branch_like_navigation.manage' })).toHaveAttribute(
+ 'href',
+ '/project/branches?id=my-project'
+ );
});
-function shallowRender(props?: Partial<HeaderProps>) {
- const branchLikes = mockSetOfBranchAndPullRequest();
-
- return shallow(
- <Header
- branchLikes={branchLikes}
- component={mockComponent()}
- currentBranchLike={branchLikes[0]}
- currentUser={mockCurrentUser()}
- {...props}
- />
+it('should render favorite button if the user is logged in', async () => {
+ const user = userEvent.setup();
+ renderHeader({ currentUser: mockLoggedInUser() });
+ expect(screen.getByRole('button', { name: 'favorite.action.TRK.add' })).toBeInTheDocument();
+
+ await user.click(screen.getByRole('button', { name: 'favorite.action.TRK.add' }));
+ expect(
+ await screen.findByRole('button', { name: 'favorite.action.TRK.remove' })
+ ).toBeInTheDocument();
+
+ await user.click(screen.getByRole('button', { name: 'favorite.action.TRK.remove' }));
+ expect(screen.getByRole('button', { name: 'favorite.action.TRK.add' })).toBeInTheDocument();
+});
+
+it.each([['github'], ['gitlab'], ['bitbucket'], ['azure']])(
+ 'should show correct %s links for a PR',
+ (alm: string) => {
+ renderHeader({
+ currentUser: mockLoggedInUser(),
+ currentBranchLike: mockPullRequest({
+ key: '1',
+ title: 'PR-1',
+ status: { qualityGateStatus: 'OK' },
+ url: alm,
+ }),
+ branchLikes: [
+ mockPullRequest({
+ key: '1',
+ title: 'PR-1',
+ status: { qualityGateStatus: 'OK' },
+ url: alm,
+ }),
+ ],
+ });
+ const image = screen.getByAltText(alm);
+ expect(image).toBeInTheDocument();
+ expect(image).toHaveAttribute('src', `/images/alm/${alm}.svg`);
+ }
+);
+
+it('should show the correct help tooltip for applications', () => {
+ renderHeader({
+ currentUser: mockLoggedInUser(),
+ component: mockComponent({
+ configuration: { showSettings: true },
+ breadcrumbs: [{ name: 'project', key: 'project', qualifier: 'APP' }],
+ qualifier: 'APP',
+ }),
+ branchLikes: [mockMainBranch()],
+ });
+ expect(screen.getByText('application.branches.help')).toBeInTheDocument();
+ expect(screen.getByText('application.branches.link')).toBeInTheDocument();
+});
+
+it('should show the correct help tooltip when branch support is not enabled', () => {
+ renderHeader(
+ {
+ currentUser: mockLoggedInUser(),
+ projectBinding: { alm: AlmKeys.GitLab, key: 'key', monorepo: true },
+ },
+ []
+ );
+ expect(screen.getByText('branch_like_navigation.no_branch_support.title.mr')).toBeInTheDocument();
+ expect(
+ screen.getByText('branch_like_navigation.no_branch_support.content_x.mr.alm.gitlab')
+ ).toBeInTheDocument();
+});
+
+function renderHeader(props?: Partial<HeaderProps>, featureList = [Feature.BranchSupport]) {
+ const branchLikes = mockSetOfBranchAndPullRequestForBranchSelector();
+
+ return renderApp(
+ '/',
+ <BranchStatusContext.Provider
+ value={{
+ branchStatusByComponent: {
+ 'my-project': {
+ 'branch-branch-1': {
+ status: 'OK',
+ },
+ 'branch-branch-2': {
+ status: 'ERROR',
+ },
+ },
+ },
+ fetchBranchStatus: () => {
+ /*noop*/
+ },
+ updateBranchStatus: () => {
+ /*noop*/
+ },
+ }}
+ >
+ <Header
+ branchLikes={branchLikes}
+ component={mockComponent({
+ breadcrumbs: [{ name: 'project', key: 'project', qualifier: 'TRK' }],
+ })}
+ currentBranchLike={branchLikes[0]}
+ currentUser={mockCurrentUser()}
+ {...props}
+ />
+ </BranchStatusContext.Provider>,
+ { featureList }
);
}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div
- className="display-flex-center flex-shrink"
->
- <Breadcrumb
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- currentBranchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- }
- }
- />
- <Favorite
- className="spacer-left"
- component="my-project"
- favorite={false}
- qualifier="TRK"
- />
- <withAvailableFeaturesContext(Component)
- branchLikes={
- [
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- },
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-1",
- },
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- },
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "1",
- "target": "master",
- "title": "PR-1",
- },
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-12",
- },
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "2",
- "target": "master",
- "title": "PR-2",
- },
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-3",
- },
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-2",
- },
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "isOrphan": true,
- "key": "2",
- "target": "llb-100",
- "title": "PR-2",
- },
- ]
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- currentBranchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- }
- }
- />
- <Memo(CurrentBranchLikeMergeInformation)
- currentBranchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- }
- }
- />
-</div>
-`;
--- /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 { HelperHintIcon } from 'design-system';
+import React from 'react';
+import DocumentationTooltip from '../../../../../components/common/DocumentationTooltip';
+import Link from '../../../../../components/common/Link';
+import HelpTooltip from '../../../../../components/controls/HelpTooltip';
+import { translate, translateWithParameters } from '../../../../../helpers/l10n';
+import { getApplicationAdminUrl } from '../../../../../helpers/urls';
+import { ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
+import { Component } from '../../../../../types/types';
+
+interface Props {
+ component: Component;
+ isApplication: boolean;
+ projectBinding?: ProjectAlmBindingResponse;
+ hasManyBranches: boolean;
+ canAdminComponent?: boolean;
+ branchSupportEnabled: boolean;
+ isGitLab: boolean;
+}
+
+export default function BranchHelpTooltip({
+ component,
+ isApplication,
+ projectBinding,
+ hasManyBranches,
+ canAdminComponent,
+ branchSupportEnabled,
+ isGitLab,
+}: Props) {
+ const helpIcon = <HelperHintIcon aria-label="help-tooltip" />;
+
+ if (isApplication) {
+ if (!hasManyBranches && canAdminComponent) {
+ return (
+ <HelpTooltip
+ overlay={
+ <>
+ <p>{translate('application.branches.help')}</p>
+ <hr className="spacer-top spacer-bottom" />
+ <Link to={getApplicationAdminUrl(component.key)}>
+ {translate('application.branches.link')}
+ </Link>
+ </>
+ }
+ >
+ {helpIcon}
+ </HelpTooltip>
+ );
+ }
+ } else {
+ if (!branchSupportEnabled) {
+ return (
+ <DocumentationTooltip
+ content={
+ projectBinding !== undefined
+ ? translateWithParameters(
+ `branch_like_navigation.no_branch_support.content_x.${isGitLab ? 'mr' : 'pr'}`,
+ translate('alm', projectBinding.alm)
+ )
+ : translate('branch_like_navigation.no_branch_support.content')
+ }
+ data-test="branches-support-disabled"
+ links={[
+ {
+ href: 'https://www.sonarsource.com/plans-and-pricing/developer/',
+ label: translate('learn_more'),
+ doc: false,
+ },
+ ]}
+ title={
+ projectBinding !== undefined
+ ? translate('branch_like_navigation.no_branch_support.title', isGitLab ? 'mr' : 'pr')
+ : translate('branch_like_navigation.no_branch_support.title')
+ }
+ >
+ {helpIcon}
+ </DocumentationTooltip>
+ );
+ }
+
+ if (!hasManyBranches) {
+ return (
+ <DocumentationTooltip
+ content={translate('branch_like_navigation.only_one_branch.content')}
+ data-test="only-one-branch-like"
+ links={[
+ {
+ href: '/analyzing-source-code/branches/branch-analysis/',
+ label: translate('branch_like_navigation.only_one_branch.documentation'),
+ },
+ {
+ href: '/analyzing-source-code/pull-request-analysis',
+ label: translate('branch_like_navigation.only_one_branch.pr_analysis'),
+ },
+ {
+ href: `/tutorials?id=${component.key}`,
+ label: translate('branch_like_navigation.tutorial_for_ci'),
+ inPlace: true,
+ doc: false,
+ },
+ ]}
+ title={translate('branch_like_navigation.only_one_branch.title')}
+ >
+ {helpIcon}
+ </DocumentationTooltip>
+ );
+ }
+ }
+
+ return null;
+}
+++ /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.
- */
-.branch-like-navigation-toggler {
- padding: 4px 8px;
- border: 1px solid transparent;
- border-radius: 2px;
-}
-
-.branch-like-navigation-toggler:hover {
- border-color: var(--blacka38);
- color: inherit !important;
-}
-
-.branch-like-navigation-toggler:active,
-.branch-like-navigation-toggler.open {
- border-color: var(--primary);
-}
-
-.branch-like-navigation-toggler-container {
- height: 26px;
-}
-
-.branch-like-navigation-toggler-container .popup {
- min-width: 430px;
- max-width: 650px;
-}
-
-.branch-like-navigation-menu .search-box-container {
- padding: var(--gridSize);
-}
-
-.branch-like-navigation-menu .search-box-container .search-box,
-.branch-like-navigation-menu .search-box-container .search-box-input {
- max-width: initial !important;
-}
-
-.branch-like-navigation-menu .item-list {
- padding-bottom: var(--gridSize);
- max-height: 300px;
- overflow-y: auto;
-}
-
-.branch-like-navigation-menu .item {
- padding: calc(var(--gridSize) / 2) var(--gridSize);
-}
-
-.branch-like-navigation-menu .item.header {
- color: var(--secondFontColor);
-}
-
-.branch-like-navigation-menu .item:not(.header):hover,
-.branch-like-navigation-menu .item:not(.header).active {
- background-color: var(--barBackgroundColor);
- cursor: pointer;
-}
-
-.branch-like-navigation-menu .hint-container {
- padding: var(--gridSize);
- background-color: var(--barBackgroundColor);
- border-top: 1px solid var(--barBorderColor);
-}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
+import { ButtonSecondary, PopupPlacement, PopupZLevel, PortalPopup } from 'design-system';
import * as React from 'react';
-import { ButtonPlain } from '../../../../../components/controls/buttons';
-import Toggler from '../../../../../components/controls/Toggler';
-import { ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
+import OutsideClickHandler from '../../../../../components/controls/OutsideClickHandler';
+import { AlmKeys, ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
import { BranchLike } from '../../../../../types/branch-like';
+import { ComponentQualifier } from '../../../../../types/component';
import { Feature } from '../../../../../types/features';
import { Component } from '../../../../../types/types';
import withAvailableFeatures, {
WithAvailableFeaturesProps,
} from '../../../available-features/withAvailableFeatures';
-import './BranchLikeNavigation.css';
+import BranchHelpTooltip from './BranchHelpTooltip';
import CurrentBranchLike from './CurrentBranchLike';
import Menu from './Menu';
+import PRLink from './PRLink';
export interface BranchLikeNavigationProps extends WithAvailableFeaturesProps {
branchLikes: BranchLike[];
projectBinding,
} = props;
+ const isApplication = component.qualifier === ComponentQualifier.Application;
+ const isGitLab = projectBinding !== undefined && projectBinding.alm === AlmKeys.GitLab;
+
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
const branchSupportEnabled = props.hasFeature(Feature.BranchSupport);
-
- const canAdminComponent = configuration && configuration.showSettings;
+ const canAdminComponent = configuration?.showSettings;
const hasManyBranches = branchLikes.length >= 2;
const isMenuEnabled = branchSupportEnabled && hasManyBranches;
const currentBranchLikeElement = (
- <CurrentBranchLike
- branchesEnabled={branchSupportEnabled}
- component={component}
- currentBranchLike={currentBranchLike}
- hasManyBranches={hasManyBranches}
- projectBinding={projectBinding}
- />
+ <CurrentBranchLike component={component} currentBranchLike={currentBranchLike} />
);
return (
- <span
- className={classNames(
- 'big-spacer-left flex-0 branch-like-navigation-toggler-container display-flex-center',
- {
- dropdown: isMenuEnabled,
- }
- )}
- >
- {isMenuEnabled ? (
- <Toggler
- onRequestClose={() => setIsMenuOpen(false)}
- open={isMenuOpen}
+ <div className="sw-flex sw-items-center sw-ml-2 it__branch-like-navigation-toggler-container">
+ <OutsideClickHandler
+ onClickOutside={() => {
+ setIsMenuOpen(false);
+ }}
+ >
+ <PortalPopup
+ allowResizing={true}
overlay={
- <Menu
- branchLikes={branchLikes}
- canAdminComponent={canAdminComponent}
- component={component}
- currentBranchLike={currentBranchLike}
- onClose={() => setIsMenuOpen(false)}
- />
+ isMenuOpen && (
+ <Menu
+ branchLikes={branchLikes}
+ canAdminComponent={canAdminComponent}
+ component={component}
+ currentBranchLike={currentBranchLike}
+ onClose={() => {
+ setIsMenuOpen(false);
+ }}
+ />
+ )
}
+ placement={PopupPlacement.BottomLeft}
+ zLevel={PopupZLevel.Global}
>
- <ButtonPlain
- className={classNames('branch-like-navigation-toggler', { open: isMenuOpen })}
- onClick={() => setIsMenuOpen(!isMenuOpen)}
+ <ButtonSecondary
+ className="sw-max-w-abs-350"
+ onClick={() => {
+ setIsMenuOpen(!isMenuOpen);
+ }}
+ disabled={!isMenuEnabled}
aria-expanded={isMenuOpen}
aria-haspopup="menu"
>
{currentBranchLikeElement}
- </ButtonPlain>
- </Toggler>
- ) : (
- currentBranchLikeElement
- )}
- </span>
+ </ButtonSecondary>
+ </PortalPopup>
+ </OutsideClickHandler>
+
+ <div className="sw-ml-2">
+ <BranchHelpTooltip
+ component={component}
+ isApplication={isApplication}
+ projectBinding={projectBinding}
+ hasManyBranches={hasManyBranches}
+ canAdminComponent={canAdminComponent}
+ branchSupportEnabled={branchSupportEnabled}
+ isGitLab={isGitLab}
+ />
+ </div>
+
+ <PRLink currentBranchLike={currentBranchLike} component={component} />
+ </div>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ChevronDownIcon, TextMuted } from 'design-system';
import * as React from 'react';
-import DocumentationTooltip from '../../../../../components/common/DocumentationTooltip';
-import Link from '../../../../../components/common/Link';
-import HelpTooltip from '../../../../../components/controls/HelpTooltip';
import BranchLikeIcon from '../../../../../components/icons/BranchLikeIcon';
-import DropdownIcon from '../../../../../components/icons/DropdownIcon';
-import PlusCircleIcon from '../../../../../components/icons/PlusCircleIcon';
import { getBranchLikeDisplayName } from '../../../../../helpers/branch-like';
-import { translate, translateWithParameters } from '../../../../../helpers/l10n';
-import { getApplicationAdminUrl } from '../../../../../helpers/urls';
-import { AlmKeys, ProjectAlmBindingResponse } from '../../../../../types/alm-settings';
-import { BranchLike } from '../../../../../types/branch-like';
-import { ComponentQualifier } from '../../../../../types/component';
+import { BranchLike, BranchStatusData } from '../../../../../types/branch-like';
import { Component } from '../../../../../types/types';
-import { colors } from '../../../../theme';
+import QualityGateStatus from './QualityGateStatus';
-export interface CurrentBranchLikeProps {
- branchesEnabled: boolean;
+export interface CurrentBranchLikeProps extends Pick<BranchStatusData, 'status'> {
component: Component;
currentBranchLike: BranchLike;
- hasManyBranches: boolean;
- projectBinding?: ProjectAlmBindingResponse;
}
export function CurrentBranchLike(props: CurrentBranchLikeProps) {
- const {
- branchesEnabled,
- component,
- component: { configuration },
- currentBranchLike,
- hasManyBranches,
- projectBinding,
- } = props;
+ const { component, currentBranchLike } = props;
const displayName = getBranchLikeDisplayName(currentBranchLike);
- const isApplication = component.qualifier === ComponentQualifier.Application;
- const canAdminComponent = configuration && configuration.showSettings;
- const isGitLab = projectBinding !== undefined && projectBinding.alm === AlmKeys.GitLab;
-
- const additionalIcon = () => {
- if (branchesEnabled && hasManyBranches) {
- return <DropdownIcon />;
- }
-
- const plusIcon = <PlusCircleIcon fill={colors.info500} size={12} />;
-
- if (isApplication) {
- if (!hasManyBranches && canAdminComponent) {
- return (
- <HelpTooltip
- overlay={
- <>
- <p>{translate('application.branches.help')}</p>
- <hr className="spacer-top spacer-bottom" />
- <Link to={getApplicationAdminUrl(component.key)}>
- {translate('application.branches.link')}
- </Link>
- </>
- }
- >
- {plusIcon}
- </HelpTooltip>
- );
- }
- } else {
- if (!branchesEnabled) {
- return (
- <DocumentationTooltip
- content={
- projectBinding !== undefined
- ? translateWithParameters(
- `branch_like_navigation.no_branch_support.content_x.${isGitLab ? 'mr' : 'pr'}`,
- translate('alm', projectBinding.alm)
- )
- : translate('branch_like_navigation.no_branch_support.content')
- }
- data-test="branches-support-disabled"
- links={[
- {
- href: 'https://www.sonarsource.com/plans-and-pricing/developer/',
- label: translate('learn_more'),
- doc: false,
- },
- ]}
- title={
- projectBinding !== undefined
- ? translate(
- 'branch_like_navigation.no_branch_support.title',
- isGitLab ? 'mr' : 'pr'
- )
- : translate('branch_like_navigation.no_branch_support.title')
- }
- >
- {plusIcon}
- </DocumentationTooltip>
- );
- }
-
- if (!hasManyBranches) {
- return (
- <DocumentationTooltip
- content={translate('branch_like_navigation.only_one_branch.content')}
- data-test="only-one-branch-like"
- links={[
- {
- href: '/analyzing-source-code/branches/branch-analysis/',
- label: translate('branch_like_navigation.only_one_branch.documentation'),
- },
- {
- href: '/analyzing-source-code/pull-request-analysis',
- label: translate('branch_like_navigation.only_one_branch.pr_analysis'),
- },
- {
- href: `/tutorials?id=${component.key}`,
- label: translate('branch_like_navigation.tutorial_for_ci'),
- inPlace: true,
- doc: false,
- },
- ]}
- title={translate('branch_like_navigation.only_one_branch.title')}
- >
- {plusIcon}
- </DocumentationTooltip>
- );
- }
- }
-
- return null;
- };
return (
- <span className="display-flex-center flex-shrink text-ellipsis">
- <BranchLikeIcon branchLike={currentBranchLike} fill={colors.info500} />
- <span
- className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
- title={displayName}
- >
- {displayName}
- </span>
- {additionalIcon()}
- </span>
+ <div className="sw-flex sw-items-center text-ellipsis">
+ <BranchLikeIcon branchLike={currentBranchLike} />
+ <TextMuted text={displayName} className="sw-ml-3" />
+ <QualityGateStatus branchLike={currentBranchLike} component={component} className="sw-ml-4" />
+ <ChevronDownIcon className="sw-ml-1" />
+ </div>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { DropdownMenu, InputSearch, ItemDivider, Link } from 'design-system';
import * as React from 'react';
-import Link from '../../../../../components/common/Link';
-import { DropdownOverlay } from '../../../../../components/controls/Dropdown';
-import SearchBox from '../../../../../components/controls/SearchBox';
import { Router, withRouter } from '../../../../../components/hoc/withRouter';
import {
getBrancheLikesAsTree,
const { canAdminComponent, component, onClose } = this.props;
const { branchLikesToDisplay, branchLikesToDisplayTree, query, selectedBranchLike } =
this.state;
-
const showManageLink = component.qualifier === ComponentQualifier.Project && canAdminComponent;
const hasResults = branchLikesToDisplay.length > 0;
return (
- <DropdownOverlay className="branch-like-navigation-menu" noPadding={true}>
- <div className="search-box-container">
- <SearchBox
- autoFocus={true}
- onChange={this.handleSearchChange}
- onKeyDown={this.handleKeyDown}
- placeholder={translate('branch_like_navigation.search_for_branch_like')}
- value={query}
- />
- </div>
-
- <div className="item-list-container">
- <MenuItemList
- branchLikeTree={branchLikesToDisplayTree}
- component={component}
- hasResults={hasResults}
- onSelect={this.handleOnSelect}
- selectedBranchLike={selectedBranchLike}
- />
- </div>
-
+ <DropdownMenu
+ className="sw-overflow-y-auto sw-overflow-x-hidden it__branch-like-navigation-menu"
+ maxHeight="38rem"
+ size="auto"
+ >
+ <InputSearch
+ className="sw-mx-3 sw-my-2"
+ autoFocus={true}
+ onChange={this.handleSearchChange}
+ onKeyDown={this.handleKeyDown}
+ placeholder={translate('branch_like_navigation.search_for_branch_like')}
+ size="auto"
+ value={query}
+ searchInputAriaLabel={translate('search_verb')}
+ clearIconAriaLabel={translate('clear')}
+ />
+ <MenuItemList
+ branchLikeTree={branchLikesToDisplayTree}
+ component={component}
+ hasResults={hasResults}
+ onSelect={this.handleOnSelect}
+ selectedBranchLike={selectedBranchLike}
+ />
{showManageLink && (
- <div className="hint-container text-right">
- <Link
- onClick={() => onClose()}
- to={{ pathname: '/project/branches', search: queryToSearch({ id: component.key }) }}
- >
- {translate('branch_like_navigation.manage')}
- </Link>
- </div>
+ <>
+ <ItemDivider />
+ <li className="sw-px-3 sw-py-2">
+ <Link
+ onClick={() => {
+ onClose();
+ }}
+ to={{ pathname: '/project/branches', search: queryToSearch({ id: component.key }) }}
+ >
+ {translate('branch_like_navigation.manage')}
+ </Link>
+ </li>
+ </>
)}
- </DropdownOverlay>
+ </DropdownMenu>
);
}
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
+import { Badge, ItemButton, TextBold, TextMuted } from 'design-system';
import * as React from 'react';
-import BranchStatus from '../../../../../components/common/BranchStatus';
import BranchLikeIcon from '../../../../../components/icons/BranchLikeIcon';
import { getBranchLikeDisplayName, isMainBranch } from '../../../../../helpers/branch-like';
import { translate } from '../../../../../helpers/l10n';
import { BranchLike } from '../../../../../types/branch-like';
import { Component } from '../../../../../types/types';
+import QualityGateStatus from './QualityGateStatus';
export interface MenuItemProps {
branchLike: BranchLike;
component: Component;
- indent?: boolean;
onSelect: (branchLike: BranchLike) => void;
selected: boolean;
setSelectedNode?: (node: HTMLLIElement) => void;
}
export function MenuItem(props: MenuItemProps) {
- const { branchLike, component, indent, setSelectedNode, onSelect, selected } = props;
+ const { branchLike, component, setSelectedNode, onSelect, selected } = props;
const displayName = getBranchLikeDisplayName(branchLike);
return (
- <li
- className={classNames('item', {
- active: selected,
- })}
- onClick={() => onSelect(branchLike)}
- ref={selected ? setSelectedNode : undefined}
+ <ItemButton
+ className={classNames({ active: selected })}
+ innerRef={selected ? setSelectedNode : undefined}
+ onClick={() => {
+ onSelect(branchLike);
+ }}
>
- <div
- className={classNames('display-flex-center display-flex-space-between', {
- 'big-spacer-left': indent,
- })}
- >
- <div className="item-name text-ellipsis" title={displayName}>
+ <div className="sw-flex sw-items-center sw-justify-between text-ellipsis sw-flex-1">
+ <div className="sw-flex sw-items-center">
<BranchLikeIcon branchLike={branchLike} />
- <span className="spacer-left">{displayName}</span>
+
{isMainBranch(branchLike) && (
- <span className="badge spacer-left">{translate('branches.main_branch')}</span>
+ <>
+ <TextBold name={displayName} className="sw-ml-4 sw-mr-2" />
+ <Badge variant="default">{translate('branches.main_branch')}</Badge>
+ </>
+ )}
+ {!isMainBranch(branchLike) && (
+ <TextMuted text={displayName} className="sw-ml-3 sw-mr-2" />
)}
</div>
- <div className="spacer-left">
- <BranchStatus branchLike={branchLike} component={component} />
- </div>
+ <QualityGateStatus
+ branchLike={branchLike}
+ component={component}
+ className="sw-flex sw-items-center sw-w-24"
+ showStatusText={true}
+ />
</div>
- </li>
+ </ItemButton>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { HelperHintIcon, ItemDivider, ItemHeader } from 'design-system';
import * as React from 'react';
import HelpTooltip from '../../../../../components/controls/HelpTooltip';
import { getBranchLikeKey, isSameBranchLike } from '../../../../../helpers/branch-like';
import { translate } from '../../../../../helpers/l10n';
-import { scrollToElement } from '../../../../../helpers/scrolling';
import { isDefined } from '../../../../../helpers/types';
import { BranchLike, BranchLikeTree } from '../../../../../types/branch-like';
import { Component } from '../../../../../types/types';
}
export function MenuItemList(props: MenuItemListProps) {
- let listNode: HTMLUListElement | null = null;
let selectedNode: HTMLLIElement | null = null;
React.useEffect(() => {
- if (listNode && selectedNode) {
- scrollToElement(selectedNode, { parent: listNode, smooth: false });
+ if (selectedNode) {
+ selectedNode.scrollIntoView({ block: 'center' });
+ selectedNode.focus();
}
});
const { branchLikeTree, component, hasResults, onSelect, selectedBranchLike } = props;
- const renderItem = (branchLike: BranchLike, indent?: boolean) => (
+ const renderItem = (branchLike: BranchLike) => (
<MenuItem
branchLike={branchLike}
component={component}
- indent={indent}
key={getBranchLikeKey(branchLike)}
onSelect={onSelect}
selected={isSameBranchLike(branchLike, selectedBranchLike)}
);
return (
- <ul className="item-list" ref={(node) => (listNode = node)}>
+ <ul className="item-list sw-overflow-scroll">
{!hasResults && (
- <li className="item">
- <span className="note">{translate('no_results')}</span>
- </li>
+ <div className="sw-px-3 sw-py-2">
+ <span>{translate('no_results')}</span>
+ </div>
)}
{/* BRANCHES & PR */}
{renderItem(tree.branch)}
{tree.pullRequests.length > 0 && (
<>
- <li className="item header">
- <span className="big-spacer-left">
- {translate('branch_like_navigation.pull_requests')}
- </span>
- </li>
- {tree.pullRequests.map((pr) => renderItem(pr, true))}
+ <ItemDivider />
+ <ItemHeader>{translate('branch_like_navigation.pull_requests')}</ItemHeader>
+ <ItemDivider />
+ {tree.pullRequests.map((pr) => renderItem(pr))}
</>
)}
- <hr />
</React.Fragment>
))}
{/* PARENTLESS PR (for display during search) */}
{branchLikeTree.parentlessPullRequests.length > 0 && (
<>
- <li className="item header">{translate('branch_like_navigation.pull_requests')}</li>
+ <ItemDivider />
+ <ItemHeader>{translate('branch_like_navigation.pull_requests')}</ItemHeader>
+ <ItemDivider />
{branchLikeTree.parentlessPullRequests.map((pr) => renderItem(pr))}
</>
)}
{/* ORPHAN PR */}
{branchLikeTree.orphanPullRequests.length > 0 && (
<>
- <li className="item header">
+ <ItemDivider />
+ <ItemHeader>
{translate('branch_like_navigation.orphan_pull_requests')}
<HelpTooltip
className="little-spacer-left"
overlay={translate('branch_like_navigation.orphan_pull_requests.tooltip')}
- />
- </li>
+ >
+ <HelperHintIcon />
+ </HelpTooltip>
+ </ItemHeader>
+ <ItemDivider />
{branchLikeTree.orphanPullRequests.map((pr) => renderItem(pr))}
</>
)}
--- /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 { Link } from 'design-system';
+import React from 'react';
+import { isPullRequest } from '../../../../../helpers/branch-like';
+import { translate, translateWithParameters } from '../../../../../helpers/l10n';
+import { getBaseUrl } from '../../../../../helpers/system';
+import { AlmKeys } from '../../../../../types/alm-settings';
+import { BranchLike } from '../../../../../types/branch-like';
+import { Component } from '../../../../../types/types';
+
+function getPRUrlAlmKey(url = '') {
+ const lowerCaseUrl = url.toLowerCase();
+ if (lowerCaseUrl.includes(AlmKeys.GitHub)) {
+ return AlmKeys.GitHub;
+ } else if (lowerCaseUrl.includes(AlmKeys.GitLab)) {
+ return AlmKeys.GitLab;
+ } else if (lowerCaseUrl.includes(AlmKeys.BitbucketServer)) {
+ return AlmKeys.BitbucketServer;
+ } else if (
+ lowerCaseUrl.includes(AlmKeys.Azure) ||
+ lowerCaseUrl.includes('microsoft') ||
+ lowerCaseUrl.includes('visualstudio')
+ ) {
+ return AlmKeys.Azure;
+ }
+ return undefined;
+}
+
+export default function PRLink({
+ currentBranchLike,
+ component,
+}: {
+ currentBranchLike: BranchLike;
+ component: Component;
+}) {
+ if (!isPullRequest(currentBranchLike)) {
+ return null;
+ }
+
+ const almKey =
+ component.alm?.key ||
+ (isPullRequest(currentBranchLike) && getPRUrlAlmKey(currentBranchLike.url));
+ return (
+ <div>
+ {currentBranchLike.url !== undefined && (
+ <Link
+ icon={
+ almKey && (
+ <img
+ alt={almKey}
+ height={16}
+ src={`${getBaseUrl()}/images/alm/${almKey}.svg`}
+ title={translateWithParameters('branches.see_the_pr_on_x', translate(almKey))}
+ />
+ )
+ }
+ key={currentBranchLike.key}
+ to={currentBranchLike.url}
+ >
+ {!almKey && translate('branches.see_the_pr')}
+ </Link>
+ )}
+ </div>
+ );
+}
--- /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 classNames from 'classnames';
+import { QualityGateIndicator } from 'design-system';
+import React, { useContext } from 'react';
+import { getBranchStatusByBranchLike } from '../../../../../helpers/branch-like';
+import { translateWithParameters } from '../../../../../helpers/l10n';
+import { formatMeasure } from '../../../../../helpers/measures';
+import { BranchLike } from '../../../../../types/branch-like';
+import { Component } from '../../../../../types/types';
+import { BranchStatusContext } from '../../../branch-status/BranchStatusContext';
+
+interface Props {
+ component: Component;
+ branchLike: BranchLike;
+ className: string;
+ showStatusText?: boolean;
+}
+
+export default function QualityGateStatus({
+ component,
+ branchLike,
+ className,
+ showStatusText,
+}: Props) {
+ const { branchStatusByComponent } = useContext(BranchStatusContext);
+ const branchStatus = getBranchStatusByBranchLike(
+ branchStatusByComponent,
+ component.key,
+ branchLike
+ );
+
+ // eslint-disable-next-line @typescript-eslint/prefer-optional-chain, @typescript-eslint/no-unnecessary-condition
+ if (!branchStatus || !branchStatus.status) {
+ return null;
+ }
+ const { status } = branchStatus;
+ const formatted = formatMeasure(status, 'LEVEL');
+ const ariaLabel = translateWithParameters('overview.quality_gate_x', formatted);
+ return (
+ <div className={classNames(className, `it__level-${status}`)}>
+ <QualityGateIndicator status={status} className="sw-mr-2" ariaLabel={ariaLabel} />
+ {showStatusText && <span>{formatted}</span>}
+ </div>
+ );
+}
+++ /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 { ButtonPlain } from '../../../../../../components/controls/buttons';
-import Toggler from '../../../../../../components/controls/Toggler';
-import { mockSetOfBranchAndPullRequest } from '../../../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../../../helpers/mocks/component';
-import { click } from '../../../../../../helpers/testUtils';
-import { BranchLikeNavigation, BranchLikeNavigationProps } from '../BranchLikeNavigation';
-
-it('should render correctly', () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render the menu trigger if branches are enabled', () => {
- const wrapper = shallowRender({ hasFeature: () => true });
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should properly toggle menu opening when clicking the anchor', () => {
- const wrapper = shallowRender({ hasFeature: () => true });
- expect(wrapper.find(Toggler).props().open).toBe(false);
-
- click(wrapper.find(ButtonPlain));
- expect(wrapper.find(Toggler).props().open).toBe(true);
-
- click(wrapper.find(ButtonPlain));
- expect(wrapper.find(Toggler).props().open).toBe(false);
-});
-
-it('should properly close menu when toggler asks for', () => {
- const wrapper = shallowRender({ hasFeature: () => true });
- expect(wrapper.find(Toggler).props().open).toBe(false);
-
- click(wrapper.find(ButtonPlain));
- expect(wrapper.find(Toggler).props().open).toBe(true);
-
- wrapper.find(Toggler).props().onRequestClose();
- expect(wrapper.find(Toggler).props().open).toBe(false);
-});
-
-function shallowRender(props?: Partial<BranchLikeNavigationProps>) {
- const branchLikes = mockSetOfBranchAndPullRequest();
-
- return shallow(
- <BranchLikeNavigation
- hasFeature={jest.fn().mockReturnValue(false)}
- branchLikes={branchLikes}
- component={mockComponent()}
- currentBranchLike={branchLikes[0]}
- {...props}
- />
- );
-}
+++ /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 {
- mockProjectGithubBindingResponse,
- mockProjectGitLabBindingResponse,
-} from '../../../../../../helpers/mocks/alm-settings';
-import { mockMainBranch } from '../../../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../../../helpers/mocks/component';
-import { ComponentQualifier } from '../../../../../../types/component';
-import { CurrentBranchLike, CurrentBranchLikeProps } from '../CurrentBranchLike';
-
-describe('applications', () => {
- it('should render correctly when there is only one branch and the user can admin the application', () => {
- const wrapper = shallowRender({
- component: mockComponent({
- configuration: { showSettings: true },
- qualifier: ComponentQualifier.Application,
- }),
- hasManyBranches: false,
- });
- expect(wrapper).toMatchSnapshot();
- });
-
- it("should render correctly when there is only one branch and the user CAN'T admin the application", () => {
- const wrapper = shallowRender({
- component: mockComponent({
- configuration: { showSettings: false },
- qualifier: ComponentQualifier.Application,
- }),
- hasManyBranches: false,
- });
- expect(wrapper).toMatchSnapshot();
- });
-
- it('should render correctly when there are many branchlikes', () => {
- const wrapper = shallowRender({
- branchesEnabled: true,
- component: mockComponent({
- qualifier: ComponentQualifier.Application,
- }),
- hasManyBranches: true,
- });
- expect(wrapper).toMatchSnapshot();
- });
-});
-
-describe('projects', () => {
- it('should render correctly when branches support is disabled', () => {
- expect(
- shallowRender({
- branchesEnabled: false,
- component: mockComponent({
- qualifier: ComponentQualifier.Project,
- }),
- })
- ).toMatchSnapshot('default');
- expect(
- shallowRender({
- branchesEnabled: false,
- component: mockComponent({
- qualifier: ComponentQualifier.Project,
- }),
- projectBinding: mockProjectGithubBindingResponse(),
- })
- ).toMatchSnapshot('alm with prs');
- expect(
- shallowRender({
- branchesEnabled: false,
- component: mockComponent({
- qualifier: ComponentQualifier.Project,
- }),
- projectBinding: mockProjectGitLabBindingResponse(),
- })
- ).toMatchSnapshot('alm with mrs');
- });
-
- it('should render correctly when there is only one branchlike', () => {
- const wrapper = shallowRender({
- branchesEnabled: true,
- component: mockComponent({
- qualifier: ComponentQualifier.Project,
- }),
- hasManyBranches: false,
- });
- expect(wrapper).toMatchSnapshot();
- });
-
- it('should render correctly when there are many branchlikes', () => {
- const wrapper = shallowRender({
- branchesEnabled: true,
- component: mockComponent({
- qualifier: ComponentQualifier.Project,
- }),
- hasManyBranches: true,
- });
- expect(wrapper).toMatchSnapshot();
- });
-});
-
-function shallowRender(props?: Partial<CurrentBranchLikeProps>) {
- return shallow(
- <CurrentBranchLike
- branchesEnabled={false}
- component={mockComponent()}
- currentBranchLike={mockMainBranch()}
- hasManyBranches={false}
- {...props}
- />
- );
-}
+++ /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 Link from '../../../../../../components/common/Link';
-import SearchBox from '../../../../../../components/controls/SearchBox';
-import { KeyboardKeys } from '../../../../../../helpers/keycodes';
-import {
- mockPullRequest,
- mockSetOfBranchAndPullRequest,
-} from '../../../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../../../helpers/mocks/component';
-import { mockRouter } from '../../../../../../helpers/testMocks';
-import { click, mockEvent } from '../../../../../../helpers/testUtils';
-import { queryToSearch } from '../../../../../../helpers/urls';
-import { Menu } from '../Menu';
-import { MenuItemList } from '../MenuItemList';
-
-it('should render correctly', () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render correctly with no current branch like', () => {
- const wrapper = shallowRender({ currentBranchLike: undefined });
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should close the menu when "manage branches" link is clicked', () => {
- const onClose = jest.fn();
- const wrapper = shallowRender({ onClose });
-
- click(wrapper.find(Link));
- expect(onClose).toHaveBeenCalled();
-});
-
-it('should change url and close menu when an element is selected', () => {
- const onClose = jest.fn();
- const push = jest.fn();
- const router = mockRouter({ push });
- const component = mockComponent();
- const pr = mockPullRequest();
-
- const wrapper = shallowRender({ component, onClose, router });
-
- wrapper.find(MenuItemList).props().onSelect(pr);
-
- expect(onClose).toHaveBeenCalled();
- expect(push).toHaveBeenCalledWith(
- expect.objectContaining({
- search: queryToSearch({
- id: component.key,
- pullRequest: pr.key,
- }),
- })
- );
-});
-
-it('should filter branchlike list correctly', () => {
- const wrapper = shallowRender();
-
- wrapper.find(SearchBox).props().onChange('PR');
-
- expect(wrapper.state().branchLikesToDisplay.length).toBe(3);
-});
-
-it('should handle keyboard shortcut correctly', () => {
- const push = jest.fn();
- const router = mockRouter({ push });
- const wrapper = shallowRender({ currentBranchLike: branchLikes[1], router });
-
- const { onKeyDown } = wrapper.find(SearchBox).props();
-
- onKeyDown!(mockEvent({ nativeEvent: { key: KeyboardKeys.UpArrow } }));
- expect(wrapper.state().selectedBranchLike).toBe(branchLikes[3]);
-
- onKeyDown!(mockEvent({ nativeEvent: { key: KeyboardKeys.DownArrow } }));
- onKeyDown!(mockEvent({ nativeEvent: { key: KeyboardKeys.DownArrow } }));
- expect(wrapper.state().selectedBranchLike).toBe(branchLikes[0]);
-
- onKeyDown!(mockEvent({ nativeEvent: { key: KeyboardKeys.Enter } }));
- expect(push).toHaveBeenCalled();
-});
-
-const branchLikes = mockSetOfBranchAndPullRequest();
-
-function shallowRender(props?: Partial<Menu['props']>) {
- return shallow<Menu>(
- <Menu
- branchLikes={branchLikes}
- canAdminComponent={true}
- component={mockComponent()}
- currentBranchLike={branchLikes[2]}
- onClose={jest.fn()}
- router={mockRouter()}
- {...props}
- />
- );
-}
+++ /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, mockPullRequest } from '../../../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../../../helpers/mocks/component';
-import { click } from '../../../../../../helpers/testUtils';
-import { MenuItem, MenuItemProps } from '../MenuItem';
-
-it('should render a main branch correctly', () => {
- const wrapper = shallowRender({ branchLike: mockMainBranch() });
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should render a non-main branch, indented and selected item correctly', () => {
- const wrapper = shallowRender({ branchLike: mockPullRequest(), indent: true, selected: true });
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should propagate click event correctly', () => {
- const onSelect = jest.fn();
- const wrapper = shallowRender({ onSelect });
-
- click(wrapper.find('li'));
- expect(onSelect).toHaveBeenCalled();
-});
-
-function shallowRender(props?: Partial<MenuItemProps>) {
- return shallow(
- <MenuItem
- branchLike={mockMainBranch()}
- component={mockComponent()}
- onSelect={jest.fn()}
- selected={false}
- setSelectedNode={jest.fn()}
- {...props}
- />
- );
-}
+++ /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 { getBrancheLikesAsTree } from '../../../../../../helpers/branch-like';
-import {
- mockPullRequest,
- mockSetOfBranchAndPullRequest,
-} from '../../../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../../../helpers/mocks/component';
-import { MenuItemList, MenuItemListProps } from '../MenuItemList';
-
-it('should render correctly', () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
-});
-
-function shallowRender(props?: Partial<MenuItemListProps>) {
- const branchLikes = [
- ...mockSetOfBranchAndPullRequest(),
- mockPullRequest({ base: 'not-in-the-list' }),
- ];
- const branchLikeTree = getBrancheLikesAsTree(branchLikes);
-
- return shallow(
- <MenuItemList
- branchLikeTree={branchLikeTree}
- component={mockComponent()}
- hasResults={false}
- onSelect={jest.fn()}
- selectedBranchLike={branchLikes[0]}
- {...props}
- />
- );
-}
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<span
- className="big-spacer-left flex-0 branch-like-navigation-toggler-container display-flex-center"
->
- <Memo(CurrentBranchLike)
- branchesEnabled={false}
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- currentBranchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- }
- }
- hasManyBranches={true}
- />
-</span>
-`;
-
-exports[`should render the menu trigger if branches are enabled 1`] = `
-<span
- className="big-spacer-left flex-0 branch-like-navigation-toggler-container display-flex-center dropdown"
->
- <Toggler
- onRequestClose={[Function]}
- open={false}
- overlay={
- <withRouter(Menu)
- branchLikes={
- [
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- },
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-1",
- },
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- },
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "1",
- "target": "master",
- "title": "PR-1",
- },
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-12",
- },
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "2",
- "target": "master",
- "title": "PR-2",
- },
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-3",
- },
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-2",
- },
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "isOrphan": true,
- "key": "2",
- "target": "llb-100",
- "title": "PR-2",
- },
- ]
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- currentBranchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- }
- }
- onClose={[Function]}
- />
- }
- >
- <ButtonPlain
- aria-expanded={false}
- aria-haspopup="menu"
- className="branch-like-navigation-toggler"
- onClick={[Function]}
- >
- <Memo(CurrentBranchLike)
- branchesEnabled={true}
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- currentBranchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- }
- }
- hasManyBranches={true}
- />
- </ButtonPlain>
- </Toggler>
-</span>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`applications should render correctly when there are many branchlikes 1`] = `
-<span
- className="display-flex-center flex-shrink text-ellipsis"
->
- <BranchLikeIcon
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- fill="#0271B9"
- />
- <span
- className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
- title="master"
- >
- master
- </span>
- <DropdownIcon />
-</span>
-`;
-
-exports[`applications should render correctly when there is only one branch and the user CAN'T admin the application 1`] = `
-<span
- className="display-flex-center flex-shrink text-ellipsis"
->
- <BranchLikeIcon
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- fill="#0271B9"
- />
- <span
- className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
- title="master"
- >
- master
- </span>
-</span>
-`;
-
-exports[`applications should render correctly when there is only one branch and the user can admin the application 1`] = `
-<span
- className="display-flex-center flex-shrink text-ellipsis"
->
- <BranchLikeIcon
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- fill="#0271B9"
- />
- <span
- className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
- title="master"
- >
- master
- </span>
- <HelpTooltip
- overlay={
- <React.Fragment>
- <p>
- application.branches.help
- </p>
- <hr
- className="spacer-top spacer-bottom"
- />
- <ForwardRef(Link)
- to={
- {
- "pathname": "/project/admin/extension/developer-server/application-console",
- "search": "?id=my-project",
- }
- }
- >
- application.branches.link
- </ForwardRef(Link)>
- </React.Fragment>
- }
- >
- <PlusCircleIcon
- fill="#0271B9"
- size={12}
- />
- </HelpTooltip>
-</span>
-`;
-
-exports[`projects should render correctly when branches support is disabled: alm with mrs 1`] = `
-<span
- className="display-flex-center flex-shrink text-ellipsis"
->
- <BranchLikeIcon
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- fill="#0271B9"
- />
- <span
- className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
- title="master"
- >
- master
- </span>
- <DocumentationTooltip
- content="branch_like_navigation.no_branch_support.content_x.mr.alm.gitlab"
- data-test="branches-support-disabled"
- links={
- [
- {
- "doc": false,
- "href": "https://www.sonarsource.com/plans-and-pricing/developer/",
- "label": "learn_more",
- },
- ]
- }
- title="branch_like_navigation.no_branch_support.title.mr"
- >
- <PlusCircleIcon
- fill="#0271B9"
- size={12}
- />
- </DocumentationTooltip>
-</span>
-`;
-
-exports[`projects should render correctly when branches support is disabled: alm with prs 1`] = `
-<span
- className="display-flex-center flex-shrink text-ellipsis"
->
- <BranchLikeIcon
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- fill="#0271B9"
- />
- <span
- className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
- title="master"
- >
- master
- </span>
- <DocumentationTooltip
- content="branch_like_navigation.no_branch_support.content_x.pr.alm.github"
- data-test="branches-support-disabled"
- links={
- [
- {
- "doc": false,
- "href": "https://www.sonarsource.com/plans-and-pricing/developer/",
- "label": "learn_more",
- },
- ]
- }
- title="branch_like_navigation.no_branch_support.title.pr"
- >
- <PlusCircleIcon
- fill="#0271B9"
- size={12}
- />
- </DocumentationTooltip>
-</span>
-`;
-
-exports[`projects should render correctly when branches support is disabled: default 1`] = `
-<span
- className="display-flex-center flex-shrink text-ellipsis"
->
- <BranchLikeIcon
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- fill="#0271B9"
- />
- <span
- className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
- title="master"
- >
- master
- </span>
- <DocumentationTooltip
- content="branch_like_navigation.no_branch_support.content"
- data-test="branches-support-disabled"
- links={
- [
- {
- "doc": false,
- "href": "https://www.sonarsource.com/plans-and-pricing/developer/",
- "label": "learn_more",
- },
- ]
- }
- title="branch_like_navigation.no_branch_support.title"
- >
- <PlusCircleIcon
- fill="#0271B9"
- size={12}
- />
- </DocumentationTooltip>
-</span>
-`;
-
-exports[`projects should render correctly when there are many branchlikes 1`] = `
-<span
- className="display-flex-center flex-shrink text-ellipsis"
->
- <BranchLikeIcon
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- fill="#0271B9"
- />
- <span
- className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
- title="master"
- >
- master
- </span>
- <DropdownIcon />
-</span>
-`;
-
-exports[`projects should render correctly when there is only one branchlike 1`] = `
-<span
- className="display-flex-center flex-shrink text-ellipsis"
->
- <BranchLikeIcon
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- fill="#0271B9"
- />
- <span
- className="spacer-left spacer-right flex-shrink text-ellipsis js-branch-like-name"
- title="master"
- >
- master
- </span>
- <DocumentationTooltip
- content="branch_like_navigation.only_one_branch.content"
- data-test="only-one-branch-like"
- links={
- [
- {
- "href": "/analyzing-source-code/branches/branch-analysis/",
- "label": "branch_like_navigation.only_one_branch.documentation",
- },
- {
- "href": "/analyzing-source-code/pull-request-analysis",
- "label": "branch_like_navigation.only_one_branch.pr_analysis",
- },
- {
- "doc": false,
- "href": "/tutorials?id=my-project",
- "inPlace": true,
- "label": "branch_like_navigation.tutorial_for_ci",
- },
- ]
- }
- title="branch_like_navigation.only_one_branch.title"
- >
- <PlusCircleIcon
- fill="#0271B9"
- size={12}
- />
- </DocumentationTooltip>
-</span>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<DropdownOverlay
- className="branch-like-navigation-menu"
- noPadding={true}
->
- <div
- className="search-box-container"
- >
- <SearchBox
- autoFocus={true}
- onChange={[Function]}
- onKeyDown={[Function]}
- placeholder="branch_like_navigation.search_for_branch_like"
- value=""
- />
- </div>
- <div
- className="item-list-container"
- >
- <Memo(MenuItemList)
- branchLikeTree={
- {
- "branchTree": [
- {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-1",
- },
- "pullRequests": [],
- },
- {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- },
- "pullRequests": [],
- },
- {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-12",
- },
- "pullRequests": [],
- },
- {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-2",
- },
- "pullRequests": [],
- },
- {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-3",
- },
- "pullRequests": [],
- },
- ],
- "mainBranchTree": {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- },
- "pullRequests": [
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "2",
- "target": "master",
- "title": "PR-2",
- },
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "1",
- "target": "master",
- "title": "PR-1",
- },
- ],
- },
- "orphanPullRequests": [
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "isOrphan": true,
- "key": "2",
- "target": "llb-100",
- "title": "PR-2",
- },
- ],
- "parentlessPullRequests": [],
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- hasResults={true}
- onSelect={[Function]}
- selectedBranchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- />
- </div>
- <div
- className="hint-container text-right"
- >
- <ForwardRef(Link)
- onClick={[Function]}
- to={
- {
- "pathname": "/project/branches",
- "search": "?id=my-project",
- }
- }
- >
- branch_like_navigation.manage
- </ForwardRef(Link)>
- </div>
-</DropdownOverlay>
-`;
-
-exports[`should render correctly with no current branch like 1`] = `
-<DropdownOverlay
- className="branch-like-navigation-menu"
- noPadding={true}
->
- <div
- className="search-box-container"
- >
- <SearchBox
- autoFocus={true}
- onChange={[Function]}
- onKeyDown={[Function]}
- placeholder="branch_like_navigation.search_for_branch_like"
- value=""
- />
- </div>
- <div
- className="item-list-container"
- >
- <Memo(MenuItemList)
- branchLikeTree={
- {
- "branchTree": [
- {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-1",
- },
- "pullRequests": [],
- },
- {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- },
- "pullRequests": [],
- },
- {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-12",
- },
- "pullRequests": [],
- },
- {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-2",
- },
- "pullRequests": [],
- },
- {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-3",
- },
- "pullRequests": [],
- },
- ],
- "mainBranchTree": {
- "branch": {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- },
- "pullRequests": [
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "2",
- "target": "master",
- "title": "PR-2",
- },
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "1",
- "target": "master",
- "title": "PR-1",
- },
- ],
- },
- "orphanPullRequests": [
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "isOrphan": true,
- "key": "2",
- "target": "llb-100",
- "title": "PR-2",
- },
- ],
- "parentlessPullRequests": [],
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- hasResults={true}
- onSelect={[Function]}
- selectedBranchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- }
- }
- />
- </div>
- <div
- className="hint-container text-right"
- >
- <ForwardRef(Link)
- onClick={[Function]}
- to={
- {
- "pathname": "/project/branches",
- "search": "?id=my-project",
- }
- }
- >
- branch_like_navigation.manage
- </ForwardRef(Link)>
- </div>
-</DropdownOverlay>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render a main branch correctly 1`] = `
-<li
- className="item"
- onClick={[Function]}
->
- <div
- className="display-flex-center display-flex-space-between"
- >
- <div
- className="item-name text-ellipsis"
- title="master"
- >
- <BranchLikeIcon
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- />
- <span
- className="spacer-left"
- >
- master
- </span>
- <span
- className="badge spacer-left"
- >
- branches.main_branch
- </span>
- </div>
- <div
- className="spacer-left"
- >
- <withBranchStatus(BranchStatus)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- />
- </div>
- </div>
-</li>
-`;
-
-exports[`should render a non-main branch, indented and selected item correctly 1`] = `
-<li
- className="item active"
- onClick={[Function]}
->
- <div
- className="display-flex-center display-flex-space-between big-spacer-left"
- >
- <div
- className="item-name text-ellipsis"
- title="1001 – Foo Bar feature"
- >
- <BranchLikeIcon
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "1001",
- "target": "master",
- "title": "Foo Bar feature",
- }
- }
- />
- <span
- className="spacer-left"
- >
- 1001 – Foo Bar feature
- </span>
- </div>
- <div
- className="spacer-left"
- >
- <withBranchStatus(BranchStatus)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "1001",
- "target": "master",
- "title": "Foo Bar feature",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- />
- </div>
- </div>
-</li>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<ul
- className="item-list"
->
- <li
- className="item"
- >
- <span
- className="note"
- >
- no_results
- </span>
- </li>
- <Memo(MenuItem)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- key="branch-master"
- onSelect={[MockFunction]}
- selected={false}
- setSelectedNode={[Function]}
- />
- <li
- className="item header"
- >
- <span
- className="big-spacer-left"
- >
- branch_like_navigation.pull_requests
- </span>
- </li>
- <Memo(MenuItem)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "2",
- "target": "master",
- "title": "PR-2",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- indent={true}
- key="pull-request-2"
- onSelect={[MockFunction]}
- selected={false}
- setSelectedNode={[Function]}
- />
- <Memo(MenuItem)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "1",
- "target": "master",
- "title": "PR-1",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- indent={true}
- key="pull-request-1"
- onSelect={[MockFunction]}
- selected={false}
- setSelectedNode={[Function]}
- />
- <hr />
- <Memo(MenuItem)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-1",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- key="branch-branch-1"
- onSelect={[MockFunction]}
- selected={false}
- setSelectedNode={[Function]}
- />
- <hr />
- <Memo(MenuItem)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- key="branch-branch-11"
- onSelect={[MockFunction]}
- selected={true}
- setSelectedNode={[Function]}
- />
- <hr />
- <Memo(MenuItem)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-12",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- key="branch-branch-12"
- onSelect={[MockFunction]}
- selected={false}
- setSelectedNode={[Function]}
- />
- <hr />
- <Memo(MenuItem)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-2",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- key="branch-branch-2"
- onSelect={[MockFunction]}
- selected={false}
- setSelectedNode={[Function]}
- />
- <hr />
- <Memo(MenuItem)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-3",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- key="branch-branch-3"
- onSelect={[MockFunction]}
- selected={false}
- setSelectedNode={[Function]}
- />
- <hr />
- <li
- className="item header"
- >
- branch_like_navigation.pull_requests
- </li>
- <Memo(MenuItem)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "base": "not-in-the-list",
- "branch": "feature/foo/bar",
- "key": "1001",
- "target": "master",
- "title": "Foo Bar feature",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- key="pull-request-1001"
- onSelect={[MockFunction]}
- selected={false}
- setSelectedNode={[Function]}
- />
- <li
- className="item header"
- >
- branch_like_navigation.orphan_pull_requests
- <HelpTooltip
- className="little-spacer-left"
- overlay="branch_like_navigation.orphan_pull_requests.tooltip"
- />
- </li>
- <Memo(MenuItem)
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "isOrphan": true,
- "key": "2",
- "target": "llb-100",
- "title": "PR-2",
- }
- }
- component={
- {
- "breadcrumbs": [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": [
- {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
- "name": "Sonar way",
- },
- ],
- "tags": [],
- }
- }
- key="pull-request-2"
- onSelect={[MockFunction]}
- selected={false}
- setSelectedNode={[Function]}
- />
-</ul>
-`;
export default function DocumentationTooltip(props: DocumentationTooltipProps) {
const nextSelectableNode = React.useRef<HTMLElement | undefined | null>();
- const linksRef = React.useRef<(HTMLAnchorElement | null)[]>([]);
+ const linksRef = React.useRef<Array<HTMLAnchorElement | null>>([]);
const helpRef = React.useRef<HTMLElement>(null);
const { className, children, content, links, title } = props;
function handleTabPress(event: KeyboardEvent) {
if (event.code === KeyboardKeys.Tab) {
- if (event.shiftKey === true) {
+ if (event.shiftKey) {
if (event.target === first(linksRef.current)) {
helpRef.current?.focus();
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { BranchIcon, MainBranchIcon, PullRequestIcon, ThemeColors } from 'design-system';
import * as React from 'react';
-import BranchIcon from '../../components/icons/BranchIcon';
import { IconProps } from '../../components/icons/Icon';
-import PullRequestIcon from '../../components/icons/PullRequestIcon';
-import { isPullRequest } from '../../helpers/branch-like';
+import { isMainBranch, isPullRequest } from '../../helpers/branch-like';
import { BranchLike } from '../../types/branch-like';
-export interface BranchLikeIconProps extends IconProps {
+export interface BranchLikeIconProps extends Omit<IconProps, 'fill'> {
branchLike: BranchLike;
+ fill?: ThemeColors;
}
export default function BranchLikeIcon({ branchLike, ...props }: BranchLikeIconProps) {
if (isPullRequest(branchLike)) {
- return <PullRequestIcon {...props} />;
+ return <PullRequestIcon fill="pageContentLight" {...props} />;
+ } else if (isMainBranch(branchLike)) {
+ return <MainBranchIcon fill="pageContentLight" {...props} />;
}
- return <BranchIcon {...props} />;
+ return <BranchIcon fill="pageContentLight" {...props} />;
}
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render branch icon correctly 1`] = `"<div><svg height="16" style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 1.41421;" version="1.1" viewBox="0 0 16 16" width="16" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"><path d="M12.5 6.5c0-1.1-.9-2-2-2s-2 .9-2 2c0 .8.5 1.5 1.2 1.8-.3.6-.7 1.1-1.2 1.4-.9.5-1.9.5-2.5.4V4c.9-.2 1.5-1 1.5-1.9 0-1.1-.9-2-2-2s-2 .9-2 2C3.5 3 4.1 3.8 5 4v8c-.9.2-1.5 1-1.5 1.9 0 1.1.9 2 2 2s2-.9 2-2c0-.9-.6-1.7-1.5-1.9v-1c.2 0 .5.1.7.1.7 0 1.5-.1 2.2-.6.8-.5 1.4-1.2 1.7-2.1 1.1 0 1.9-.9 1.9-1.9zm-8-4.4c0-.6.4-1 1-1s1 .4 1 1-.4 1-1 1-1-.5-1-1zm2 11.9c0 .6-.4 1-1 1s-1-.4-1-1 .4-1 1-1 1 .4 1 1zm4-6.5c-.6 0-1-.4-1-1s.4-1 1-1 1 .4 1 1-.4 1-1 1z" style="fill: #236a97;"></path></svg></div>"`;
+exports[`should render branch icon correctly 1`] = `"<div><svg aria-hidden="true" focusable="false" role="img" class="octicon octicon-git-branch" viewBox="0 0 16 16" width="16" height="16" fill="rgb(106,117,144)" style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"></path></svg></div>"`;
-exports[`should render pull request icon correctly 1`] = `"<div><svg height="16" style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 1.41421;" version="1.1" viewBox="0 0 16 16" width="16" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"><path d="M13,11.9L13,5.5C13,5.4 13.232,1.996 7.9,2L9.1,0.8L8.5,0.1L5.9,2.6L8.5,5.1L9.2,4.4L7.905,3.008C12.256,2.99 12,5.4 12,5.5L12,11.9C11.1,12.1 10.5,12.9 10.5,13.8C10.5,14.9 11.4,15.8 12.5,15.8C13.6,15.8 14.5,14.9 14.5,13.8C14.5,12.9 13.9,12.2 13,11.9ZM4,11.9C4.9,12.2 5.5,12.9 5.5,13.8C5.5,14.9 4.6,15.8 3.5,15.8C2.4,15.8 1.5,14.9 1.5,13.8C1.5,12.9 2.1,12.1 3,11.9L3,4.1C2.1,3.9 1.5,3.1 1.5,2.2C1.5,1.1 2.4,0.2 3.5,0.2C4.6,0.2 5.5,1.1 5.5,2.2C5.5,3.1 4.9,3.9 4,4.1L4,11.9ZM12.5,14.9C11.9,14.9 11.5,14.5 11.5,13.9C11.5,13.3 11.9,12.9 12.5,12.9C13.1,12.9 13.5,13.3 13.5,13.9C13.5,14.5 13.1,14.9 12.5,14.9ZM3.5,14.9C2.9,14.9 2.5,14.5 2.5,13.9C2.5,13.3 2.9,12.9 3.5,12.9C4.1,12.9 4.5,13.3 4.5,13.9C4.5,14.5 4.1,14.9 3.5,14.9ZM2.5,2.2C2.5,1.6 2.9,1.2 3.5,1.2C4.1,1.2 4.5,1.6 4.5,2.2C4.5,2.8 4.1,3.2 3.5,3.2C2.9,3.2 2.5,2.8 2.5,2.2Z" style="fill: #236a97;"></path></svg></div>"`;
+exports[`should render pull request icon correctly 1`] = `"<div><svg aria-hidden="true" focusable="false" role="img" class="octicon octicon-git-pull-request" viewBox="0 0 16 16" width="16" height="16" fill="rgb(106,117,144)" style="display: inline-block; user-select: none; vertical-align: middle; overflow: visible;"><path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z"></path></svg></div>"`;
mockPullRequest({ key: '2', title: 'PR-2' }),
mockBranch({ name: 'branch-3' }),
mockBranch({ name: 'branch-2' }),
- mockPullRequest({ key: '2', title: 'PR-2', target: 'llb-100', isOrphan: true }),
+ mockPullRequest({
+ key: '2',
+ title: 'PR-2',
+ target: 'llb-100',
+ isOrphan: true,
+ }),
+ ];
+}
+
+export function mockSetOfBranchAndPullRequestForBranchSelector(): BranchLike[] {
+ return [
+ mockBranch({ name: 'branch-1', status: { qualityGateStatus: 'OK' } }),
+ mockMainBranch(),
+ mockPullRequest({ key: '1', title: 'PR-1', status: { qualityGateStatus: 'OK' } }),
+ mockBranch({ name: 'branch-2', status: { qualityGateStatus: 'OK' } }),
+ mockPullRequest({ key: '2', title: 'PR-2', status: { qualityGateStatus: 'OK' } }),
+ mockBranch({ name: 'branch-3', status: { qualityGateStatus: 'OK' } }),
];
}
overview.X_conditions_failed={0} conditions failed
overview.fix_failed_conditions_with_sonarlint=Fix issues before they fail your Quality Gate with {link} in your IDE. Power up with connected mode!
overview.quality_gate=Quality Gate Status
+overview.quality_gate_x=Quality Gate: {0}
overview.quality_gate.help=A Quality Gate is a set of measure-based Boolean conditions. It helps you know immediately whether your project is production-ready. If your current status is not Passed, you'll see which measures caused the problem and the values required to pass.
overview.quality_gate_failed_with_x=with {0} errors
overview.quality_gate_code_clean=Your code is clean!
branches.main_branch=Main Branch
branches.pr=PR
branches.see_the_pr=See the PR
+branches.see_the_pr_on_x=See the PR on {0}
#------------------------------------------------------------------------------
#
favorite.action.TRK.remove=Remove this project from favorites
favorite.action.VW.add=Add this portfolio to favorites
favorite.action.VW.remove=Remove this portfolio from favorites
-favorite.action.SVW.add=Add sub-this portfolio to favorites
-favorite.action.SVW.remove=Remove sub-this portfolio from favorites
+favorite.action.SVW.add=Add this sub-portfolio to favorites
+favorite.action.SVW.remove=Remove this sub-portfolio from favorites
favorite.action.APP.add=Add this application to favorites
favorite.action.APP.remove=Remove this application from favorites
favorite.action.TRK.add_x=Add project {0} to favorites