From 14d6de3529b12ec0af367e551cf66ac6daae1ca7 Mon Sep 17 00:00:00 2001 From: Jacek Date: Mon, 8 Jun 2020 17:15:43 +0200 Subject: [PATCH] SONAR-13398 fail with 503 api/issues/search WS if needIssueSync is set to true --- .../org/sonar/db/component/BranchDao.java | 16 ++- .../org/sonar/db/component/BranchMapper.java | 3 + .../org/sonar/db/component/BranchMapper.xml | 23 ++++ .../org/sonar/db/component/BranchDaoTest.java | 53 +++++++- .../es/EsIndexSyncInProgressException.java | 9 +- .../index/IssueIndexSyncProgressChecker.java | 30 ++--- .../IssueIndexSyncProgressCheckerTest.java | 115 +++++++++++++++++- .../sonar/server/issue/ws/SearchAction.java | 21 +++- .../issue/ws/SearchActionComponentsTest.java | 6 +- .../issue/ws/SearchActionFacetsTest.java | 5 +- .../server/issue/ws/SearchActionTest.java | 4 +- 11 files changed, 251 insertions(+), 34 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 70e89109b08..fc7981ccd26 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 @@ -21,10 +21,10 @@ package org.sonar.db.component; import java.util.Collection; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import org.sonar.api.utils.System2; import org.sonar.db.Dao; import org.sonar.db.DbSession; @@ -155,4 +155,18 @@ public class BranchDao implements Dao { long now = system2.now(); return mapper(dbSession).updateNeedIssueSync(branchUuid, needIssueSync, now); } + + public boolean doAnyOfComponentsNeedIssueSync(DbSession session, List components, @Nullable String branch, + @Nullable String pullRequest) { + if (!components.isEmpty()) { + List result = new LinkedList<>(); + return executeLargeInputs(components, input -> { + boolean groupNeedIssueSync = mapper(session).doAnyOfComponentsNeedIssueSync(components, branch, pullRequest) > 0; + result.add(groupNeedIssueSync); + return result; + }).stream() + .anyMatch(b -> b); + } + return false; + } } 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 31d867c5d17..62bbfa9955d 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 @@ -66,4 +66,7 @@ public interface BranchMapper { long updateNeedIssueSync(@Param("uuid") String uuid, @Param("needIssueSync")boolean needIssueSync,@Param("now") long now); + short doAnyOfComponentsNeedIssueSync(@Param("componentKeys") List components, @Nullable @Param("branch") String branch, + @Nullable @Param("pullRequest") String pullRequest); + } 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 836a428a9fa..982a77009e4 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 @@ -188,4 +188,27 @@ uuid = #{uuid, jdbcType=VARCHAR} + + 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 0ffa0b010d9..d7c4051565c 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 @@ -27,6 +27,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,6 +40,7 @@ import org.sonar.db.project.ProjectDto; import org.sonar.db.protobuf.DbProjectBranches; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.apache.commons.lang.StringUtils.repeat; @@ -576,9 +579,9 @@ public class BranchDaoTest { assertThat(underTest.countByNeedIssueSync(dbSession, true)).isZero(); assertThat(underTest.countByNeedIssueSync(dbSession, false)).isZero(); - //master branch with flag set to false + // master branch with flag set to false ComponentDto project = db.components().insertPrivateProject(); - //branches & PRs + // branches & PRs db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(true)); db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(true)); db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(false)); @@ -608,7 +611,7 @@ public class BranchDaoTest { } @Test - public void selectBranchNeedingIssueSync(){ + public void selectBranchNeedingIssueSync() { ComponentDto project = db.components().insertPrivateProject(); String uuid = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(true)).uuid(); db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(false)); @@ -619,7 +622,7 @@ public class BranchDaoTest { } @Test - public void updateAllNeedIssueSync(){ + public void updateAllNeedIssueSync() { ComponentDto project = db.components().insertPrivateProject(); String uuid1 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(true)).uuid(); String uuid2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(false)).uuid(); @@ -636,7 +639,7 @@ public class BranchDaoTest { } @Test - public void updateNeedIssueSync(){ + public void updateNeedIssueSync() { ComponentDto project = db.components().insertPrivateProject(); String uuid1 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(false)).uuid(); String uuid2 = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(true)).uuid(); @@ -652,4 +655,44 @@ public class BranchDaoTest { assertThat(project2).isPresent(); assertThat(project2.get().isNeedIssueSync()).isFalse(); } + + @Test + public void doAnyOfComponentsNeedIssueSync() { + assertThat(underTest.doAnyOfComponentsNeedIssueSync(dbSession, emptyList(), null, null)).isFalse(); + + ComponentDto project = db.components().insertPrivateProject(); + ProjectDto projectDto = db.components().getProjectDto(project); + db.components().insertProjectBranch(projectDto, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(true)); + BranchDto projectBranch1 = db.components().insertProjectBranch(projectDto, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(true)); + BranchDto projectBranch2 = db.components().insertProjectBranch(projectDto, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(false)); + db.components().insertProjectBranch(projectDto, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(false)); + BranchDto pullRequest1 = db.components().insertProjectBranch(projectDto, b -> b.setBranchType(BranchType.PULL_REQUEST).setNeedIssueSync(true)); + BranchDto pullRequest2 = db.components().insertProjectBranch(projectDto, b -> b.setBranchType(BranchType.PULL_REQUEST).setNeedIssueSync(false)); + db.components().insertProjectBranch(projectDto, b -> b.setBranchType(BranchType.PULL_REQUEST).setNeedIssueSync(true)); + + assertThat(underTest.doAnyOfComponentsNeedIssueSync(dbSession, singletonList(project.getKey()), null, null)).isTrue(); + + assertThat(underTest.doAnyOfComponentsNeedIssueSync(dbSession, singletonList(project.getKey()), projectBranch1.getKey(), null)).isTrue(); + assertThat(underTest.doAnyOfComponentsNeedIssueSync(dbSession, singletonList(project.getKey()), projectBranch2.getKey(), null)).isFalse(); + + assertThat(underTest.doAnyOfComponentsNeedIssueSync(dbSession, singletonList(project.getKey()), null, pullRequest1.getKey())).isTrue(); + assertThat(underTest.doAnyOfComponentsNeedIssueSync(dbSession, singletonList(project.getKey()), null, pullRequest2.getKey())).isFalse(); + } + + @Test + public void doAnyOfComponentsNeedIssueSync_test_more_than_1000() { + List componentKeys = IntStream.range(0, 1100).mapToObj(value -> db.components().insertPrivateProject()) + .map(ComponentDto::getDbKey) + .collect(Collectors.toList()); + + assertThat(underTest.doAnyOfComponentsNeedIssueSync(dbSession, componentKeys, null, null)).isFalse(); + + ComponentDto project = db.components().insertPrivateProject(); + ProjectDto projectDto = db.components().getProjectDto(project); + db.components().insertProjectBranch(projectDto, b -> b.setBranchType(BranchType.BRANCH).setNeedIssueSync(true)); + + componentKeys.add(project.getDbKey()); + + assertThat(underTest.doAnyOfComponentsNeedIssueSync(dbSession, componentKeys, null, null)).isTrue(); + } } diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/es/EsIndexSyncInProgressException.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/es/EsIndexSyncInProgressException.java index bc75d400841..fe72723b7a7 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/es/EsIndexSyncInProgressException.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/es/EsIndexSyncInProgressException.java @@ -20,13 +20,14 @@ package org.sonar.server.es; import org.sonar.server.es.IndexType.IndexMainType; +import org.sonar.server.exceptions.ServerException; -public class EsIndexSyncInProgressException extends RuntimeException { +public class EsIndexSyncInProgressException extends ServerException { - private IndexMainType indexType; + private final IndexMainType indexType; - public EsIndexSyncInProgressException(IndexMainType indexType) { - super(String.format("Synchronization of %s index is in progress", indexType.toString())); + public EsIndexSyncInProgressException(IndexMainType indexType, String message) { + super(503, message); this.indexType = indexType; } diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndexSyncProgressChecker.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndexSyncProgressChecker.java index 24e85e69222..6e9ea790226 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndexSyncProgressChecker.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndexSyncProgressChecker.java @@ -22,6 +22,7 @@ package org.sonar.server.issue.index; import com.google.common.collect.Sets; import java.util.Collection; import java.util.List; +import javax.annotation.Nullable; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.server.es.EsIndexSyncInProgressException; @@ -39,31 +40,26 @@ public class IssueIndexSyncProgressChecker { return new IssueSyncProgress(completed, total); } - /** - * Checks if issue index sync is in progress, if it is, method throws exception org.sonar.server.es.EsIndexSyncInProgressException - */ - public void checkIfIssueSyncInProgress(DbSession dbSession) throws EsIndexSyncInProgressException { - if (isIssueSyncInProgress(dbSession)) { - throw new EsIndexSyncInProgressException(IssueIndexDefinition.TYPE_ISSUE.getMainType()); + public void checkIfAnyComponentsIssueSyncInProgress(DbSession dbSession, List componentKeys, @Nullable String branch, + @Nullable String pullRequest) { + boolean needIssueSync = dbClient.branchDao().doAnyOfComponentsNeedIssueSync(dbSession, componentKeys, branch, pullRequest); + if (needIssueSync) { + throw new EsIndexSyncInProgressException(IssueIndexDefinition.TYPE_ISSUE.getMainType(), + "Results are temporarily unavailable. Indexing of issues is in progress."); } } /** - * Checks if project issue index sync is in progress, if it is, method throws exception org.sonar.server.es.EsIndexSyncInProgressException + * Checks if issue index sync is in progress, if it is, method throws exception org.sonar.server.es.EsIndexSyncInProgressException */ - public void checkIfProjectIssueSyncInProgress(DbSession dbSession, String projectUuid) throws EsIndexSyncInProgressException { - if (doProjectNeedIssueSync(dbSession, projectUuid)) { - throw new EsIndexSyncInProgressException(IssueIndexDefinition.TYPE_ISSUE.getMainType()); - } - } - - public void checkIfAnyProjectIssueSyncInProgress(DbSession dbSession, Collection projectUuids) throws EsIndexSyncInProgressException { - if (!findProjectUuidsWithIssuesSyncNeed(dbSession, projectUuids).isEmpty()) { - throw new EsIndexSyncInProgressException(IssueIndexDefinition.TYPE_ISSUE.getMainType()); + public void checkIfIssueSyncInProgress(DbSession dbSession) { + if (isIssueSyncInProgress(dbSession)) { + throw new EsIndexSyncInProgressException(IssueIndexDefinition.TYPE_ISSUE.getMainType(), + "Results are temporarily unavailable. Indexing of issues is in progress."); } } - public boolean isIssueSyncInProgress(DbSession dbSession) throws EsIndexSyncInProgressException { + public boolean isIssueSyncInProgress(DbSession dbSession) { return dbClient.branchDao().hasAnyBranchWhereNeedIssueSync(dbSession, true); } diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSyncProgressCheckerTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSyncProgressCheckerTest.java index 3315c8cb37e..d036d47eea0 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSyncProgressCheckerTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueIndexSyncProgressCheckerTest.java @@ -22,15 +22,23 @@ package org.sonar.server.issue.index; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.stream.IntStream; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.sonar.api.utils.System2; +import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; import org.sonar.db.project.ProjectDto; +import org.sonar.server.es.EsIndexSyncInProgressException; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; @RunWith(DataProviderRunner.class) public class IssueIndexSyncProgressCheckerTest { @@ -107,10 +115,115 @@ public class IssueIndexSyncProgressCheckerTest { assertThat(result.isCompleted()).isFalse(); } - private void insertProjectWithBranches(boolean needIssueSync, int numberOfBranches) { + @Test + public void checkIfAnyComponentsIssueSyncInProgress_throws_exception_if_all_components_have_need_issue_sync_TRUE() { + ProjectDto projectDto1 = insertProjectWithBranches(true, 0); + ProjectDto projectDto2 = insertProjectWithBranches(true, 0); + DbSession session = db.getSession(); + List projectKeys = Arrays.asList(projectDto1.getKey(), projectDto2.getKey()); + assertThatThrownBy(() -> underTest.checkIfAnyComponentsIssueSyncInProgress(session, projectKeys, null, null)) + .isInstanceOf(EsIndexSyncInProgressException.class) + .hasFieldOrPropertyWithValue("httpCode", 503) + .hasMessage("Results are temporarily unavailable. Indexing of issues is in progress."); + } + + @Test + public void checkIfAnyComponentsIssueSyncInProgress_does_not_throw_exception_if_all_components_have_need_issue_sync_FALSE() { + underTest.checkIfAnyComponentsIssueSyncInProgress(db.getSession(), Collections.emptyList(), null, null); + ProjectDto projectDto1 = insertProjectWithBranches(false, 0); + ProjectDto projectDto2 = insertProjectWithBranches(false, 0); + underTest.checkIfAnyComponentsIssueSyncInProgress(db.getSession(), Arrays.asList(projectDto1.getKey(), projectDto2.getKey()), null, null); + } + + @Test + public void checkIfAnyComponentsIssueSyncInProgress_throws_exception_if_at_least_one_component_has_need_issue_sync_TRUE() { + ProjectDto projectDto1 = insertProjectWithBranches(false, 0); + ProjectDto projectDto2 = insertProjectWithBranches(true, 0); + + DbSession session = db.getSession(); + List projectKeys = Arrays.asList(projectDto1.getKey(), projectDto2.getKey()); + assertThatThrownBy(() -> underTest.checkIfAnyComponentsIssueSyncInProgress(session, projectKeys, null, null)) + .isInstanceOf(EsIndexSyncInProgressException.class) + .hasFieldOrPropertyWithValue("httpCode", 503) + .hasMessage("Results are temporarily unavailable. Indexing of issues is in progress."); + } + + @Test + public void checkIfAnyComponentsIssueSyncInProgress_single_component() { + ProjectDto projectDto1 = insertProjectWithBranches(true, 0); + ProjectDto projectDto2 = insertProjectWithBranches(false, 0); + + DbSession session = db.getSession(); + List projectKey1 = singletonList(projectDto2.getKey()); + // do nothing when need issue sync false + underTest.checkIfAnyComponentsIssueSyncInProgress(session, projectKey1, null, null); + + List projectKey2 = singletonList(projectDto1.getKey()); + // throws if flag set to TRUE + assertThatThrownBy(() -> underTest.checkIfAnyComponentsIssueSyncInProgress(session, + projectKey2, null, null)) + .isInstanceOf(EsIndexSyncInProgressException.class) + .hasFieldOrPropertyWithValue("httpCode", 503) + .hasMessage("Results are temporarily unavailable. Indexing of issues is in progress."); + } + + @Test + public void checkIfAnyComponentsNeedIssueSync_single_view_subview_or_app() { + ProjectDto projectDto1 = insertProjectWithBranches(true, 0); + + ComponentDto app = db.components().insertPublicApplication(); + ComponentDto view = db.components().insertPrivatePortfolio(); + ComponentDto subview = db.components().insertSubView(view); + + DbSession session = db.getSession(); + List appViewOrSubviewKeys = Arrays.asList(projectDto1.getKey(), app.getDbKey(), view.getDbKey(), subview.getDbKey()); + + // throws if flag set to TRUE + assertThatThrownBy(() -> underTest.checkIfAnyComponentsIssueSyncInProgress(session, + appViewOrSubviewKeys, null, null)) + .isInstanceOf(EsIndexSyncInProgressException.class) + .hasFieldOrPropertyWithValue("httpCode", 503) + .hasMessage("Results are temporarily unavailable. Indexing of issues is in progress."); + } + + @Test + public void checkIfIssueSyncInProgress_throws_exception_if_at_least_one_component_has_need_issue_sync_TRUE() { + insertProjectWithBranches(false, 0); + underTest.checkIfIssueSyncInProgress(db.getSession()); + insertProjectWithBranches(true, 0); + + DbSession session = db.getSession(); + assertThatThrownBy(() -> underTest.checkIfIssueSyncInProgress(session)) + .isInstanceOf(EsIndexSyncInProgressException.class) + .hasFieldOrPropertyWithValue("httpCode", 503) + .hasMessage("Results are temporarily unavailable. Indexing of issues is in progress."); + } + + @Test + public void doProjectNeedIssueSync() { + ProjectDto projectDto1 = insertProjectWithBranches(false, 0); + assertThat(underTest.doProjectNeedIssueSync(db.getSession(), projectDto1.getUuid())).isFalse(); + ProjectDto projectDto2 = insertProjectWithBranches(true, 0); + assertThat(underTest.doProjectNeedIssueSync(db.getSession(), projectDto2.getUuid())).isTrue(); + } + + @Test + public void findProjectUuidsWithIssuesSyncNeed() { + ProjectDto projectDto1 = insertProjectWithBranches(false, 0); + ProjectDto projectDto2 = insertProjectWithBranches(false, 0); + ProjectDto projectDto3 = insertProjectWithBranches(true, 0); + ProjectDto projectDto4 = insertProjectWithBranches(true, 0); + + assertThat(underTest.findProjectUuidsWithIssuesSyncNeed(db.getSession(), + Arrays.asList(projectDto1.getUuid(), projectDto2.getUuid(), projectDto3.getUuid(), projectDto4.getUuid()))) + .containsOnly(projectDto3.getUuid(), projectDto4.getUuid()); + } + + private ProjectDto insertProjectWithBranches(boolean needIssueSync, int numberOfBranches) { ProjectDto projectDto = db.components() .insertPrivateProjectDto(db.getDefaultOrganization(), branchDto -> branchDto.setNeedIssueSync(needIssueSync)); IntStream.range(0, numberOfBranches).forEach( i -> db.components().insertProjectBranch(projectDto, branchDto -> branchDto.setNeedIssueSync(needIssueSync))); + return projectDto; } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 8056f814ef2..aa4ab9338ec 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -49,6 +49,7 @@ import org.sonar.server.es.Facets; import org.sonar.server.es.SearchOptions; import org.sonar.server.issue.SearchRequest; import org.sonar.server.issue.index.IssueIndex; +import org.sonar.server.issue.index.IssueIndexSyncProgressChecker; import org.sonar.server.issue.index.IssueQuery; import org.sonar.server.issue.index.IssueQueryFactory; import org.sonar.server.security.SecurityStandards.SQCategory; @@ -160,16 +161,18 @@ public class SearchAction implements IssuesWsAction { private final UserSession userSession; private final IssueIndex issueIndex; private final IssueQueryFactory issueQueryFactory; + private final IssueIndexSyncProgressChecker issueIndexSyncProgressChecker; private final SearchResponseLoader searchResponseLoader; private final SearchResponseFormat searchResponseFormat; private final System2 system2; private final DbClient dbClient; - public SearchAction(UserSession userSession, IssueIndex issueIndex, IssueQueryFactory issueQueryFactory, + public SearchAction(UserSession userSession, IssueIndex issueIndex, IssueQueryFactory issueQueryFactory, IssueIndexSyncProgressChecker issueIndexSyncProgressChecker, SearchResponseLoader searchResponseLoader, SearchResponseFormat searchResponseFormat, System2 system2, DbClient dbClient) { this.userSession = userSession; this.issueIndex = issueIndex; this.issueQueryFactory = issueQueryFactory; + this.issueIndexSyncProgressChecker = issueIndexSyncProgressChecker; this.searchResponseLoader = searchResponseLoader; this.searchResponseFormat = searchResponseFormat; this.system2 = system2; @@ -181,7 +184,8 @@ public class SearchAction implements IssuesWsAction { WebService.NewAction action = controller .createAction(ACTION_SEARCH) .setHandler(this) - .setDescription("Search for issues.
Requires the 'Browse' permission on the specified project(s).") + .setDescription("Search for issues.
Requires the 'Browse' permission on the specified project(s)." + + "
When issue indexation is in progress returns 503 service unavailable HTTP code.") .setSince("3.6") .setChangelog( new Change("8.4", "parameters 'componentUuids', 'projectKeys' has been dropped."), @@ -369,6 +373,7 @@ public class SearchAction implements IssuesWsAction { public final void handle(Request request, Response response) { try (DbSession dbSession = dbClient.openSession(false)) { SearchRequest searchRequest = toSearchWsRequest(dbSession, request); + checkIfNeedIssueSync(dbSession, searchRequest); SearchWsResponse searchWsResponse = doHandle(searchRequest); writeProtobuf(searchWsResponse, request, response); } @@ -536,6 +541,18 @@ public class SearchAction implements IssuesWsAction { .setSonarsourceSecurity(request.paramAsStrings(PARAM_SONARSOURCE_SECURITY)); } + private void checkIfNeedIssueSync(DbSession dbSession, SearchRequest searchRequest) { + List components = searchRequest.getComponents(); + if (components != null && !components.isEmpty()) { + String branch = searchRequest.getBranch(); + String pullRequest = searchRequest.getPullRequest(); + issueIndexSyncProgressChecker.checkIfAnyComponentsIssueSyncInProgress(dbSession, components, branch, pullRequest); + } else { + // component keys not provided - asking for global + issueIndexSyncProgressChecker.checkIfIssueSyncInProgress(dbSession); + } + } + private static List allRuleTypesExceptHotspotsIfEmpty(@Nullable List types) { if (types == null || types.isEmpty()) { return ALL_RULE_TYPES_EXCEPT_SECURITY_HOTSPOTS.stream().map(Enum::name).collect(toList()); diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java index 17a6f09b360..1aa8af9744a 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java @@ -42,6 +42,7 @@ import org.sonar.server.issue.IssueFieldsSetter; import org.sonar.server.issue.TextRangeResponseFormatter; import org.sonar.server.issue.TransitionService; import org.sonar.server.issue.index.IssueIndex; +import org.sonar.server.issue.index.IssueIndexSyncProgressChecker; import org.sonar.server.issue.index.IssueIndexer; import org.sonar.server.issue.index.IssueIteratorFactory; import org.sonar.server.issue.index.IssueQueryFactory; @@ -106,7 +107,10 @@ public class SearchActionComponentsTest { private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), languages, new TextRangeResponseFormatter(), userFormatter); private PermissionIndexerTester permissionIndexer = new PermissionIndexerTester(es, issueIndexer); - private WsActionTester ws = new WsActionTester(new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat, + private IssueIndexSyncProgressChecker issueIndexSyncProgressChecker = new IssueIndexSyncProgressChecker(db.getDbClient()); + + private WsActionTester ws = new WsActionTester( + new SearchAction(userSession, issueIndex, issueQueryFactory, issueIndexSyncProgressChecker, searchResponseLoader, searchResponseFormat, System2.INSTANCE, dbClient)); @Test diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionFacetsTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionFacetsTest.java index 4bb32521612..ea2cac81ae0 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionFacetsTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionFacetsTest.java @@ -44,6 +44,7 @@ import org.sonar.server.issue.AvatarResolverImpl; import org.sonar.server.issue.TextRangeResponseFormatter; import org.sonar.server.issue.TransitionService; import org.sonar.server.issue.index.IssueIndex; +import org.sonar.server.issue.index.IssueIndexSyncProgressChecker; import org.sonar.server.issue.index.IssueIndexer; import org.sonar.server.issue.index.IssueIteratorFactory; import org.sonar.server.issue.index.IssueQueryFactory; @@ -94,9 +95,9 @@ public class SearchActionFacetsTest { private Languages languages = new Languages(); private UserResponseFormatter userFormatter = new UserResponseFormatter(new AvatarResolverImpl()); private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), languages, new TextRangeResponseFormatter(), userFormatter); - + private IssueIndexSyncProgressChecker issueIndexSyncProgressChecker = new IssueIndexSyncProgressChecker(db.getDbClient()); private WsActionTester ws = new WsActionTester( - new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat, System2.INSTANCE, db.getDbClient())); + new SearchAction(userSession, issueIndex, issueQueryFactory, issueIndexSyncProgressChecker, searchResponseLoader, searchResponseFormat, System2.INSTANCE, db.getDbClient())); @Test public void display_all_facets() { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java index 70f59774dd9..3fb46b34b0a 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java @@ -67,6 +67,7 @@ import org.sonar.server.issue.IssueFieldsSetter; import org.sonar.server.issue.TextRangeResponseFormatter; import org.sonar.server.issue.TransitionService; import org.sonar.server.issue.index.IssueIndex; +import org.sonar.server.issue.index.IssueIndexSyncProgressChecker; import org.sonar.server.issue.index.IssueIndexer; import org.sonar.server.issue.index.IssueIteratorFactory; import org.sonar.server.issue.index.IssueQuery; @@ -136,8 +137,9 @@ public class SearchActionTest { private Languages languages = new Languages(); private UserResponseFormatter userFormatter = new UserResponseFormatter(new AvatarResolverImpl()); private SearchResponseFormat searchResponseFormat = new SearchResponseFormat(new Durations(), languages, new TextRangeResponseFormatter(), userFormatter); + private IssueIndexSyncProgressChecker issueIndexSyncProgressChecker = new IssueIndexSyncProgressChecker(dbClient); private WsActionTester ws = new WsActionTester( - new SearchAction(userSession, issueIndex, issueQueryFactory, searchResponseLoader, searchResponseFormat, System2.INSTANCE, dbClient)); + new SearchAction(userSession, issueIndex, issueQueryFactory, issueIndexSyncProgressChecker, searchResponseLoader, searchResponseFormat, System2.INSTANCE, dbClient)); private StartupIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer); @Before -- 2.39.5