From 11ed4099b4fdf36aaccaffef189bf236d8c5d63d Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Tue, 31 Jul 2018 09:23:10 +0200 Subject: [PATCH] SONARCLOUD-108 Adapt the Bitbucket Cloud app to favor the new binding --- server/sonar-bitbucketcloud/public/index.html | 1 + .../src/main/ts/components/Config.tsx | 48 +++++-- .../ts/components/RepoWidgetNotConfigured.tsx | 47 ++++--- .../ts/components/__tests__/Config-test.tsx | 16 ++- .../RepoWidgetNotConfigured-test.tsx | 24 +++- .../__snapshots__/Config-test.tsx.snap | 126 ++++++++++++++++++ .../RepoWidgetNotConfigured-test.tsx.snap | 64 ++++++++- .../sonar-bitbucketcloud/src/main/ts/utils.ts | 4 + .../sonar/db/alm/ProjectAlmBindingsDao.java | 5 + .../db/alm/ProjectAlmBindingsMapper.java | 2 + .../sonar/db/alm/ProjectAlmBindingsMapper.xml | 16 ++- .../db/alm/ProjectAlmBindingsDaoTest.java | 24 ++++ 12 files changed, 338 insertions(+), 39 deletions(-) diff --git a/server/sonar-bitbucketcloud/public/index.html b/server/sonar-bitbucketcloud/public/index.html index c9cd601e6ee..097f9b472aa 100644 --- a/server/sonar-bitbucketcloud/public/index.html +++ b/server/sonar-bitbucketcloud/public/index.html @@ -20,6 +20,7 @@ window.bbcJWT = '%BBC_JWT%'; window.bbcWidgetKey = '%BBC_WIDGET_KEY%'; window.projectKey = '%BBC_PROJECT_KEY%'; + window.allowManualBinding = '%BBC_ALLOW_MANUAL_BINDING%'; <% for (var chunk in htmlWebpackPlugin.files.chunks) { %> diff --git a/server/sonar-bitbucketcloud/src/main/ts/components/Config.tsx b/server/sonar-bitbucketcloud/src/main/ts/components/Config.tsx index 9983088ea73..d8818f58ac3 100644 --- a/server/sonar-bitbucketcloud/src/main/ts/components/Config.tsx +++ b/server/sonar-bitbucketcloud/src/main/ts/components/Config.tsx @@ -28,7 +28,7 @@ import LoginForm from './LoginForm'; import SonarCloudIcon from './SonarCloudIcon'; import { AppContext } from '../types'; import { bindProject, displayWSError, getMyProjects, putStoredProperty } from '../api'; -import { displayMessage } from '../utils'; +import { displayMessage, isManualBindingAllowed } from '../utils'; interface ProjectOption { label: string; @@ -70,14 +70,16 @@ export default class Config extends React.PureComponent { authenticated: false, saving: false, disabled: props.disabled, - loading: true, + loading: isManualBindingAllowed(), projects: [] }; } componentDidMount() { this.mounted = true; - this.fetchMyProjects(); + if (isManualBindingAllowed()) { + this.fetchMyProjects(); + } } componentWillUnmount() { @@ -141,7 +143,11 @@ export default class Config extends React.PureComponent { } const { selectedProject } = this; - if (selectedProject && selectedProject.value !== this.props.projectKey) { + if ( + isManualBindingAllowed() && + selectedProject && + selectedProject.value !== this.props.projectKey + ) { updateBinding = true; promises.push( bindProject({ ...this.props.context, projectKey: selectedProject.value }).then(() => { @@ -149,6 +155,7 @@ export default class Config extends React.PureComponent { }) ); } + if (promises.length > 0) { (document.activeElement as HTMLElement).blur(); this.setState({ saving: true }); @@ -175,6 +182,29 @@ export default class Config extends React.PureComponent { }; renderContainer = (children: React.ReactNode) => { + const { projectKey } = this.props; + let descriptionComponent: React.ReactNode = ( + <> + To display the quality of your repository, you have to{' '} + provision a project on SonarCloud and + trigger an analysis. + + ); + + if (isManualBindingAllowed()) { + descriptionComponent = + 'To display the quality of your repository, you have to link it with a project analyzed on SonarCloud.'; + } else if (projectKey) { + descriptionComponent = ( + <> + This repository is already bound to a{' '} + - repository settings - - ) : ( - 'repository settings' - ); + let msgComponent: React.ReactNode = + 'Contact a repository administrator to link the repository with a SonarCloud project.'; + if (isRepoAdmin()) { + if (isManualBindingAllowed()) { + msgComponent = ( + <> + You have to link your repository with an existing SonarCloud project. Go to your{' '} + {settingsUrl ? ( + + repository settings + + ) : ( + 'repository settings' + )}. + + ); + } else { + msgComponent = ( + <> + You have to provision a project in + SonarCloud for this repository. + + ); + } + } + return ( <>
@@ -62,16 +82,7 @@ export default class RepoWidgetNotConfigured extends React.PureComponent<{}, Sta

Code Quality

-
- {isRepoAdmin() ? ( - <> - You have to link your repository with an existing SonarCloud project. Go to your{' '} - {settingsLink}. - - ) : ( - 'Contact a repository administrator to link the repository with a SonarCloud project.' - )} -
+
{msgComponent}
); } diff --git a/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/Config-test.tsx b/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/Config-test.tsx index e3f8cd28b59..0dee0434d1e 100644 --- a/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/Config-test.tsx +++ b/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/Config-test.tsx @@ -21,9 +21,11 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import Config from '../Config'; import { bindProject, getMyProjects } from '../../api'; +import { isManualBindingAllowed } from '../../utils'; jest.mock('../../utils', () => ({ - displayMessage: jest.fn() + displayMessage: jest.fn(), + isManualBindingAllowed: jest.fn(() => true) })); jest.mock('../../api', () => ({ @@ -58,6 +60,18 @@ it('should display correctly', async () => { expect(wrapper).toMatchSnapshot(); }); +it('should display correctly for auto binding', () => { + (isManualBindingAllowed as jest.Mock).mockReturnValue(false); + expect(getWrapper()).toMatchSnapshot(); + (isManualBindingAllowed as jest.Mock).mockReturnValue(true); +}); + +it('should display correctly for auto binding with already a projectKey', () => { + (isManualBindingAllowed as jest.Mock).mockReturnValue(false); + expect(getWrapper()).toMatchSnapshot(); + (isManualBindingAllowed as jest.Mock).mockReturnValue(true); +}); + it('should display the authentication component and the display checkbox', async () => { (getMyProjects as jest.Mock).mockImplementationOnce(() => Promise.reject({ response: { status: 401 } }) diff --git a/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/RepoWidgetNotConfigured-test.tsx b/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/RepoWidgetNotConfigured-test.tsx index 6b9aac19361..89f4b27f68f 100644 --- a/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/RepoWidgetNotConfigured-test.tsx +++ b/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/RepoWidgetNotConfigured-test.tsx @@ -21,17 +21,19 @@ import * as React from 'react'; import { shallow } from 'enzyme'; import RepoWidgetNotConfigured from '../RepoWidgetNotConfigured'; +import { isManualBindingAllowed, isRepoAdmin } from '../../utils'; jest.mock('../../utils', () => ({ - getRepoSettingsUrl: jest.fn(() => - Promise.resolve( + getRepoSettingsUrl: jest + .fn() + .mockResolvedValue( 'https://bitbucketcloud.org/{}/{repo_uuid}/admin/addon/admin/app-key/repository-config' - ) - ), - isRepoAdmin: jest.fn(() => true) + ), + isRepoAdmin: jest.fn().mockReturnValue(true), + isManualBindingAllowed: jest.fn().mockReturnValue(true) })); -it('should display correctly', async () => { +it('should display correctly for admin', async () => { const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); @@ -39,3 +41,13 @@ it('should display correctly', async () => { wrapper.update(); expect(wrapper).toMatchSnapshot(); }); + +it('should display correctly for non admin', () => { + (isRepoAdmin as jest.Mock).mockReturnValueOnce(false); + expect(shallow()).toMatchSnapshot(); +}); + +it('should display a link to SonarCloud for auto provisioning', () => { + (isManualBindingAllowed as jest.Mock).mockReturnValueOnce(false); + expect(shallow()).toMatchSnapshot(); +}); diff --git a/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/__snapshots__/Config-test.tsx.snap b/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/__snapshots__/Config-test.tsx.snap index 4d8d3d87ed3..1667e404154 100644 --- a/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/__snapshots__/Config-test.tsx.snap +++ b/server/sonar-bitbucketcloud/src/main/ts/components/__tests__/__snapshots__/Config-test.tsx.snap @@ -235,6 +235,132 @@ exports[`should display correctly 2`] = ` `; +exports[`should display correctly for auto binding 1`] = ` + +

+ SonarCloud Settings +

+
+
+ +
+

+ + This repository is already bound to a + + + SonarCloud project + + +

+ +
+
+ +
+
+ + Save + +
+
+ +
+
+
+`; + +exports[`should display correctly for auto binding with already a projectKey 1`] = ` + +

+ SonarCloud Settings +

+
+
+ +
+

+ + This repository is already bound to a + + + SonarCloud project + + +

+ +
+
+ +
+
+ + Save + +
+
+ +
+
+
+`; + exports[`should display the authentication component and the display checkbox 1`] = `
+
+
+ +

+ Code Quality +

+
+
+
+ + You have to + + provision + + a project in SonarCloud for this repository. + +
+ +`; + +exports[`should display correctly for admin 1`] = `
`; -exports[`should display correctly 2`] = ` +exports[`should display correctly for admin 2`] = `
`; + +exports[`should display correctly for non admin 1`] = ` + +
+
+ +

+ Code Quality +

+
+
+
+ Contact a repository administrator to link the repository with a SonarCloud project. +
+
+`; diff --git a/server/sonar-bitbucketcloud/src/main/ts/utils.ts b/server/sonar-bitbucketcloud/src/main/ts/utils.ts index 237f4a260f4..0108b6669de 100644 --- a/server/sonar-bitbucketcloud/src/main/ts/utils.ts +++ b/server/sonar-bitbucketcloud/src/main/ts/utils.ts @@ -49,6 +49,10 @@ export function getProjectKey(): string | undefined { return (window as any).projectKey; } +export function isManualBindingAllowed(): boolean { + return (window as any).allowManualBinding !== 'false'; +} + export function getPullRequestContext(): PullRequestContext { return { prId: (window as any).prId || query.prId, diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsDao.java index f2d6cc18428..e4a77591ab0 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsDao.java @@ -22,6 +22,7 @@ package org.sonar.db.alm; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.Optional; import javax.annotation.Nullable; import org.sonar.api.utils.System2; import org.sonar.core.util.UuidFactory; @@ -72,6 +73,10 @@ public class ProjectAlmBindingsDao implements Dao { return executeLargeInputs(repoIds, partionnedIds -> getMapper(session).selectByRepoIds(alm.getId(), partionnedIds)); } + public Optional selectByRepoId(final DbSession session, ALM alm, String repoId) { + return Optional.ofNullable(getMapper(session).selectByRepoId(alm.getId(), repoId)); + } + private static void checkAlm(@Nullable ALM alm) { Objects.requireNonNull(alm, "alm can't be null"); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsMapper.java index 9fa3f788f39..b8c5464a254 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/alm/ProjectAlmBindingsMapper.java @@ -34,4 +34,6 @@ public interface ProjectAlmBindingsMapper { @Nullable @Param("githubSlug") String githubSlug, @Param("url") String url, @Param("now") long now); List selectByRepoIds(@Param("almId") String almId, @Param("repoIds") List repoIds); + + ProjectAlmBindingDto selectByRepoId(@Param("almId") String almId, @Param("repoId") String repoId); } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/ProjectAlmBindingsMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/ProjectAlmBindingsMapper.xml index b31e45e6481..9b6b65d2ae2 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/ProjectAlmBindingsMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/alm/ProjectAlmBindingsMapper.xml @@ -58,11 +58,25 @@ url from project_alm_bindings where - alm_id =#{almId, jdbcType=VARCHAR} + alm_id = #{almId, jdbcType=VARCHAR} and repo_id in #{repoId,jdbcType=VARCHAR} + + diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/alm/ProjectAlmBindingsDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/alm/ProjectAlmBindingsDaoTest.java index c0b5150d0b6..287582125c0 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/alm/ProjectAlmBindingsDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/alm/ProjectAlmBindingsDaoTest.java @@ -23,6 +23,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import javax.annotation.Nullable; import org.assertj.core.api.AbstractAssert; import org.junit.Rule; @@ -207,6 +208,29 @@ public class ProjectAlmBindingsDaoTest { assertThat(underTest.bindingExists(dbSession, GITHUB, A_REPO)).isTrue(); } + @Test + public void select_by_repo_id() { + when(system2.now()).thenReturn(DATE); + when(uuidFactory.create()) + .thenReturn("uuid1") + .thenReturn("uuid2") + .thenReturn("uuid3"); + underTest.insertOrUpdate(dbSession, GITHUB, A_REPO, A_UUID, A_GITHUB_SLUG, A_URL); + underTest.insertOrUpdate(dbSession, GITHUB, ANOTHER_REPO, ANOTHER_UUID, null, ANOTHER_URL); + underTest.insertOrUpdate(dbSession, BITBUCKETCLOUD, ANOTHER_REPO, "foo", null, "http://foo"); + + assertThat(underTest.selectByRepoId(dbSession, GITHUB, "foo")).isNotPresent(); + + Optional dto = underTest.selectByRepoId(dbSession, GITHUB, A_REPO); + assertThat(dto).isPresent(); + assertThat(dto.get().getUuid()).isEqualTo("uuid1"); + assertThat(dto.get().getAlmId()).isEqualTo(GITHUB.getId()); + assertThat(dto.get().getRepoId()).isEqualTo(A_REPO); + assertThat(dto.get().getProjectUuid()).isEqualTo(A_UUID); + assertThat(dto.get().getUrl()).isEqualTo(A_URL); + assertThat(dto.get().getGithubSlug()).isEqualTo(A_GITHUB_SLUG); + } + @Test public void select_by_repo_ids() { when(system2.now()).thenReturn(DATE); -- 2.39.5