From 93ed0d5e1a9eb3df0cfad3ff4ee610aa1e0b9260 Mon Sep 17 00:00:00 2001 From: Jacek Date: Tue, 2 Jun 2020 11:33:40 +0200 Subject: [PATCH] SONAR-13399 return 'needIssueSync' flag in api/components/show WS --- .../org/sonar/db/component/BranchDao.java | 4 ++ .../org/sonar/db/component/BranchMapper.java | 1 + .../org/sonar/db/component/BranchMapper.xml | 15 +++- .../org/sonar/db/component/BranchDaoTest.java | 16 +++++ .../sonar/server/component/ws/ShowAction.java | 32 ++++++--- .../server/component/ws/ShowActionTest.java | 69 +++++++++++++++++++ 6 files changed, 126 insertions(+), 11 deletions(-) diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java index aec3bfa0284..04d03bb1557 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchDao.java @@ -119,6 +119,10 @@ public class BranchDao implements Dao { return executeLargeInputs(uuids, mapper(session)::selectProjectUuidsWithIssuesNeedSync); } + public boolean hasAnyBranchWhereNeedIssueSync(DbSession session, boolean needIssueSync) { + return mapper(session).hasAnyBranchWhereNeedIssueSync(needIssueSync) > 0; + } + public boolean hasNonMainBranches(DbSession dbSession) { return mapper(dbSession).countNonMainBranches() > 0L; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java index 689c1bc5bf6..53c29667441 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/BranchMapper.java @@ -55,4 +55,5 @@ public interface BranchMapper { long countByTypeAndCreationDate(@Param("branchType") String branchType, @Param("sinceDate") long sinceDate); + short hasAnyBranchWhereNeedIssueSync(@Param("needIssueSync") boolean needIssueSync); } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml index c2e0e21a0a4..dbd6b6dceb5 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/component/BranchMapper.xml @@ -135,8 +135,19 @@ select count(pb.uuid) from project_branches pb where - pb.branch_type = #{branchType, jdbcType=VARCHAR} - and pb.created_at >= #{sinceDate, jdbcType=BIGINT} + pb.branch_type = #{branchType, jdbcType=VARCHAR} + and pb.created_at >= #{sinceDate, jdbcType=BIGINT} + + + diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java index 7cdb7809998..96edb3ba470 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/BranchDaoTest.java @@ -538,6 +538,22 @@ public class BranchDaoTest { assertThat(underTest.hasNonMainBranches(dbSession)).isTrue(); } + @Test + public void hasAnyBranchWhereNeedIssueSync() { + assertThat(underTest.hasAnyBranchWhereNeedIssueSync(dbSession, true)).isFalse(); + assertThat(underTest.hasAnyBranchWhereNeedIssueSync(dbSession, false)).isFalse(); + + ComponentDto project = db.components().insertPrivateProject(); + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setNeedIssueSync(false)); + + assertThat(underTest.hasAnyBranchWhereNeedIssueSync(dbSession, true)).isFalse(); + assertThat(underTest.hasAnyBranchWhereNeedIssueSync(dbSession, false)).isTrue(); + + project = db.components().insertPrivateProject(); + branch = db.components().insertProjectBranch(project, b -> b.setNeedIssueSync(true)); + assertThat(underTest.hasAnyBranchWhereNeedIssueSync(dbSession, true)).isTrue(); + } + @Test public void countByTypeAndCreationDate() { assertThat(underTest.countByTypeAndCreationDate(dbSession, BranchType.BRANCH, 0L)).isEqualTo(0); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ShowAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ShowAction.java index e6812eef9a1..2dd0bf2a036 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ShowAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ShowAction.java @@ -20,6 +20,7 @@ package org.sonar.server.component.ws; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; import java.util.List; import java.util.Optional; import java.util.Set; @@ -56,6 +57,7 @@ import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COM public class ShowAction implements ComponentsWsAction { private static final Set PROJECT_OR_APP_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.APP); + private static final Set VIEW_OR_SUBVIEW_QUALIFIERS = ImmutableSet.of(Qualifiers.VIEW, Qualifiers.SUBVIEW); private final UserSession userSession; private final DbClient dbClient; private final ComponentFinder componentFinder; @@ -109,7 +111,7 @@ public class ShowAction implements ComponentsWsAction { Optional lastAnalysis = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.projectUuid()); List ancestors = dbClient.componentDao().selectAncestors(dbSession, component); OrganizationDto organizationDto = componentFinder.getOrganization(dbSession, component); - return buildResponse(dbClient, dbSession, component, organizationDto, ancestors, lastAnalysis.orElse(null)); + return buildResponse(dbSession, component, organizationDto, ancestors, lastAnalysis.orElse(null)); } } @@ -121,34 +123,38 @@ public class ShowAction implements ComponentsWsAction { return componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, componentKey, branch, pullRequest); } - private static ShowWsResponse buildResponse(DbClient dbClient, DbSession dbSession, ComponentDto component, + private ShowWsResponse buildResponse(DbSession dbSession, ComponentDto component, OrganizationDto organizationDto, List orderedAncestors, @Nullable SnapshotDto lastAnalysis) { ShowWsResponse.Builder response = ShowWsResponse.newBuilder(); - response.setComponent(toWsComponent(dbClient, dbSession, component, organizationDto, lastAnalysis)); - addAncestorsToResponse(dbClient, dbSession, response, orderedAncestors, organizationDto, lastAnalysis); + response.setComponent(toWsComponent(dbSession, component, organizationDto, lastAnalysis)); + addAncestorsToResponse(dbSession, response, orderedAncestors, organizationDto, lastAnalysis); return response.build(); } - private static void addAncestorsToResponse(DbClient dbClient, DbSession dbSession, ShowWsResponse.Builder response, List orderedAncestors, + private void addAncestorsToResponse(DbSession dbSession, ShowWsResponse.Builder response, List orderedAncestors, OrganizationDto organizationDto, @Nullable SnapshotDto lastAnalysis) { // ancestors are ordered from root to leaf, whereas it's the opposite in WS response int size = orderedAncestors.size() - 1; IntStream.rangeClosed(0, size).forEach( - index -> response.addAncestors(toWsComponent(dbClient, dbSession, orderedAncestors.get(size - index), organizationDto, lastAnalysis))); + index -> response.addAncestors(toWsComponent(dbSession, orderedAncestors.get(size - index), organizationDto, lastAnalysis))); } - private static Components.Component.Builder toWsComponent(DbClient dbClient, DbSession dbSession, ComponentDto component, OrganizationDto organizationDto, + private Components.Component.Builder toWsComponent(DbSession dbSession, ComponentDto component, OrganizationDto organizationDto, @Nullable SnapshotDto lastAnalysis) { if (isProjectOrApp(component)) { ProjectDto project = dbClient.projectDao().selectProjectOrAppByKey(dbSession, component.getKey()) .orElseThrow(() -> new IllegalStateException("Project is in invalid state.")); - return projectOrAppToWsComponent(project, organizationDto, lastAnalysis); + boolean needIssueSync = needIssueSync(dbSession, component, project); + return projectOrAppToWsComponent(project, organizationDto, lastAnalysis) + .setNeedIssueSync(needIssueSync); } else { Optional parentProject = dbClient.projectDao().selectByUuid(dbSession, ofNullable(component.getMainBranchProjectUuid()).orElse(component.projectUuid())); - return componentDtoToWsComponent(component, parentProject.orElse(null), organizationDto, lastAnalysis); + boolean needIssueSync = needIssueSync(dbSession, component, parentProject.orElse(null)); + return componentDtoToWsComponent(component, parentProject.orElse(null), organizationDto, lastAnalysis) + .setNeedIssueSync(needIssueSync); } } @@ -156,6 +162,14 @@ public class ShowAction implements ComponentsWsAction { return component.getMainBranchProjectUuid() == null && PROJECT_OR_APP_QUALIFIERS.contains(component.qualifier()); } + private boolean needIssueSync(DbSession dbSession, ComponentDto component, @Nullable ProjectDto projectDto) { + if (projectDto == null || VIEW_OR_SUBVIEW_QUALIFIERS.contains(component.qualifier())) { + return dbClient.branchDao().hasAnyBranchWhereNeedIssueSync(dbSession, true); + } + + return !dbClient.branchDao().selectProjectUuidsWithIssuesNeedSync(dbSession, Sets.newHashSet(projectDto.getUuid())).isEmpty(); + } + private static Request toShowWsRequest(org.sonar.api.server.ws.Request request) { return new Request() .setComponentKey(request.mandatoryParam(PARAM_COMPONENT)) diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/ShowActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/ShowActionTest.java index 39f4754f7e5..6d0252f1a5f 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/ShowActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/ShowActionTest.java @@ -20,6 +20,7 @@ package org.sonar.server.component.ws; import java.util.Date; +import java.util.Optional; import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; @@ -30,6 +31,7 @@ import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; import org.sonar.db.DbTester; +import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.organization.OrganizationDto; import org.sonar.server.component.TestComponentFinder; @@ -46,6 +48,7 @@ import static org.assertj.core.api.Assertions.tuple; import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.api.web.UserRole.USER; +import static org.sonar.db.component.BranchType.BRANCH; import static org.sonar.db.component.BranchType.PULL_REQUEST; import static org.sonar.db.component.ComponentTesting.newDirectory; import static org.sonar.db.component.ComponentTesting.newFileDto; @@ -310,6 +313,72 @@ public class ShowActionTest { tuple(branch.getKey(), pullRequest, "1.1")); } + @Test + public void verify_need_issue_sync_pr() { + ComponentDto portfolio1 = db.components().insertPublicPortfolio(db.getDefaultOrganization()); + ComponentDto portfolio2 = db.components().insertPublicPortfolio(db.getDefaultOrganization()); + ComponentDto subview = db.components().insertSubView(portfolio1); + + ComponentDto project1 = db.components().insertPrivateProject(); + ComponentDto branch1 = db.components().insertProjectBranch(project1, b -> b.setBranchType(PULL_REQUEST).setNeedIssueSync(true)); + ComponentDto module = db.components().insertComponent(newModuleDto(branch1)); + ComponentDto directory = db.components().insertComponent(newDirectory(module, "dir")); + ComponentDto file = db.components().insertComponent(newFileDto(directory)); + + ComponentDto project2 = db.components().insertPrivateProject(); + ComponentDto branch2 = db.components().insertProjectBranch(project2, b -> b.setBranchType(BRANCH).setNeedIssueSync(true)); + ComponentDto branch3 = db.components().insertProjectBranch(project2, b -> b.setBranchType(BRANCH).setNeedIssueSync(false)); + + ComponentDto project3 = db.components().insertPrivateProject(); + ComponentDto branch4 = db.components().insertProjectBranch(project3, b -> b.setBranchType(PULL_REQUEST).setNeedIssueSync(false)); + ComponentDto moduleOfBranch4 = db.components().insertComponent(newModuleDto(branch4)); + ComponentDto directoryOfBranch4 = db.components().insertComponent(newDirectory(moduleOfBranch4, "dir")); + ComponentDto fileOfBranch4 = db.components().insertComponent(newFileDto(directoryOfBranch4)); + ComponentDto branch5 = db.components().insertProjectBranch(project3, b -> b.setBranchType(BRANCH).setNeedIssueSync(false)); + + userSession.addMembership(db.getDefaultOrganization()); + userSession.addProjectPermission(UserRole.USER, project1, project2, project3); + userSession.registerComponents(portfolio1, portfolio2, subview, project1, project2, project3); + + //for portfolios, sub-views need issue sync flag is set to true if any project need sync + assertNeedIssueSyncEqual(null, null, portfolio1, true); + assertNeedIssueSyncEqual(null, null, subview, true); + assertNeedIssueSyncEqual(null, null, portfolio2, true); + + //if branch need sync it is propagated to other components + assertNeedIssueSyncEqual(null, null, project1, true); + assertNeedIssueSyncEqual(branch1.getPullRequest(), null, branch1, true); + assertNeedIssueSyncEqual(branch1.getPullRequest(), null, module, true); + assertNeedIssueSyncEqual(branch1.getPullRequest(), null, directory, true); + assertNeedIssueSyncEqual(branch1.getPullRequest(), null, file, true); + + assertNeedIssueSyncEqual(null, null, project2, true); + assertNeedIssueSyncEqual(null, branch2.getBranch(), branch2, true); + assertNeedIssueSyncEqual(null, branch3.getBranch(), branch3, true); + + //if all branches are synced, need issue sync on project is is set to false + assertNeedIssueSyncEqual(null, null, project3, false); + assertNeedIssueSyncEqual(branch4.getPullRequest(), null, branch4, false); + assertNeedIssueSyncEqual(branch4.getPullRequest(), null, moduleOfBranch4, false); + assertNeedIssueSyncEqual(branch4.getPullRequest(), null, directoryOfBranch4, false); + assertNeedIssueSyncEqual(branch4.getPullRequest(), null, fileOfBranch4, false); + assertNeedIssueSyncEqual(null, branch5.getBranch(), branch5, false); + } + + private void assertNeedIssueSyncEqual(@Nullable String pullRequest, @Nullable String branch, ComponentDto component, boolean needIssueSync) { + TestRequest testRequest = ws.newRequest() + .setParam(PARAM_COMPONENT, component.getKey()); + + Optional.ofNullable(pullRequest).ifPresent(pr -> testRequest.setParam(PARAM_PULL_REQUEST, pr)); + Optional.ofNullable(branch).ifPresent(br -> testRequest.setParam(PARAM_BRANCH, br)); + + ShowWsResponse response = testRequest.executeProtobuf(ShowWsResponse.class); + + assertThat(response.getComponent()) + .extracting(Component::getNeedIssueSync) + .isEqualTo(needIssueSync); + } + @Test public void throw_ForbiddenException_if_user_doesnt_have_browse_permission_on_project() { userSession.logIn(); -- 2.39.5