Skip to content

Commit 7c3845b

Browse files
Hartesicsonartech
authored andcommitted
SONAR-25545 GitHub logo on a bound project now redirects to repository
GitOrigin-RevId: 382f7ceb94c26dd4a36f9b11e6dc76a8fd5ccf69
1 parent f24d6a2 commit 7c3845b

File tree

5 files changed

+166
-40
lines changed

5 files changed

+166
-40
lines changed

apps/sq-server/src/main/js/app/components/nav/component/ProjectBindingStatus.tsx

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@ import {
3131
TooltipSide,
3232
} from '@sonarsource/echoes-react';
3333
import classNames from 'classnames';
34-
import { forwardRef } from 'react';
34+
import { noop } from 'lodash';
35+
import { forwardRef, useEffect, useMemo, useState } from 'react';
3536
import { FormattedMessage, useIntl } from 'react-intl';
3637
import { Image } from '~adapters/components/common/Image';
37-
import { useCurrentUser } from '~sq-server-commons/context/current-user/CurrentUserContext';
38+
import { useCurrentUser } from '~adapters/helpers/users';
39+
import { getGithubRepositories } from '~sq-server-commons/api/alm-integrations';
3840
import { getProjectSettingsUrl } from '~sq-server-commons/helpers/urls';
3941
import { useProjectBindingQuery } from '~sq-server-commons/queries/devops-integration';
4042
import { AlmKeys } from '~sq-server-commons/types/alm-settings';
@@ -67,6 +69,8 @@ export function ProjectBindingStatus({
6769
className,
6870
component,
6971
}: Readonly<ProjectNavBindingStatusProps>) {
72+
const [repositoryUrl, setRepositoryUrl] = useState<string | undefined>();
73+
7074
const { formatMessage } = useIntl();
7175

7276
const { currentUser } = useCurrentUser();
@@ -83,9 +87,9 @@ export function ProjectBindingStatus({
8387
{ dop: formatMessage({ id: DOP_LABEL_IDS[almKey] }) },
8488
);
8589

86-
return (
87-
<Spinner isLoading={isLoadingProjectBinding}>
88-
{almKey && (
90+
const dopLogoComponent = useMemo(
91+
() =>
92+
almKey ? (
8993
<Image
9094
alt={imageTitle}
9195
className={classNames('sw-px-1 sw-align-text-top', className)}
@@ -94,6 +98,47 @@ export function ProjectBindingStatus({
9498
title={imageTitle}
9599
width={16}
96100
/>
101+
) : null,
102+
[almKey, imageTitle, className],
103+
);
104+
105+
useEffect(() => {
106+
// Retrieve the URL of the repository bound to this project
107+
// Only works for GitHub for now because:
108+
// - search_[DOP]_repos endpoints don't return the repository URL for Azure and Bitbucket
109+
// - we can filter on search_gitlab_repos with the repository name but
110+
// the project binding object only contains the repository id and not the name
111+
if (almKey === undefined) {
112+
return;
113+
}
114+
115+
if (almKey === AlmKeys.GitHub) {
116+
const [organization, repository] = projectBinding?.repository?.split('/') ?? [];
117+
getGithubRepositories({
118+
almSetting: almKey,
119+
organization,
120+
pageSize: 1,
121+
query: repository,
122+
})
123+
.then(({ repositories }) => {
124+
setRepositoryUrl(repositories[0].url);
125+
})
126+
.catch(noop);
127+
}
128+
}, [almKey, component, projectBinding]);
129+
130+
return (
131+
<Spinner isLoading={isLoadingProjectBinding}>
132+
{almKey && repositoryUrl ? (
133+
<LinkStandalone
134+
className="sw-flex sw-items-center"
135+
highlight={LinkHighlight.Default}
136+
to={repositoryUrl}
137+
>
138+
{dopLogoComponent}
139+
</LinkStandalone>
140+
) : (
141+
dopLogoComponent
97142
)}
98143

99144
{!almKey && !isLoadingProjectBinding && !component.configuration?.showSettings && (

apps/sq-server/src/main/js/app/components/nav/component/__tests__/Header-test.tsx

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@
2020

2121
import { screen } from '@testing-library/react';
2222
import userEvent from '@testing-library/user-event';
23+
import { byRole } from '~shared/helpers/testSelector';
2324
import { ComponentQualifier } from '~shared/types/component';
25+
import AlmIntegrationsServiceMock from '~sq-server-commons/api/mocks/AlmIntegrationsServiceMock';
2426
import AlmSettingsServiceMock from '~sq-server-commons/api/mocks/AlmSettingsServiceMock';
2527
import BranchesServiceMock from '~sq-server-commons/api/mocks/BranchesServiceMock';
28+
import { mockGitHubRepository } from '~sq-server-commons/helpers/mocks/alm-integrations';
29+
import { mockProjectAlmBindingResponse } from '~sq-server-commons/helpers/mocks/alm-settings';
2630
import { mockMainBranch, mockPullRequest } from '~sq-server-commons/helpers/mocks/branch-like';
2731
import { mockComponent } from '~sq-server-commons/helpers/mocks/component';
2832
import { mockCurrentUser, mockLoggedInUser } from '~sq-server-commons/helpers/testMocks';
@@ -35,13 +39,29 @@ jest.mock('~sq-server-commons/api/favorites', () => ({
3539
addFavorite: jest.fn().mockResolvedValue({}),
3640
removeFavorite: jest.fn().mockResolvedValue({}),
3741
}));
42+
jest.mock('~adapters/helpers/users', () => ({
43+
...jest.requireActual<typeof import('~adapters/helpers/users')>('~adapters/helpers/users'),
44+
useCurrentUser: () => ({ currentUser: mockLoggedInUser() }),
45+
}));
46+
47+
const ui = {
48+
bindProjectLink: byRole('link', { name: 'project_navigation.binding_status.bind' }),
49+
bindingLink: byRole('link', {
50+
name: /project_navigation.binding_status.bound_to_x/,
51+
}),
52+
bindingLogo: byRole('img', {
53+
name: /project_navigation.binding_status.bound_to_x/,
54+
}),
55+
};
3856

3957
const handler = new BranchesServiceMock();
4058
const almHandler = new AlmSettingsServiceMock();
59+
const almIntegrationsHandler = new AlmIntegrationsServiceMock();
4160

4261
beforeEach(() => {
4362
handler.reset();
4463
almHandler.reset();
64+
almIntegrationsHandler.reset();
4565
});
4666

4767
it('should render correctly when there is only 1 branch', async () => {
@@ -108,6 +128,65 @@ it('should show the correct help tooltip when branch support is not enabled', as
108128
).toBeInTheDocument();
109129
});
110130

131+
it('should show "bind project" link when project is not bound and user can bind project', async () => {
132+
renderHeader({
133+
component: mockComponent({
134+
breadcrumbs: [{ name: 'project', key: 'project', qualifier: ComponentQualifier.Project }],
135+
configuration: {
136+
showSettings: true,
137+
},
138+
}),
139+
currentUser: mockLoggedInUser(),
140+
});
141+
142+
expect(await ui.bindProjectLink.find()).toBeInTheDocument();
143+
});
144+
145+
it('should show GitHub logo linking to repository when project is bound to GitHub', async () => {
146+
almIntegrationsHandler.githubRepositories = [
147+
mockGitHubRepository({
148+
url: 'https://github.com/org/repo',
149+
}),
150+
];
151+
almHandler.projectsBindings['project-bound'] = mockProjectAlmBindingResponse({
152+
key: 'project-bound',
153+
slug: 'org/repo',
154+
});
155+
renderHeader({
156+
component: mockComponent({
157+
breadcrumbs: [{ name: 'project', key: 'project', qualifier: ComponentQualifier.Project }],
158+
configuration: {
159+
showSettings: true,
160+
},
161+
key: 'project-bound',
162+
}),
163+
currentUser: mockLoggedInUser(),
164+
});
165+
166+
expect(await ui.bindingLink.find()).toBeInTheDocument();
167+
});
168+
169+
it('should show GitLab logo (without link) when project is bound to GitLab', async () => {
170+
almHandler.projectsBindings['project-bound'] = mockProjectAlmBindingResponse({
171+
alm: AlmKeys.GitLab,
172+
key: 'project-bound',
173+
slug: 'gitlab-repo',
174+
});
175+
renderHeader({
176+
component: mockComponent({
177+
breadcrumbs: [{ name: 'project', key: 'project', qualifier: ComponentQualifier.Project }],
178+
configuration: {
179+
showSettings: true,
180+
},
181+
key: 'project-bound',
182+
}),
183+
currentUser: mockLoggedInUser(),
184+
});
185+
186+
expect(await ui.bindingLogo.find()).toBeInTheDocument();
187+
expect(ui.bindingLink.query()).not.toBeInTheDocument();
188+
});
189+
111190

112191
function renderHeader(
113192
props?: Partial<HeaderProps>,
@@ -118,8 +197,8 @@ function renderHeader(
118197
'/',
119198
<Header
120199
component={mockComponent({
121-
key: 'header-project',
122200
breadcrumbs: [{ name: 'project', key: 'project', qualifier: ComponentQualifier.Project }],
201+
key: 'header-project',
123202
})}
124203
currentUser={mockCurrentUser()}
125204
{...props}

libs/sq-server-commons/src/api/mocks/AlmIntegrationsServiceMock.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ import {
6464
setupGitlabProjectCreation,
6565
} from '../alm-integrations';
6666

67+
jest.mock('../alm-integrations');
68+
6769
function createUniqueNumber() {
6870
return Math.floor(Date.now() * Math.random());
6971
}

0 commit comments

Comments
 (0)