]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10412 Fix display of project badges
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 21 Mar 2018 09:17:37 +0000 (10:17 +0100)
committerSonarTech <sonartech@sonarsource.com>
Thu, 29 Mar 2018 18:20:47 +0000 (20:20 +0200)
server/sonar-server/src/main/java/org/sonar/server/badge/ws/MeasureAction.java
server/sonar-server/src/main/java/org/sonar/server/badge/ws/QualityGateAction.java
server/sonar-server/src/main/java/org/sonar/server/badge/ws/SvgGenerator.java
server/sonar-server/src/test/java/org/sonar/server/badge/ws/MeasureActionTest.java
server/sonar-server/src/test/java/org/sonar/server/badge/ws/QualityGateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/badge/ws/SvgGeneratorTest.java [new file with mode: 0644]

index 0fbac10bdb522de5d53a293dd818496a816d1225..083e858ed3f9b967419aeb3d7b785fe6e953eef5 100644 (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) {
index bdf45ad78c6c686784674e2d783e4103e28d8c87..066940e2c1f10775f30605de92662f41e715d0db 100644 (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)
index 0da1c8fa6543195acbe9406ce33da15ed05cd7fe..b486dab9298e8b0c41655f489683e50a997bc8b2 100644 (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) {
index 1f1f9e00f119eaed4da92fae5bb6443091e9642b..3691bbdfb877ceb78cf029197f2f4e15b494d670 100644 (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
index 5b9d0c94fecdc5492e075e76e0d8fb79fc9aad35..1daf402058396a9664b74f92fb736aa0f170ffdf 100644 (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
diff --git a/server/sonar-server/src/test/java/org/sonar/server/badge/ws/SvgGeneratorTest.java b/server/sonar-server/src/test/java/org/sonar/server/badge/ws/SvgGeneratorTest.java
new file mode 100644 (file)
index 0000000..e797133
--- /dev/null
@@ -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);
+    }
+  }
+
+}