Skip to content

Commit

Permalink
SONAR-10713 Badges can only be used on projects, long living branches…
Browse files Browse the repository at this point in the history
… and applications
  • Loading branch information
julienlancelot authored and SonarTech committed May 28, 2018
1 parent aff59ce commit 0f0c3b7
Show file tree
Hide file tree
Showing 26 changed files with 664 additions and 202 deletions.
Expand Up @@ -34,11 +34,9 @@
import org.sonar.db.measure.LiveMeasureDto; import org.sonar.db.measure.LiveMeasureDto;
import org.sonar.db.metric.MetricDto; import org.sonar.db.metric.MetricDto;
import org.sonar.server.badge.ws.SvgGenerator.Color; import org.sonar.server.badge.ws.SvgGenerator.Color;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating; import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.user.UserSession;


import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format; import static java.lang.String.format;
Expand All @@ -60,7 +58,6 @@
import static org.sonar.api.measures.Metric.Level.ERROR; import static org.sonar.api.measures.Metric.Level.ERROR;
import static org.sonar.api.measures.Metric.Level.OK; import static org.sonar.api.measures.Metric.Level.OK;
import static org.sonar.api.measures.Metric.Level.WARN; import static org.sonar.api.measures.Metric.Level.WARN;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.server.badge.ws.SvgFormatter.formatDuration; import static org.sonar.server.badge.ws.SvgFormatter.formatDuration;
import static org.sonar.server.badge.ws.SvgFormatter.formatNumeric; import static org.sonar.server.badge.ws.SvgFormatter.formatNumeric;
import static org.sonar.server.badge.ws.SvgFormatter.formatPercent; import static org.sonar.server.badge.ws.SvgFormatter.formatPercent;
Expand All @@ -70,16 +67,10 @@
import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.D; import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.D;
import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.E; import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.E;
import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.valueOf; import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.valueOf;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
import static org.sonarqube.ws.MediaTypes.SVG; import static org.sonarqube.ws.MediaTypes.SVG;


public class MeasureAction implements ProjectBadgesWsAction { public class MeasureAction implements ProjectBadgesWsAction {


private static final String PARAM_PROJECT = "project";
private static final String PARAM_BRANCH = "branch";
private static final String PARAM_PULL_REQUEST = "pullRequest";
private static final String PARAM_METRIC = "metric"; private static final String PARAM_METRIC = "metric";


private static final Map<String, String> METRIC_NAME_BY_KEY = ImmutableMap.<String, String>builder() private static final Map<String, String> METRIC_NAME_BY_KEY = ImmutableMap.<String, String>builder()
Expand Down Expand Up @@ -113,15 +104,13 @@ public class MeasureAction implements ProjectBadgesWsAction {
D, Color.RATING_D, D, Color.RATING_D,
E, Color.RATING_E)); E, Color.RATING_E));


private final UserSession userSession;
private final DbClient dbClient; private final DbClient dbClient;
private final ComponentFinder componentFinder; private final ProjectBadgesSupport support;
private final SvgGenerator svgGenerator; private final SvgGenerator svgGenerator;


public MeasureAction(UserSession userSession, DbClient dbClient, ComponentFinder componentFinder, SvgGenerator svgGenerator) { public MeasureAction(DbClient dbClient, ProjectBadgesSupport support, SvgGenerator svgGenerator) {
this.userSession = userSession;
this.dbClient = dbClient; this.dbClient = dbClient;
this.componentFinder = componentFinder; this.support = support;
this.svgGenerator = svgGenerator; this.svgGenerator = svgGenerator;
} }


Expand All @@ -133,18 +122,7 @@ public void define(WebService.NewController controller) {
"Requires 'Browse' permission on the specified project.") "Requires 'Browse' permission on the specified project.")
.setSince("7.1") .setSince("7.1")
.setResponseExample(Resources.getResource(getClass(), "measure-example.svg")); .setResponseExample(Resources.getResource(getClass(), "measure-example.svg"));
action.createParam(PARAM_PROJECT) support.addProjectAndBranchParams(action);
.setDescription("Project key")
.setRequired(true)
.setExampleValue(KEY_PROJECT_EXAMPLE_001);
action
.createParam(PARAM_BRANCH)
.setDescription("Branch key")
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
action
.createParam(PARAM_PULL_REQUEST)
.setDescription("Pull request id")
.setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
action.createParam(PARAM_METRIC) action.createParam(PARAM_METRIC)
.setDescription("Metric key") .setDescription("Metric key")
.setRequired(true) .setRequired(true)
Expand All @@ -156,7 +134,7 @@ public void handle(Request request, Response response) throws Exception {
response.stream().setMediaType(SVG); response.stream().setMediaType(SVG);
String metricKey = request.mandatoryParam(PARAM_METRIC); String metricKey = request.mandatoryParam(PARAM_METRIC);
try (DbSession dbSession = dbClient.openSession(false)) { try (DbSession dbSession = dbClient.openSession(false)) {
ComponentDto project = getProject(dbSession, request); ComponentDto project = support.getComponent(dbSession, request);
MetricDto metric = dbClient.metricDao().selectByKey(dbSession, metricKey); MetricDto metric = dbClient.metricDao().selectByKey(dbSession, metricKey);
checkState(metric != null && metric.isEnabled(), "Metric '%s' hasn't been found", metricKey); checkState(metric != null && metric.isEnabled(), "Metric '%s' hasn't been found", metricKey);
LiveMeasureDto measure = getMeasure(dbSession, project, metricKey); LiveMeasureDto measure = getMeasure(dbSession, project, metricKey);
Expand All @@ -166,19 +144,6 @@ public void handle(Request request, Response response) throws Exception {
} }
} }


private ComponentDto getProject(DbSession dbSession, Request request) {
try {
String projectKey = request.mandatoryParam(PARAM_PROJECT);
String branch = request.param(PARAM_BRANCH);
String pullRequest = request.param(PARAM_PULL_REQUEST);
ComponentDto project = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branch, pullRequest);
userSession.checkComponentPermission(USER, project);
return project;
} catch (NotFoundException e) {
throw new NotFoundException("Component not found");
}
}

private LiveMeasureDto getMeasure(DbSession dbSession, ComponentDto project, String metricKey) { private LiveMeasureDto getMeasure(DbSession dbSession, ComponentDto project, String metricKey) {
return dbClient.liveMeasureDao().selectMeasure(dbSession, project.uuid(), metricKey) return dbClient.liveMeasureDao().selectMeasure(dbSession, project.uuid(), metricKey)
.orElseThrow(() -> new ProjectBadgesException("Measure has not been found")); .orElseThrow(() -> new ProjectBadgesException("Measure has not been found"));
Expand Down Expand Up @@ -218,7 +183,7 @@ private String generateBadge(MetricDto metric, String value, Color color) {


private static <PARAM> PARAM getNonNullValue(LiveMeasureDto measure, Function<LiveMeasureDto, PARAM> function) { private static <PARAM> PARAM getNonNullValue(LiveMeasureDto measure, Function<LiveMeasureDto, PARAM> function) {
PARAM value = function.apply(measure); PARAM value = function.apply(measure);
checkState(value != null, "Measure not found"); checkState(value != null, "Measure has not been found");
return value; return value;
} }


Expand Down
@@ -0,0 +1,96 @@
/*
* SonarQube
* Copyright (C) 2009-2018 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

package org.sonar.server.badge.ws;

import java.util.Optional;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.BranchDto;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.user.UserSession;

import static org.sonar.api.resources.Qualifiers.APP;
import static org.sonar.api.resources.Qualifiers.PROJECT;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.db.component.BranchType.LONG;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;

public class ProjectBadgesSupport {

private static final String PARAM_PROJECT = "project";
private static final String PARAM_BRANCH = "branch";

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

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

void addProjectAndBranchParams(WebService.NewAction action) {
action.createParam(PARAM_PROJECT)
.setDescription("Project or application key")
.setRequired(true)
.setExampleValue(KEY_PROJECT_EXAMPLE_001);
action
.createParam(PARAM_BRANCH)
.setDescription("Long living branch key")
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
}

ComponentDto getComponent(DbSession dbSession, Request request) {
try {
String projectKey = request.mandatoryParam(PARAM_PROJECT);
String branchName = request.param(PARAM_BRANCH);
ComponentDto project = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branchName, null);
checkComponentType(dbSession, project);
userSession.checkComponentPermission(USER, project);
return project;
} catch (NotFoundException e) {
throw new NotFoundException("Project has not been found");
}
}

private void checkComponentType(DbSession dbSession, ComponentDto project) {
Optional<BranchDto> branch = dbClient.branchDao().selectByUuid(dbSession, project.uuid());
if (project.isPrivate()) {
throw generateInvalidProjectExcpetion();
}
if (branch.isPresent() && !branch.get().getBranchType().equals(LONG)) {
throw generateInvalidProjectExcpetion();
}
if (!project.qualifier().equals(PROJECT) && !project.qualifier().equals(APP)) {
throw generateInvalidProjectExcpetion();
}
}

private static ProjectBadgesException generateInvalidProjectExcpetion() {
return new ProjectBadgesException("Project is invalid");
}
}
Expand Up @@ -29,6 +29,7 @@ protected void configureModule() {
ProjectBadgesWs.class, ProjectBadgesWs.class,
QualityGateAction.class, QualityGateAction.class,
MeasureAction.class, MeasureAction.class,
SvgGenerator.class); SvgGenerator.class,
ProjectBadgesSupport.class);
} }
} }
Expand Up @@ -20,7 +20,6 @@
package org.sonar.server.badge.ws; package org.sonar.server.badge.ws;


import com.google.common.io.Resources; import com.google.common.io.Resources;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric.Level; import org.sonar.api.measures.Metric.Level;
import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.Response;
Expand All @@ -30,35 +29,23 @@
import org.sonar.db.DbSession; import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentDto;
import org.sonar.db.measure.LiveMeasureDto; import org.sonar.db.measure.LiveMeasureDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.user.UserSession;


import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.io.IOUtils.write; import static org.apache.commons.io.IOUtils.write;
import static org.sonar.api.web.UserRole.USER; import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
import static org.sonarqube.ws.MediaTypes.SVG; import static org.sonarqube.ws.MediaTypes.SVG;


public class QualityGateAction implements ProjectBadgesWsAction { public class QualityGateAction implements ProjectBadgesWsAction {


private static final String PARAM_PROJECT = "project";
private static final String PARAM_BRANCH = "branch";
private static final String PARAM_PULL_REQUEST = "pullRequest";

private final UserSession userSession;
private final DbClient dbClient; private final DbClient dbClient;
private final ComponentFinder componentFinder; private final ProjectBadgesSupport support;
private final SvgGenerator svgGenerator; private final SvgGenerator svgGenerator;


public QualityGateAction(UserSession userSession, DbClient dbClient, ComponentFinder componentFinder, SvgGenerator svgGenerator) { public QualityGateAction(DbClient dbClient, ProjectBadgesSupport support, SvgGenerator svgGenerator) {
this.userSession = userSession;
this.dbClient = dbClient; this.dbClient = dbClient;
this.componentFinder = componentFinder; this.support = support;
this.svgGenerator = svgGenerator; this.svgGenerator = svgGenerator;
} }


Expand All @@ -70,50 +57,25 @@ public void define(WebService.NewController controller) {
.setDescription("Generate badge for project's quality gate as an SVG.<br/>" + .setDescription("Generate badge for project's quality gate as an SVG.<br/>" +
"Requires 'Browse' permission on the specified project.") "Requires 'Browse' permission on the specified project.")
.setResponseExample(Resources.getResource(getClass(), "quality_gate-example.svg")); .setResponseExample(Resources.getResource(getClass(), "quality_gate-example.svg"));
action.createParam(PARAM_PROJECT) support.addProjectAndBranchParams(action);
.setDescription("Project key")
.setRequired(true)
.setExampleValue(KEY_PROJECT_EXAMPLE_001);
action
.createParam(PARAM_BRANCH)
.setDescription("Branch key")
.setExampleValue(KEY_BRANCH_EXAMPLE_001);
action
.createParam(PARAM_PULL_REQUEST)
.setDescription("Pull request id")
.setExampleValue(KEY_PULL_REQUEST_EXAMPLE_001);
} }


@Override @Override
public void handle(Request request, Response response) throws Exception { public void handle(Request request, Response response) throws Exception {
response.stream().setMediaType(SVG); response.stream().setMediaType(SVG);
try (DbSession dbSession = dbClient.openSession(false)) { try (DbSession dbSession = dbClient.openSession(false)) {
ComponentDto project = getProject(dbSession, request); ComponentDto project = support.getComponent(dbSession, request);
userSession.checkComponentPermission(USER, project);
Level qualityGateStatus = getQualityGate(dbSession, project); Level qualityGateStatus = getQualityGate(dbSession, project);
write(svgGenerator.generateQualityGate(qualityGateStatus), response.stream().output(), UTF_8); write(svgGenerator.generateQualityGate(qualityGateStatus), response.stream().output(), UTF_8);
} catch (ProjectBadgesException | ForbiddenException | NotFoundException e) { } catch (ProjectBadgesException | ForbiddenException | NotFoundException e) {
write(svgGenerator.generateError(e.getMessage()), response.stream().output(), UTF_8); write(svgGenerator.generateError(e.getMessage()), response.stream().output(), UTF_8);
} }
} }


private ComponentDto getProject(DbSession dbSession, Request request) {
try {
String projectKey = request.mandatoryParam(PARAM_PROJECT);
String branch = request.param(PARAM_BRANCH);
String pullRequest = request.param(PARAM_PULL_REQUEST);
ComponentDto project = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branch, pullRequest);
userSession.checkComponentPermission(USER, project);
return project;
} catch (NotFoundException e) {
throw new NotFoundException("Component not found");
}
}

private Level getQualityGate(DbSession dbSession, ComponentDto project) { private Level getQualityGate(DbSession dbSession, ComponentDto project) {
return Level.valueOf(dbClient.liveMeasureDao().selectMeasure(dbSession, project.uuid(), CoreMetrics.ALERT_STATUS_KEY) return Level.valueOf(dbClient.liveMeasureDao().selectMeasure(dbSession, project.uuid(), ALERT_STATUS_KEY)
.map(LiveMeasureDto::getTextValue) .map(LiveMeasureDto::getTextValue)
.orElseThrow(() -> new ProjectBadgesException(format("Quality gate has not been found for project '%s' and branch '%s'", project.getKey(), project.getBranch())))); .orElseThrow(() -> new ProjectBadgesException("Quality gate has not been found")));
} }


} }
Expand Up @@ -175,7 +175,7 @@ PARAMETER_TOTAL_WIDTH, valueOf(MARGIN + computeWidth(error) + MARGIN),


private static int computeWidth(String text) { private static int computeWidth(String text) {
return text.chars() return text.chars()
.mapToObj(i -> (char)i) .mapToObj(i -> (char) i)
.mapToInt(c -> { .mapToInt(c -> {
Integer length = CHAR_LENGTH.get(c); Integer length = CHAR_LENGTH.get(c);
checkState(length != null, "Invalid character '%s'", c); checkState(length != null, "Invalid character '%s'", c);
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0f0c3b7

Please sign in to comment.