From b211e45ffe83cbf8eaa98c3539b2ee3a952162e2 Mon Sep 17 00:00:00 2001 From: Revanshu Paliwal Date: Tue, 21 Mar 2023 17:13:04 +0100 Subject: SONAR-18776 Migrating breadcrumb and branch selector to MIUI --- .../global-search/GlobalSearchResult.tsx | 4 +- .../js/app/components/nav/component/Breadcrumb.tsx | 93 ++--- .../js/app/components/nav/component/Header.tsx | 18 +- .../nav/component/__tests__/Breadcrumb-test.tsx | 73 ---- .../nav/component/__tests__/Header-test.tsx | 186 +++++++-- .../__tests__/__snapshots__/Header-test.tsx.snap | 153 -------- .../component/branch-like/BranchHelpTooltip.tsx | 130 +++++++ .../component/branch-like/BranchLikeNavigation.css | 78 ---- .../component/branch-like/BranchLikeNavigation.tsx | 98 ++--- .../component/branch-like/CurrentBranchLike.tsx | 138 +------ .../components/nav/component/branch-like/Menu.tsx | 72 ++-- .../nav/component/branch-like/MenuItem.tsx | 47 +-- .../nav/component/branch-like/MenuItemList.tsx | 44 +-- .../nav/component/branch-like/PRLink.tsx | 83 ++++ .../component/branch-like/QualityGateStatus.tsx | 63 ++++ .../__tests__/BranchLikeNavigation-test.tsx | 73 ---- .../__tests__/CurrentBranchLike-test.tsx | 129 ------- .../component/branch-like/__tests__/Menu-test.tsx | 116 ------ .../branch-like/__tests__/MenuItem-test.tsx | 56 --- .../branch-like/__tests__/MenuItemList-test.tsx | 52 --- .../BranchLikeNavigation-test.tsx.snap | 195 ---------- .../__snapshots__/CurrentBranchLike-test.tsx.snap | 307 --------------- .../__tests__/__snapshots__/Menu-test.tsx.snap | 323 ---------------- .../__tests__/__snapshots__/MenuItem-test.tsx.snap | 146 -------- .../__snapshots__/MenuItemList-test.tsx.snap | 417 --------------------- .../js/components/common/DocumentationTooltip.tsx | 4 +- .../main/js/components/icons/BranchLikeIcon.tsx | 14 +- .../__snapshots__/BranchLikeIcon-test.tsx.snap | 4 +- .../src/main/js/helpers/mocks/branch-like.ts | 18 +- 29 files changed, 657 insertions(+), 2477 deletions(-) delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/__tests__/Breadcrumb-test.tsx delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Header-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.css create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx create mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/QualityGateStatus.tsx delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/BranchLikeNavigation-test.tsx delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/MenuItem-test.tsx delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/MenuItemList-test.tsx delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/BranchLikeNavigation-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/Menu-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap delete mode 100644 server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItemList-test.tsx.snap (limited to 'server/sonar-web/src/main/js') diff --git a/server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResult.tsx b/server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResult.tsx index 982f207add5..fc86449221d 100644 --- a/server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResult.tsx +++ b/server/sonar-web/src/main/js/app/components/global-search/GlobalSearchResult.tsx @@ -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 { to={to} >
- +
{component.isFavorite && } {!component.isFavorite && component.isRecentlyBrowsed && ( diff --git a/server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx b/server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx index 50c85a8c046..2dd18a0151a 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/Breadcrumb.tsx @@ -17,87 +17,48 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { last } from 'lodash'; +import { HoverLink, TextMuted } from 'design-system'; import * as React from 'react'; -import Link from '../../../../components/common/Link'; -import QualifierIcon from '../../../../components/icons/QualifierIcon'; -import { isMainBranch } from '../../../../helpers/branch-like'; +import Favorite from '../../../../components/controls/Favorite'; import { getComponentOverviewUrl } from '../../../../helpers/urls'; -import { BranchLike } from '../../../../types/branch-like'; import { Component } from '../../../../types/types'; -import { colors } from '../../../theme'; +import { CurrentUser, isLoggedIn } from '../../../../types/users'; export interface BreadcrumbProps { component: Component; - currentBranchLike: BranchLike | undefined; + currentUser: CurrentUser; } export function Breadcrumb(props: BreadcrumbProps) { - const { - component: { breadcrumbs }, - currentBranchLike, - } = props; - const lastBreadcrumbElement = last(breadcrumbs); - const isNotMainBranch = currentBranchLike && !isMainBranch(currentBranchLike); + const { component, currentUser } = props; return ( -
- {breadcrumbs.map((breadcrumbElement, i) => { - const isFirst = i === 0; - const isNotLast = i < breadcrumbs.length - 1; +
+ {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 && ( - - )} - - {showQualifierIcon && isNotMainBranch && ( - - )} - {breadcrumbElement.name} - - - ) : ( - <> - {showQualifierIcon && ( - - )} - {breadcrumbElement.name} - - ); return ( - - {isLast ? ( -

- {name} -

- ) : ( - - {name} - +
+ {isLast && isLoggedIn(currentUser) && ( + )} - {isNotLast && } - + + + + {isNotLast && } +
); })}
diff --git a/server/sonar-web/src/main/js/app/components/nav/component/Header.tsx b/server/sonar-web/src/main/js/app/components/nav/component/Header.tsx index 145ae9377dd..08f786d3a91 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/Header.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/Header.tsx @@ -18,14 +18,12 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import Favorite from '../../../../components/controls/Favorite'; import { ProjectAlmBindingResponse } from '../../../../types/alm-settings'; import { BranchLike } from '../../../../types/branch-like'; import { Component } from '../../../../types/types'; -import { CurrentUser, isLoggedIn } from '../../../../types/users'; +import { CurrentUser } from '../../../../types/users'; import withCurrentUserContext from '../../current-user/withCurrentUserContext'; import BranchLikeNavigation from './branch-like/BranchLikeNavigation'; -import CurrentBranchLikeMergeInformation from './branch-like/CurrentBranchLikeMergeInformation'; import { Breadcrumb } from './Breadcrumb'; export interface HeaderProps { @@ -40,25 +38,17 @@ export function Header(props: HeaderProps) { const { branchLikes, component, currentBranchLike, currentUser, projectBinding } = props; return ( -
- - {isLoggedIn(currentUser) && ( - - )} +
+ {currentBranchLike && ( <> + - )}
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 index 25074c59040..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Breadcrumb-test.tsx +++ /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 = {}) { - return renderComponent( - - ); -} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Header-test.tsx b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Header-test.tsx index b1f3942d0b6..5add2af3e0e 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Header-test.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/__tests__/Header-test.tsx @@ -17,34 +17,178 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { shallow } from 'enzyme'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import * as React from 'react'; -import Favorite from '../../../../../components/controls/Favorite'; -import { mockSetOfBranchAndPullRequest } from '../../../../../helpers/mocks/branch-like'; +import { + mockMainBranch, + mockPullRequest, + mockSetOfBranchAndPullRequestForBranchSelector, +} from '../../../../../helpers/mocks/branch-like'; import { mockComponent } from '../../../../../helpers/mocks/component'; -import { mockCurrentUser } from '../../../../../helpers/testMocks'; +import { mockCurrentUser, mockLoggedInUser } from '../../../../../helpers/testMocks'; +import { renderApp } from '../../../../../helpers/testReactTestingUtils'; +import { AlmKeys } from '../../../../../types/alm-settings'; +import { Feature } from '../../../../../types/features'; +import { BranchStatusContext } from '../../../branch-status/BranchStatusContext'; import { Header, HeaderProps } from '../Header'; -it('should render correctly', () => { - const wrapper = shallowRender({ currentUser: mockCurrentUser({ isLoggedIn: true }) }); - expect(wrapper).toMatchSnapshot(); +jest.mock('../../../../../api/favorites', () => ({ + addFavorite: jest.fn().mockResolvedValue({}), + removeFavorite: jest.fn().mockResolvedValue({}), +})); + +it('should render correctly when there is only 1 branch', () => { + renderHeader({ branchLikes: [mockMainBranch()] }); + expect(screen.getByText('project')).toBeInTheDocument(); + expect(screen.getByLabelText('help-tooltip')).toBeInTheDocument(); + expect( + screen.getByRole('button', { name: 'branch-1 overview.quality_gate_x.OK' }) + ).toBeDisabled(); +}); + +it('should render correctly when there are multiple branch', async () => { + const user = userEvent.setup(); + renderHeader(); + expect(screen.getByRole('button', { name: 'branch-1 overview.quality_gate_x.OK' })).toBeEnabled(); + expect(screen.queryByLabelText('help-tooltip')).not.toBeInTheDocument(); + + await user.click(screen.getByRole('button', { name: 'branch-1 overview.quality_gate_x.OK' })); + expect(screen.getByText('branches.main_branch')).toBeInTheDocument(); + expect( + screen.getByRole('menuitem', { name: 'branch-2 overview.quality_gate_x.ERROR ERROR' }) + ).toBeInTheDocument(); + expect(screen.getByRole('menuitem', { name: 'branch-3' })).toBeInTheDocument(); + expect(screen.getByRole('menuitem', { name: '1 – PR-1' })).toBeInTheDocument(); + expect(screen.getByRole('menuitem', { name: '2 – PR-2' })).toBeInTheDocument(); + + await user.click( + screen.getByRole('menuitem', { name: 'branch-2 overview.quality_gate_x.ERROR ERROR' }) + ); + expect(screen.getByText('/dashboard?branch=branch-2&id=my-project')).toBeInTheDocument(); }); -it('should not render favorite button if the user is not logged in', () => { - const wrapper = shallowRender(); - expect(wrapper.find(Favorite).exists()).toBe(false); +it('should show manage branch and pull request button for admin', async () => { + const user = userEvent.setup(); + renderHeader({ + currentUser: mockLoggedInUser(), + component: mockComponent({ + configuration: { showSettings: true }, + breadcrumbs: [{ name: 'project', key: 'project', qualifier: 'TRK' }], + }), + }); + await user.click(screen.getByRole('button', { name: 'branch-1 overview.quality_gate_x.OK' })); + + expect(screen.getByRole('link', { name: 'branch_like_navigation.manage' })).toBeInTheDocument(); + expect(screen.getByRole('link', { name: 'branch_like_navigation.manage' })).toHaveAttribute( + 'href', + '/project/branches?id=my-project' + ); }); -function shallowRender(props?: Partial) { - const branchLikes = mockSetOfBranchAndPullRequest(); - - return shallow( -
+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, featureList = [Feature.BranchSupport]) { + const branchLikes = mockSetOfBranchAndPullRequestForBranchSelector(); + + return renderApp( + '/', + { + /*noop*/ + }, + updateBranchStatus: () => { + /*noop*/ + }, + }} + > +
+ , + { 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 index bac9e18453c..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/__tests__/__snapshots__/Header-test.tsx.snap +++ /dev/null @@ -1,153 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
- - - - -
-`; 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 index 00000000000..160b46a5ccf --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchHelpTooltip.tsx @@ -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 = ; + + if (isApplication) { + if (!hasManyBranches && canAdminComponent) { + return ( + +

{translate('application.branches.help')}

+
+ + {translate('application.branches.link')} + + + } + > + {helpIcon} +
+ ); + } + } else { + if (!branchSupportEnabled) { + return ( + + {helpIcon} + + ); + } + + if (!hasManyBranches) { + return ( + + {helpIcon} + + ); + } + } + + 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 index b96f85754b4..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.css +++ /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); -} diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx index d4e761b60f5..65d4741665d 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/BranchLikeNavigation.tsx @@ -17,20 +17,21 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import classNames from 'classnames'; +import { ButtonSecondary, PopupPlacement, PopupZLevel, PortalPopup } from 'design-system'; import * as React from 'react'; -import { ButtonPlain } from '../../../../../components/controls/buttons'; -import Toggler from '../../../../../components/controls/Toggler'; -import { ProjectAlmBindingResponse } from '../../../../../types/alm-settings'; +import OutsideClickHandler from '../../../../../components/controls/OutsideClickHandler'; +import { AlmKeys, ProjectAlmBindingResponse } from '../../../../../types/alm-settings'; import { BranchLike } from '../../../../../types/branch-like'; +import { ComponentQualifier } from '../../../../../types/component'; import { Feature } from '../../../../../types/features'; import { Component } from '../../../../../types/types'; import withAvailableFeatures, { WithAvailableFeaturesProps, } from '../../../available-features/withAvailableFeatures'; -import './BranchLikeNavigation.css'; +import BranchHelpTooltip from './BranchHelpTooltip'; import CurrentBranchLike from './CurrentBranchLike'; import Menu from './Menu'; +import PRLink from './PRLink'; export interface BranchLikeNavigationProps extends WithAvailableFeaturesProps { branchLikes: BranchLike[]; @@ -48,59 +49,72 @@ export function BranchLikeNavigation(props: BranchLikeNavigationProps) { projectBinding, } = props; + const isApplication = component.qualifier === ComponentQualifier.Application; + const isGitLab = projectBinding !== undefined && projectBinding.alm === AlmKeys.GitLab; + const [isMenuOpen, setIsMenuOpen] = React.useState(false); const branchSupportEnabled = props.hasFeature(Feature.BranchSupport); - - const canAdminComponent = configuration && configuration.showSettings; + const canAdminComponent = configuration?.showSettings; const hasManyBranches = branchLikes.length >= 2; const isMenuEnabled = branchSupportEnabled && hasManyBranches; const currentBranchLikeElement = ( - + ); return ( - - {isMenuEnabled ? ( - setIsMenuOpen(false)} - open={isMenuOpen} +
+ { + setIsMenuOpen(false); + }} + > + setIsMenuOpen(false)} - /> + isMenuOpen && ( + { + setIsMenuOpen(false); + }} + /> + ) } + placement={PopupPlacement.BottomLeft} + zLevel={PopupZLevel.Global} > - setIsMenuOpen(!isMenuOpen)} + { + setIsMenuOpen(!isMenuOpen); + }} + disabled={!isMenuEnabled} aria-expanded={isMenuOpen} aria-haspopup="menu" > {currentBranchLikeElement} - - - ) : ( - currentBranchLikeElement - )} - + + + + +
+ +
+ + +
); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx index 589b1219abb..2de026da87e 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/CurrentBranchLike.tsx @@ -17,147 +17,31 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { ChevronDownIcon, TextMuted } from 'design-system'; import * as React from 'react'; -import DocumentationTooltip from '../../../../../components/common/DocumentationTooltip'; -import Link from '../../../../../components/common/Link'; -import HelpTooltip from '../../../../../components/controls/HelpTooltip'; import BranchLikeIcon from '../../../../../components/icons/BranchLikeIcon'; -import DropdownIcon from '../../../../../components/icons/DropdownIcon'; -import PlusCircleIcon from '../../../../../components/icons/PlusCircleIcon'; import { getBranchLikeDisplayName } from '../../../../../helpers/branch-like'; -import { translate, translateWithParameters } from '../../../../../helpers/l10n'; -import { getApplicationAdminUrl } from '../../../../../helpers/urls'; -import { AlmKeys, ProjectAlmBindingResponse } from '../../../../../types/alm-settings'; -import { BranchLike } from '../../../../../types/branch-like'; -import { ComponentQualifier } from '../../../../../types/component'; +import { BranchLike, BranchStatusData } from '../../../../../types/branch-like'; import { Component } from '../../../../../types/types'; -import { colors } from '../../../../theme'; +import QualityGateStatus from './QualityGateStatus'; -export interface CurrentBranchLikeProps { - branchesEnabled: boolean; +export interface CurrentBranchLikeProps extends Pick { 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 ; - } - - const plusIcon = ; - - if (isApplication) { - if (!hasManyBranches && canAdminComponent) { - return ( - -

{translate('application.branches.help')}

-
- - {translate('application.branches.link')} - - - } - > - {plusIcon} -
- ); - } - } else { - if (!branchesEnabled) { - return ( - - {plusIcon} - - ); - } - - if (!hasManyBranches) { - return ( - - {plusIcon} - - ); - } - } - - return null; - }; return ( - - - - {displayName} - - {additionalIcon()} - +
+ + + + +
); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx index e1eaf9af75a..bab191696bb 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/Menu.tsx @@ -17,10 +17,8 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { DropdownMenu, InputSearch, ItemDivider, Link } from 'design-system'; import * as React from 'react'; -import Link from '../../../../../components/common/Link'; -import { DropdownOverlay } from '../../../../../components/controls/Dropdown'; -import SearchBox from '../../../../../components/controls/SearchBox'; import { Router, withRouter } from '../../../../../components/hoc/withRouter'; import { getBrancheLikesAsTree, @@ -156,43 +154,49 @@ export class Menu extends React.PureComponent { 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 ( - -
- -
- -
- -
- + + + {showManageLink && ( -
- onClose()} - to={{ pathname: '/project/branches', search: queryToSearch({ id: component.key }) }} - > - {translate('branch_like_navigation.manage')} - -
+ <> + +
  • + { + onClose(); + }} + to={{ pathname: '/project/branches', search: queryToSearch({ id: component.key }) }} + > + {translate('branch_like_navigation.manage')} + +
  • + )} -
    + ); } } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx index 2cc14207c37..421addeaa93 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItem.tsx @@ -18,52 +18,57 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import classNames from 'classnames'; +import { Badge, ItemButton, TextBold, TextMuted } from 'design-system'; import * as React from 'react'; -import BranchStatus from '../../../../../components/common/BranchStatus'; import BranchLikeIcon from '../../../../../components/icons/BranchLikeIcon'; import { getBranchLikeDisplayName, isMainBranch } from '../../../../../helpers/branch-like'; import { translate } from '../../../../../helpers/l10n'; import { BranchLike } from '../../../../../types/branch-like'; import { Component } from '../../../../../types/types'; +import QualityGateStatus from './QualityGateStatus'; export interface MenuItemProps { branchLike: BranchLike; component: Component; - indent?: boolean; onSelect: (branchLike: BranchLike) => void; selected: boolean; setSelectedNode?: (node: HTMLLIElement) => void; } export function MenuItem(props: MenuItemProps) { - const { branchLike, component, indent, setSelectedNode, onSelect, selected } = props; + const { branchLike, component, setSelectedNode, onSelect, selected } = props; const displayName = getBranchLikeDisplayName(branchLike); return ( -
  • onSelect(branchLike)} - ref={selected ? setSelectedNode : undefined} + { + onSelect(branchLike); + }} > -
    -
    +
    +
    - {displayName} + {isMainBranch(branchLike) && ( - {translate('branches.main_branch')} + <> + + {translate('branches.main_branch')} + + )} + {!isMainBranch(branchLike) && ( + )}
    -
    - -
    +
    -
  • + ); } diff --git a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItemList.tsx b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItemList.tsx index f6c749f926a..ee21a7a07bc 100644 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItemList.tsx +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/MenuItemList.tsx @@ -17,11 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { HelperHintIcon, ItemDivider, ItemHeader } from 'design-system'; import * as React from 'react'; import HelpTooltip from '../../../../../components/controls/HelpTooltip'; import { getBranchLikeKey, isSameBranchLike } from '../../../../../helpers/branch-like'; import { translate } from '../../../../../helpers/l10n'; -import { scrollToElement } from '../../../../../helpers/scrolling'; import { isDefined } from '../../../../../helpers/types'; import { BranchLike, BranchLikeTree } from '../../../../../types/branch-like'; import { Component } from '../../../../../types/types'; @@ -36,22 +36,21 @@ export interface MenuItemListProps { } export function MenuItemList(props: MenuItemListProps) { - let listNode: HTMLUListElement | null = null; let selectedNode: HTMLLIElement | null = null; React.useEffect(() => { - if (listNode && selectedNode) { - scrollToElement(selectedNode, { parent: listNode, smooth: false }); + if (selectedNode) { + selectedNode.scrollIntoView({ block: 'center' }); + selectedNode.focus(); } }); const { branchLikeTree, component, hasResults, onSelect, selectedBranchLike } = props; - const renderItem = (branchLike: BranchLike, indent?: boolean) => ( + const renderItem = (branchLike: BranchLike) => ( (listNode = node)}> +
      {!hasResults && ( -
    • - {translate('no_results')} -
    • +
      + {translate('no_results')} +
      )} {/* BRANCHES & PR */} @@ -75,22 +74,21 @@ export function MenuItemList(props: MenuItemListProps) { {renderItem(tree.branch)} {tree.pullRequests.length > 0 && ( <> -
    • - - {translate('branch_like_navigation.pull_requests')} - -
    • - {tree.pullRequests.map((pr) => renderItem(pr, true))} + + {translate('branch_like_navigation.pull_requests')} + + {tree.pullRequests.map((pr) => renderItem(pr))} )} -
      ))} {/* PARENTLESS PR (for display during search) */} {branchLikeTree.parentlessPullRequests.length > 0 && ( <> -
    • {translate('branch_like_navigation.pull_requests')}
    • + + {translate('branch_like_navigation.pull_requests')} + {branchLikeTree.parentlessPullRequests.map((pr) => renderItem(pr))} )} @@ -98,13 +96,17 @@ export function MenuItemList(props: MenuItemListProps) { {/* ORPHAN PR */} {branchLikeTree.orphanPullRequests.length > 0 && ( <> -
    • + + {translate('branch_like_navigation.orphan_pull_requests')} -
    • + > + + + + {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 index 00000000000..43b590e6ed7 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/PRLink.tsx @@ -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 ( +
      + {currentBranchLike.url !== undefined && ( + + ) + } + key={currentBranchLike.key} + to={currentBranchLike.url} + > + {!almKey && translate('branches.see_the_pr')} + + )} +
      + ); +} 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 index 00000000000..66daeb57842 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/nav/component/branch-like/QualityGateStatus.tsx @@ -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 ( +
      + + {showStatusText && {formatted}} +
      + ); +} 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 index 8b3b4bd9c40..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/BranchLikeNavigation-test.tsx +++ /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) { - const branchLikes = mockSetOfBranchAndPullRequest(); - - return shallow( - - ); -} 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 index 973cdc0c4f2..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/CurrentBranchLike-test.tsx +++ /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) { - return shallow( - - ); -} 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 index 4a95f97830c..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/Menu-test.tsx +++ /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) { - return shallow( - - ); -} 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 index a5ce319f57f..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/MenuItem-test.tsx +++ /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) { - return shallow( - - ); -} 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 index d448b0af2aa..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/MenuItemList-test.tsx +++ /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) { - const branchLikes = [ - ...mockSetOfBranchAndPullRequest(), - mockPullRequest({ base: 'not-in-the-list' }), - ]; - const branchLikeTree = getBrancheLikesAsTree(branchLikes); - - return shallow( - - ); -} 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 index ba319b91b1d..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/BranchLikeNavigation-test.tsx.snap +++ /dev/null @@ -1,195 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - - - -`; - -exports[`should render the menu trigger if branches are enabled 1`] = ` - - - } - > - - - - - -`; 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 index 8026ce65c2b..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/CurrentBranchLike-test.tsx.snap +++ /dev/null @@ -1,307 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`applications should render correctly when there are many branchlikes 1`] = ` - - - - master - - - -`; - -exports[`applications should render correctly when there is only one branch and the user CAN'T admin the application 1`] = ` - - - - master - - -`; - -exports[`applications should render correctly when there is only one branch and the user can admin the application 1`] = ` - - - - master - - -

      - application.branches.help -

      -
      - - application.branches.link - - - } - > - -
      -
      -`; - -exports[`projects should render correctly when branches support is disabled: alm with mrs 1`] = ` - - - - master - - - - - -`; - -exports[`projects should render correctly when branches support is disabled: alm with prs 1`] = ` - - - - master - - - - - -`; - -exports[`projects should render correctly when branches support is disabled: default 1`] = ` - - - - master - - - - - -`; - -exports[`projects should render correctly when there are many branchlikes 1`] = ` - - - - master - - - -`; - -exports[`projects should render correctly when there is only one branchlike 1`] = ` - - - - master - - - - - -`; 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 index 9afa94a156d..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/Menu-test.tsx.snap +++ /dev/null @@ -1,323 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - -
      - -
      -
      - -
      -
      - - branch_like_navigation.manage - -
      -
      -`; - -exports[`should render correctly with no current branch like 1`] = ` - -
      - -
      -
      - -
      -
      - - branch_like_navigation.manage - -
      -
      -`; 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 index 57f5a7527b9..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItem-test.tsx.snap +++ /dev/null @@ -1,146 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render a main branch correctly 1`] = ` -
    • -
      -
      - - - master - - - branches.main_branch - -
      -
      - -
      -
      -
    • -`; - -exports[`should render a non-main branch, indented and selected item correctly 1`] = ` -
    • -
      -
      - - - 1001 – Foo Bar feature - -
      -
      - -
      -
      -
    • -`; 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 index 408f21b26a3..00000000000 --- a/server/sonar-web/src/main/js/app/components/nav/component/branch-like/__tests__/__snapshots__/MenuItemList-test.tsx.snap +++ /dev/null @@ -1,417 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
        -
      • - - no_results - -
      • - -
      • - - branch_like_navigation.pull_requests - -
      • - - -
        - -
        - -
        - -
        - -
        - -
        -
      • - branch_like_navigation.pull_requests -
      • - -
      • - branch_like_navigation.orphan_pull_requests - -
      • - -
      -`; diff --git a/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx b/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx index 9c799d8921c..cd75a88aa40 100644 --- a/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx +++ b/server/sonar-web/src/main/js/components/common/DocumentationTooltip.tsx @@ -34,7 +34,7 @@ export interface DocumentationTooltipProps { export default function DocumentationTooltip(props: DocumentationTooltipProps) { const nextSelectableNode = React.useRef(); - const linksRef = React.useRef<(HTMLAnchorElement | null)[]>([]); + const linksRef = React.useRef>([]); const helpRef = React.useRef(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(); } diff --git a/server/sonar-web/src/main/js/components/icons/BranchLikeIcon.tsx b/server/sonar-web/src/main/js/components/icons/BranchLikeIcon.tsx index 754acf2a4a6..a5d48bd0d62 100644 --- a/server/sonar-web/src/main/js/components/icons/BranchLikeIcon.tsx +++ b/server/sonar-web/src/main/js/components/icons/BranchLikeIcon.tsx @@ -17,20 +17,22 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +import { BranchIcon, MainBranchIcon, PullRequestIcon, ThemeColors } from 'design-system'; import * as React from 'react'; -import BranchIcon from '../../components/icons/BranchIcon'; import { IconProps } from '../../components/icons/Icon'; -import PullRequestIcon from '../../components/icons/PullRequestIcon'; -import { isPullRequest } from '../../helpers/branch-like'; +import { isMainBranch, isPullRequest } from '../../helpers/branch-like'; import { BranchLike } from '../../types/branch-like'; -export interface BranchLikeIconProps extends IconProps { +export interface BranchLikeIconProps extends Omit { branchLike: BranchLike; + fill?: ThemeColors; } export default function BranchLikeIcon({ branchLike, ...props }: BranchLikeIconProps) { if (isPullRequest(branchLike)) { - return ; + return ; + } else if (isMainBranch(branchLike)) { + return ; } - return ; + return ; } diff --git a/server/sonar-web/src/main/js/components/icons/__tests__/__snapshots__/BranchLikeIcon-test.tsx.snap b/server/sonar-web/src/main/js/components/icons/__tests__/__snapshots__/BranchLikeIcon-test.tsx.snap index dfe0b95f619..561ef92174d 100644 --- a/server/sonar-web/src/main/js/components/icons/__tests__/__snapshots__/BranchLikeIcon-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/icons/__tests__/__snapshots__/BranchLikeIcon-test.tsx.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`should render branch icon correctly 1`] = `"
      "`; +exports[`should render branch icon correctly 1`] = `"
      "`; -exports[`should render pull request icon correctly 1`] = `"
      "`; +exports[`should render pull request icon correctly 1`] = `"
      "`; diff --git a/server/sonar-web/src/main/js/helpers/mocks/branch-like.ts b/server/sonar-web/src/main/js/helpers/mocks/branch-like.ts index becee29b800..30cb65c1a7a 100644 --- a/server/sonar-web/src/main/js/helpers/mocks/branch-like.ts +++ b/server/sonar-web/src/main/js/helpers/mocks/branch-like.ts @@ -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' } }), ]; } -- cgit v1.2.3