]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18776 Migrating breadcrumb and branch selector to MIUI
authorRevanshu Paliwal <revanshu.paliwal@sonarsource.com>
Tue, 21 Mar 2023 16:13:04 +0000 (17:13 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 27 Mar 2023 20:03:03 +0000 (20:03 +0000)
45 files changed:
server/sonar-web/design-system/src/components/Badge.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/InputSearch.tsx
server/sonar-web/design-system/src/components/QualityGateIndicator.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/Text.tsx
server/sonar-web/design-system/src/components/__tests__/Badge-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/InputSearch-test.tsx
server/sonar-web/design-system/src/components/__tests__/QualityGateIndicator-test.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/__tests__/Text-test.tsx
server/sonar-web/design-system/src/components/icons/BranchIcon.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/HelperHintIcon.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/MainBranchIcon.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/PullRequestIcon.tsx [new file with mode: 0644]
server/sonar-web/design-system/src/components/icons/index.ts
server/sonar-web/design-system/src/components/index.ts
server/sonar-web/design-system/src/index.ts
server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResult.tsx
server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx
server/sonar-web/src/main/js/app/components/nav/component/Header.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/Breadcrumb-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/__tests__/Header-test.tsx
server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Header-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.css [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItemList.tsx
server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/QualityGateStatus.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/BranchLikeNavigation-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/MenuItem-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/MenuItemList-test.tsx [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/BranchLikeNavigation-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/Menu-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItemList-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx
server/sonar-web/src/main/js/components/icons/BranchLikeIcon.tsx
server/sonar-web/src/main/js/components/icons/__tests__/__snapshots__/BranchLikeIcon-test.tsx.snap
server/sonar-web/src/main/js/helpers/mocks/branch-like.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/design-system/src/components/Badge.tsx b/server/sonar-web/design-system/src/components/Badge.tsx
new file mode 100644 (file)
index 0000000..a343c76
--- /dev/null
@@ -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`}
+  }
+`;
index 304a1d3dd2c3468256c4b14acf16f617ab15d88e..1d86d98131db7f9d2e4c1c42db38a8a2f2492353 100644 (file)
@@ -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>
diff --git a/server/sonar-web/design-system/src/components/QualityGateIndicator.tsx b/server/sonar-web/design-system/src/components/QualityGateIndicator.tsx
new file mode 100644 (file)
index 0000000..5c8ac70
--- /dev/null
@@ -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>
+  );
+}
index 277f5eca4b59f8793828fef08e7d0c6baaed15ce..e83f2198f4d57217567610c603b1dcffe61c7dec 100644 (file)
@@ -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`
diff --git a/server/sonar-web/design-system/src/components/__tests__/Badge-test.tsx b/server/sonar-web/design-system/src/components/__tests__/Badge-test.tsx
new file mode 100644 (file)
index 0000000..384dcfd
--- /dev/null
@@ -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');
+});
index 96d24f7f7bc62f9615b974c794f44d1cc8d405ab..6dde045207d8b394bc9518d0a329d66d3cc3e8e0 100644 (file)
@@ -82,7 +82,7 @@ function setupWithProps(props: Partial<FCProps<typeof InputSearch>> = {}) {
       onChange={jest.fn()}
       placeholder="placeholder"
       searchInputAriaLabel=""
-      tooShortText=""
+      tooShortText="too short"
       value="foo"
       {...props}
     />
diff --git a/server/sonar-web/design-system/src/components/__tests__/QualityGateIndicator-test.tsx b/server/sonar-web/design-system/src/components/__tests__/QualityGateIndicator-test.tsx
new file mode 100644 (file)
index 0000000..0ab9b9a
--- /dev/null
@@ -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} />);
+}
index 5743a92a7b0abc06848610fb9cdf0661a701e3d6..7c366168bf2200d5fa155de62b190079175cfe24 100644 (file)
 
 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',
diff --git a/server/sonar-web/design-system/src/components/icons/BranchIcon.tsx b/server/sonar-web/design-system/src/components/icons/BranchIcon.tsx
new file mode 100644 (file)
index 0000000..532434e
--- /dev/null
@@ -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');
diff --git a/server/sonar-web/design-system/src/components/icons/HelperHintIcon.tsx b/server/sonar-web/design-system/src/components/icons/HelperHintIcon.tsx
new file mode 100644 (file)
index 0000000..0a5e696
--- /dev/null
@@ -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>
+  );
+}
diff --git a/server/sonar-web/design-system/src/components/icons/MainBranchIcon.tsx b/server/sonar-web/design-system/src/components/icons/MainBranchIcon.tsx
new file mode 100644 (file)
index 0000000..e954c1e
--- /dev/null
@@ -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>
+  );
+}
diff --git a/server/sonar-web/design-system/src/components/icons/PullRequestIcon.tsx b/server/sonar-web/design-system/src/components/icons/PullRequestIcon.tsx
new file mode 100644 (file)
index 0000000..4f058f0
--- /dev/null
@@ -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');
index a8ba3597407d78235d2ee05516651671e3a3b1ef..b0992dbdb916fce5fef3739be84695b4cd951e27 100644 (file)
  * 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';
index 4cb65f8730c7a2c27412fda8c139295f3f567a73..c3fcbab1c4e43fa17244eea437bf9ce531e2cd3c 100644 (file)
@@ -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';
index cd4bd05a51b7a3f90ede345f40d7d2901108f6be..c8e853ca7a18903999e359c522df8d0bf8635c3b 100644 (file)
@@ -21,3 +21,4 @@
 export * from './components';
 export * from './helpers';
 export * from './theme';
+export * from './types/theme';
index 982f207add55b41f8ab95fbda6de399c69b8ed63..fc86449221deb0826f577c8e604bc96f0b60d7f6 100644 (file)
@@ -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 && (
index 50c85a8c046167e018f701ec0bcafe211587e7e0..2dd18a0151a4fd192243e78323aaf65a0cb9f501 100644 (file)
  * 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>
index 145ae9377dd07940a67e49b46d128d78ecfc3f97..08f786d3a91fcafc7c5e8995675794d80c529a04 100644 (file)
  * 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>
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Breadcrumb-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Breadcrumb-test.tsx
deleted file mode 100644 (file)
index 25074c5..0000000
+++ /dev/null
@@ -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}
-    />
-  );
-}
index b1f3942d0b6d1c8f919aa39b4e1c4db0512b3b24..5add2af3e0e2608ff6bf367763da021e9e9b41ca 100644 (file)
  * 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 }
   );
 }
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Header-test.tsx.snap
deleted file mode 100644 (file)
index bac9e18..0000000
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx
new file mode 100644 (file)
index 0000000..160b46a
--- /dev/null
@@ -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;
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.css b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.css
deleted file mode 100644 (file)
index b96f857..0000000
+++ /dev/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);
-}
index d4e761b60f5ecc55dc89cbe0bcb41a1f3697e249..65d4741665d51bfc9f800ad78045afb6cb73126f 100644 (file)
  * 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>
   );
 }
 
index 589b1219abbbdb3754c756eb2be3c39d5a561cb8..2de026da87e5e84d047aafe1b54effb9871881ab 100644 (file)
  * 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>
   );
 }
 
index e1eaf9af75a95e910a9a08e79e319615182b618d..bab191696bbdfa0b5efe196a8256fe2edee1f5c8 100644 (file)
  * 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>
     );
   }
 }
index 2cc14207c3792867d17afc1c995597743151f54b..421addeaa9301cf845b70f7041200729f5371374 100644 (file)
  * 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>
   );
 }
 
index f6c749f926a243c3695a0a5bb260470ae314241e..ee21a7a07bc69f423610e4e6764b0a9270b238dc 100644 (file)
  * 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))}
         </>
       )}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx
new file mode 100644 (file)
index 0000000..43b590e
--- /dev/null
@@ -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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/QualityGateStatus.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/QualityGateStatus.tsx
new file mode 100644 (file)
index 0000000..66daeb5
--- /dev/null
@@ -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>
+  );
+}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/BranchLikeNavigation-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/BranchLikeNavigation-test.tsx
deleted file mode 100644 (file)
index 8b3b4bd..0000000
+++ /dev/null
@@ -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}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx
deleted file mode 100644 (file)
index 973cdc0..0000000
+++ /dev/null
@@ -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}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx
deleted file mode 100644 (file)
index 4a95f97..0000000
+++ /dev/null
@@ -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}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/MenuItem-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/MenuItem-test.tsx
deleted file mode 100644 (file)
index a5ce319..0000000
+++ /dev/null
@@ -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}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/MenuItemList-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/MenuItemList-test.tsx
deleted file mode 100644 (file)
index d448b0a..0000000
+++ /dev/null
@@ -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}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/BranchLikeNavigation-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/BranchLikeNavigation-test.tsx.snap
deleted file mode 100644 (file)
index ba319b9..0000000
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap
deleted file mode 100644 (file)
index 8026ce6..0000000
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/Menu-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/Menu-test.tsx.snap
deleted file mode 100644 (file)
index 9afa94a..0000000
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap
deleted file mode 100644 (file)
index 57f5a75..0000000
+++ /dev/null
@@ -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>
-`;
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItemList-test.tsx.snap b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItemList-test.tsx.snap
deleted file mode 100644 (file)
index 408f21b..0000000
+++ /dev/null
@@ -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>
-`;
index 9c799d8921c650c43f330cac86a86d99cd88a4c7..cd75a88aa404f60b421c3d4d001d2b54b2319863 100644 (file)
@@ -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();
         }
index 754acf2a4a645cd8e98cd46245acc80e80be72a4..a5d48bd0d621a47228c3dcc5af39bfec4b515fc4 100644 (file)
  * 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} />;
 }
index dfe0b95f6199a7c4b7909f12988dea770eb88809..561ef92174d440d438e9a648ad5e8b231a6a2542 100644 (file)
@@ -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>"`;
index becee29b800ee967be3d6cda8ff58fbee6af39ca..30cb65c1a7a45ca22854b69e97adc882c5d481a9 100644 (file)
@@ -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' } }),
   ];
 }
index 7d1f9f70f6a321a15a51ae58033b4c5ccd7dc9bc..1f3384f16a8da525cbe77133860b1da49c569d9a 100644 (file)
@@ -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