From 3e190e0e5588f2e5c02d207423b131f1a2d7285b Mon Sep 17 00:00:00 2001 From: Janos Gyerik Date: Mon, 17 Sep 2018 17:03:25 +0200 Subject: [PATCH] SONAR-11260 Add web service to provide analysis status --- .../java/org/sonar/db/ce/CeActivityDao.java | 8 + .../org/sonar/db/ce/CeActivityMapper.java | 6 + .../org/sonar/db/ce/CeActivityMapper.xml | 23 ++ .../server/ce/ws/AnalysisStatusAction.java | 159 ++++++++ .../org/sonar/server/ce/ws/CeWsModule.java | 1 + .../sonar/server/ce/ws/CeWsParameters.java | 3 + .../server/ce/ws/analysis_status-example.json | 8 + .../ce/ws/AnalysisStatusActionTest.java | 358 ++++++++++++++++++ .../sonar/server/ce/ws/CeWsModuleTest.java | 2 +- sonar-ws/src/main/protobuf/ws-ce.proto | 14 + 10 files changed, 581 insertions(+), 1 deletion(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java create mode 100644 server/sonar-server/src/main/resources/org/sonar/server/ce/ws/analysis_status-example.json create mode 100644 server/sonar-server/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDao.java index 71c6c5ea4ba..9b7dddf3b7e 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDao.java @@ -80,6 +80,14 @@ public class CeActivityDao implements Dao { return mapper(dbSession).countLastByStatusAndMainComponentUuid(status, mainComponentUuid); } + public Optional selectLastByComponentUuid(DbSession dbSession, String componentUuid) { + return Optional.ofNullable(mapper(dbSession).selectLastByComponentUuid(componentUuid)); + } + + public Optional selectLastByMainComponentUuid(DbSession dbSession, String mainComponentUuid) { + return Optional.ofNullable(mapper(dbSession).selectLastByMainComponentUuid(mainComponentUuid)); + } + private static CeActivityMapper mapper(DbSession dbSession) { return dbSession.getMapper(CeActivityMapper.class); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityMapper.java index 42b7fedab7c..e3a39376cf8 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityMapper.java @@ -43,4 +43,10 @@ public interface CeActivityMapper { void clearMainIsLast(@Param("mainIsLastKey") String mainIsLastKey, @Param("updatedAt") long updatedAt); void deleteByUuids(@Param("uuids") List uuids); + + @CheckForNull + CeActivityDto selectLastByComponentUuid(@Param("componentUuid") String componentUuid); + + @CheckForNull + CeActivityDto selectLastByMainComponentUuid(@Param("mainComponentUuid") String mainComponentUuid); } diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml index 61cc9cded15..a195f296ba2 100644 --- a/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml +++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/ce/CeActivityMapper.xml @@ -223,4 +223,27 @@ #{uuid,jdbcType=VARCHAR} + + + + + diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java new file mode 100644 index 00000000000..280995c7b93 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/AnalysisStatusAction.java @@ -0,0 +1,159 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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. + */ +package org.sonar.server.ce.ws; + +import java.util.List; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Scopes; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.ce.CeActivityDto; +import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; +import org.sonar.server.ws.KeyExamples; +import org.sonarqube.ws.Ce.AnalysisStatusWsResponse; + +import static org.sonar.server.ce.ws.CeWsParameters.PARAM_BRANCH; +import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT; +import static org.sonar.server.ce.ws.CeWsParameters.PARAM_PULL_REQUEST; +import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001; +import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001; +import static org.sonar.server.ws.WsUtils.checkRequest; +import static org.sonar.server.ws.WsUtils.writeProtobuf; + +public class AnalysisStatusAction implements CeWsAction { + + private final UserSession userSession; + private final DbClient dbClient; + private final ComponentFinder componentFinder; + + public AnalysisStatusAction(UserSession userSession, DbClient dbClient, ComponentFinder componentFinder) { + this.userSession = userSession; + this.dbClient = dbClient; + this.componentFinder = componentFinder; + } + + @Override + public void define(WebService.NewController controller) { + WebService.NewAction action = controller.createAction("analysis_status") + .setDescription("Get the analysis status of a given component: a project, branch or pull request.
" + + "Requires the following permission: 'Browse' on the specified component.") + .setSince("7.4") + .setResponseExample(getClass().getResource("analysis_status-example.json")) + .setInternal(true) + .setHandler(this); + + action.createParam(PARAM_COMPONENT) + .setRequired(true) + .setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001); + + action.createParam(PARAM_BRANCH) + .setDescription("Branch key") + .setExampleValue(KEY_BRANCH_EXAMPLE_001) + .setInternal(true); + + action.createParam(PARAM_PULL_REQUEST) + .setDescription("Pull request id") + .setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001) + .setInternal(true); + } + + @Override + public void handle(Request request, Response response) throws Exception { + String componentKey = request.mandatoryParam(PARAM_COMPONENT); + String branchKey = request.param(PARAM_BRANCH); + String pullRequestKey = request.param(PARAM_PULL_REQUEST); + + checkRequest(branchKey == null || pullRequestKey == null, + "Parameters '%s' and '%s' must not be specified at the same time", PARAM_BRANCH, PARAM_PULL_REQUEST); + + doHandle(request, response, componentKey, branchKey, pullRequestKey); + } + + private void doHandle(Request request, Response response, String componentKey, @Nullable String branchKey, @Nullable String pullRequestKey) { + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto component = loadComponent(dbSession, componentKey, branchKey, pullRequestKey); + userSession.checkComponentPermission(UserRole.USER, component); + + checkRequest(isProject(component), "Component '%s' must be a project.", componentKey); + + AnalysisStatusWsResponse.Builder responseBuilder = AnalysisStatusWsResponse.newBuilder(); + CeActivityDto lastActivity = dbClient.ceActivityDao().selectLastByComponentUuid(dbSession, component.uuid()).orElse(null); + responseBuilder.setComponent(formatComponent(dbSession, component, lastActivity, branchKey, pullRequestKey)); + + writeProtobuf(responseBuilder.build(), request, response); + } + } + + private static boolean isProject(ComponentDto project) { + return Scopes.PROJECT.equals(project.scope()) && Qualifiers.PROJECT.equals(project.qualifier()); + } + + private ComponentDto loadComponent(DbSession dbSession, String componentKey, @Nullable String branchKey, @Nullable String pullRequestKey) { + if (branchKey != null) { + return componentFinder.getByKeyAndBranch(dbSession, componentKey, branchKey); + } + if (pullRequestKey != null) { + return componentFinder.getByKeyAndPullRequest(dbSession, componentKey, pullRequestKey); + } + return componentFinder.getByKey(dbSession, componentKey); + } + + private AnalysisStatusWsResponse.Component formatComponent(DbSession dbSession, ComponentDto component, @Nullable CeActivityDto lastActivity, + @Nullable String branchKey, @Nullable String pullRequestKey) { + + AnalysisStatusWsResponse.Component.Builder builder = AnalysisStatusWsResponse.Component.newBuilder() + .setOrganization(getOrganizationKey(dbSession, component)) + .setKey(component.getKey()) + .setName(component.name()); + + if (branchKey != null) { + builder.setBranch(branchKey); + } else if (pullRequestKey != null) { + builder.setPullRequest(pullRequestKey); + } + + if (lastActivity != null) { + List warnings = dbClient.ceTaskMessageDao().selectByTask(dbSession, lastActivity.getUuid()).stream() + .map(CeTaskMessageDto::getMessage) + .collect(Collectors.toList()); + + builder.addAllWarnings(warnings); + } + + return builder.build(); + } + + private String getOrganizationKey(DbSession dbSession, ComponentDto component) { + String organizationUuid = component.getOrganizationUuid(); + return dbClient.organizationDao().selectByUuid(dbSession, organizationUuid) + .orElseThrow(() -> new IllegalStateException("Unknown organization: " + organizationUuid)) + .getKey(); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsModule.java index ac415b72a75..5aead082768 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsModule.java @@ -28,6 +28,7 @@ public class CeWsModule extends Module { CeWs.class, ActivityAction.class, ActivityStatusAction.class, + AnalysisStatusAction.class, CancelAction.class, CancelAllAction.class, ComponentAction.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsParameters.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsParameters.java index fdfb881f88b..5ceb1c8cdc3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsParameters.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/CeWsParameters.java @@ -33,6 +33,9 @@ public class CeWsParameters { public static final String PARAM_MIN_SUBMITTED_AT = "minSubmittedAt"; public static final String PARAM_MAX_EXECUTED_AT = "maxExecutedAt"; + public static final String PARAM_BRANCH = "branch"; + public static final String PARAM_PULL_REQUEST = "pullRequest"; + private CeWsParameters() { // prevent instantiation } diff --git a/server/sonar-server/src/main/resources/org/sonar/server/ce/ws/analysis_status-example.json b/server/sonar-server/src/main/resources/org/sonar/server/ce/ws/analysis_status-example.json new file mode 100644 index 00000000000..33a06b140c3 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/ce/ws/analysis_status-example.json @@ -0,0 +1,8 @@ +{ + "component": { + "organization": "my-org-1", + "key": "com.github.kevinsawicki:http-request-parent", + "name": "HttpRequest", + "warnings": [] + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java new file mode 100644 index 00000000000..9720443e4f5 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/AnalysisStatusActionTest.java @@ -0,0 +1,358 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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. + */ +package org.sonar.server.ce.ws; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.function.Function; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.sonar.api.utils.System2; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.ce.CeActivityDto; +import org.sonar.db.ce.CeQueueDto; +import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.ce.CeTaskTypes; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.server.component.TestComponentFinder; +import org.sonar.server.exceptions.BadRequestException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.WsActionTester; +import org.sonarqube.ws.Ce; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.db.ce.CeActivityDto.Status.SUCCESS; +import static org.sonar.server.ce.ws.CeWsParameters.PARAM_BRANCH; +import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT; +import static org.sonar.server.ce.ws.CeWsParameters.PARAM_PULL_REQUEST; +import static org.sonar.test.JsonAssert.assertJson; + +@RunWith(DataProviderRunner.class) +public class AnalysisStatusActionTest { + private static final String BRANCH_WITH_WARNING = "feature-with-warning"; + private static final String BRANCH_WITHOUT_WARNING = "feature-without-warning"; + private static final String PULL_REQUEST = "pr1"; + + private static final String WARNING_IN_MAIN = "warning in main"; + private static final String WARNING_IN_BRANCH = "warning in branch"; + private static final String WARNING_IN_PR = "warning in pr"; + + private static int counter = 1; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone().logIn().setSystemAdministrator(); + @Rule + public DbTester db = DbTester.create(System2.INSTANCE); + + private DbClient dbClient = db.getDbClient(); + private WsActionTester ws = new WsActionTester(new AnalysisStatusAction(userSession, dbClient, TestComponentFinder.from(db))); + + @Test + public void fail_if_component_key_not_provided() { + expectedException.expect(IllegalArgumentException.class); + + ws.newRequest().execute(); + } + + @Test + public void fail_if_component_key_is_unknown() { + expectedException.expect(NotFoundException.class); + + ws.newRequest().setParam(PARAM_COMPONENT, "nonexistent").execute(); + } + + @Test + public void fail_if_both_branch_and_pullRequest_are_specified() { + expectedException.expect(BadRequestException.class); + + ws.newRequest() + .setParam(PARAM_COMPONENT, "dummy") + .setParam(PARAM_BRANCH, "feature1") + .setParam(PARAM_PULL_REQUEST, "pr1") + .execute(); + } + + @Test + @UseDataProvider("nonProjectComponentFactory") + public void fail_if_component_is_not_a_project(Function nonProjectComponentFactory) { + expectedException.expect(BadRequestException.class); + expectedException.expectMessage("must be a project"); + + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + + ComponentDto component = nonProjectComponentFactory.apply(project); + db.components().insertComponent(component); + + ws.newRequest() + .setParam(PARAM_COMPONENT, component.getKey()) + .execute(); + } + + @DataProvider + public static Object[][] nonProjectComponentFactory() { + return new Object[][] { + {(Function) ComponentTesting::newModuleDto}, + {(Function) p -> ComponentTesting.newDirectory(p, "foo")}, + {(Function) ComponentTesting::newFileDto} + }; + } + + @Test + public void json_example() { + OrganizationDto organization = db.organizations().insert(o -> o.setKey("my-org-1")); + ComponentDto project = db.components().insertPrivateProject(organization, + p -> p.setUuid("AU_w74XMgAS1Hm6h4-Y-"), + p -> p.setDbKey("com.github.kevinsawicki:http-request-parent"), + p -> p.setName("HttpRequest")); + + userSession.addProjectPermission(UserRole.USER, project); + + String result = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .execute() + .getInput(); + + assertJson(result).isSimilarTo(getClass().getResource("analysis_status-example.json")); + } + + @Test + public void no_errors_no_warnings() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + + Ce.AnalysisStatusWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(response.getComponent().getWarningsList()).isEmpty(); + } + + @Test + public void return_warnings_for_last_analysis_of_main() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + + SnapshotDto analysis = db.components().insertSnapshot(project); + CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis); + createTaskMessage(activity, WARNING_IN_MAIN); + + Ce.AnalysisStatusWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(response.getComponent().getWarningsList()).containsExactly(WARNING_IN_MAIN); + + SnapshotDto analysis2 = db.components().insertSnapshot(project); + insertActivity("task-uuid" + counter++, project, SUCCESS, analysis2); + + Ce.AnalysisStatusWsResponse response2 = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(response2.getComponent().getWarningsList()).isEmpty(); + } + + @Test + public void return_warnings_for_last_analysis_of_branch() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + + ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITH_WARNING)); + SnapshotDto analysis = db.components().insertSnapshot(branch); + CeActivityDto activity = insertActivity("task-uuid" + counter++, branch, SUCCESS, analysis); + createTaskMessage(activity, WARNING_IN_BRANCH); + + Ce.AnalysisStatusWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .setParam(PARAM_BRANCH, BRANCH_WITH_WARNING) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(response.getComponent().getWarningsList()).containsExactly(WARNING_IN_BRANCH); + + SnapshotDto analysis2 = db.components().insertSnapshot(branch); + insertActivity("task-uuid" + counter++, branch, SUCCESS, analysis2); + + Ce.AnalysisStatusWsResponse response2 = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .setParam(PARAM_BRANCH, BRANCH_WITH_WARNING) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(response2.getComponent().getWarningsList()).isEmpty(); + } + + @Test + public void return_warnings_for_last_analysis_of_pull_request() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> { + b.setBranchType(BranchType.PULL_REQUEST); + b.setKey(PULL_REQUEST); + }); + SnapshotDto analysis = db.components().insertSnapshot(pullRequest); + CeActivityDto activity = insertActivity("task-uuid" + counter++, pullRequest, SUCCESS, analysis); + createTaskMessage(activity, WARNING_IN_PR); + + Ce.AnalysisStatusWsResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .setParam(PARAM_PULL_REQUEST, PULL_REQUEST) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(response.getComponent().getWarningsList()).containsExactly(WARNING_IN_PR); + + SnapshotDto analysis2 = db.components().insertSnapshot(pullRequest); + insertActivity("task-uuid" + counter++, pullRequest, SUCCESS, analysis2); + + Ce.AnalysisStatusWsResponse response2 = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .setParam(PARAM_PULL_REQUEST, PULL_REQUEST) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(response2.getComponent().getWarningsList()).isEmpty(); + } + + @Test + public void return_warnings_per_branch() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + + SnapshotDto analysis = db.components().insertSnapshot(project); + CeActivityDto activity = insertActivity("task-uuid" + counter++, project, SUCCESS, analysis); + createTaskMessage(activity, WARNING_IN_MAIN); + + ComponentDto branchWithWarning = db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITH_WARNING)); + SnapshotDto branchAnalysis = db.components().insertSnapshot(branchWithWarning); + CeActivityDto branchActivity = insertActivity("task-uuid" + counter++, branchWithWarning, SUCCESS, branchAnalysis); + createTaskMessage(branchActivity, WARNING_IN_BRANCH); + + ComponentDto branchWithoutWarning = db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITHOUT_WARNING)); + SnapshotDto branchWithoutWarningAnalysis = db.components().insertSnapshot(branchWithoutWarning); + insertActivity("task-uuid" + counter++, branchWithoutWarning, SUCCESS, branchWithoutWarningAnalysis); + + ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> { + b.setBranchType(BranchType.PULL_REQUEST); + b.setKey(PULL_REQUEST); + }); + SnapshotDto prAnalysis = db.components().insertSnapshot(pullRequest); + CeActivityDto prActivity = insertActivity("task-uuid" + counter++, pullRequest, SUCCESS, prAnalysis); + createTaskMessage(prActivity, WARNING_IN_PR); + + Ce.AnalysisStatusWsResponse responseForMain = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(responseForMain.getComponent().getWarningsList()).containsExactly(WARNING_IN_MAIN); + + Ce.AnalysisStatusWsResponse responseForBranchWithWarning = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .setParam(PARAM_BRANCH, BRANCH_WITH_WARNING) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(responseForBranchWithWarning.getComponent().getWarningsList()).containsExactly(WARNING_IN_BRANCH); + + Ce.AnalysisStatusWsResponse responseForBranchWithoutWarning = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .setParam(PARAM_BRANCH, BRANCH_WITHOUT_WARNING) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(responseForBranchWithoutWarning.getComponent().getWarningsList()).isEmpty(); + + Ce.AnalysisStatusWsResponse responseForPr = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .setParam(PARAM_PULL_REQUEST, PULL_REQUEST) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(responseForPr.getComponent().getWarningsList()).containsExactly(WARNING_IN_PR); + } + + @Test + public void response_contains_branch_or_pullRequest_for_branch_or_pullRequest_only() { + ComponentDto project = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, project); + + db.components().insertProjectBranch(project, b -> b.setKey(BRANCH_WITHOUT_WARNING)); + + db.components().insertProjectBranch(project, b -> { + b.setBranchType(BranchType.PULL_REQUEST); + b.setKey(PULL_REQUEST); + }); + + Ce.AnalysisStatusWsResponse responseForMain = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(responseForMain.getComponent().hasBranch()).isFalse(); + assertThat(responseForMain.getComponent().hasPullRequest()).isFalse(); + + Ce.AnalysisStatusWsResponse responseForBranchWithoutWarning = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .setParam(PARAM_BRANCH, BRANCH_WITHOUT_WARNING) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(responseForBranchWithoutWarning.getComponent().getBranch()).isEqualTo(BRANCH_WITHOUT_WARNING); + assertThat(responseForBranchWithoutWarning.getComponent().hasPullRequest()).isFalse(); + + Ce.AnalysisStatusWsResponse responseForPr = ws.newRequest() + .setParam(PARAM_COMPONENT, project.getKey()) + .setParam(PARAM_PULL_REQUEST, PULL_REQUEST) + .executeProtobuf(Ce.AnalysisStatusWsResponse.class); + + assertThat(responseForPr.getComponent().hasBranch()).isFalse(); + assertThat(responseForPr.getComponent().getPullRequest()).isEqualTo(PULL_REQUEST); + } + + private void createTaskMessage(CeActivityDto activity, String warning) { + db.getDbClient().ceTaskMessageDao().insert(db.getSession(), new CeTaskMessageDto() + .setUuid("m-uuid-" + counter++) + .setTaskUuid(activity.getUuid()) + .setMessage(warning) + .setCreatedAt(counter)); + db.commit(); + } + + private CeActivityDto insertActivity(String taskUuid, ComponentDto component, CeActivityDto.Status status, @Nullable SnapshotDto analysis) { + CeQueueDto queueDto = new CeQueueDto(); + queueDto.setTaskType(CeTaskTypes.REPORT); + queueDto.setComponent(component); + queueDto.setUuid(taskUuid); + CeActivityDto activityDto = new CeActivityDto(queueDto); + activityDto.setStatus(status); + activityDto.setExecutionTimeMs(500L); + activityDto.setAnalysisUuid(analysis == null ? null : analysis.getUuid()); + activityDto.setExecutedAt((long) counter++); + db.getDbClient().ceActivityDao().insert(db.getSession(), activityDto); + db.getSession().commit(); + return activityDto; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java index a16e0b554fa..81bcddb6a3c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/CeWsModuleTest.java @@ -31,6 +31,6 @@ public class CeWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new CeWsModule().configure(container); - assertThat(container.size()).isEqualTo(15 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER); + assertThat(container.size()).isEqualTo(16 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER); } } diff --git a/sonar-ws/src/main/protobuf/ws-ce.proto b/sonar-ws/src/main/protobuf/ws-ce.proto index 3216ab435e0..0443f65eed0 100644 --- a/sonar-ws/src/main/protobuf/ws-ce.proto +++ b/sonar-ws/src/main/protobuf/ws-ce.proto @@ -51,6 +51,20 @@ message ActivityStatusWsResponse { optional int32 inProgress = 3; } +// GET api/ce/analysis_status +message AnalysisStatusWsResponse { + optional Component component = 1; + + message Component { + optional string organization = 1; + optional string key = 2; + optional string name = 3; + repeated string warnings = 4; + optional string branch = 5; + optional string pullRequest = 6; + } +} + // GET api/ce/component message ComponentResponse { repeated Task queue = 1; -- 2.39.5