import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import static org.apache.commons.lang.StringUtils.defaultString;
import static org.sonar.api.utils.DateUtils.parseEndingDateOrDateTime;
import static org.sonar.api.utils.DateUtils.parseStartingDateOrDateTime;
+import static org.sonar.core.util.stream.MoreCollectors.toList;
import static org.sonar.db.Pagination.forPage;
+import static org.sonar.server.ws.WsUtils.checkFoundWithOptional;
import static org.sonar.server.ws.WsUtils.checkRequest;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.ce.CeWsParameters.PARAM_COMPONENT_ID;
public class ActivityAction implements CeWsAction {
private static final int MAX_PAGE_SIZE = 1000;
- private static final List<String> POSSIBLE_QUALIFIERS = ImmutableList.of(Qualifiers.PROJECT, Qualifiers.APP, Qualifiers.VIEW, "DEV", Qualifiers.MODULE);
+ private static final String[] POSSIBLE_QUALIFIERS = new String[] {Qualifiers.PROJECT, Qualifiers.APP, Qualifiers.VIEW, "DEV", Qualifiers.MODULE};
private final UserSession userSession;
private final DbClient dbClient;
WebService.NewAction action = controller.createAction("activity")
.setDescription(format("Search for tasks.<br> " +
"Requires the system administration permission, " +
- "or project administration permission if %s is set.<br/>" +
- "Since 5.5, it's no more possible to specify the page parameter.<br/>" +
- "Since 6.1, field \"logs\" is deprecated and its value is always false.", PARAM_COMPONENT_ID))
+ "or project administration permission if %s is set.", PARAM_COMPONENT_ID))
.setResponseExample(getClass().getResource("activity-example.json"))
.setHandler(this)
+ .setChangelog(
+ new Change("5.5", "it's no more possible to specify the page parameter.<"),
+ new Change("6.1", "field \"logs\" is deprecated and its value is always false"),
+ new Change("6.6", "field \"incremental\" is added"))
.setSince("5.2");
action.createParam(PARAM_COMPONENT_ID)
}
private ActivityResponse doHandle(ActivityWsRequest request) {
- checkPermission(request);
try (DbSession dbSession = dbClient.openSession(false)) {
+ ComponentDto component = loadComponent(dbSession, request);
+ checkPermission(component);
// if a task searched by uuid is found all other parameters are ignored
Optional<WsCe.Task> taskSearchedById = searchTaskByUuid(dbSession, request);
if (taskSearchedById.isPresent()) {
request.getPageSize());
}
- CeTaskQuery query = buildQuery(dbSession, request);
- Iterable<WsCe.Task> queuedTasks = loadQueuedTasks(dbSession, request, query);
- Iterable<WsCe.Task> pastTasks = loadPastTasks(dbSession, request, query);
-
+ CeTaskQuery query = buildQuery(dbSession, request, component);
+ List<WsCe.Task> queuedTasks = loadQueuedTasks(dbSession, request, query);
+ List<WsCe.Task> pastTasks = loadPastTasks(dbSession, request, query);
return buildResponse(
queuedTasks,
pastTasks,
}
}
- private void checkPermission(ActivityWsRequest request) {
+ @CheckForNull
+ private ComponentDto loadComponent(DbSession dbSession, ActivityWsRequest request) {
+ String componentId = request.getComponentId();
+ if (componentId == null) {
+ return null;
+ }
+ return checkFoundWithOptional(dbClient.componentDao().selectByUuid(dbSession, componentId), "Component '%s' does not exist", componentId);
+ }
+
+ private void checkPermission(@Nullable ComponentDto component) {
// fail fast if not logged in
userSession.checkLoggedIn();
- if (request.getComponentId() == null) {
+ if (component == null) {
userSession.checkIsSystemAdministrator();
} else {
- userSession.checkComponentUuidPermission(UserRole.ADMIN, request.getComponentId());
+ userSession.checkComponentPermission(UserRole.ADMIN, component);
}
}
}
java.util.Optional<CeActivityDto> activity = dbClient.ceActivityDao().selectByUuid(dbSession, textQuery);
- if (activity.isPresent()) {
- return Optional.of(formatter.formatActivity(dbSession, activity.get()));
- }
+ return activity.map(ceActivityDto -> Optional.of(formatter.formatActivity(dbSession, ceActivityDto))).orElseGet(Optional::absent);
- return Optional.absent();
}
- private CeTaskQuery buildQuery(DbSession dbSession, ActivityWsRequest request) {
+ private CeTaskQuery buildQuery(DbSession dbSession, ActivityWsRequest request, @Nullable ComponentDto component) {
CeTaskQuery query = new CeTaskQuery();
query.setType(request.getType());
query.setOnlyCurrents(request.getOnlyCurrents());
query.setStatuses(request.getStatus());
}
- query.setComponentUuids(loadComponentUuids(dbSession, request));
+ query.setComponentUuids(component == null ? loadComponentUuids(dbSession, request).stream().map(ComponentDto::uuid).collect(toList()) : singletonList(component.uuid()));
return query;
}
- @CheckForNull
- private List<String> loadComponentUuids(DbSession dbSession, ActivityWsRequest request) {
- String componentUuid = request.getComponentId();
+ private List<ComponentDto> loadComponentUuids(DbSession dbSession, ActivityWsRequest request) {
String componentQuery = request.getQuery();
-
- if (componentUuid != null) {
- return singletonList(componentUuid);
- }
- if (componentQuery != null) {
- ComponentQuery componentDtoQuery = ComponentQuery.builder()
- .setNameOrKeyQuery(componentQuery)
- .setQualifiers(POSSIBLE_QUALIFIERS.toArray(new String[0]))
- .build();
- List<ComponentDto> componentDtos = dbClient.componentDao().selectByQuery(dbSession, componentDtoQuery, 0, CeTaskQuery.MAX_COMPONENT_UUIDS);
- return Lists.transform(componentDtos, ComponentDto::uuid);
- }
-
- return null;
+ ComponentQuery componentDtoQuery = ComponentQuery.builder()
+ .setNameOrKeyQuery(componentQuery)
+ .setQualifiers(POSSIBLE_QUALIFIERS)
+ .build();
+ return dbClient.componentDao().selectByQuery(dbSession, componentDtoQuery, 0, CeTaskQuery.MAX_COMPONENT_UUIDS);
}
private List<WsCe.Task> loadQueuedTasks(DbSession dbSession, ActivityWsRequest request, CeTaskQuery query) {
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.sonar.db.ce.CeActivityDto;
import org.sonar.db.ce.CeQueueDto;
import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonarqube.ws.WsCe;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonList;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.core.util.Protobuf.setNullable;
return dtos.stream().map(input -> formatQueue(input, cache)).collect(MoreCollectors.toList(dtos.size()));
}
- public WsCe.Task formatQueue(DbSession dbSession, CeQueueDto dto) {
- return formatQueue(dto, ComponentDtoCache.forUuid(dbClient, dbSession, dto.getComponentUuid()));
+ public WsCe.Task formatQueue(DbSession dbSession, CeQueueDto queue) {
+ return formatQueue(queue, ComponentDtoCache.forQueueDtos(dbClient, dbSession, singletonList(queue)));
}
public WsCe.Task formatQueue(DbSession dbSession, CeQueueDto dto, Optional<ComponentDto> component) {
return builder.build();
}
- public WsCe.Task formatActivity(DbSession dbSession, CeActivityDto dto) {
- return formatActivity(dto, ComponentDtoCache.forUuid(dbClient, dbSession, dto.getComponentUuid()), null);
+ public WsCe.Task formatActivity(DbSession dbSession, CeActivityDto activity) {
+ return formatActivity(activity, ComponentDtoCache.forActivityDtos(dbClient, dbSession, singletonList(activity)), null);
}
public WsCe.Task formatActivity(DbSession dbSession, CeActivityDto dto, Optional<ComponentDto> component,
builder.setStatus(WsCe.TaskStatus.valueOf(dto.getStatus().name()));
builder.setType(dto.getTaskType());
builder.setLogs(false);
- if (dto.getComponentUuid() != null) {
- builder.setComponentId(dto.getComponentUuid());
- buildComponent(builder, componentDtoCache.getComponent(dto.getComponentUuid()));
+ setNullable(dto.getComponentUuid(), uuid -> buildComponent(builder, componentDtoCache.getComponent(uuid)).setComponentId(uuid));
+ String analysisUuid = dto.getAnalysisUuid();
+ if (analysisUuid != null) {
+ builder.setAnalysisId(analysisUuid);
+ setNullable(componentDtoCache.getAnalysis(analysisUuid), analysis -> builder.setIncremental(analysis.getIncremental()));
}
- setNullable(dto.getAnalysisUuid(), builder::setAnalysisId);
+ setNullable(analysisUuid, builder::setAnalysisId);
setNullable(dto.getSubmitterLogin(), builder::setSubmitterLogin);
builder.setSubmittedAt(formatDateTime(new Date(dto.getSubmittedAt())));
setNullable(dto.getStartedAt(), builder::setStartedAt, DateUtils::formatDateTime);
return builder.build();
}
- private static void buildComponent(WsCe.Task.Builder builder, @Nullable ComponentDto componentDto) {
+ private static WsCe.Task.Builder buildComponent(WsCe.Task.Builder builder, @Nullable ComponentDto componentDto) {
if (componentDto != null) {
builder.setComponentKey(componentDto.getDbKey());
builder.setComponentName(componentDto.name());
builder.setComponentQualifier(componentDto.qualifier());
}
+ return builder;
}
private static class ComponentDtoCache {
private final Map<String, ComponentDto> componentsByUuid;
private final Map<String, OrganizationDto> organizationsByUuid;
+ private final Map<String, SnapshotDto> analysisByUuid;
- private ComponentDtoCache(Map<String, ComponentDto> componentsByUuid, Map<String, OrganizationDto> organizationsByUuid) {
+ private ComponentDtoCache(Map<String, ComponentDto> componentsByUuid, Map<String, OrganizationDto> organizationsByUuid, Map<String, SnapshotDto> analysisByUuid) {
this.componentsByUuid = componentsByUuid;
this.organizationsByUuid = organizationsByUuid;
+ this.analysisByUuid = analysisByUuid;
}
static ComponentDtoCache forQueueDtos(DbClient dbClient, DbSession dbSession, Collection<CeQueueDto> ceQueueDtos) {
Map<String, ComponentDto> componentsByUuid = dbClient.componentDao().selectByUuids(dbSession, uuidOfCeQueueDtos(ceQueueDtos))
.stream()
.collect(MoreCollectors.uniqueIndex(ComponentDto::uuid));
- return new ComponentDtoCache(componentsByUuid, buildOrganizationsByUuid(dbClient, dbSession, componentsByUuid));
+ return new ComponentDtoCache(componentsByUuid, buildOrganizationsByUuid(dbClient, dbSession, componentsByUuid), Collections.emptyMap());
}
private static Set<String> uuidOfCeQueueDtos(Collection<CeQueueDto> ceQueueDtos) {
uuidOfCeActivityDtos(ceActivityDtos))
.stream()
.collect(MoreCollectors.uniqueIndex(ComponentDto::uuid));
- return new ComponentDtoCache(componentsByUuid, buildOrganizationsByUuid(dbClient, dbSession, componentsByUuid));
+ Set<String> analysisUuids = ceActivityDtos.stream().map(CeActivityDto::getAnalysisUuid).filter(Objects::nonNull).collect(MoreCollectors.toSet());
+ Map<String, SnapshotDto> analysisByUuid = dbClient.snapshotDao().selectByUuids(dbSession, analysisUuids).stream().collect(MoreCollectors.uniqueIndex(SnapshotDto::getUuid));
+ return new ComponentDtoCache(componentsByUuid, buildOrganizationsByUuid(dbClient, dbSession, componentsByUuid), analysisByUuid);
}
private static Set<String> uuidOfCeActivityDtos(Collection<CeActivityDto> ceActivityDtos) {
.collect(MoreCollectors.toSet(ceActivityDtos.size()));
}
- static ComponentDtoCache forUuid(DbClient dbClient, DbSession dbSession, String uuid) {
- Optional<ComponentDto> component = dbClient.componentDao().selectByUuid(dbSession, uuid);
- return forComponentDto(dbClient, dbSession, component);
- }
-
static ComponentDtoCache forComponentDto(DbClient dbClient, DbSession dbSession, Optional<ComponentDto> component) {
Map<String, ComponentDto> componentsByUuid = component.isPresent() ? ImmutableMap.of(component.get().uuid(), component.get()) : emptyMap();
- return new ComponentDtoCache(componentsByUuid, buildOrganizationsByUuid(dbClient, dbSession, componentsByUuid));
+ return new ComponentDtoCache(componentsByUuid, buildOrganizationsByUuid(dbClient, dbSession, componentsByUuid), Collections.emptyMap());
}
private static Map<String, OrganizationDto> buildOrganizationsByUuid(DbClient dbClient, DbSession dbSession, Map<String, ComponentDto> componentsByUuid) {
checkState(organizationDto != null, "Organization with uuid '%s' not found", organizationUuid);
return organizationDto.getKey();
}
+
+ @CheckForNull
+ SnapshotDto getAnalysis(String analysisUuid) {
+ return analysisByUuid.get(analysisUuid);
+ }
}
/**
import java.io.IOException;
import java.util.Date;
import java.util.List;
+import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.Mockito.mock;
import static org.sonar.api.utils.DateUtils.formatDate;
import static org.sonar.api.utils.DateUtils.formatDateTime;
ComponentDto project2 = db.components().insertPrivateProject(org2);
SnapshotDto analysisProject1 = db.components().insertSnapshot(project1);
insertActivity("T1", project1, SUCCESS, analysisProject1);
- SnapshotDto analysisProject2 = db.components().insertSnapshot(project2);
- insertActivity("T2", project2, FAILED, analysisProject2);
+ insertActivity("T2", project2, FAILED, null);
ActivityResponse activityResponse = call(ws.newRequest()
.setParam(PARAM_MAX_EXECUTED_AT, formatDateTime(EXECUTED_AT + 2_000)));
assertThat(task.getId()).isEqualTo("T2");
assertThat(task.getStatus()).isEqualTo(WsCe.TaskStatus.FAILED);
assertThat(task.getComponentId()).isEqualTo(project2.uuid());
- assertThat(task.getAnalysisId()).isEqualTo(analysisProject2.getUuid());
+ assertThat(task.hasAnalysisId()).isFalse();
assertThat(task.getExecutionTimeMs()).isEqualTo(500L);
assertThat(task.getLogs()).isFalse();
+ assertThat(task.getIncremental()).isFalse();
+
task = activityResponse.getTasks(1);
assertThat(task.getId()).isEqualTo("T1");
assertThat(task.getStatus()).isEqualTo(WsCe.TaskStatus.SUCCESS);
assertThat(task.getComponentId()).isEqualTo(project1.uuid());
assertThat(task.getLogs()).isFalse();
assertThat(task.getOrganization()).isEqualTo(org1.getKey());
+ assertThat(task.getIncremental()).isFalse();
}
@Test
assertPage(10, asList("T3", "T2", "T1"));
}
- @Test
- public void throws_IAE_if_pageSize_is_0() {
- logInAsSystemAdministrator();
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("page size must be >= 1");
-
- call(ws.newRequest()
- .setParam(Param.PAGE_SIZE, Integer.toString(0))
- .setParam(PARAM_STATUS, "SUCCESS,FAILED,CANCELED,IN_PROGRESS,PENDING"));
- }
-
- private void assertPage(int pageSize, List<String> expectedOrderedTaskIds) {
- ActivityResponse activityResponse = call(ws.newRequest()
- .setParam(Param.PAGE_SIZE, Integer.toString(pageSize))
- .setParam(PARAM_STATUS, "SUCCESS,FAILED,CANCELED,IN_PROGRESS,PENDING"));
-
- assertThat(activityResponse.getTasksCount()).isEqualTo(expectedOrderedTaskIds.size());
- for (int i = 0; i < expectedOrderedTaskIds.size(); i++) {
- String expectedTaskId = expectedOrderedTaskIds.get(i);
- assertThat(activityResponse.getTasks(i).getId()).isEqualTo(expectedTaskId);
- }
- }
-
@Test
public void project_administrator_can_access_his_project_activity() {
ComponentDto project1 = db.components().insertPrivateProject();
assertThat(result.getTasksCount()).isEqualTo(2);
}
+ @Test
+ public void incremental_analysis_on_single_project() {
+ ComponentDto project = db.components().insertPrivateProject();
+ SnapshotDto incrementalAnalysis = db.components().insertSnapshot(project, s -> s.setIncremental(true));
+ insertActivity("T1", project, SUCCESS, incrementalAnalysis);
+ userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
+
+ ActivityResponse activityResponse = call(ws.newRequest()
+ .setParam(PARAM_COMPONENT_ID, project.uuid()));
+
+ assertThat(activityResponse.getTasksList())
+ .extracting(Task::getId, Task::getIncremental)
+ .containsExactlyInAnyOrder(tuple("T1", true));
+ }
+
+ @Test
+ public void incremental_analysis_on_search_text() {
+ ComponentDto project = db.components().insertPrivateProject();
+ SnapshotDto incrementalAnalysis = db.components().insertSnapshot(project, s -> s.setIncremental(true));
+ SnapshotDto standardAnalysis = db.components().insertSnapshot(project, s -> s.setIncremental(false));
+ insertActivity("T1", project, SUCCESS, incrementalAnalysis);
+ insertActivity("T2", project, SUCCESS, standardAnalysis);
+ logInAsSystemAdministrator();
+
+ ActivityResponse activityResponse = call(ws.newRequest()
+ .setParam(PARAM_COMPONENT_QUERY, project.name()));
+
+ assertThat(activityResponse.getTasksList())
+ .extracting(Task::getId, Task::getIncremental)
+ .containsExactlyInAnyOrder(
+ tuple("T1", true),
+ tuple("T2", false));
+ }
+
+ @Test
+ public void incremental_analysis_on_search_uuid() {
+ ComponentDto project = db.components().insertPrivateProject();
+ SnapshotDto incrementalAnalysis = db.components().insertSnapshot(project, s -> s.setIncremental(true));
+ insertActivity("T1", project, SUCCESS, incrementalAnalysis);
+ logInAsSystemAdministrator();
+
+ ActivityResponse activityResponse = call(ws.newRequest()
+ .setParam(PARAM_COMPONENT_QUERY, "T1"));
+
+ assertThat(activityResponse.getTasksList())
+ .extracting(Task::getId, Task::getIncremental)
+ .containsExactlyInAnyOrder(tuple("T1", true));
+ }
+
@Test
public void fail_if_both_filters_on_component_id_and_name() {
expectedException.expect(BadRequestException.class);
.execute();
}
+ @Test
+ public void throws_IAE_if_pageSize_is_0() {
+ logInAsSystemAdministrator();
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("page size must be >= 1");
+
+ call(ws.newRequest()
+ .setParam(Param.PAGE_SIZE, Integer.toString(0))
+ .setParam(PARAM_STATUS, "SUCCESS,FAILED,CANCELED,IN_PROGRESS,PENDING"));
+ }
+
+ @Test
+ public void fail_when_project_does_not_exist() {
+ logInAsSystemAdministrator();
+
+ expectedException.expect(NotFoundException.class);
+ expectedException.expectMessage("Component 'unknown' does not exist");
+
+ ws.newRequest()
+ .setParam(PARAM_COMPONENT_ID, "unknown")
+ .execute();
+ }
+
+ private void assertPage(int pageSize, List<String> expectedOrderedTaskIds) {
+ ActivityResponse activityResponse = call(ws.newRequest()
+ .setParam(Param.PAGE_SIZE, Integer.toString(pageSize))
+ .setParam(PARAM_STATUS, "SUCCESS,FAILED,CANCELED,IN_PROGRESS,PENDING"));
+
+ assertThat(activityResponse.getTasksCount()).isEqualTo(expectedOrderedTaskIds.size());
+ for (int i = 0; i < expectedOrderedTaskIds.size(); i++) {
+ String expectedTaskId = expectedOrderedTaskIds.get(i);
+ assertThat(activityResponse.getTasks(i).getId()).isEqualTo(expectedTaskId);
+ }
+ }
+
@Test
public void support_json_response() {
logInAsSystemAdministrator();
return insertActivity(taskUuid, project, status, db.components().insertSnapshot(project));
}
- private CeActivityDto insertActivity(String taskUuid, ComponentDto project, Status status, SnapshotDto analysis) {
+ private CeActivityDto insertActivity(String taskUuid, ComponentDto project, Status status, @Nullable SnapshotDto analysis) {
CeQueueDto queueDto = new CeQueueDto();
queueDto.setTaskType(CeTaskTypes.REPORT);
queueDto.setComponentUuid(project.uuid());
activityDto.setStatus(status);
activityDto.setExecutionTimeMs(500L);
activityDto.setExecutedAt(EXECUTED_AT);
- activityDto.setAnalysisUuid(analysis.getUuid());
+ activityDto.setAnalysisUuid(analysis == null ? null : analysis.getUuid());
db.getDbClient().ceActivityDao(). insert(db.getSession(), activityDto);
db.commit();
return activityDto;
}
message Task {
- optional string organization = 20;
optional string id = 1;
optional string type = 2;
optional string componentId = 3;
optional string errorStacktrace = 17;
optional string scannerContext = 18;
optional bool hasScannerContext = 19;
+ optional string organization = 20;
+ optional bool incremental = 21;
}
enum TaskStatus {