Browse Source

SONAR-10412 Fix display of project badges

tags/7.5
Julien Lancelot 6 years ago
parent
commit
b69f8ce778

+ 15
- 6
server/sonar-server/src/main/java/org/sonar/server/badge/ws/MeasureAction.java View File

@@ -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) {

+ 14
- 4
server/sonar-server/src/main/java/org/sonar/server/badge/ws/QualityGateAction.java View File

@@ -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)

+ 89
- 13
server/sonar-server/src/main/java/org/sonar/server/badge/ws/SvgGenerator.java View File

@@ -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) {

+ 3
- 3
server/sonar-server/src/test/java/org/sonar/server/badge/ws/MeasureActionTest.java View File

@@ -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

+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/badge/ws/QualityGateActionTest.java View File

@@ -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

+ 124
- 0
server/sonar-server/src/test/java/org/sonar/server/badge/ws/SvgGeneratorTest.java View File

@@ -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);
}
}

}

Loading…
Cancel
Save