@@ -154,13 +154,9 @@ public class MeasureAction implements ProjectBadgesWsAction { | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
response.stream().setMediaType(SVG); | |||
String projectKey = request.mandatoryParam(PARAM_PROJECT); | |||
String branch = request.param(PARAM_BRANCH); | |||
String pullRequest = request.param(PARAM_PULL_REQUEST); | |||
String metricKey = request.mandatoryParam(PARAM_METRIC); | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
ComponentDto project = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branch, pullRequest); | |||
userSession.checkComponentPermission(USER, project); | |||
ComponentDto project = getProject(dbSession, request); | |||
MetricDto metric = dbClient.metricDao().selectByKey(dbSession, metricKey); | |||
checkState(metric != null && metric.isEnabled(), "Metric '%s' hasn't been found", metricKey); | |||
LiveMeasureDto measure = getMeasure(dbSession, project, metricKey); | |||
@@ -170,9 +166,22 @@ public class MeasureAction implements ProjectBadgesWsAction { | |||
} | |||
} | |||
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) { | |||
return dbClient.liveMeasureDao().selectMeasure(dbSession, project.uuid(), metricKey) | |||
.orElseThrow(() -> new ProjectBadgesException(format("Measure '%s' has not been found for project '%s' and branch '%s'", metricKey, project.getKey(), project.getBranch()))); | |||
.orElseThrow(() -> new ProjectBadgesException("Measure has not been found")); | |||
} | |||
private String generateSvg(MetricDto metric, LiveMeasureDto measure) { |
@@ -87,11 +87,8 @@ public class QualityGateAction implements ProjectBadgesWsAction { | |||
@Override | |||
public void handle(Request request, Response response) throws Exception { | |||
response.stream().setMediaType(SVG); | |||
String projectKey = request.mandatoryParam(PARAM_PROJECT); | |||
String branch = request.param(PARAM_BRANCH); | |||
String pullRequest = request.param(PARAM_PULL_REQUEST); | |||
try (DbSession dbSession = dbClient.openSession(false)) { | |||
ComponentDto project = componentFinder.getByKeyAndOptionalBranchOrPullRequest(dbSession, projectKey, branch, pullRequest); | |||
ComponentDto project = getProject(dbSession, request); | |||
userSession.checkComponentPermission(USER, project); | |||
Level qualityGateStatus = getQualityGate(dbSession, project); | |||
write(svgGenerator.generateQualityGate(qualityGateStatus), response.stream().output(), UTF_8); | |||
@@ -100,6 +97,19 @@ public class QualityGateAction implements ProjectBadgesWsAction { | |||
} | |||
} | |||
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) { | |||
return Level.valueOf(dbClient.liveMeasureDao().selectMeasure(dbSession, project.uuid(), CoreMetrics.ALERT_STATUS_KEY) | |||
.map(LiveMeasureDto::getTextValue) |
@@ -20,9 +20,6 @@ | |||
package org.sonar.server.badge.ws; | |||
import com.google.common.collect.ImmutableMap; | |||
import java.awt.Font; | |||
import java.awt.font.FontRenderContext; | |||
import java.awt.geom.AffineTransform; | |||
import java.io.IOException; | |||
import java.util.Map; | |||
import org.apache.commons.io.IOUtils; | |||
@@ -31,6 +28,7 @@ import org.sonar.api.config.Configuration; | |||
import org.sonar.api.measures.Metric; | |||
import org.sonar.api.server.ServerSide; | |||
import static com.google.common.base.Preconditions.checkState; | |||
import static java.lang.String.valueOf; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static org.sonar.api.measures.Metric.Level.ERROR; | |||
@@ -41,19 +39,88 @@ import static org.sonar.process.ProcessProperties.Property.SONARCLOUD_ENABLED; | |||
@ServerSide | |||
public class SvgGenerator { | |||
private static final Map<Character, Integer> CHAR_LENGTH = ImmutableMap.<Character, Integer>builder() | |||
.put('0', 7) | |||
.put('1', 7) | |||
.put('2', 7) | |||
.put('3', 7) | |||
.put('4', 7) | |||
.put('5', 7) | |||
.put('6', 7) | |||
.put('7', 7) | |||
.put('8', 7) | |||
.put('9', 7) | |||
.put('a', 7) | |||
.put('b', 7) | |||
.put('c', 6) | |||
.put('d', 7) | |||
.put('e', 6) | |||
.put('f', 4) | |||
.put('g', 7) | |||
.put('h', 7) | |||
.put('i', 3) | |||
.put('j', 5) | |||
.put('k', 6) | |||
.put('l', 3) | |||
.put('m', 11) | |||
.put('n', 7) | |||
.put('o', 7) | |||
.put('p', 7) | |||
.put('q', 7) | |||
.put('r', 5) | |||
.put('s', 6) | |||
.put('t', 4) | |||
.put('u', 7) | |||
.put('v', 6) | |||
.put('w', 9) | |||
.put('x', 6) | |||
.put('y', 6) | |||
.put('z', 6) | |||
.put('A', 7) | |||
.put('B', 7) | |||
.put('C', 8) | |||
.put('D', 8) | |||
.put('E', 7) | |||
.put('F', 6) | |||
.put('G', 8) | |||
.put('H', 8) | |||
.put('I', 5) | |||
.put('J', 5) | |||
.put('K', 7) | |||
.put('L', 6) | |||
.put('M', 9) | |||
.put('N', 8) | |||
.put('O', 9) | |||
.put('P', 7) | |||
.put('Q', 9) | |||
.put('R', 8) | |||
.put('S', 7) | |||
.put('T', 7) | |||
.put('U', 8) | |||
.put('V', 10) | |||
.put('W', 10) | |||
.put('X', 7) | |||
.put('Y', 7) | |||
.put('Z', 7) | |||
.put('%', 12) | |||
.put(' ', 4) | |||
.put('.', 4) | |||
.put('_', 7) | |||
.put('\'', 3) | |||
.build(); | |||
private static final String TEMPLATES_SONARCLOUD = "templates/sonarcloud"; | |||
private static final String TEMPLATES_SONARQUBE = "templates/sonarqube"; | |||
private static final FontRenderContext FONT_RENDER_CONTEXT = new FontRenderContext(new AffineTransform(), true, true); | |||
private static final Font FONT = new Font("Verdana", Font.PLAIN, 11); | |||
private static final int MARGIN = 6; | |||
private static final int ICON_WIDTH = 20; | |||
private static final String PARAMETER_ICON_WIDTH_PLUS_MARGIN = "iconWidthPlusMargin"; | |||
private static final String PARAMETER_TOTAL_WIDTH = "totalWidth"; | |||
private static final String PARAMETER_LEFT_WIDTH = "leftWidth"; | |||
private static final String PARAMETER_LEFT_WIDTH_PLUS_MARGIN = "leftWidthPlusMargin"; | |||
private static final String PARAMETER_RIGHT_WIDTH = "rightWidth"; | |||
private static final String PARAMETER_LABEL_WIDTH = "labelWidth"; | |||
private static final String PARAMETER_LABEL_WIDTH_PLUS_MARGIN = "labelWidthPlusMargin"; | |||
private static final String PARAMETER_VALUE_WIDTH = "valueWidth"; | |||
private static final String PARAMETER_COLOR = "color"; | |||
private static final String PARAMETER_LABEL = "label"; | |||
@@ -79,11 +146,13 @@ public class SvgGenerator { | |||
int valueWidth = computeWidth(value); | |||
Map<String, String> values = ImmutableMap.<String, String>builder() | |||
.put(PARAMETER_ICON_WIDTH_PLUS_MARGIN, valueOf(MARGIN + ICON_WIDTH)) | |||
.put(PARAMETER_TOTAL_WIDTH, valueOf(MARGIN * 4 + ICON_WIDTH + labelWidth + valueWidth)) | |||
.put(PARAMETER_LABEL_WIDTH, valueOf(MARGIN * 2 + ICON_WIDTH + labelWidth)) | |||
.put(PARAMETER_LABEL_WIDTH_PLUS_MARGIN, valueOf( MARGIN * 3 + ICON_WIDTH + labelWidth)) | |||
.put(PARAMETER_VALUE_WIDTH, valueOf(MARGIN * 2 + valueWidth)) | |||
.put(PARAMETER_LABEL_WIDTH, valueOf(labelWidth)) | |||
.put(PARAMETER_VALUE_WIDTH, valueOf(valueWidth)) | |||
.put(PARAMETER_LEFT_WIDTH, valueOf(MARGIN * 2 + ICON_WIDTH + labelWidth)) | |||
.put(PARAMETER_LEFT_WIDTH_PLUS_MARGIN, valueOf(MARGIN * 3 + ICON_WIDTH + labelWidth)) | |||
.put(PARAMETER_RIGHT_WIDTH, valueOf(MARGIN * 2 + valueWidth)) | |||
.put(PARAMETER_ICON_WIDTH_PLUS_MARGIN, valueOf(MARGIN + ICON_WIDTH)) | |||
.put(PARAMETER_COLOR, backgroundValueColor.getValue()) | |||
.put(PARAMETER_LABEL, label) | |||
.put(PARAMETER_VALUE, value) | |||
@@ -92,7 +161,7 @@ public class SvgGenerator { | |||
return strSubstitutor.replace(badgeTemplate); | |||
} | |||
public String generateQualityGate(Metric.Level level){ | |||
public String generateQualityGate(Metric.Level level) { | |||
return qualityGateTemplates.get(level); | |||
} | |||
@@ -105,7 +174,14 @@ public class SvgGenerator { | |||
} | |||
private static int computeWidth(String text) { | |||
return (int) FONT.getStringBounds(text, FONT_RENDER_CONTEXT).getWidth(); | |||
return text.chars() | |||
.mapToObj(i -> (char)i) | |||
.mapToInt(c -> { | |||
Integer length = CHAR_LENGTH.get(c); | |||
checkState(length != null, "Invalid character '%s'", c); | |||
return length; | |||
}) | |||
.sum(); | |||
} | |||
private String readTemplate(String template) { |
@@ -214,7 +214,7 @@ public class MeasureActionTest { | |||
.setParam("metric", metric.getKey()) | |||
.execute().getInput(); | |||
checkError(response, "Component key 'unknown' not found"); | |||
checkError(response, "Component not found"); | |||
} | |||
@Test | |||
@@ -231,7 +231,7 @@ public class MeasureActionTest { | |||
.setParam("metric", metric.getKey()) | |||
.execute().getInput(); | |||
checkError(response, String.format("Component '%s' on branch 'unknown' not found", branch.getKey())); | |||
checkError(response, "Component not found"); | |||
} | |||
@Test | |||
@@ -245,7 +245,7 @@ public class MeasureActionTest { | |||
.setParam("metric", metric.getKey()) | |||
.execute().getInput(); | |||
checkError(response, String.format("Measure '%s' has not been found for project '%s' and branch 'null'", metric.getKey(), project.getKey())); | |||
checkError(response, "Measure has not been found"); | |||
} | |||
@Test |
@@ -109,7 +109,7 @@ public class QualityGateActionTest { | |||
.setParam("project", "unknown") | |||
.execute().getInput(); | |||
checkError(response, "Component key 'unknown' not found"); | |||
checkError(response, "Component not found"); | |||
} | |||
@Test | |||
@@ -123,7 +123,7 @@ public class QualityGateActionTest { | |||
.setParam("branch", "unknown") | |||
.execute().getInput(); | |||
checkError(response, format("Component '%s' on branch 'unknown' not found", branch.getKey())); | |||
checkError(response, format("Component not found", branch.getKey())); | |||
} | |||
@Test |
@@ -0,0 +1,124 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2017 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.io.IOException; | |||
import org.apache.commons.io.IOUtils; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.config.internal.MapSettings; | |||
import org.sonar.api.measures.Metric; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.server.tester.UserSessionRule; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.sonar.api.measures.Metric.Level.WARN; | |||
import static org.sonar.server.badge.ws.SvgGenerator.Color.DEFAULT; | |||
public class SvgGeneratorTest { | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Rule | |||
public UserSessionRule userSession = UserSessionRule.standalone(); | |||
@Rule | |||
public DbTester db = DbTester.create(); | |||
private MapSettings mapSettings = new MapSettings(); | |||
private SvgGenerator underTest; | |||
@Test | |||
public void generate_badge() { | |||
mapSettings.setProperty("sonar.sonarcloud.enabled", false); | |||
initSvgGenerator(); | |||
String result = underTest.generateBadge("label", "10", DEFAULT); | |||
checkBadge(result, "label", "10", DEFAULT); | |||
} | |||
@Test | |||
public void generate_quality_gate() { | |||
mapSettings.setProperty("sonar.sonarcloud.enabled", false); | |||
initSvgGenerator(); | |||
String result = underTest.generateQualityGate(WARN); | |||
checkQualityGate(result, WARN); | |||
} | |||
@Test | |||
public void generate_error() { | |||
mapSettings.setProperty("sonar.sonarcloud.enabled", false); | |||
initSvgGenerator(); | |||
String result = underTest.generateError("Error"); | |||
assertThat(result).contains("<text", ">Error</text>"); | |||
} | |||
@Test | |||
public void fail_when_unknown_character() { | |||
mapSettings.setProperty("sonar.sonarcloud.enabled", false); | |||
initSvgGenerator(); | |||
expectedException.expectMessage("Invalid character 'é'"); | |||
underTest.generateError("Méssage with accent"); | |||
} | |||
private void initSvgGenerator() { | |||
underTest = new SvgGenerator(mapSettings.asConfig()); | |||
} | |||
private void checkBadge(String svg, String expectedLabel, String expectedValue, SvgGenerator.Color expectedColorValue) { | |||
assertThat(svg).contains( | |||
"<text", expectedLabel + "</text>", | |||
"<text", expectedValue + "</text>", | |||
"rect fill=\"" + expectedColorValue.getValue() + "\""); | |||
} | |||
private void checkQualityGate(String response, Metric.Level status) { | |||
switch (status) { | |||
case OK: | |||
assertThat(response).isEqualTo(readTemplate("quality_gate_passed.svg")); | |||
break; | |||
case WARN: | |||
assertThat(response).isEqualTo(readTemplate("quality_gate_warn.svg")); | |||
break; | |||
case ERROR: | |||
assertThat(response).isEqualTo(readTemplate("quality_gate_failed.svg")); | |||
break; | |||
} | |||
} | |||
private String readTemplate(String template) { | |||
try { | |||
return IOUtils.toString(getClass().getResource("templates/sonarqube/" + template), UTF_8); | |||
} catch (IOException e) { | |||
throw new IllegalStateException(String.format("Can't read svg template '%s'", template), e); | |||
} | |||
} | |||
} |