From: Sébastien Lesaint Date: Tue, 11 Sep 2018 12:04:57 +0000 (+0200) Subject: SONAR-11238 api/ce/[task|activity|component] return task warnings X-Git-Tag: 7.5~381 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e4364789b5e4347ec6c94fdfc3c14b4e568481f2;p=sonarqube.git SONAR-11238 api/ce/[task|activity|component] return task warnings all return warnings count, warnings list is only available from api/ce/task on demand --- diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDto.java index af3e004aa75..a3fcffe471a 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeActivityDto.java @@ -96,9 +96,14 @@ public class CeActivityDto { * Flag indicating whether the analysis of the current activity has a scanner context or not. *

* This property can not be populated when inserting but is populated when reading. - *

*/ private boolean hasScannerContext; + /** + * Count of warnings attached to the current activity. + *

+ * This property can not be populated when inserting but is populated when retrieving the activity by UUID. + */ + private int warningCount = 0; CeActivityDto() { // required for MyBatis @@ -311,8 +316,19 @@ public class CeActivityDto { return hasScannerContext; } - protected void setHasScannerContext(boolean hasScannerContext) { + protected CeActivityDto setHasScannerContext(boolean hasScannerContext) { this.hasScannerContext = hasScannerContext; + return this; + } + + public int getWarningCount() { + return warningCount; + } + + protected CeActivityDto setWarningCount(int warningCount) { + checkArgument(warningCount >= 0); + this.warningCount = warningCount; + return this; } @Override @@ -339,6 +355,7 @@ public class CeActivityDto { ", errorMessage='" + errorMessage + '\'' + ", errorStacktrace='" + errorStacktrace + '\'' + ", hasScannerContext=" + hasScannerContext + + ", warningCount=" + warningCount + '}'; } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDao.java index 12af5bf80f0..787d73b1d85 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDao.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageDao.java @@ -19,6 +19,7 @@ */ package org.sonar.db.ce; +import java.util.List; import org.sonar.db.Dao; import org.sonar.db.DbSession; @@ -27,6 +28,13 @@ public class CeTaskMessageDao implements Dao { getMapper(dbSession).insert(dto); } + /** + * @return the messages for the specific task, if any, in ascending order of column {@code CREATED_AT}. + */ + public List selectByTask(DbSession dbSession, String taskUuid) { + return getMapper(dbSession).selectByTask(taskUuid); + } + private static CeTaskMessageMapper getMapper(DbSession dbSession) { return dbSession.getMapper(CeTaskMessageMapper.class); } diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageMapper.java index ad312927f3a..e44c8b61a10 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageMapper.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/ce/CeTaskMessageMapper.java @@ -19,9 +19,12 @@ */ package org.sonar.db.ce; +import java.util.List; import org.apache.ibatis.annotations.Param; public interface CeTaskMessageMapper { void insert(@Param("dto") CeTaskMessageDto dto); + List selectByTask(@Param("taskUuid") String taskUuid); + } 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 372c6a04d13..61cc9cded15 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 @@ -15,6 +15,10 @@ csc.task_uuid is not null as hasScannerContext + + (select count(1) from ce_task_message ctm where ctm.task_uuid = ca.uuid) as warningCount + + ca.id, ca.uuid, @@ -42,25 +46,31 @@ + select + + from + ce_task_message ctm + where + ctm.task_uuid=#{taskUuid,jdbcType=VARCHAR} + order by + ctm.created_at asc + + insert into ce_task_message ( diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java index c38abaec26e..eef82b7b76d 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDaoTest.java @@ -29,6 +29,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.Random; +import java.util.stream.IntStream; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.assertj.core.api.AbstractListAssert; @@ -75,7 +77,7 @@ public class CeActivityDaoTest { @Test public void test_insert() { - CeActivityDto inserted = insert("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); + CeActivityDto inserted = insert("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, SUCCESS); Optional saved = underTest.selectByUuid(db.getSession(), "TASK_1"); assertThat(saved).isPresent(); @@ -83,7 +85,7 @@ public class CeActivityDaoTest { assertThat(dto.getUuid()).isEqualTo("TASK_1"); assertThat(dto.getMainComponentUuid()).isEqualTo(MAINCOMPONENT_1); assertThat(dto.getComponentUuid()).isEqualTo(COMPONENT_1); - assertThat(dto.getStatus()).isEqualTo(CeActivityDto.Status.SUCCESS); + assertThat(dto.getStatus()).isEqualTo(SUCCESS); assertThat(dto.getSubmitterUuid()).isEqualTo("submitter uuid"); assertThat(dto.getSubmittedAt()).isEqualTo(1_450_000_000_000L); assertThat(dto.getWorkerUuid()).isEqualTo("worker uuid"); @@ -101,6 +103,34 @@ public class CeActivityDaoTest { assertThat(dto.getErrorStacktrace()).isNull(); assertThat(dto.getErrorType()).isNull(); assertThat(dto.isHasScannerContext()).isFalse(); + assertThat(dto.getWarningCount()).isZero(); + } + + @Test + public void selectByUuid_populates_warning_count() { + CeActivityDto[] tasks = { + insert("TASK_1", REPORT, "PROJECT_1", SUCCESS), + insert("TASK_2", REPORT, "PROJECT_1", SUCCESS), + insert("TASK_3", REPORT, "PROJECT_1", SUCCESS) + }; + int moreThan1 = 2 + new Random().nextInt(5); + insertWarnings(tasks[0], moreThan1); + insertWarnings(tasks[1], 0); + insertWarnings(tasks[2], 1); + + assertThat(underTest.selectByUuid(dbSession, tasks[0].getUuid()).get().getWarningCount()).isEqualTo(moreThan1); + assertThat(underTest.selectByUuid(dbSession, tasks[1].getUuid()).get().getWarningCount()).isEqualTo(0); + assertThat(underTest.selectByUuid(dbSession, tasks[2].getUuid()).get().getWarningCount()).isEqualTo(1); + } + + private void insertWarnings(CeActivityDto task, int warningCount) { + IntStream.range(0, warningCount).forEach(i -> db.getDbClient().ceTaskMessageDao().insert(dbSession, + new CeTaskMessageDto() + .setUuid(UuidFactoryFast.getInstance().create()) + .setTaskUuid(task.getUuid()) + .setMessage("message_" + task.getUuid() + "_" + i) + .setCreatedAt(task.getUuid().hashCode() + i))); + db.commit(); } @Test @@ -296,7 +326,7 @@ public class CeActivityDaoTest { @Test public void test_insert_of_errorMessage_of_1_000_chars() { - CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED) + CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, FAILED) .setErrorMessage(Strings.repeat("x", 1_000)); underTest.insert(db.getSession(), dto); @@ -307,7 +337,7 @@ public class CeActivityDaoTest { @Test public void test_insert_of_errorMessage_of_1_001_chars_is_truncated_to_1000() { String expected = Strings.repeat("x", 1_000); - CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED) + CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, FAILED) .setErrorMessage(expected + "y"); underTest.insert(db.getSession(), dto); @@ -317,7 +347,7 @@ public class CeActivityDaoTest { @Test public void test_insert_error_message_and_stacktrace() { - CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED) + CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, FAILED) .setErrorStacktrace("error stack"); underTest.insert(db.getSession(), dto); @@ -330,7 +360,7 @@ public class CeActivityDaoTest { @Test public void test_insert_error_message_only() { - CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, CeActivityDto.Status.FAILED); + CeActivityDto dto = createActivityDto("TASK_1", REPORT, COMPONENT_1, MAINCOMPONENT_1, FAILED); underTest.insert(db.getSession(), dto); Optional saved = underTest.selectByUuid(db.getSession(), "TASK_1"); @@ -342,11 +372,11 @@ public class CeActivityDaoTest { @Test public void insert_must_set_relevant_is_last_field() { // only a single task on MAINCOMPONENT_1 -> is_last=true - insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); + insert("TASK_1", REPORT, MAINCOMPONENT_1, SUCCESS); assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").get().getIsLast()).isTrue(); // only a single task on MAINCOMPONENT_2 -> is_last=true - insert("TASK_2", REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS); + insert("TASK_2", REPORT, MAINCOMPONENT_2, SUCCESS); assertThat(underTest.selectByUuid(db.getSession(), "TASK_2").get().getIsLast()).isTrue(); // two tasks on MAINCOMPONENT_1, the most recent one is TASK_3 @@ -356,7 +386,7 @@ public class CeActivityDaoTest { assertThat(underTest.selectByUuid(db.getSession(), "TASK_3").get().getIsLast()).isTrue(); // inserting a cancelled task does not change the last task - insert("TASK_4", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.CANCELED); + insert("TASK_4", REPORT, MAINCOMPONENT_1, CANCELED); assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").get().getIsLast()).isFalse(); assertThat(underTest.selectByUuid(db.getSession(), "TASK_2").get().getIsLast()).isTrue(); assertThat(underTest.selectByUuid(db.getSession(), "TASK_3").get().getIsLast()).isTrue(); @@ -365,10 +395,10 @@ public class CeActivityDaoTest { @Test public void test_selectByQuery() { - insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); + insert("TASK_1", REPORT, MAINCOMPONENT_1, SUCCESS); insert("TASK_2", REPORT, MAINCOMPONENT_1, FAILED); - insert("TASK_3", REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS); - insert("TASK_4", "views", null, CeActivityDto.Status.SUCCESS); + insert("TASK_3", REPORT, MAINCOMPONENT_2, SUCCESS); + insert("TASK_4", "views", null, SUCCESS); // no filters CeTaskQuery query = new CeTaskQuery().setStatuses(Collections.emptyList()); @@ -381,7 +411,7 @@ public class CeActivityDaoTest { assertThat(dtos).extracting("uuid").containsExactly("TASK_2", "TASK_1"); // select by status - query = new CeTaskQuery().setStatuses(singletonList(CeActivityDto.Status.SUCCESS.name())); + query = new CeTaskQuery().setStatuses(singletonList(SUCCESS.name())); dtos = underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(100)); assertThat(dtos).extracting("uuid").containsExactly("TASK_4", "TASK_3", "TASK_1"); @@ -423,12 +453,62 @@ public class CeActivityDaoTest { assertThat(dto.isHasScannerContext()).isTrue(); } + @Test + public void selectByQuery_populates_warningCount() { + CeActivityDto[] tasks = { + insert("TASK_1", REPORT, "PROJECT_1", SUCCESS), + insert("TASK_2", REPORT, "PROJECT_1", FAILED), + insert("TASK_3", REPORT, "PROJECT_2", SUCCESS), + insert("TASK_4", "views", null, SUCCESS) + }; + int moreThan1 = 2 + new Random().nextInt(5); + insertWarnings(tasks[0], moreThan1); + insertWarnings(tasks[1], 3); + insertWarnings(tasks[2], 1); + insertWarnings(tasks[3], 6); + + // no filters + CeTaskQuery query = new CeTaskQuery().setStatuses(Collections.emptyList()); + assertThat(underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(10))) + .extracting(CeActivityDto::getUuid, CeActivityDto::getWarningCount) + .containsExactly(tuple("TASK_4", 6), tuple("TASK_3", 1), tuple("TASK_2", 3), tuple("TASK_1", moreThan1)); + + // select by component uuid + query = new CeTaskQuery().setMainComponentUuid("PROJECT_1"); + assertThat(underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(100))) + .extracting(CeActivityDto::getUuid, CeActivityDto::getWarningCount) + .containsExactly(tuple("TASK_2", 3), tuple("TASK_1", moreThan1)); + + // select by status + query = new CeTaskQuery().setStatuses(singletonList(SUCCESS.name())); + assertThat(underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(100))) + .extracting(CeActivityDto::getUuid, CeActivityDto::getWarningCount) + .containsExactly(tuple("TASK_4", 6), tuple("TASK_3", 1), tuple("TASK_1", moreThan1)); + + // select by type + query = new CeTaskQuery().setType(REPORT); + assertThat(underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(100))) + .extracting(CeActivityDto::getUuid, CeActivityDto::getWarningCount) + .containsExactly(tuple("TASK_3", 1), tuple("TASK_2", 3), tuple("TASK_1", moreThan1)); + query = new CeTaskQuery().setType("views"); + assertThat(underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(100))) + .extracting(CeActivityDto::getUuid, CeActivityDto::getWarningCount) + .containsExactly(tuple("TASK_4", 6)); + + // select by multiple conditions + query = new CeTaskQuery().setType(REPORT).setOnlyCurrents(true).setMainComponentUuid("PROJECT_1"); + assertThat(underTest.selectByQuery(db.getSession(), query, forPage(1).andSize(100))) + .extracting(CeActivityDto::getUuid, CeActivityDto::getWarningCount) + .containsExactly(tuple("TASK_2", 3)); + + } + @Test public void selectByQuery_is_paginated_and_return_results_sorted_from_last_to_first() { - insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); - insert("TASK_2", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.FAILED); - insert("TASK_3", REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS); - insert("TASK_4", "views", null, CeActivityDto.Status.SUCCESS); + insert("TASK_1", REPORT, MAINCOMPONENT_1, SUCCESS); + insert("TASK_2", REPORT, MAINCOMPONENT_1, FAILED); + insert("TASK_3", REPORT, MAINCOMPONENT_2, SUCCESS); + insert("TASK_4", "views", null, SUCCESS); assertThat(selectPageOfUuids(forPage(1).andSize(1))).containsExactly("TASK_4"); assertThat(selectPageOfUuids(forPage(2).andSize(1))).containsExactly("TASK_3"); @@ -441,7 +521,7 @@ public class CeActivityDaoTest { @Test public void selectByQuery_no_results_if_shortcircuited_by_component_uuids() { - insert("TASK_1", REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); + insert("TASK_1", REPORT, MAINCOMPONENT_1, SUCCESS); CeTaskQuery query = new CeTaskQuery(); query.setMainComponentUuids(Collections.emptyList()); @@ -474,7 +554,7 @@ public class CeActivityDaoTest { queueDto.setUuid(uuid); queueDto.setTaskType("fake"); CeActivityDto dto = new CeActivityDto(queueDto); - dto.setStatus(CeActivityDto.Status.SUCCESS); + dto.setStatus(SUCCESS); dto.setSubmittedAt(submittedAt); dto.setExecutedAt(executedAt); underTest.insert(db.getSession(), dto); @@ -513,11 +593,26 @@ public class CeActivityDaoTest { .extracting("errorStacktrace").containsOnly((String) null); } + @Test + public void selectOlderThan_does_not_populate_warningCount() { + CeActivityDto activity1 = insert("TASK_1", REPORT, "PROJECT_1", FAILED); + insertWarnings(activity1, 10); + CeActivityDto activity2 = insert("TASK_2", REPORT, "PROJECT_1", SUCCESS); + insertWarnings(activity2, 1); + + List dtos = underTest.selectOlderThan(db.getSession(), system2.now() + 1_000_000L); + + assertThat(dtos) + .hasSize(2) + .extracting(CeActivityDto::getWarningCount) + .containsOnly(0); + } + @Test public void deleteByUuids() { - insert("TASK_1", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); - insert("TASK_2", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); - insert("TASK_3", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); + insert("TASK_1", "REPORT", MAINCOMPONENT_1, SUCCESS); + insert("TASK_2", "REPORT", MAINCOMPONENT_1, SUCCESS); + insert("TASK_3", "REPORT", MAINCOMPONENT_1, SUCCESS); underTest.deleteByUuids(db.getSession(), ImmutableSet.of("TASK_1", "TASK_3")); assertThat(underTest.selectByUuid(db.getSession(), "TASK_1").isPresent()).isFalse(); @@ -527,7 +622,7 @@ public class CeActivityDaoTest { @Test public void deleteByUuids_does_nothing_if_uuid_does_not_exist() { - insert("TASK_1", "REPORT", MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); + insert("TASK_1", "REPORT", MAINCOMPONENT_1, SUCCESS); // must not fail underTest.deleteByUuids(db.getSession(), singleton("TASK_2")); @@ -537,14 +632,14 @@ public class CeActivityDaoTest { @Test public void count_last_by_status_and_main_component_uuid() { - insert("TASK_1", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); + insert("TASK_1", CeTaskTypes.REPORT, MAINCOMPONENT_1, SUCCESS); // component 2 - insert("TASK_2", CeTaskTypes.REPORT, MAINCOMPONENT_2, CeActivityDto.Status.SUCCESS); + insert("TASK_2", CeTaskTypes.REPORT, MAINCOMPONENT_2, SUCCESS); // status failed - insert("TASK_3", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.FAILED); + insert("TASK_3", CeTaskTypes.REPORT, MAINCOMPONENT_1, FAILED); // status canceled - insert("TASK_4", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.CANCELED); - insert("TASK_5", CeTaskTypes.REPORT, MAINCOMPONENT_1, CeActivityDto.Status.SUCCESS); + insert("TASK_4", CeTaskTypes.REPORT, MAINCOMPONENT_1, CANCELED); + insert("TASK_5", CeTaskTypes.REPORT, MAINCOMPONENT_1, SUCCESS); db.commit(); assertThat(underTest.countLastByStatusAndMainComponentUuid(dbSession, SUCCESS, MAINCOMPONENT_1)).isEqualTo(1); @@ -601,7 +696,7 @@ public class CeActivityDaoTest { queueDto.setTaskType("fake"); CeActivityDto dto = new CeActivityDto(queueDto); - dto.setStatus(CeActivityDto.Status.SUCCESS); + dto.setStatus(SUCCESS); dto.setAnalysisUuid(uuid + "_AA"); system2.setNow(date); underTest.insert(db.getSession(), dto); diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDtoTest.java index 1035ea80d9f..c11d17e14db 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDtoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeActivityDtoTest.java @@ -132,6 +132,16 @@ public class CeActivityDtoTest { assertThat(underTest.getErrorMessage()).isEqualTo(before + after); } + @Test + public void setWarningCount_throws_IAE_if_less_than_0() { + underTest.setWarningCount(0); + underTest.setWarningCount(1 + new Random().nextInt(10)); + + expectedException.expect(IllegalArgumentException.class); + + underTest.setWarningCount(-1 - new Random().nextInt(10)); + } + @DataProvider public static Object[][] stringsWithChar0() { return new Object[][] { diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskMessageDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskMessageDaoTest.java index f219e8873ab..23db043dc96 100644 --- a/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskMessageDaoTest.java +++ b/server/sonar-db-dao/src/test/java/org/sonar/db/ce/CeTaskMessageDaoTest.java @@ -19,13 +19,16 @@ */ package org.sonar.db.ce; +import java.util.List; import org.assertj.core.groups.Tuple; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.utils.System2; +import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; public class CeTaskMessageDaoTest { @@ -49,6 +52,51 @@ public class CeTaskMessageDaoTest { .hasSize(1) .extracting(t -> t.get("UUID"), t -> t.get("TASK_UUID"), t -> t.get("MESSAGE"), t -> t.get("CREATED_AT")) .containsOnly(Tuple.tuple("uuid_1", "task_uuid_1", "message_1", 1_222_333L)); + } + + @Test + public void selectByTask_returns_empty_on_empty_table() { + String taskUuid = randomAlphabetic(10); + + List dto = underTest.selectByTask(dbTester.getSession(), taskUuid); + + assertThat(dto).isEmpty(); + } + + @Test + public void selectByTask_returns_message_of_task_ordered_by_CREATED_AT_asc() { + String task1 = "task1"; + String task2 = "task2"; + CeTaskMessageDto[] messages = { + insertMessage(task1, 0, 1_222_333L), + insertMessage(task2, 1, 2_222_333L), + insertMessage(task2, 2, 1_111_333L), + insertMessage(task1, 3, 1_222_111L), + insertMessage(task1, 4, 222_111L), + insertMessage(task1, 5, 3_222_111L) + }; + + assertThat(underTest.selectByTask(dbTester.getSession(), task1)) + .extracting(CeTaskMessageDto::getUuid) + .containsExactly(messages[4].getUuid(), messages[3].getUuid(), messages[0].getUuid(), messages[5].getUuid()); + + assertThat(underTest.selectByTask(dbTester.getSession(), task2)) + .extracting(CeTaskMessageDto::getUuid) + .containsExactly(messages[2].getUuid(), messages[1].getUuid()); + + assertThat(underTest.selectByTask(dbTester.getSession(), randomAlphabetic(5))) + .isEmpty(); + } + private CeTaskMessageDto insertMessage(String taskUuid, int i, long createdAt) { + CeTaskMessageDto res = new CeTaskMessageDto() + .setUuid("message_" + i) + .setTaskUuid(taskUuid) + .setMessage("test_" + i) + .setCreatedAt(createdAt); + DbSession dbSession = dbTester.getSession(); + underTest.insert(dbSession, res); + dbSession.commit(); + return res; } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java index c80223efdf7..ceb2cbcf15d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ActivityAction.java @@ -20,12 +20,12 @@ package org.sonar.server.ce.ws; import com.google.common.base.Joiner; -import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import java.util.Collections; import java.util.Date; import java.util.LinkedHashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -53,6 +53,7 @@ import static java.lang.Boolean.parseBoolean; import static java.lang.Integer.parseInt; import static java.lang.String.format; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.apache.commons.lang.StringUtils.defaultString; import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime; @@ -209,16 +210,16 @@ public class ActivityAction implements CeWsAction { private Optional searchTaskByUuid(DbSession dbSession, Request request) { String textQuery = request.getQ(); if (textQuery == null) { - return Optional.absent(); + return Optional.empty(); } - java.util.Optional queue = dbClient.ceQueueDao().selectByUuid(dbSession, textQuery); + Optional queue = dbClient.ceQueueDao().selectByUuid(dbSession, textQuery); if (queue.isPresent()) { return Optional.of(formatter.formatQueue(dbSession, queue.get())); } - java.util.Optional activity = dbClient.ceActivityDao().selectByUuid(dbSession, textQuery); - return activity.map(ceActivityDto -> Optional.of(formatter.formatActivity(dbSession, ceActivityDto, null))).orElseGet(Optional::absent); + Optional activity = dbClient.ceActivityDao().selectByUuid(dbSession, textQuery); + return activity.map(ceActivityDto -> formatter.formatActivity(dbSession, ceActivityDto, null, emptyList())); } private CeTaskQuery buildQuery(DbSession dbSession, Request request, @Nullable ComponentDto component) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ComponentAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ComponentAction.java index 8374b8ff42d..89a09228a41 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ComponentAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/ComponentAction.java @@ -38,11 +38,12 @@ import org.sonar.server.ws.KeyExamples; import org.sonarqube.ws.Ce; import org.sonarqube.ws.Ce.ComponentResponse; +import static java.util.Collections.emptyList; import static org.sonar.db.Pagination.forPage; -import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_COMPONENT; -import static org.sonar.server.ws.WsUtils.writeProtobuf; import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT; import static org.sonar.server.ce.ws.CeWsParameters.PARAM_COMPONENT_ID; +import static org.sonar.server.component.ComponentFinder.ParamNames.COMPONENT_ID_AND_COMPONENT; +import static org.sonar.server.ws.WsUtils.writeProtobuf; public class ComponentAction implements CeWsAction { @@ -97,7 +98,7 @@ public class ComponentAction implements CeWsAction { Ce.ComponentResponse.Builder wsResponseBuilder = ComponentResponse.newBuilder(); wsResponseBuilder.addAllQueue(formatter.formatQueue(dbSession, queueDtos)); if (activityDtos.size() == 1) { - wsResponseBuilder.setCurrent(formatter.formatActivity(dbSession, activityDtos.get(0), null)); + wsResponseBuilder.setCurrent(formatter.formatActivity(dbSession, activityDtos.get(0), null, emptyList())); } writeProtobuf(wsResponseBuilder.build(), wsRequest, wsResponse); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskAction.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskAction.java index 0d1305b6216..6e7d7ae330d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskAction.java @@ -38,6 +38,7 @@ import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeQueueDto; +import org.sonar.db.ce.CeTaskMessageDto; import org.sonar.db.component.ComponentDto; import org.sonar.db.permission.OrganizationPermission; import org.sonar.server.user.UserSession; @@ -105,7 +106,9 @@ public class TaskAction implements CeWsAction { Set additionalFields = AdditionalField.getFromRequest(wsRequest); maskErrorStacktrace(ceActivityDto, additionalFields); wsTaskResponse.setTask( - wsTaskFormatter.formatActivity(dbSession, ceActivityDto, extractScannerContext(dbSession, ceActivityDto, additionalFields))); + wsTaskFormatter.formatActivity(dbSession, ceActivityDto, + extractScannerContext(dbSession, ceActivityDto, additionalFields), + extractWarnings(dbSession, ceActivityDto, additionalFields))); } writeProtobuf(wsTaskResponse.build(), wsRequest, wsResponse); } @@ -147,9 +150,20 @@ public class TaskAction implements CeWsAction { return null; } + private List extractWarnings(DbSession dbSession, CeActivityDto activityDto, Set additionalFields) { + if (additionalFields.contains(AdditionalField.WARNINGS)) { + List dtos = dbClient.ceTaskMessageDao().selectByTask(dbSession, activityDto.getUuid()); + return dtos.stream() + .map(CeTaskMessageDto::getMessage) + .collect(MoreCollectors.toList(dtos.size())); + } + return Collections.emptyList(); + } + private enum AdditionalField { STACKTRACE("stacktrace"), - SCANNER_CONTEXT("scannerContext"); + SCANNER_CONTEXT("scannerContext"), + WARNINGS("warnings"); private final String label; diff --git a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java index 54e48e3c160..31c3687e977 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ce/ws/TaskFormatter.java @@ -46,6 +46,7 @@ import org.sonarqube.ws.Common; import static com.google.common.base.Preconditions.checkState; import static java.lang.String.format; +import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.core.util.Protobuf.setNullable; @@ -95,18 +96,18 @@ public class TaskFormatter { return builder.build(); } - public Ce.Task formatActivity(DbSession dbSession, CeActivityDto dto, @Nullable String scannerContext) { - return formatActivity(dto, DtoCache.forActivityDtos(dbClient, dbSession, singletonList(dto)), scannerContext); + public Ce.Task formatActivity(DbSession dbSession, CeActivityDto dto, @Nullable String scannerContext, List warnings) { + return formatActivity(dto, DtoCache.forActivityDtos(dbClient, dbSession, singletonList(dto)), scannerContext, warnings); } public List formatActivity(DbSession dbSession, List dtos) { DtoCache cache = DtoCache.forActivityDtos(dbClient, dbSession, dtos); return dtos.stream() - .map(input -> formatActivity(input, cache, null)) + .map(input -> formatActivity(input, cache, null, emptyList())) .collect(MoreCollectors.toList(dtos.size())); } - private static Ce.Task formatActivity(CeActivityDto dto, DtoCache cache, @Nullable String scannerContext) { + private static Ce.Task formatActivity(CeActivityDto dto, DtoCache cache, @Nullable String scannerContext, List warnings) { Ce.Task.Builder builder = Ce.Task.newBuilder(); String organizationKey = cache.getOrganizationKey(dto.getComponentUuid()); setNullable(organizationKey, builder::setOrganization); @@ -129,6 +130,9 @@ public class TaskFormatter { setNullable(dto.getErrorType(), builder::setErrorType); setNullable(scannerContext, builder::setScannerContext); builder.setHasScannerContext(dto.isHasScannerContext()); + builder.setWarningCount(dto.getWarningCount()); + warnings.forEach(builder::addWarnings); + return builder.build(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java index 846a358ca91..4d0792f8095 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java @@ -22,6 +22,7 @@ package org.sonar.server.ce.ws; import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.stream.IntStream; import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; @@ -36,6 +37,7 @@ import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeActivityDto.Status; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeTaskCharacteristicDto; +import org.sonar.db.ce.CeTaskMessageDto; import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; @@ -57,6 +59,7 @@ import org.sonarqube.ws.Common; import org.sonarqube.ws.MediaTypes; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.Mockito.mock; @@ -116,6 +119,7 @@ public class ActivityActionTest { assertThat(task.hasAnalysisId()).isFalse(); assertThat(task.getExecutionTimeMs()).isEqualTo(500L); assertThat(task.getLogs()).isFalse(); + assertThat(task.getWarningCount()).isZero(); task = activityResponse.getTasks(1); assertThat(task.getId()).isEqualTo("T1"); @@ -123,6 +127,7 @@ public class ActivityActionTest { assertThat(task.getComponentId()).isEqualTo(project1.uuid()); assertThat(task.getLogs()).isFalse(); assertThat(task.getOrganization()).isEqualTo(org1.getKey()); + assertThat(task.getWarningCount()).isZero(); } @Test @@ -214,6 +219,36 @@ public class ActivityActionTest { assertPage(10, asList("T3", "T2", "T1")); } + @Test + public void return_warnings_count_on_queue_and_activity_but_no_warnings_list() { + logInAsSystemAdministrator(); + ComponentDto project1 = db.components().insertPrivateProject(); + ComponentDto project2 = db.components().insertPrivateProject(); + insertActivity("T1", project1, SUCCESS); + insertActivity("T2", project2, FAILED); + insertQueue("T3", project1, IN_PROGRESS); + insertMessages("T1", 2); + insertMessages("T2", 0); + insertMessages("T3", 5); + + ActivityResponse activityResponse = call(ws.newRequest() + .setParam(Param.PAGE_SIZE, Integer.toString(10)) + .setParam(PARAM_STATUS, "SUCCESS,FAILED,CANCELED,IN_PROGRESS,PENDING")); + assertThat(activityResponse.getTasksList()) + .extracting(Task::getId, Task::getWarningCount, Task::getWarningsList) + .containsOnly(tuple("T1", 2, emptyList()), tuple("T2", 0, emptyList()), tuple("T3", 0, emptyList())); + } + + private void insertMessages(String taskUuid, int messageCount) { + IntStream.range(0, messageCount) + .forEach(i -> db.getDbClient().ceTaskMessageDao().insert(db.getSession(), new CeTaskMessageDto() + .setUuid("uuid_" + taskUuid + "_" + i) + .setTaskUuid(taskUuid) + .setMessage("m_" + taskUuid + "_" + i) + .setCreatedAt(taskUuid.hashCode() + i))); + db.commit(); + } + @Test public void project_administrator_can_access_his_project_activity() { ComponentDto project1 = db.components().insertPrivateProject(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java index ff927dc762d..4bbbea6cfea 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/ComponentActionTest.java @@ -20,6 +20,8 @@ package org.sonar.server.ce.ws; import java.util.Collections; +import java.util.Random; +import java.util.stream.IntStream; import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; @@ -31,6 +33,7 @@ import org.sonar.db.DbTester; import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeTaskCharacteristicDto; +import org.sonar.db.ce.CeTaskMessageDto; import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.SnapshotDto; @@ -44,6 +47,7 @@ import org.sonarqube.ws.Ce; import org.sonarqube.ws.Common; import org.sonarqube.ws.MediaTypes; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.sonar.db.ce.CeActivityDto.Status.SUCCESS; @@ -103,12 +107,15 @@ public class ComponentActionTest { assertThat(response.getQueue(1).getId()).isEqualTo("T5"); // T3 is the latest task executed on PROJECT_1 assertThat(response.hasCurrent()).isTrue(); - assertThat(response.getCurrent().getId()).isEqualTo("T3"); - assertThat(response.getCurrent().hasAnalysisId()).isFalse(); + Ce.Task current = response.getCurrent(); + assertThat(current.getId()).isEqualTo("T3"); + assertThat(current.hasAnalysisId()).isFalse(); + assertThat(current.getWarningCount()).isZero(); + assertThat(current.getWarningsList()).isEmpty(); assertThat(response.getQueueList()) .extracting(Ce.Task::getOrganization) .containsOnly(organization.getKey()); - assertThat(response.getCurrent().getOrganization()).isEqualTo(organization.getKey()); + assertThat(current.getOrganization()).isEqualTo(organization.getKey()); } @Test @@ -122,8 +129,11 @@ public class ComponentActionTest { .setParam(PARAM_COMPONENT, project.getDbKey()) .executeProtobuf(Ce.ComponentResponse.class); assertThat(response.hasCurrent()).isTrue(); - assertThat(response.getCurrent().getId()).isEqualTo("T1"); - assertThat(response.getCurrent().getAnalysisId()).isEqualTo(analysis.getUuid()); + Ce.Task current = response.getCurrent(); + assertThat(current.getId()).isEqualTo("T1"); + assertThat(current.getAnalysisId()).isEqualTo(analysis.getUuid()); + assertThat(current.getWarningCount()).isZero(); + assertThat(current.getWarningsList()).isEmpty(); } @Test @@ -137,8 +147,11 @@ public class ComponentActionTest { .setParam(PARAM_COMPONENT_ID, project.uuid()) .executeProtobuf(Ce.ComponentResponse.class); assertThat(response.hasCurrent()).isTrue(); - assertThat(response.getCurrent().getId()).isEqualTo("T1"); - assertThat(response.getCurrent().getAnalysisId()).isEqualTo(analysis.getUuid()); + Ce.Task current = response.getCurrent(); + assertThat(current.getId()).isEqualTo("T1"); + assertThat(current.getAnalysisId()).isEqualTo(analysis.getUuid()); + assertThat(current.getWarningCount()).isZero(); + assertThat(current.getWarningsList()).isEmpty(); } @Test @@ -157,7 +170,10 @@ public class ComponentActionTest { assertThat(response.getQueueCount()).isEqualTo(0); // T3 is the latest task executed on PROJECT_1 ignoring Canceled ones assertThat(response.hasCurrent()).isTrue(); - assertThat(response.getCurrent().getId()).isEqualTo("T3"); + Ce.Task current = response.getCurrent(); + assertThat(current.getId()).isEqualTo("T3"); + assertThat(current.getWarningCount()).isZero(); + assertThat(current.getWarningsList()).isEmpty(); } @Test @@ -175,9 +191,9 @@ public class ComponentActionTest { .executeProtobuf(Ce.ComponentResponse.class); assertThat(response.getCurrent()) - .extracting(Ce.Task::getId, Ce.Task::getBranch, Ce.Task::getBranchType, Ce.Task::getStatus, Ce.Task::getComponentKey) + .extracting(Ce.Task::getId, Ce.Task::getBranch, Ce.Task::getBranchType, Ce.Task::getStatus, Ce.Task::getComponentKey, Ce.Task::getWarningCount, Ce.Task::getWarningsList) .containsOnly( - "T1", longLivingBranch.getBranch(), Common.BranchType.LONG, Ce.TaskStatus.SUCCESS, project.getKey()); + "T1", longLivingBranch.getBranch(), Common.BranchType.LONG, Ce.TaskStatus.SUCCESS, project.getKey(), 0, emptyList()); } @Test @@ -197,10 +213,10 @@ public class ComponentActionTest { .executeProtobuf(Ce.ComponentResponse.class); assertThat(response.getQueueList()) - .extracting(Ce.Task::getId, Ce.Task::getBranch, Ce.Task::getBranchType, Ce.Task::getStatus, Ce.Task::getComponentKey) + .extracting(Ce.Task::getId, Ce.Task::getBranch, Ce.Task::getBranchType, Ce.Task::getStatus, Ce.Task::getComponentKey, Ce.Task::getWarningCount, Ce.Task::getWarningsList) .containsOnly( - tuple("T1", longLivingBranch.getBranch(), Common.BranchType.LONG, Ce.TaskStatus.IN_PROGRESS, project.getKey()), - tuple("T2", longLivingBranch.getBranch(), Common.BranchType.LONG, Ce.TaskStatus.PENDING, project.getKey())); + tuple("T1", longLivingBranch.getBranch(), Common.BranchType.LONG, Ce.TaskStatus.IN_PROGRESS, project.getKey(), 0, emptyList()), + tuple("T2", longLivingBranch.getBranch(), Common.BranchType.LONG, Ce.TaskStatus.PENDING, project.getKey(), 0, emptyList())); } @Test @@ -222,11 +238,34 @@ public class ComponentActionTest { .executeProtobuf(Ce.ComponentResponse.class); assertThat(response.getQueueList()) - .extracting(Ce.Task::getId, Ce.Task::getComponentKey, Ce.Task::getBranch, Ce.Task::getBranchType) + .extracting(Ce.Task::getId, Ce.Task::getComponentKey, Ce.Task::getBranch, Ce.Task::getBranchType, Ce.Task::getWarningCount, Ce.Task::getWarningsList) .containsOnly( - tuple("Main", project.getKey(), "", Common.BranchType.UNKNOWN_BRANCH_TYPE), - tuple("Long", longLivingBranch.getKey(), longLivingBranch.getBranch(), Common.BranchType.LONG), - tuple("Short", shortLivingBranch.getKey(), shortLivingBranch.getBranch(), Common.BranchType.SHORT)); + tuple("Main", project.getKey(), "", Common.BranchType.UNKNOWN_BRANCH_TYPE, 0, emptyList()), + tuple("Long", longLivingBranch.getKey(), longLivingBranch.getBranch(), Common.BranchType.LONG, 0, emptyList()), + tuple("Short", shortLivingBranch.getKey(), shortLivingBranch.getBranch(), Common.BranchType.SHORT, 0, emptyList())); + } + + @Test + public void populates_warning_count_of_activities_but_not_warnings() { + ComponentDto privateProject = db.components().insertPrivateProject(); + userSession.addProjectPermission(UserRole.USER, privateProject); + SnapshotDto analysis = db.components().insertSnapshot(privateProject); + CeActivityDto activity = insertActivity("Short", privateProject, SUCCESS, analysis); + int messageCount = 1 + new Random().nextInt(10); + IntStream.range(0, messageCount).forEach(i -> db.getDbClient().ceTaskMessageDao().insert(db.getSession(), new CeTaskMessageDto() + .setUuid("uuid_" + i) + .setTaskUuid(activity.getUuid()) + .setMessage("m_" + i) + .setCreatedAt(i))); + db.commit(); + + Ce.ComponentResponse response = ws.newRequest() + .setParam(PARAM_COMPONENT, privateProject.getKey()) + .executeProtobuf(Ce.ComponentResponse.class); + assertThat(response.hasCurrent()).isTrue(); + assertThat(response.getCurrent()) + .extracting(Ce.Task::getWarningCount, Ce.Task::getWarningsList) + .containsOnly(messageCount, emptyList()); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskActionTest.java index 44333e12674..21e124a1a52 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskActionTest.java @@ -20,6 +20,8 @@ package org.sonar.server.ce.ws; import java.util.Collections; +import java.util.Random; +import java.util.stream.IntStream; import javax.annotation.Nullable; import org.junit.Before; import org.junit.Rule; @@ -29,14 +31,17 @@ import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; import org.sonar.core.permission.GlobalPermissions; import org.sonar.core.util.CloseableIterator; +import org.sonar.core.util.UuidFactoryFast; import org.sonar.core.util.Uuids; import org.sonar.db.DbTester; import org.sonar.db.ce.CeActivityDto; import org.sonar.db.ce.CeQueueDto; import org.sonar.db.ce.CeTaskCharacteristicDto; +import org.sonar.db.ce.CeTaskMessageDto; import org.sonar.db.ce.CeTaskTypes; import org.sonar.db.component.ComponentDto; import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.permission.OrganizationPermission; import org.sonar.db.user.UserDto; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; @@ -47,6 +52,7 @@ import org.sonarqube.ws.Common; import static java.util.Collections.singleton; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION; import static org.sonar.db.ce.CeTaskCharacteristicDto.BRANCH_KEY; import static org.sonar.db.ce.CeTaskCharacteristicDto.BRANCH_TYPE_KEY; import static org.sonar.db.component.BranchType.LONG; @@ -65,16 +71,18 @@ public class TaskActionTest { @Rule public DbTester db = DbTester.create(System2.INSTANCE); - private OrganizationDto organizationDto; - private ComponentDto project; + private OrganizationDto organization; + private ComponentDto privateProject; + private ComponentDto publicProject; private TaskFormatter formatter = new TaskFormatter(db.getDbClient(), System2.INSTANCE); private TaskAction underTest = new TaskAction(db.getDbClient(), formatter, userSession); private WsActionTester ws = new WsActionTester(underTest); @Before public void setUp() { - organizationDto = db.organizations().insert(); - project = db.components().insertPrivateProject(organizationDto); + organization = db.organizations().insert(); + privateProject = db.components().insertPrivateProject(organization); + publicProject = db.components().insertPublicProject(organization); } @Test @@ -85,7 +93,7 @@ public class TaskActionTest { CeQueueDto queueDto = new CeQueueDto(); queueDto.setTaskType(CeTaskTypes.REPORT); queueDto.setUuid(SOME_TASK_UUID); - queueDto.setComponentUuid(project.uuid()); + queueDto.setComponentUuid(privateProject.uuid()); queueDto.setStatus(CeQueueDto.Status.PENDING); queueDto.setSubmitterUuid(user.getUuid()); persist(queueDto); @@ -93,15 +101,39 @@ public class TaskActionTest { Ce.TaskResponse taskResponse = ws.newRequest() .setParam("id", SOME_TASK_UUID) .executeProtobuf(Ce.TaskResponse.class); - assertThat(taskResponse.getTask().getOrganization()).isEqualTo(organizationDto.getKey()); + assertThat(taskResponse.getTask().getOrganization()).isEqualTo(organization.getKey()); assertThat(taskResponse.getTask().getId()).isEqualTo(SOME_TASK_UUID); assertThat(taskResponse.getTask().getStatus()).isEqualTo(Ce.TaskStatus.PENDING); assertThat(taskResponse.getTask().getSubmitterLogin()).isEqualTo(user.getLogin()); - assertThat(taskResponse.getTask().getComponentId()).isEqualTo(project.uuid()); - assertThat(taskResponse.getTask().getComponentKey()).isEqualTo(project.getDbKey()); - assertThat(taskResponse.getTask().getComponentName()).isEqualTo(project.name()); + assertThat(taskResponse.getTask().getComponentId()).isEqualTo(privateProject.uuid()); + assertThat(taskResponse.getTask().getComponentKey()).isEqualTo(privateProject.getDbKey()); + assertThat(taskResponse.getTask().getComponentName()).isEqualTo(privateProject.name()); assertThat(taskResponse.getTask().hasExecutionTimeMs()).isFalse(); assertThat(taskResponse.getTask().getLogs()).isFalse(); + assertThat(taskResponse.getTask().getWarningCount()).isZero(); + assertThat(taskResponse.getTask().getWarningsList()).isEmpty(); + } + + @Test + public void no_warning_detail_on_task_in_queue() { + UserDto user = db.users().insertUser(); + userSession.logIn(user).setRoot(); + CeQueueDto queueDto = createAndPersistQueueTask(null, user); + IntStream.range(0, 1 + new Random().nextInt(5)) + .forEach(i -> db.getDbClient().ceTaskMessageDao().insert(db.getSession(), + new CeTaskMessageDto() + .setUuid("u_" + i) + .setTaskUuid(queueDto.getUuid()) + .setMessage("m_" + i) + .setCreatedAt(queueDto.getUuid().hashCode() + i))); + db.commit(); + + Ce.TaskResponse taskResponse = ws.newRequest() + .setParam("id", SOME_TASK_UUID) + .executeProtobuf(Ce.TaskResponse.class); + Ce.Task task = taskResponse.getTask(); + assertThat(task.getWarningCount()).isZero(); + assertThat(task.getWarningsList()).isEmpty(); } @Test @@ -116,15 +148,17 @@ public class TaskActionTest { .setParam("id", SOME_TASK_UUID) .executeProtobuf(Ce.TaskResponse.class); Ce.Task task = taskResponse.getTask(); - assertThat(task.getOrganization()).isEqualTo(organizationDto.getKey()); + assertThat(task.getOrganization()).isEqualTo(organization.getKey()); assertThat(task.getId()).isEqualTo(SOME_TASK_UUID); assertThat(task.getStatus()).isEqualTo(Ce.TaskStatus.FAILED); - assertThat(task.getComponentId()).isEqualTo(project.uuid()); - assertThat(task.getComponentKey()).isEqualTo(project.getDbKey()); - assertThat(task.getComponentName()).isEqualTo(project.name()); + assertThat(task.getComponentId()).isEqualTo(privateProject.uuid()); + assertThat(task.getComponentKey()).isEqualTo(privateProject.getDbKey()); + assertThat(task.getComponentName()).isEqualTo(privateProject.name()); assertThat(task.getAnalysisId()).isEqualTo(activityDto.getAnalysisUuid()); assertThat(task.getExecutionTimeMs()).isEqualTo(500L); assertThat(task.getLogs()).isFalse(); + assertThat(task.getWarningCount()).isZero(); + assertThat(task.getWarningsList()).isEmpty(); } @Test @@ -268,8 +302,39 @@ public class TaskActionTest { @Test public void get_project_queue_task_with_scan_permission_on_project() { UserDto user = db.users().insertUser(); - userSession.logIn(user).addProjectPermission(GlobalPermissions.SCAN_EXECUTION, project); - CeQueueDto task = createAndPersistQueueTask(project, user); + userSession.logIn(user).addProjectPermission(GlobalPermissions.SCAN_EXECUTION, privateProject); + CeQueueDto task = createAndPersistQueueTask(privateProject, user); + + call(task.getUuid()); + } + + @Test + public void getting_project_queue_task_of_public_project_fails_with_ForbiddenException() { + UserDto user = db.users().insertUser(); + userSession.logIn().registerComponents(publicProject); + CeQueueDto task = createAndPersistQueueTask(publicProject, user); + + expectedException.expect(ForbiddenException.class); + + call(task.getUuid()); + } + + @Test + public void get_project_queue_task_of_private_project_with_user_permission_fails_with_ForbiddenException() { + UserDto user = db.users().insertUser(); + userSession.logIn().addProjectPermission(UserRole.USER, privateProject); + CeQueueDto task = createAndPersistQueueTask(privateProject, user); + + expectedException.expect(ForbiddenException.class); + + call(task.getUuid()); + } + + @Test + public void get_project_queue_task_on_public_project() { + UserDto user = db.users().insertUser(); + userSession.logIn(user).addProjectPermission(SCAN_EXECUTION, privateProject); + CeQueueDto task = createAndPersistQueueTask(privateProject, user); call(task.getUuid()); } @@ -277,8 +342,8 @@ public class TaskActionTest { @Test public void get_project_queue_task_with_scan_permission_on_organization_but_not_on_project() { UserDto user = db.users().insertUser(); - userSession.logIn(user).addPermission(SCAN, project.getOrganizationUuid()); - CeQueueDto task = createAndPersistQueueTask(project, user); + userSession.logIn(user).addPermission(SCAN, privateProject.getOrganizationUuid()); + CeQueueDto task = createAndPersistQueueTask(privateProject, user); call(task.getUuid()); } @@ -287,7 +352,7 @@ public class TaskActionTest { public void getting_project_queue_task_throws_ForbiddenException_if_no_admin_nor_scan_permissions() { UserDto user = db.users().insertUser(); userSession.logIn(user); - CeQueueDto task = createAndPersistQueueTask(project, user); + CeQueueDto task = createAndPersistQueueTask(privateProject, user); expectedException.expect(ForbiddenException.class); @@ -316,16 +381,26 @@ public class TaskActionTest { @Test public void get_project_archived_task_with_scan_permission_on_project() { - userSession.logIn().addProjectPermission(GlobalPermissions.SCAN_EXECUTION, project); - CeActivityDto task = createAndPersistArchivedTask(project); + userSession.logIn().addProjectPermission(GlobalPermissions.SCAN_EXECUTION, privateProject); + CeActivityDto task = createAndPersistArchivedTask(privateProject); + + call(task.getUuid()); + } + + @Test + public void getting_archived_task_of_public_project_fails_with_ForbiddenException() { + userSession.logIn().registerComponents(publicProject); + CeActivityDto task = createAndPersistArchivedTask(publicProject); + expectedException.expect(ForbiddenException.class); + call(task.getUuid()); } @Test public void get_project_archived_task_with_scan_permission_on_organization_but_not_on_project() { - userSession.logIn().addPermission(SCAN, project.getOrganizationUuid()); - CeActivityDto task = createAndPersistArchivedTask(project); + userSession.logIn().addPermission(SCAN, privateProject.getOrganizationUuid()); + CeActivityDto task = createAndPersistArchivedTask(privateProject); call(task.getUuid()); } @@ -333,7 +408,7 @@ public class TaskActionTest { @Test public void getting_project_archived_task_throws_ForbiddenException_if_no_admin_nor_scan_permissions() { userSession.logIn(); - CeActivityDto task = createAndPersistArchivedTask(project); + CeActivityDto task = createAndPersistArchivedTask(privateProject); expectedException.expect(ForbiddenException.class); @@ -358,6 +433,67 @@ public class TaskActionTest { call(task.getUuid()); } + @Test + public void get_warnings_on_global_archived_task_requires_to_be_system_administrator() { + logInAsSystemAdministrator(); + + getWarningsImpl(createAndPersistArchivedTask(null)); + } + + @Test + public void get_warnings_on_public_project_archived_task_if_not_admin_fails_with_ForbiddenException() { + userSession.logIn().registerComponents(publicProject); + + expectedException.expect(ForbiddenException.class); + + getWarningsImpl(createAndPersistArchivedTask(publicProject)); + } + + @Test + public void get_warnings_on_private_project_archived_task_if_user_fails_with_ForbiddenException() { + userSession.logIn().addProjectPermission(UserRole.USER, privateProject); + + expectedException.expect(ForbiddenException.class); + + getWarningsImpl(createAndPersistArchivedTask(privateProject)); + } + + @Test + public void get_warnings_on_private_project_archived_task_if_scan() { + userSession.logIn().addProjectPermission(SCAN_EXECUTION, privateProject); + + getWarningsImpl(createAndPersistArchivedTask(privateProject)); + } + + @Test + public void get_warnings_on_private_project_archived_task_if_scan_on_organization() { + userSession.logIn().addPermission(OrganizationPermission.SCAN, organization); + + getWarningsImpl(createAndPersistArchivedTask(privateProject)); + } + + private void getWarningsImpl(CeActivityDto task) { + String[] warnings = IntStream.range(0, 1 + new Random().nextInt(10)) + .mapToObj(i -> insertWarning(task, i)) + .map(CeTaskMessageDto::getMessage) + .toArray(String[]::new); + + Ce.Task taskWithWarnings = callWithWarnings(task.getUuid()); + assertThat(taskWithWarnings.getWarningCount()).isEqualTo(warnings.length); + assertThat(taskWithWarnings.getWarningsList()).containsExactly(warnings); + } + + private CeTaskMessageDto insertWarning(CeActivityDto task, int i) { + CeTaskMessageDto res = new CeTaskMessageDto() + .setUuid(UuidFactoryFast.getInstance().create()) + .setTaskUuid(task.getUuid()) + .setMessage("msg_" + task.getUuid() + "_" + i) + .setCreatedAt(task.getUuid().hashCode() + i); + db.getDbClient().ceTaskMessageDao().insert(db.getSession(), res); + db.getSession().commit(); + return res; + } + private CeActivityDto createAndPersistArchivedTask(@Nullable ComponentDto component) { CeQueueDto queueDto = new CeQueueDto(); queueDto.setTaskType(CeTaskTypes.REPORT); @@ -374,10 +510,7 @@ public class TaskActionTest { } private CeActivityDto createActivityDto(String uuid) { - CeQueueDto queueDto = new CeQueueDto(); - queueDto.setTaskType(CeTaskTypes.REPORT); - queueDto.setUuid(uuid); - queueDto.setComponentUuid(project.uuid()); + CeQueueDto queueDto = createQueueDto(uuid); CeActivityDto activityDto = new CeActivityDto(queueDto); activityDto.setStatus(CeActivityDto.Status.FAILED); activityDto.setExecutionTimeMs(500L); @@ -385,6 +518,14 @@ public class TaskActionTest { return activityDto; } + private CeQueueDto createQueueDto(String uuid) { + CeQueueDto queueDto = new CeQueueDto(); + queueDto.setTaskType(CeTaskTypes.REPORT); + queueDto.setUuid(uuid); + queueDto.setComponentUuid(privateProject.uuid()); + return queueDto; + } + private CeQueueDto createAndPersistQueueTask(@Nullable ComponentDto component, UserDto user) { CeQueueDto dto = new CeQueueDto(); dto.setTaskType(CeTaskTypes.REPORT); @@ -449,4 +590,14 @@ public class TaskActionTest { assertThat(task.getId()).isEqualTo(taskUuid); } + private Ce.Task callWithWarnings(String taskUuid) { + Ce.TaskResponse taskResponse = ws.newRequest() + .setParam("id", taskUuid) + .setParam("additionalFields", "warnings") + .executeProtobuf(Ce.TaskResponse.class); + Ce.Task task = taskResponse.getTask(); + assertThat(task.getId()).isEqualTo(taskUuid); + return task; + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskFormatterTest.java b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskFormatterTest.java index bcd8b84e7f1..2ce1c6131b1 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskFormatterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ce/ws/TaskFormatterTest.java @@ -19,11 +19,15 @@ */ package org.sonar.server.ce.ws; +import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.Random; +import java.util.stream.IntStream; import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.System2; import org.sonar.db.DbTester; @@ -35,6 +39,8 @@ import org.sonar.db.user.UserDto; import org.sonarqube.ws.Ce; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.Mockito.mock; @@ -45,6 +51,10 @@ public class TaskFormatterTest { @Rule public DbTester db = DbTester.create(System2.INSTANCE); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private int warningCount = new Random().nextInt(10); private System2 system2 = mock(System2.class); private TaskFormatter underTest = new TaskFormatter(db.getDbClient(), system2); @@ -163,12 +173,22 @@ public class TaskFormatterTest { assertThat(wsTasks).extracting("id").containsExactly("UUID1", "UUID2"); } + @Test + public void formatActivity_throws_NPE_if_warnings_parameter_is_null() { + UserDto user = db.users().insertUser(); + CeActivityDto dto = newActivity("UUID", "COMPONENT_UUID", CeActivityDto.Status.FAILED, user); + + expectedException.expect(NullPointerException.class); + + underTest.formatActivity(db.getSession(), dto, "foo", null); + } + @Test public void formatActivity() { UserDto user = db.users().insertUser(); CeActivityDto dto = newActivity("UUID", "COMPONENT_UUID", CeActivityDto.Status.FAILED, user); - Ce.Task wsTask = underTest.formatActivity(db.getSession(), dto, null); + Ce.Task wsTask = underTest.formatActivity(db.getSession(), dto, null, emptyList()); assertThat(wsTask.getType()).isEqualTo(CeTaskTypes.REPORT); assertThat(wsTask.getId()).isEqualTo("UUID"); @@ -179,6 +199,8 @@ public class TaskFormatterTest { assertThat(wsTask.getAnalysisId()).isEqualTo("U1"); assertThat(wsTask.getLogs()).isFalse(); assertThat(wsTask.hasScannerContext()).isFalse(); + assertThat(wsTask.getWarningCount()).isEqualTo(warningCount); + assertThat(wsTask.getWarningsList()).isEmpty(); } @Test @@ -186,12 +208,23 @@ public class TaskFormatterTest { CeActivityDto dto = newActivity("UUID", "COMPONENT_UUID", CeActivityDto.Status.FAILED, null); String expected = "scanner context baby!"; - Ce.Task wsTask = underTest.formatActivity(db.getSession(), dto, expected); + Ce.Task wsTask = underTest.formatActivity(db.getSession(), dto, expected, emptyList()); assertThat(wsTask.hasScannerContext()).isTrue(); assertThat(wsTask.getScannerContext()).isEqualTo(expected); } + @Test + public void formatActivity_set_warnings_list_if_argument_is_non_empty_not_checking_consistency_with_warning_count() { + CeActivityDto dto = newActivity("UUID", "COMPONENT_UUID", CeActivityDto.Status.FAILED, null); + String[] warnings = IntStream.range(0, warningCount + 1 + new Random().nextInt(5)).mapToObj(i -> "warning_" + i).toArray(String[]::new); + + Ce.Task wsTask = underTest.formatActivity(db.getSession(), dto, null, Arrays.stream(warnings).collect(toList())); + + assertThat(wsTask.getWarningCount()).isEqualTo(warningCount); + assertThat(wsTask.getWarningsList()).containsExactly(warnings); + } + @Test public void formatActivities() { UserDto user1 = db.users().insertUser(); @@ -251,9 +284,28 @@ public class TaskFormatterTest { .setComponentUuid(componentUuid) .setSubmitterUuid(user == null ? null : user.getUuid()) .setUuid(taskUuid); - return new CeActivityDto(queueDto) + TestActivityDto testActivityDto = new TestActivityDto(queueDto); + testActivityDto.setWarningCount(warningCount); + return testActivityDto .setStatus(status) .setExecutionTimeMs(500L) .setAnalysisUuid("U1"); } + + private class TestActivityDto extends CeActivityDto { + + public TestActivityDto(CeQueueDto queueDto) { + super(queueDto); + } + + @Override + public CeActivityDto setHasScannerContext(boolean hasScannerContext) { + return super.setHasScannerContext(hasScannerContext); + } + + @Override + public CeActivityDto setWarningCount(int warningCount) { + return super.setWarningCount(warningCount); + } + } } diff --git a/sonar-ws/src/main/protobuf/ws-ce.proto b/sonar-ws/src/main/protobuf/ws-ce.proto index 76d8446fcbf..3216ab435e0 100644 --- a/sonar-ws/src/main/protobuf/ws-ce.proto +++ b/sonar-ws/src/main/protobuf/ws-ce.proto @@ -105,6 +105,8 @@ message Task { optional string errorType = 23; optional string pullRequest = 24; optional string pullRequestTitle = 25; + optional int32 warningCount = 26; + repeated string warnings = 27; } enum TaskStatus {