diff options
author | Revanshu Paliwal <revanshu.paliwal@sonarsource.com> | 2023-03-21 17:13:04 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-03-27 20:03:03 +0000 |
commit | b211e45ffe83cbf8eaa98c3539b2ee3a952162e2 (patch) | |
tree | 700099740e34c64a82bc4ed02041d7f2c5e51406 /server/sonar-web/src/main/js | |
parent | 54686faf91547cd918e84c8e081a722ce974c89d (diff) | |
download | sonarqube-b211e45ffe83cbf8eaa98c3539b2ee3a952162e2.tar.gz sonarqube-b211e45ffe83cbf8eaa98c3539b2ee3a952162e2.zip |
SONAR-18776 Migrating breadcrumb and branch selector to MIUI
Diffstat (limited to 'server/sonar-web/src/main/js')
29 files changed, 657 insertions, 2477 deletions
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<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 && ( 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 ( - <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> 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 ( - <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 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<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} - /> - ); -} 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<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 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`] = ` -<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 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 = <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 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 = ( - <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> ); } 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<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> ); } 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<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> ); } } 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 ( - <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> ); } 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) => ( <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 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 ( + <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 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 ( + <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 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<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 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<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 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<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 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<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 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<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 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`] = ` -<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 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`] = ` -<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 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`] = ` -<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 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`] = ` -<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 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`] = ` -<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> -`; 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<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(); } 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<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} />; } 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`] = `"<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>"`; 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' } }), ]; } |