@@ -0,0 +1,98 @@ | |||
/* | |||
* 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`} | |||
} | |||
`; |
@@ -49,7 +49,7 @@ interface Props { | |||
placeholder: string; | |||
searchInputAriaLabel: string; | |||
size?: InputSizeKeys; | |||
tooShortText: string; | |||
tooShortText?: string; | |||
value?: string; | |||
} | |||
@@ -129,7 +129,7 @@ export default function InputSearch({ | |||
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 | |||
@@ -161,7 +161,7 @@ export default function InputSearch({ | |||
/> | |||
)} | |||
{tooShort && isDefined(minLength) && ( | |||
{tooShort && tooShortText && isDefined(minLength) && ( | |||
<StyledNote className="sw-ml-1" role="note"> | |||
{tooShortText} | |||
</StyledNote> |
@@ -0,0 +1,182 @@ | |||
/* | |||
* 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> | |||
); | |||
} |
@@ -21,25 +21,33 @@ import styled from '@emotion/styled'; | |||
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` |
@@ -0,0 +1,32 @@ | |||
/* | |||
* 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'); | |||
}); |
@@ -82,7 +82,7 @@ function setupWithProps(props: Partial<FCProps<typeof InputSearch>> = {}) { | |||
onChange={jest.fn()} | |||
placeholder="placeholder" | |||
searchInputAriaLabel="" | |||
tooShortText="" | |||
tooShortText="too short" | |||
value="foo" | |||
{...props} | |||
/> |
@@ -0,0 +1,67 @@ | |||
/* | |||
* 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} />); | |||
} |
@@ -22,10 +22,10 @@ | |||
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', |
@@ -0,0 +1,23 @@ | |||
/* | |||
* 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'); |
@@ -0,0 +1,35 @@ | |||
/* | |||
* 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> | |||
); | |||
} |
@@ -0,0 +1,36 @@ | |||
/* | |||
* 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> | |||
); | |||
} |
@@ -0,0 +1,23 @@ | |||
/* | |||
* 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'); |
@@ -17,15 +17,19 @@ | |||
* 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'; |
@@ -19,6 +19,7 @@ | |||
*/ | |||
export * from './Avatar'; | |||
export { default as Badge } from './Badge'; | |||
export * from './buttons'; | |||
export { default as DeferredSpinner } from './DeferredSpinner'; | |||
export { default as Dropdown } from './Dropdown'; | |||
@@ -29,12 +30,14 @@ export * from './GenericAvatar'; | |||
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'; |
@@ -21,3 +21,4 @@ | |||
export * from './components'; | |||
export * from './helpers'; | |||
export * from './theme'; | |||
export * from './types/theme'; |
@@ -18,7 +18,7 @@ | |||
* 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'; | |||
@@ -54,7 +54,7 @@ export default class GlobalSearchResult extends React.PureComponent<Props> { | |||
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 && ( |
@@ -17,87 +17,48 @@ | |||
* 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> |
@@ -18,14 +18,12 @@ | |||
* 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 { | |||
@@ -40,25 +38,17 @@ export function Header(props: 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> |
@@ -1,73 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { 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} | |||
/> | |||
); | |||
} |
@@ -17,34 +17,178 @@ | |||
* 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 } | |||
); | |||
} |
@@ -1,153 +0,0 @@ | |||
// 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> | |||
`; |
@@ -0,0 +1,130 @@ | |||
/* | |||
* 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; | |||
} |
@@ -1,78 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
.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); | |||
} |
@@ -17,20 +17,21 @@ | |||
* 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[]; | |||
@@ -48,59 +49,72 @@ export function BranchLikeNavigation(props: BranchLikeNavigationProps) { | |||
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> | |||
); | |||
} | |||
@@ -17,147 +17,31 @@ | |||
* 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> | |||
); | |||
} | |||
@@ -17,10 +17,8 @@ | |||
* 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, | |||
@@ -156,43 +154,49 @@ export class Menu extends React.PureComponent<Props, State> { | |||
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> | |||
); | |||
} | |||
} |
@@ -18,52 +18,57 @@ | |||
* 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> | |||
); | |||
} | |||
@@ -17,11 +17,11 @@ | |||
* 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'; | |||
@@ -36,22 +36,21 @@ export interface MenuItemListProps { | |||
} | |||
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)} | |||
@@ -60,11 +59,11 @@ export function MenuItemList(props: MenuItemListProps) { | |||
); | |||
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 */} | |||
@@ -75,22 +74,21 @@ export function MenuItemList(props: MenuItemListProps) { | |||
{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))} | |||
</> | |||
)} | |||
@@ -98,13 +96,17 @@ export function MenuItemList(props: MenuItemListProps) { | |||
{/* 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))} | |||
</> | |||
)} |
@@ -0,0 +1,83 @@ | |||
/* | |||
* 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> | |||
); | |||
} |
@@ -0,0 +1,63 @@ | |||
/* | |||
* 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> | |||
); | |||
} |
@@ -1,73 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { 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} | |||
/> | |||
); | |||
} |
@@ -1,129 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { | |||
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} | |||
/> | |||
); | |||
} |
@@ -1,116 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import 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} | |||
/> | |||
); | |||
} |
@@ -1,56 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { mockMainBranch, 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} | |||
/> | |||
); | |||
} |
@@ -1,52 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { 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} | |||
/> | |||
); | |||
} |
@@ -1,195 +0,0 @@ | |||
// 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> | |||
`; |
@@ -1,307 +0,0 @@ | |||
// 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> | |||
`; |
@@ -1,323 +0,0 @@ | |||
// 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> | |||
`; |
@@ -1,146 +0,0 @@ | |||
// 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> | |||
`; |
@@ -1,417 +0,0 @@ | |||
// 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> | |||
`; |
@@ -34,7 +34,7 @@ export interface DocumentationTooltipProps { | |||
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; | |||
@@ -49,7 +49,7 @@ export default function DocumentationTooltip(props: DocumentationTooltipProps) { | |||
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(); | |||
} |
@@ -17,20 +17,22 @@ | |||
* 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} />; | |||
} |
@@ -1,5 +1,5 @@ | |||
// 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>"`; |
@@ -59,6 +59,22 @@ export function mockSetOfBranchAndPullRequest(): BranchLike[] { | |||
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' } }), | |||
]; | |||
} |
@@ -3332,6 +3332,7 @@ overview.1_condition_failed=1 condition failed | |||
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! | |||
@@ -4260,6 +4261,7 @@ branches.branch=Branch | |||
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} | |||
#------------------------------------------------------------------------------ | |||
# | |||
@@ -4516,8 +4518,8 @@ favorite.action.TRK.add=Add this project to favorites | |||
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 |