Skip to content

Commit

Permalink
SONAR-7152 WS api/qualitygates/project_status search by project id or…
Browse files Browse the repository at this point in the history
… key
  • Loading branch information
teryk committed Feb 2, 2016
1 parent 6518e55 commit a210c65
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 101 deletions.
Expand Up @@ -21,30 +21,42 @@

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.core.util.Uuids;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.measure.MeasureDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.component.ComponentFinder.ParamNames;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.user.UserSession;
import org.sonar.server.ws.KeyExamples;
import org.sonarqube.ws.WsQualityGates.ProjectStatusWsResponse;
import org.sonarqube.ws.client.qualitygate.ProjectStatusWsRequest;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN;
import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
import static org.sonar.server.ws.WsUtils.checkFound;
import static org.sonar.server.ws.WsUtils.checkRequest;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.qualitygate.QualityGatesWsParameters.PARAM_ANALYSIS_ID;
import static org.sonarqube.ws.client.qualitygate.QualityGatesWsParameters.PARAM_PROJECT_ID;
import static org.sonarqube.ws.client.qualitygate.QualityGatesWsParameters.PARAM_PROJECT_KEY;

public class ProjectStatusAction implements QGateWsAction {
private static final String QG_STATUSES_ONE_LINE = Joiner.on(", ")
Expand All @@ -55,30 +67,44 @@ public String apply(@Nonnull ProjectStatusWsResponse.Status input) {
return input.toString();
}
}));
private static final String MSG_ONE_PARAMETER_ONLY = String.format("One (and only one) of the following parameters must be provided '%s', '%s', '%s'",
PARAM_ANALYSIS_ID, PARAM_PROJECT_ID, PARAM_PROJECT_KEY);

private final DbClient dbClient;
private final ComponentFinder componentFinder;
private final UserSession userSession;

public ProjectStatusAction(DbClient dbClient, UserSession userSession) {
public ProjectStatusAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) {
this.dbClient = dbClient;
this.componentFinder = componentFinder;
this.userSession = userSession;
}

@Override
public void define(WebService.NewController controller) {
WebService.NewAction action = controller.createAction("project_status")
.setDescription(String.format("Quality gate status for a given Compute Engine task. <br />" +
"The different statuses returned are: %s. The %s status is returned when there is no Quality Gate associated with the analysis.<br />" +
"Returns a http code 404 if the analysis associated with the task is not found or does not exist.<br />" +
.setDescription(String.format("Get the quality gate status of a project or a Compute Engine task.<br />" +
MSG_ONE_PARAMETER_ONLY + "<br />" +
"The different statuses returned are: %s. The %s status is returned when there is no quality gate associated with the analysis.<br />" +
"Returns an HTTP code 404 if the analysis associated with the task is not found or does not exist.<br />" +
"Requires 'Administer System' or 'Execute Analysis' permission.", QG_STATUSES_ONE_LINE, ProjectStatusWsResponse.Status.NONE))
.setResponseExample(getClass().getResource("project_status-example.json"))
.setSince("5.3")
.setHandler(this);

action.createParam("analysisId")
action.createParam(PARAM_ANALYSIS_ID)
.setDescription("Analysis id")
.setRequired(true)
.setExampleValue("2963");

action.createParam(PARAM_PROJECT_ID)
.setSince("5.4")
.setDescription("Project id")
.setExampleValue(Uuids.UUID_EXAMPLE_01);

action.createParam(PARAM_PROJECT_KEY)
.setSince("5.4")
.setDescription("Project key")
.setExampleValue(KeyExamples.KEY_PROJECT_EXAMPLE_001);
}

@Override
Expand All @@ -90,20 +116,42 @@ public void handle(Request request, Response response) throws Exception {
private ProjectStatusWsResponse doHandle(ProjectStatusWsRequest request) {
DbSession dbSession = dbClient.openSession(false);
try {
String snapshotId = request.getAnalysisId();
SnapshotDto snapshotDto = getSnapshot(dbSession, snapshotId);
ComponentDto projectDto = dbClient.componentDao().selectOrFailById(dbSession, snapshotDto.getComponentId());
checkPermission(projectDto.uuid());
String measureData = getQualityGateDetailsMeasureData(dbSession, snapshotDto);
ProjectAndSnapshot projectAndSnapshot = getProjectAndSnapshot(dbSession, request);
checkPermission(projectAndSnapshot.project.uuid());
Optional<String> measureData = getQualityGateDetailsMeasureData(dbSession, projectAndSnapshot.snapshotDto);

return ProjectStatusWsResponse.newBuilder()
.setProjectStatus(new QualityGateDetailsFormatter(measureData, snapshotDto).format())
.setProjectStatus(new QualityGateDetailsFormatter(measureData, projectAndSnapshot.snapshotDto).format())
.build();
} finally {
dbClient.closeSession(dbSession);
}
}

private ProjectAndSnapshot getProjectAndSnapshot(DbSession dbSession, ProjectStatusWsRequest request) {
String snapshotId = request.getAnalysisId();
if (!isNullOrEmpty(request.getAnalysisId())) {
return getSnapshotThenProject(dbSession, snapshotId);
} else if (!isNullOrEmpty(request.getProjectId()) ^ !isNullOrEmpty(request.getProjectKey())) {
return getProjectThenSnapshot(dbSession, request);
}

throw new BadRequestException(MSG_ONE_PARAMETER_ONLY);
}

private ProjectAndSnapshot getProjectThenSnapshot(DbSession dbSession, ProjectStatusWsRequest request) {
ComponentDto projectDto = componentFinder.getByUuidOrKey(dbSession, request.getProjectId(), request.getProjectKey(), ParamNames.PROJECT_ID_AND_KEY);
SnapshotDto snapshotDto = dbClient.snapshotDao().selectLastSnapshotByComponentId(dbSession, projectDto.getId());
checkState(snapshotDto != null, "Last analysis of project '%s' not found", projectDto.getKey());
return new ProjectAndSnapshot(projectDto, snapshotDto);
}

private ProjectAndSnapshot getSnapshotThenProject(DbSession dbSession, String snapshotId) {
SnapshotDto snapshotDto = getSnapshot(dbSession, snapshotId);
ComponentDto projectDto = dbClient.componentDao().selectOrFailById(dbSession, snapshotDto.getComponentId());
return new ProjectAndSnapshot(projectDto, snapshotDto);
}

private SnapshotDto getSnapshot(DbSession dbSession, String snapshotIdFromRequest) {
Long snapshotId = null;
try {
Expand All @@ -120,19 +168,30 @@ private SnapshotDto getSnapshot(DbSession dbSession, String snapshotIdFromReques
return checkFound(snapshotDto, "Analysis with id '%s' is not found", snapshotIdFromRequest);
}

@CheckForNull
private String getQualityGateDetailsMeasureData(DbSession dbSession, SnapshotDto snapshotDto) {
List<MeasureDto> measures = dbClient.measureDao().selectBySnapshotIdAndMetricKeys(snapshotDto.getId(),
private Optional<String> getQualityGateDetailsMeasureData(DbSession dbSession, Optional<SnapshotDto> snapshotDto) {
if (!snapshotDto.isPresent()) {
return Optional.absent();
}

List<MeasureDto> measures = dbClient.measureDao().selectBySnapshotIdAndMetricKeys(snapshotDto.get().getId(),
Collections.singleton(CoreMetrics.QUALITY_GATE_DETAILS_KEY), dbSession);

return measures.isEmpty()
? null
: measures.get(0).getData();
? Optional.<String>absent()
: Optional.fromNullable(measures.get(0).getData());
}

private static ProjectStatusWsRequest toProjectStatusWsRequest(Request request) {
return new ProjectStatusWsRequest()
.setAnalysisId(request.mandatoryParam("analysisId"));
ProjectStatusWsRequest projectStatusWsRequest = new ProjectStatusWsRequest()
.setAnalysisId(request.param(PARAM_ANALYSIS_ID))
.setProjectId(request.param(PARAM_PROJECT_ID))
.setProjectKey(request.param(PARAM_PROJECT_KEY));
checkRequest(
!isNullOrEmpty(projectStatusWsRequest.getAnalysisId())
^ !isNullOrEmpty(projectStatusWsRequest.getProjectId())
^ !isNullOrEmpty(projectStatusWsRequest.getProjectKey()),
MSG_ONE_PARAMETER_ONLY);
return projectStatusWsRequest;
}

private void checkPermission(String projectUuid) {
Expand All @@ -141,4 +200,14 @@ private void checkPermission(String projectUuid) {
throw insufficientPrivilegesException();
}
}

private static class ProjectAndSnapshot {
private final ComponentDto project;
private final Optional<SnapshotDto> snapshotDto;

private ProjectAndSnapshot(ComponentDto project, @Nullable SnapshotDto snapshotDto) {
this.project = project;
this.snapshotDto = Optional.fromNullable(snapshotDto);
}
}
}
Expand Up @@ -19,11 +19,11 @@
*/
package org.sonar.server.qualitygate.ws;

import com.google.common.base.Optional;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.db.component.SnapshotDto;
import org.sonarqube.ws.WsQualityGates.ProjectStatusWsResponse;
Expand All @@ -32,24 +32,23 @@
import static org.sonar.api.utils.DateUtils.formatDateTime;

public class QualityGateDetailsFormatter {
@CheckForNull
private final String measureData;
private final SnapshotDto snapshot;
private final Optional<String> optionalMeasureData;
private final Optional<SnapshotDto> optionalSnapshot;
private final ProjectStatusWsResponse.ProjectStatus.Builder projectStatusBuilder;

public QualityGateDetailsFormatter(@Nullable String measureData, SnapshotDto snapshot) {
this.measureData = measureData;
this.snapshot = snapshot;
public QualityGateDetailsFormatter(Optional<String> measureData, Optional<SnapshotDto> snapshot) {
this.optionalMeasureData = measureData;
this.optionalSnapshot = snapshot;
this.projectStatusBuilder = ProjectStatusWsResponse.ProjectStatus.newBuilder();
}

public ProjectStatusWsResponse.ProjectStatus format() {
if (isNullOrEmpty(measureData)) {
if (!optionalMeasureData.isPresent()) {
return newResponseWithoutQualityGateDetails();
}

JsonParser parser = new JsonParser();
JsonObject json = parser.parse(measureData).getAsJsonObject();
JsonObject json = parser.parse(optionalMeasureData.get()).getAsJsonObject();

ProjectStatusWsResponse.Status qualityGateStatus = measureLevelToQualityGateStatus(json.get("level").getAsString());
projectStatusBuilder.setStatus(qualityGateStatus);
Expand All @@ -61,11 +60,16 @@ public ProjectStatusWsResponse.ProjectStatus format() {
}

private void formatPeriods() {
if (!optionalSnapshot.isPresent()) {
return;
}

ProjectStatusWsResponse.Period.Builder periodBuilder = ProjectStatusWsResponse.Period.newBuilder();
for (int i = 1; i <= 5; i++) {
periodBuilder.clear();
boolean doesPeriodExist = false;

SnapshotDto snapshot = this.optionalSnapshot.get();
if (!isNullOrEmpty(snapshot.getPeriodMode(i))) {
doesPeriodExist = true;
periodBuilder.setIndex(i);
Expand Down Expand Up @@ -97,44 +101,66 @@ private void formatConditions(@Nullable JsonArray jsonConditions) {
private void formatCondition(JsonObject jsonCondition) {
ProjectStatusWsResponse.Condition.Builder conditionBuilder = ProjectStatusWsResponse.Condition.newBuilder();

JsonElement measureLevel = jsonCondition.get("level");
if (measureLevel != null && !isNullOrEmpty(measureLevel.getAsString())) {
conditionBuilder.setStatus(measureLevelToQualityGateStatus(measureLevel.getAsString()));
formatConditionLevel(conditionBuilder, jsonCondition);
formatConditionMetric(conditionBuilder, jsonCondition);
formatConditionOperation(conditionBuilder, jsonCondition);
formatConditionPeriod(conditionBuilder, jsonCondition);
formatConditionWarningThreshold(conditionBuilder, jsonCondition);
formatConditionErrorThreshold(conditionBuilder, jsonCondition);
formatConditionActual(conditionBuilder, jsonCondition);

projectStatusBuilder.addConditions(conditionBuilder);
}

private static void formatConditionActual(ProjectStatusWsResponse.Condition.Builder conditionBuilder, JsonObject jsonCondition) {
JsonElement actual = jsonCondition.get("actual");
if (actual != null && !isNullOrEmpty(actual.getAsString())) {
conditionBuilder.setActualValue(actual.getAsString());
}
}

JsonElement metric = jsonCondition.get("metric");
if (metric != null && !isNullOrEmpty(metric.getAsString())) {
conditionBuilder.setMetricKey(metric.getAsString());
private static void formatConditionErrorThreshold(ProjectStatusWsResponse.Condition.Builder conditionBuilder, JsonObject jsonCondition) {
JsonElement error = jsonCondition.get("error");
if (error != null && !isNullOrEmpty(error.getAsString())) {
conditionBuilder.setErrorThreshold(error.getAsString());
}
}

JsonElement op = jsonCondition.get("op");
if (op != null && !isNullOrEmpty(op.getAsString())) {
String stringOp = op.getAsString();
ProjectStatusWsResponse.Comparator comparator = measureOpToQualityGateComparator(stringOp);
conditionBuilder.setComparator(comparator);
private static void formatConditionWarningThreshold(ProjectStatusWsResponse.Condition.Builder conditionBuilder, JsonObject jsonCondition) {
JsonElement warning = jsonCondition.get("warning");
if (warning != null && !isNullOrEmpty(warning.getAsString())) {
conditionBuilder.setWarningThreshold(warning.getAsString());
}
}

private static void formatConditionPeriod(ProjectStatusWsResponse.Condition.Builder conditionBuilder, JsonObject jsonCondition) {
JsonElement periodIndex = jsonCondition.get("period");
if (periodIndex != null && !isNullOrEmpty(periodIndex.getAsString())) {
conditionBuilder.setPeriodIndex(periodIndex.getAsInt());
}
}

JsonElement warning = jsonCondition.get("warning");
if (warning != null && !isNullOrEmpty(warning.getAsString())) {
conditionBuilder.setWarningThreshold(warning.getAsString());
private static void formatConditionOperation(ProjectStatusWsResponse.Condition.Builder conditionBuilder, JsonObject jsonCondition) {
JsonElement op = jsonCondition.get("op");
if (op != null && !isNullOrEmpty(op.getAsString())) {
String stringOp = op.getAsString();
ProjectStatusWsResponse.Comparator comparator = measureOpToQualityGateComparator(stringOp);
conditionBuilder.setComparator(comparator);
}
}

JsonElement error = jsonCondition.get("error");
if (error != null && !isNullOrEmpty(error.getAsString())) {
conditionBuilder.setErrorThreshold(error.getAsString());
private static void formatConditionMetric(ProjectStatusWsResponse.Condition.Builder conditionBuilder, JsonObject jsonCondition) {
JsonElement metric = jsonCondition.get("metric");
if (metric != null && !isNullOrEmpty(metric.getAsString())) {
conditionBuilder.setMetricKey(metric.getAsString());
}
}

JsonElement actual = jsonCondition.get("actual");
if (actual != null && !isNullOrEmpty(actual.getAsString())) {
conditionBuilder.setActualValue(actual.getAsString());
private static void formatConditionLevel(ProjectStatusWsResponse.Condition.Builder conditionBuilder, JsonObject jsonCondition) {
JsonElement measureLevel = jsonCondition.get("level");
if (measureLevel != null && !isNullOrEmpty(measureLevel.getAsString())) {
conditionBuilder.setStatus(measureLevelToQualityGateStatus(measureLevel.getAsString()));
}

projectStatusBuilder.addConditions(conditionBuilder);
}

private static ProjectStatusWsResponse.Status measureLevelToQualityGateStatus(String measureLevel) {
Expand Down

0 comments on commit a210c65

Please sign in to comment.