]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6611 WS custom_measures/update update a custom measure 390/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Mon, 22 Jun 2015 10:10:47 +0000 (12:10 +0200)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Wed, 24 Jun 2015 09:41:07 +0000 (11:41 +0200)
17 files changed:
server/sonar-server/src/main/java/org/sonar/server/measure/custom/persistence/CustomMeasureDao.java
server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CreateAction.java
server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java
server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValidator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValueDescription.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModule.java
server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/UpdateAction.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasureValidatorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsTest.java
server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/util/TypeValidationsTesting.java [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/CreateActionTest/custom-measure.json
server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/UpdateActionTest/custom-measure.json [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/measure/custom/db/CustomMeasureMapper.java
sonar-core/src/main/resources/org/sonar/core/measure/custom/db/CustomMeasureMapper.xml

index ea62814d830c87f26cbb09f4342f4dbce515b6c1..6a454ee272038833b70b1e5daf296bc077ace573 100644 (file)
@@ -38,6 +38,10 @@ public class CustomMeasureDao implements DaoComponent {
     mapper(session).insert(customMeasureDto);
   }
 
+  public void update(DbSession session, CustomMeasureDto customMeasure) {
+    mapper(session).update(customMeasure);
+  }
+
   public void delete(DbSession session, long id) {
     mapper(session).delete(id);
   }
@@ -60,7 +64,7 @@ public class CustomMeasureDao implements DaoComponent {
   public CustomMeasureDto selectById(DbSession session, long id) {
     CustomMeasureDto customMeasure = selectNullableById(session, id);
     if (customMeasure == null) {
-      throw new NotFoundException(String.format("CustomMeasure '%d' not found", id));
+      throw new NotFoundException(String.format("Custom measure '%d' not found.", id));
     }
     return customMeasure;
   }
index 4586689a37e2f3b55d33497225c4524bbcc413a5..af5c91f22ef0413a99edb5cb9d2a687925247d76 100644 (file)
 
 package org.sonar.server.measure.custom.ws;
 
-import com.google.common.base.Joiner;
 import java.net.HttpURLConnection;
-import org.sonar.api.PropertyType;
-import org.sonar.api.measures.Metric;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
@@ -40,9 +37,11 @@ import org.sonar.server.db.DbClient;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.exceptions.ServerException;
 import org.sonar.server.user.UserSession;
-import org.sonar.server.util.TypeValidations;
+import org.sonar.server.user.index.UserDoc;
+import org.sonar.server.user.index.UserIndex;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static org.sonar.server.measure.custom.ws.CustomMeasureValueDescription.measureValueDescription;
 
 public class CreateAction implements CustomMeasuresWsAction {
   public static final String ACTION = "create";
@@ -53,28 +52,21 @@ public class CreateAction implements CustomMeasuresWsAction {
   public static final String PARAM_VALUE = "value";
   public static final String PARAM_DESCRIPTION = "description";
 
-  private static final String FIELD_ID = "id";
-  private static final String FIELD_PROJECT_ID = PARAM_PROJECT_ID;
-  private static final String FIELD_PROJECT_KEY = PARAM_PROJECT_KEY;
-  private static final String FIELD_VALUE = PARAM_VALUE;
-  private static final String FIELD_DESCRIPTION = PARAM_DESCRIPTION;
-  private static final String FIELD_METRIC = "metric";
-  private static final String FIELD_METRIC_KEY = "key";
-  private static final String FIELD_METRIC_ID = "id";
-  private static final String FIELD_METRIC_TYPE = "type";
-
   private final DbClient dbClient;
   private final UserSession userSession;
   private final System2 system;
-  private final TypeValidations typeValidations;
+  private final CustomMeasureValidator validator;
   private final CustomMeasureJsonWriter customMeasureJsonWriter;
+  private final UserIndex userIndex;
 
-  public CreateAction(DbClient dbClient, UserSession userSession, System2 system, TypeValidations typeValidations, CustomMeasureJsonWriter customMeasureJsonWriter) {
+  public CreateAction(DbClient dbClient, UserSession userSession, System2 system, CustomMeasureValidator validator, CustomMeasureJsonWriter customMeasureJsonWriter,
+    UserIndex userIndex) {
     this.dbClient = dbClient;
     this.userSession = userSession;
     this.system = system;
-    this.typeValidations = typeValidations;
+    this.validator = validator;
     this.customMeasureJsonWriter = customMeasureJsonWriter;
+    this.userIndex = userIndex;
   }
 
   @Override
@@ -116,6 +108,7 @@ public class CreateAction implements CustomMeasuresWsAction {
   @Override
   public void handle(Request request, Response response) throws Exception {
     DbSession dbSession = dbClient.openSession(false);
+    String valueAsString = request.mandatoryParam(PARAM_VALUE);
     String description = request.param(PARAM_DESCRIPTION);
     long now = system.now();
 
@@ -124,18 +117,20 @@ public class CreateAction implements CustomMeasuresWsAction {
       MetricDto metric = searchMetric(dbSession, request);
       checkPermissions(component);
       checkMeasureDoesNotExistAlready(dbSession, component, metric);
+      UserDoc user = userIndex.getByLogin(userSession.getLogin());
       CustomMeasureDto measure = new CustomMeasureDto()
         .setComponentUuid(component.uuid())
         .setComponentId(component.getId())
         .setMetricId(metric.getId())
         .setDescription(description)
+        .setUserLogin(user.login())
         .setCreatedAt(now);
-      setMeasureValue(measure, request, metric);
+      validator.setMeasureValue(measure, valueAsString, metric);
       dbClient.customMeasureDao().insert(dbSession, measure);
       dbSession.commit();
 
       JsonWriter json = response.newJsonWriter();
-      writeMeasure(json, measure, component, metric, request.mandatoryParam(PARAM_VALUE));
+      customMeasureJsonWriter.write(json, measure, metric, component, user);
       json.close();
     } finally {
       MyBatis.closeQuietly(dbSession);
@@ -158,71 +153,6 @@ public class CreateAction implements CustomMeasuresWsAction {
     }
   }
 
-  private void writeMeasure(JsonWriter json, CustomMeasureDto measure, ComponentDto component, MetricDto metric, String measureWithoutInternalFormatting) {
-    customMeasureJsonWriter.write(json, measure, metric, component);
-  }
-
-  private void setMeasureValue(CustomMeasureDto measure, Request request, MetricDto metric) {
-    String valueAsString = request.mandatoryParam(PARAM_VALUE);
-    Metric.ValueType metricType = Metric.ValueType.valueOf(metric.getValueType());
-    try {
-      switch (metricType) {
-        case BOOL:
-          checkAndSetBooleanMeasureValue(measure, valueAsString);
-          break;
-        case INT:
-        case MILLISEC:
-          checkAndSetIntegerMeasureValue(measure, valueAsString);
-          break;
-        case WORK_DUR:
-          checkAndSetWorkDurationMeasureValue(measure, valueAsString);
-          break;
-        case FLOAT:
-        case PERCENT:
-        case RATING:
-          checkAndSetFloatMeasureValue(measure, valueAsString);
-          break;
-        case LEVEL:
-          checkAndSetLevelMeasureValue(measure, valueAsString);
-          break;
-        case STRING:
-        case DATA:
-        case DISTRIB:
-          measure.setTextValue(valueAsString);
-          break;
-        default:
-          throw new IllegalArgumentException("Unsupported metric type:" + metricType.description());
-      }
-    } catch (Exception e) {
-      throw new IllegalArgumentException(String.format("Ill formatted value '%s' for metric type '%s'", valueAsString, metricType.description()), e);
-    }
-  }
-
-  private void checkAndSetLevelMeasureValue(CustomMeasureDto measure, String valueAsString) {
-    typeValidations.validate(valueAsString, PropertyType.METRIC_LEVEL.name(), null);
-    measure.setTextValue(valueAsString);
-  }
-
-  private void checkAndSetFloatMeasureValue(CustomMeasureDto measure, String valueAsString) {
-    typeValidations.validate(valueAsString, PropertyType.FLOAT.name(), null);
-    measure.setValue(Double.parseDouble(valueAsString));
-  }
-
-  private void checkAndSetWorkDurationMeasureValue(CustomMeasureDto measure, String valueAsString) {
-    typeValidations.validate(valueAsString, PropertyType.LONG.name(), null);
-    measure.setValue(Long.parseLong(valueAsString));
-  }
-
-  private void checkAndSetIntegerMeasureValue(CustomMeasureDto measure, String valueAsString) {
-    typeValidations.validate(valueAsString, PropertyType.INTEGER.name(), null);
-    measure.setValue(Integer.parseInt(valueAsString));
-  }
-
-  private void checkAndSetBooleanMeasureValue(CustomMeasureDto measure, String valueAsString) {
-    typeValidations.validate(valueAsString, PropertyType.BOOLEAN.name(), null);
-    measure.setValue(Boolean.parseBoolean(valueAsString) ? 1.0d : 0.0d);
-  }
-
   private MetricDto searchMetric(DbSession dbSession, Request request) {
     Integer metricId = request.paramAsInt(PARAM_METRIC_ID);
     String metricKey = request.param(PARAM_METRIC_KEY);
@@ -256,45 +186,4 @@ public class CreateAction implements CustomMeasuresWsAction {
 
     return project;
   }
-
-  private static String measureValueDescription() {
-    StringBuilder description = new StringBuilder("Measure value. Value type depends on metric type:");
-    description.append("<ul>");
-    for (Metric.ValueType metricType : Metric.ValueType.values()) {
-      description.append("<li>");
-      description.append(String.format("%s - %s", metricType.description(), metricTypeWsDescription(metricType)));
-      description.append("</li>");
-    }
-    description.append("</ul>");
-
-    return description.toString();
-  }
-
-  private static String metricTypeWsDescription(Metric.ValueType metricType) {
-    switch (metricType) {
-      case BOOL:
-        return "the possible values are true or false";
-      case INT:
-      case MILLISEC:
-        return "type: integer";
-      case FLOAT:
-      case PERCENT:
-      case RATING:
-        return "type: double";
-      case LEVEL:
-        return "the possible values are " + formattedMetricLevelNames();
-      case STRING:
-      case DATA:
-      case DISTRIB:
-        return "type: string";
-      case WORK_DUR:
-        return "long representing the number of minutes";
-      default:
-        return "metric type not supported";
-    }
-  }
-
-  private static String formattedMetricLevelNames() {
-    return Joiner.on(", ").join(Metric.Level.names());
-  }
 }
index cbb6211c5fb812c9b961622b85fa8349dc5a6f11..0f359ff9bc1dab2a8f4a5243dba6c4ff3e6668d1 100644 (file)
 
 package org.sonar.server.measure.custom.ws;
 
+import java.util.Date;
 import org.sonar.api.measures.Metric;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.user.User;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.core.component.ComponentDto;
 import org.sonar.core.measure.custom.db.CustomMeasureDto;
 import org.sonar.core.metric.db.MetricDto;
+import org.sonar.server.user.ws.UserJsonWriter;
 
 import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_DESCRIPTION;
 import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_PROJECT_ID;
 import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_PROJECT_KEY;
 import static org.sonar.server.measure.custom.ws.CreateAction.PARAM_VALUE;
 
+@ServerSide
 public class CustomMeasureJsonWriter {
   private static final String FIELD_ID = "id";
   private static final String FIELD_PROJECT_ID = PARAM_PROJECT_ID;
@@ -41,8 +46,17 @@ public class CustomMeasureJsonWriter {
   private static final String FIELD_METRIC_KEY = "key";
   private static final String FIELD_METRIC_ID = "id";
   private static final String FIELD_METRIC_TYPE = "type";
+  private static final String FIELD_CREATED_AT = "createdAt";
+  private static final String FIELD_UPDATED_AT = "updatedAt";
+  private static final String FIELD_USER = "user";
 
-  public void write(JsonWriter json, CustomMeasureDto measure, MetricDto metric, ComponentDto component) {
+  private final UserJsonWriter userJsonWriter;
+
+  public CustomMeasureJsonWriter(UserJsonWriter userJsonWriter) {
+    this.userJsonWriter = userJsonWriter;
+  }
+
+  public void write(JsonWriter json, CustomMeasureDto measure, MetricDto metric, ComponentDto component, User user) {
     json.beginObject();
     json.prop(FIELD_ID, String.valueOf(measure.getId()));
     json.name(FIELD_METRIC);
@@ -51,6 +65,10 @@ public class CustomMeasureJsonWriter {
     json.prop(FIELD_PROJECT_KEY, component.key());
     json.prop(FIELD_DESCRIPTION, measure.getDescription());
     json.prop(FIELD_VALUE, measureValue(measure, metric));
+    json.propDateTime(FIELD_CREATED_AT, new Date(measure.getCreatedAt()));
+    json.propDateTime(FIELD_UPDATED_AT, new Date(measure.getUpdatedAt()));
+    json.name(FIELD_USER);
+    userJsonWriter.write(json, user);
     json.endObject();
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValidator.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValidator.java
new file mode 100644 (file)
index 0000000..2d38586
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.measure.custom.ws;
+
+import org.sonar.api.PropertyType;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.measure.custom.db.CustomMeasureDto;
+import org.sonar.core.metric.db.MetricDto;
+import org.sonar.server.util.TypeValidations;
+
+@ServerSide
+public class CustomMeasureValidator {
+  private final TypeValidations typeValidations;
+
+  public CustomMeasureValidator(TypeValidations typeValidations) {
+    this.typeValidations = typeValidations;
+  }
+
+  public void setMeasureValue(CustomMeasureDto measure, String valueAsString, MetricDto metric) {
+    Metric.ValueType metricType = Metric.ValueType.valueOf(metric.getValueType());
+    try {
+      switch (metricType) {
+        case BOOL:
+          checkAndSetBooleanMeasureValue(measure, valueAsString);
+          break;
+        case INT:
+        case MILLISEC:
+          checkAndSetIntegerMeasureValue(measure, valueAsString);
+          break;
+        case WORK_DUR:
+          checkAndSetLongMeasureValue(measure, valueAsString);
+          break;
+        case FLOAT:
+        case PERCENT:
+        case RATING:
+          checkAndSetFloatMeasureValue(measure, valueAsString);
+          break;
+        case LEVEL:
+          checkAndSetLevelMeasureValue(measure, valueAsString);
+          break;
+        case STRING:
+        case DATA:
+        case DISTRIB:
+          measure.setTextValue(valueAsString);
+          break;
+        default:
+          throw new IllegalArgumentException("Unsupported metric type:" + metricType.description());
+      }
+    } catch (Exception e) {
+      throw new IllegalArgumentException(String.format("Ill formatted value '%s' for metric type '%s'", valueAsString, metricType.description()), e);
+    }
+
+  }
+
+  private void checkAndSetLevelMeasureValue(CustomMeasureDto measure, String valueAsString) {
+    typeValidations.validate(valueAsString, PropertyType.METRIC_LEVEL.name(), null);
+    measure.setTextValue(valueAsString);
+  }
+
+  private void checkAndSetFloatMeasureValue(CustomMeasureDto measure, String valueAsString) {
+    typeValidations.validate(valueAsString, PropertyType.FLOAT.name(), null);
+    measure.setValue(Double.parseDouble(valueAsString));
+  }
+
+  private void checkAndSetLongMeasureValue(CustomMeasureDto measure, String valueAsString) {
+    typeValidations.validate(valueAsString, PropertyType.LONG.name(), null);
+    measure.setValue(Long.parseLong(valueAsString));
+  }
+
+  private void checkAndSetIntegerMeasureValue(CustomMeasureDto measure, String valueAsString) {
+    typeValidations.validate(valueAsString, PropertyType.INTEGER.name(), null);
+    measure.setValue(Integer.parseInt(valueAsString));
+  }
+
+  private void checkAndSetBooleanMeasureValue(CustomMeasureDto measure, String valueAsString) {
+    typeValidations.validate(valueAsString, PropertyType.BOOLEAN.name(), null);
+    measure.setValue(Boolean.parseBoolean(valueAsString) ? 1.0d : 0.0d);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValueDescription.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValueDescription.java
new file mode 100644 (file)
index 0000000..2970556
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.measure.custom.ws;
+
+import com.google.common.base.Joiner;
+import org.sonar.api.measures.Metric;
+
+class CustomMeasureValueDescription {
+  private CustomMeasureValueDescription() {
+    // utility class
+  }
+
+  static String measureValueDescription() {
+    StringBuilder description = new StringBuilder("Measure value. Value type depends on metric type:");
+    description.append("<ul>");
+    for (Metric.ValueType metricType : Metric.ValueType.values()) {
+      description.append("<li>");
+      description.append(String.format("%s - %s", metricType.description(), metricTypeWsDescription(metricType)));
+      description.append("</li>");
+    }
+    description.append("</ul>");
+
+    return description.toString();
+  }
+
+  private static String metricTypeWsDescription(Metric.ValueType metricType) {
+    switch (metricType) {
+      case BOOL:
+        return "the possible values are true or false";
+      case INT:
+      case MILLISEC:
+        return "type: integer";
+      case FLOAT:
+      case PERCENT:
+      case RATING:
+        return "type: double";
+      case LEVEL:
+        return "the possible values are " + formattedMetricLevelNames();
+      case STRING:
+      case DATA:
+      case DISTRIB:
+        return "type: string";
+      case WORK_DUR:
+        return "long representing the number of minutes";
+      default:
+        return "metric type not supported";
+    }
+  }
+
+  private static String formattedMetricLevelNames() {
+    return Joiner.on(", ").join(Metric.Level.names());
+  }
+}
index ae49ecf50075e311a38916bd09cab2742f27c677..51270b190fd0d9c548fb787089def31f41f027b3 100644 (file)
@@ -27,8 +27,10 @@ public class CustomMeasuresWsModule extends Module {
   protected void configureModule() {
     add(
       CustomMeasuresWs.class,
+      CreateAction.class,
+      UpdateAction.class,
       DeleteAction.class,
       CustomMeasureJsonWriter.class,
-      CreateAction.class);
+      CustomMeasureValidator.class);
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/UpdateAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/UpdateAction.java
new file mode 100644 (file)
index 0000000..3dbf8a4
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.measure.custom.ws;
+
+import javax.annotation.Nullable;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.user.User;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.measure.custom.db.CustomMeasureDto;
+import org.sonar.core.metric.db.MetricDto;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.user.UserSession;
+import org.sonar.server.user.index.UserIndex;
+
+import static org.sonar.server.measure.custom.ws.CustomMeasureValueDescription.measureValueDescription;
+
+public class UpdateAction implements CustomMeasuresWsAction {
+  public static final String ACTION = "update";
+  public static final String PARAM_ID = "id";
+  public static final String PARAM_VALUE = "value";
+  public static final String PARAM_DESCRIPTION = "description";
+
+  private final DbClient dbClient;
+  private final UserSession userSession;
+  private final System2 system;
+  private final CustomMeasureValidator validator;
+  private final CustomMeasureJsonWriter customMeasureJsonWriter;
+  private final UserIndex userIndex;
+
+  public UpdateAction(DbClient dbClient, UserSession userSession, System2 system, CustomMeasureValidator validator, CustomMeasureJsonWriter customMeasureJsonWriter, UserIndex userIndex) {
+    this.dbClient = dbClient;
+    this.userSession = userSession;
+    this.system = system;
+    this.validator = validator;
+    this.customMeasureJsonWriter = customMeasureJsonWriter;
+    this.userIndex = userIndex;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context.createAction(ACTION)
+      .setPost(true)
+      .setDescription("Update a custom measure. Value and/or description must be provided<br />" +
+        "Requires 'Administer System' permission or 'Administer' permission on the project.")
+      .setHandler(this)
+      .setSince("5.2");
+
+    action.createParam(PARAM_ID)
+      .setRequired(true)
+      .setDescription("id")
+      .setExampleValue("42");
+
+    action.createParam(PARAM_VALUE)
+      .setExampleValue("true")
+      .setDescription(measureValueDescription());
+
+    action.createParam(PARAM_DESCRIPTION)
+      .setExampleValue("Team size growing.");
+  }
+
+  @Override
+  public void handle(Request request, Response response) throws Exception {
+    int id = request.mandatoryParamAsInt(PARAM_ID);
+    String value = request.param(PARAM_VALUE);
+    String description = request.param(PARAM_DESCRIPTION);
+    checkParameters(value, description);
+
+    DbSession dbSession = dbClient.openSession(true);
+    try {
+      CustomMeasureDto customMeasure = dbClient.customMeasureDao().selectById(dbSession, id);
+      MetricDto metric = dbClient.metricDao().selectById(dbSession, customMeasure.getMetricId());
+      ComponentDto component = dbClient.componentDao().selectByUuid(dbSession, customMeasure.getComponentUuid());
+      User user = userIndex.getByLogin(userSession.getLogin());
+
+      checkPermissions(component);
+
+      setValue(customMeasure, value, metric);
+      setDescription(customMeasure, description);
+      customMeasure.setUserLogin(user.login());
+      customMeasure.setUpdatedAt(system.now());
+      dbClient.customMeasureDao().update(dbSession, customMeasure);
+      dbSession.commit();
+
+      JsonWriter json = response.newJsonWriter();
+      customMeasureJsonWriter.write(json, customMeasure, metric, component, user);
+      json.close();
+    } finally {
+      MyBatis.closeQuietly(dbSession);
+    }
+  }
+
+  private void setValue(CustomMeasureDto customMeasure, @Nullable String value, MetricDto metric) {
+    if (value != null) {
+      validator.setMeasureValue(customMeasure, value, metric);
+    }
+  }
+
+  private static void setDescription(CustomMeasureDto customMeasure, @Nullable String description) {
+    if (description != null) {
+      customMeasure.setDescription(description);
+    }
+  }
+
+  private static void checkParameters(@Nullable String value, @Nullable String description) {
+    if (value == null && description == null) {
+      throw new IllegalArgumentException("Value or description must be provided.");
+    }
+  }
+
+  private void checkPermissions(ComponentDto component) {
+    if (userSession.hasGlobalPermission(GlobalPermissions.SYSTEM_ADMIN)) {
+      return;
+    }
+
+    userSession.checkLoggedIn().checkProjectUuidPermission(UserRole.ADMIN, component.projectUuid());
+  }
+}
index 6fccdd1153d39cf46aa2cc37425260f3ed599c29..fdc8581c9f0282b6d4423e4591a8cd0010e21fe5 100644 (file)
 
 package org.sonar.server.measure.custom.ws;
 
-import java.util.Arrays;
 import java.util.List;
 import org.assertj.core.data.Offset;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.rules.ExpectedException;
+import org.sonar.api.config.Settings;
 import org.sonar.api.measures.Metric;
 import org.sonar.api.measures.Metric.ValueType;
 import org.sonar.api.utils.System2;
@@ -42,6 +43,7 @@ import org.sonar.core.persistence.DbTester;
 import org.sonar.server.component.ComponentTesting;
 import org.sonar.server.component.db.ComponentDao;
 import org.sonar.server.db.DbClient;
+import org.sonar.server.es.EsTester;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.exceptions.ServerException;
@@ -49,17 +51,16 @@ import org.sonar.server.measure.custom.persistence.CustomMeasureDao;
 import org.sonar.server.metric.persistence.MetricDao;
 import org.sonar.server.metric.ws.MetricTesting;
 import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.util.BooleanTypeValidation;
-import org.sonar.server.util.FloatTypeValidation;
-import org.sonar.server.util.IntegerTypeValidation;
-import org.sonar.server.util.LongTypeValidation;
-import org.sonar.server.util.MetricLevelTypeValidation;
-import org.sonar.server.util.TypeValidations;
+import org.sonar.server.user.index.UserDoc;
+import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexDefinition;
+import org.sonar.server.user.ws.UserJsonWriter;
 import org.sonar.server.ws.WsTester;
 import org.sonar.test.DbTests;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.data.Offset.offset;
+import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations;
 
 @Category(DbTests.class)
 public class CreateActionTest {
@@ -72,20 +73,30 @@ public class CreateActionTest {
   public ExpectedException expectedException = ExpectedException.none();
   @ClassRule
   public static DbTester db = new DbTester();
+  @ClassRule
+  public static EsTester es = new EsTester().addDefinitions(new UserIndexDefinition(new Settings()));
   DbClient dbClient;
   DbSession dbSession;
 
   WsTester ws;
 
+  @BeforeClass
+  public static void setUpClass() throws Exception {
+    es.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, new UserDoc()
+      .setLogin("login")
+      .setName("Login")
+      .setEmail("login@login.com")
+      .setActive(true));
+  }
+
   @Before
   public void setUp() {
     dbClient = new DbClient(db.database(), db.myBatis(), new CustomMeasureDao(), new MetricDao(), new ComponentDao());
     dbSession = dbClient.openSession(false);
-    TypeValidations typeValidations = new TypeValidations(Arrays.asList(new BooleanTypeValidation(), new IntegerTypeValidation(), new FloatTypeValidation(),
-      new MetricLevelTypeValidation(), new LongTypeValidation()));
-    ws = new WsTester(new CustomMeasuresWs(new CreateAction(dbClient, userSession, System2.INSTANCE, typeValidations, new CustomMeasureJsonWriter())));
+    ws = new WsTester(new CustomMeasuresWs(new CreateAction(dbClient, userSession, System2.INSTANCE, new CustomMeasureValidator(newFullTypeValidations()),
+      new CustomMeasureJsonWriter(new UserJsonWriter(userSession)), new UserIndex(es.client()))));
     db.truncateTables();
-    userSession.setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+    userSession.login("login").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
   }
 
   @After
@@ -186,7 +197,7 @@ public class CreateActionTest {
   }
 
   @Test
-  public void create_float_custom_measure_indb() throws Exception {
+  public void create_float_custom_measure_in_db() throws Exception {
     MetricDto metric = insertMetricAndProject(ValueType.FLOAT, DEFAULT_PROJECT_UUID);
 
     newRequest()
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasureValidatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasureValidatorTest.java
new file mode 100644 (file)
index 0000000..c546022
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.measure.custom.ws;
+
+import org.assertj.core.data.Offset;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.core.measure.custom.db.CustomMeasureDto;
+import org.sonar.server.measure.custom.persistence.CustomMeasureTesting;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.measures.Metric.Level.WARN;
+import static org.sonar.api.measures.Metric.ValueType.BOOL;
+import static org.sonar.api.measures.Metric.ValueType.FLOAT;
+import static org.sonar.api.measures.Metric.ValueType.INT;
+import static org.sonar.api.measures.Metric.ValueType.LEVEL;
+import static org.sonar.api.measures.Metric.ValueType.STRING;
+import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
+import static org.sonar.server.metric.ws.MetricTesting.newMetricDto;
+import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations;
+
+public class CustomMeasureValidatorTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  CustomMeasureValidator sut = new CustomMeasureValidator(newFullTypeValidations());
+  CustomMeasureDto customMeasure = CustomMeasureTesting.newCustomMeasureDto();
+
+  @Test
+  public void set_boolean_true_value() {
+    sut.setMeasureValue(customMeasure, "true", newMetricDto().setValueType(BOOL.name()));
+
+    assertThat(customMeasure.getValue()).isCloseTo(1.0d, defaultOffset());
+  }
+
+  @Test
+  public void set_boolean_false_value() {
+    sut.setMeasureValue(customMeasure, "false", newMetricDto().setValueType(BOOL.name()));
+
+    assertThat(customMeasure.getValue()).isCloseTo(0.0d, defaultOffset());
+  }
+
+  @Test
+  public void set_integer_value() {
+    sut.setMeasureValue(customMeasure, "1984", newMetricDto().setValueType(INT.name()));
+
+    assertThat(customMeasure.getValue()).isCloseTo(1984d, defaultOffset());
+  }
+
+  @Test
+  public void set_float_value() {
+    sut.setMeasureValue(customMeasure, "3.14", newMetricDto().setValueType(FLOAT.name()));
+
+    assertThat(customMeasure.getValue()).isCloseTo(3.14d, defaultOffset());
+  }
+
+  @Test
+  public void set_long_value() {
+    sut.setMeasureValue(customMeasure, "123456789", newMetricDto().setValueType(WORK_DUR.name()));
+
+    assertThat(customMeasure.getValue()).isCloseTo(123456789d, defaultOffset());
+  }
+
+  @Test
+  public void set_level_value() {
+    sut.setMeasureValue(customMeasure, WARN.name(), newMetricDto().setValueType(LEVEL.name()));
+
+    assertThat(customMeasure.getTextValue()).isEqualTo(WARN.name());
+  }
+
+  @Test
+  public void set_string_value() {
+    sut.setMeasureValue(customMeasure, "free-text-string", newMetricDto().setValueType(STRING.name()));
+
+    assertThat(customMeasure.getTextValue()).isEqualTo("free-text-string");
+  }
+
+  @Test
+  public void fail_when_non_compliant_value() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Ill formatted value 'non-compliant-boolean-value' for metric type 'Yes/No'");
+
+    sut.setMeasureValue(customMeasure, "non-compliant-boolean-value", newMetricDto().setValueType(BOOL.name()));
+  }
+
+  @Test
+  public void fail_when_non_compliant_level_value() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Ill formatted value 'non-compliant-level-value' for metric type 'Level'");
+
+    sut.setMeasureValue(customMeasure, "non-compliant-level-value", newMetricDto().setValueType(LEVEL.name()));
+  }
+
+  private Offset<Double> defaultOffset() {
+    return Offset.offset(0.01d);
+  }
+}
index 098819b9cb06eec1ad3adefbd65923d6d4d9868f..7a65f3a7b074ea4eaa1c7d9b2800a789c31d42d4 100644 (file)
@@ -30,6 +30,6 @@ public class CustomMeasuresWsModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new CustomMeasuresWsModule().configure(container);
-    assertThat(container.size()).isEqualTo(5);
+    assertThat(container.size()).isEqualTo(8);
   }
 }
index 3737f9236edbb697f0debbd55f781e6159a67190..7ed41878f217f8921c9d940146c8f63b38f72b43 100644 (file)
@@ -26,11 +26,12 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.System2;
 import org.sonar.server.db.DbClient;
 import org.sonar.server.user.UserSession;
-import org.sonar.server.util.TypeValidations;
+import org.sonar.server.user.index.UserIndex;
 import org.sonar.server.ws.WsTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
+import static org.sonar.server.measure.custom.ws.CustomMeasuresWs.ENDPOINT;
 
 public class CustomMeasuresWsTest {
   WsTester ws;
@@ -41,7 +42,8 @@ public class CustomMeasuresWsTest {
     UserSession userSession = mock(UserSession.class);
     ws = new WsTester(new CustomMeasuresWs(
       new DeleteAction(dbClient, userSession),
-      new CreateAction(dbClient, userSession, System2.INSTANCE, mock(TypeValidations.class), mock(CustomMeasureJsonWriter.class))
+      new CreateAction(dbClient, userSession, System2.INSTANCE, mock(CustomMeasureValidator.class), mock(CustomMeasureJsonWriter.class), mock(UserIndex.class)),
+      new UpdateAction(dbClient, userSession, System2.INSTANCE, mock(CustomMeasureValidator.class), mock(CustomMeasureJsonWriter.class), mock(UserIndex.class))
       ));
   }
 
@@ -50,18 +52,24 @@ public class CustomMeasuresWsTest {
     WebService.Controller controller = ws.controller("api/custom_measures");
     assertThat(controller).isNotNull();
     assertThat(controller.description()).isNotEmpty();
-    assertThat(controller.actions()).hasSize(2);
+    assertThat(controller.actions()).hasSize(3);
   }
 
   @Test
   public void delete_action_properties() {
-    WebService.Action deleteAction = ws.controller("api/custom_measures").action("delete");
+    WebService.Action deleteAction = ws.controller(ENDPOINT).action("delete");
     assertThat(deleteAction.isPost()).isTrue();
   }
 
   @Test
   public void create_action_properties() {
-    WebService.Action action = ws.controller("api/custom_measures").action("create");
+    WebService.Action action = ws.controller(ENDPOINT).action("create");
+    assertThat(action.isPost()).isTrue();
+  }
+
+  @Test
+  public void create_update_properties() {
+    WebService.Action action = ws.controller(ENDPOINT).action("update");
     assertThat(action.isPost()).isTrue();
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java
new file mode 100644 (file)
index 0000000..188dd68
--- /dev/null
@@ -0,0 +1,321 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.measure.custom.ws;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.Settings;
+import org.sonar.api.measures.Metric.ValueType;
+import org.sonar.api.utils.System2;
+import org.sonar.core.component.ComponentDto;
+import org.sonar.core.measure.custom.db.CustomMeasureDto;
+import org.sonar.core.metric.db.MetricDto;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.core.persistence.DbSession;
+import org.sonar.core.persistence.DbTester;
+import org.sonar.server.component.ComponentTesting;
+import org.sonar.server.component.db.ComponentDao;
+import org.sonar.server.db.DbClient;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.exceptions.ServerException;
+import org.sonar.server.measure.custom.persistence.CustomMeasureDao;
+import org.sonar.server.metric.persistence.MetricDao;
+import org.sonar.server.metric.ws.MetricTesting;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.user.index.UserDoc;
+import org.sonar.server.user.index.UserIndex;
+import org.sonar.server.user.index.UserIndexDefinition;
+import org.sonar.server.user.ws.UserJsonWriter;
+import org.sonar.server.ws.WsTester;
+import org.sonar.test.DbTests;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.data.Offset.offset;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.measure.custom.persistence.CustomMeasureTesting.newCustomMeasureDto;
+import static org.sonar.server.measure.custom.ws.UpdateAction.PARAM_DESCRIPTION;
+import static org.sonar.server.measure.custom.ws.UpdateAction.PARAM_ID;
+import static org.sonar.server.measure.custom.ws.UpdateAction.PARAM_VALUE;
+import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations;
+
+@Category(DbTests.class)
+public class UpdateActionTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone();
+  @ClassRule
+  public static DbTester db = new DbTester();
+  @ClassRule
+  public static EsTester es = new EsTester().addDefinitions(new UserIndexDefinition(new Settings()));
+  DbClient dbClient;
+  DbSession dbSession;
+  System2 system;
+  WsTester ws;
+
+  @BeforeClass
+  public static void setUpClass() throws Exception {
+    es.putDocuments(UserIndexDefinition.INDEX, UserIndexDefinition.TYPE_USER, new UserDoc()
+      .setLogin("login")
+      .setName("Login")
+      .setEmail("login@login.com")
+      .setActive(true));
+  }
+
+  @Before
+  public void setUp() throws Exception {
+    dbClient = new DbClient(db.database(), db.myBatis(), new CustomMeasureDao(), new ComponentDao(), new MetricDao());
+    dbSession = dbClient.openSession(false);
+    db.truncateTables();
+    system = mock(System2.class);
+    CustomMeasureValidator validator = new CustomMeasureValidator(newFullTypeValidations());
+    ws = new WsTester(new CustomMeasuresWs(new UpdateAction(dbClient, userSessionRule, system, validator, new CustomMeasureJsonWriter(new UserJsonWriter(userSessionRule)),
+      new UserIndex(es.client()))));
+    userSessionRule.login("login").setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+  }
+
+  @After
+  public void tearDown() {
+    dbSession.close();
+  }
+
+  @Test
+  public void update_text_value_and_description_in_db() throws Exception {
+    MetricDto metric = insertNewMetric(ValueType.STRING);
+    ComponentDto component = insertNewProject("project-uuid");
+    CustomMeasureDto customMeasure = newCustomMeasure(component, metric)
+      .setDescription("custom-measure-description")
+      .setTextValue("text-measure-value");
+    dbClient.customMeasureDao().insert(dbSession, customMeasure);
+    dbSession.commit();
+    when(system.now()).thenReturn(123_456_789L);
+
+    ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION)
+      .setParam(PARAM_ID, String.valueOf(customMeasure.getId()))
+      .setParam(PARAM_DESCRIPTION, "new-custom-measure-description")
+      .setParam(PARAM_VALUE, "new-text-measure-value")
+      .execute();
+
+    CustomMeasureDto updatedCustomMeasure = dbClient.customMeasureDao().selectById(dbSession, customMeasure.getId());
+    assertThat(updatedCustomMeasure.getTextValue()).isEqualTo("new-text-measure-value");
+    assertThat(updatedCustomMeasure.getDescription()).isEqualTo("new-custom-measure-description");
+    assertThat(updatedCustomMeasure.getUpdatedAt()).isEqualTo(123_456_789L);
+    assertThat(customMeasure.getCreatedAt()).isEqualTo(updatedCustomMeasure.getCreatedAt());
+  }
+
+  @Test
+  public void update_double_value_and_description_in_db() throws Exception {
+    MetricDto metric = insertNewMetric(ValueType.INT);
+    ComponentDto component = insertNewProject("project-uuid");
+    CustomMeasureDto customMeasure = newCustomMeasure(component, metric)
+      .setDescription("custom-measure-description")
+      .setValue(42d);
+    dbClient.customMeasureDao().insert(dbSession, customMeasure);
+    dbSession.commit();
+
+    ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION)
+      .setParam(PARAM_ID, String.valueOf(customMeasure.getId()))
+      .setParam(PARAM_DESCRIPTION, "new-custom-measure-description")
+      .setParam(PARAM_VALUE, "1984")
+      .execute();
+
+    CustomMeasureDto updatedCustomMeasure = dbClient.customMeasureDao().selectById(dbSession, customMeasure.getId());
+    assertThat(updatedCustomMeasure.getValue()).isCloseTo(1984d, offset(0.01d));
+    assertThat(updatedCustomMeasure.getDescription()).isEqualTo("new-custom-measure-description");
+    assertThat(customMeasure.getCreatedAt()).isEqualTo(updatedCustomMeasure.getCreatedAt());
+  }
+
+  @Test
+  public void returns_full_object_in_response() throws Exception {
+    MetricDto metric = MetricTesting.newMetricDto().setEnabled(true)
+      .setValueType(ValueType.STRING.name())
+      .setKey("metric-key");
+    dbClient.metricDao().insert(dbSession, metric);
+    ComponentDto component = ComponentTesting.newProjectDto("project-uuid").setKey("project-key");
+    dbClient.componentDao().insert(dbSession, component);
+    CustomMeasureDto customMeasure = newCustomMeasure(component, metric)
+      .setCreatedAt(100_000_000L)
+      .setDescription("custom-measure-description")
+      .setTextValue("text-measure-value");
+    dbClient.customMeasureDao().insert(dbSession, customMeasure);
+    dbSession.commit();
+    when(system.now()).thenReturn(123_456_789L);
+
+    WsTester.Result response = ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION)
+      .setParam(PARAM_ID, String.valueOf(customMeasure.getId()))
+      .setParam(PARAM_DESCRIPTION, "new-custom-measure-description")
+      .setParam(PARAM_VALUE, "new-text-measure-value")
+      .execute();
+
+    response.assertJson(getClass(), "custom-measure.json");
+    String responseAsString = response.outputAsString();
+    assertThat(responseAsString).matches(String.format(".*\"id\"\\s*:\\s*\"%s\".*", customMeasure.getId()));
+    assertThat(responseAsString).matches(String.format(".*\"id\"\\s*:\\s*\"%s\".*", metric.getId()));
+    assertThat(responseAsString).matches(".*createdAt.*updatedAt.*");
+  }
+
+  @Test
+  public void update_value_only() throws Exception {
+    MetricDto metric = insertNewMetric(ValueType.STRING);
+    ComponentDto component = insertNewProject("project-uuid");
+    CustomMeasureDto customMeasure = newCustomMeasure(component, metric)
+      .setDescription("custom-measure-description")
+      .setTextValue("text-measure-value");
+    dbClient.customMeasureDao().insert(dbSession, customMeasure);
+    dbSession.commit();
+    when(system.now()).thenReturn(123_456_789L);
+
+    ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION)
+      .setParam(PARAM_ID, String.valueOf(customMeasure.getId()))
+      .setParam(PARAM_DESCRIPTION, "new-custom-measure-description")
+      .execute();
+
+    CustomMeasureDto updatedCustomMeasure = dbClient.customMeasureDao().selectById(dbSession, customMeasure.getId());
+    assertThat(updatedCustomMeasure.getTextValue()).isEqualTo("text-measure-value");
+    assertThat(updatedCustomMeasure.getDescription()).isEqualTo("new-custom-measure-description");
+    assertThat(updatedCustomMeasure.getUpdatedAt()).isEqualTo(123_456_789L);
+    assertThat(customMeasure.getCreatedAt()).isEqualTo(updatedCustomMeasure.getCreatedAt());
+  }
+
+  @Test
+  public void update_description_only() throws Exception {
+    MetricDto metric = insertNewMetric(ValueType.STRING);
+    ComponentDto component = insertNewProject("project-uuid");
+    CustomMeasureDto customMeasure = newCustomMeasure(component, metric)
+      .setMetricId(metric.getId())
+      .setComponentId(component.getId())
+      .setComponentUuid(component.uuid())
+      .setCreatedAt(system.now())
+      .setDescription("custom-measure-description")
+      .setTextValue("text-measure-value");
+    dbClient.customMeasureDao().insert(dbSession, customMeasure);
+    dbSession.commit();
+    when(system.now()).thenReturn(123_456_789L);
+
+    ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION)
+      .setParam(PARAM_ID, String.valueOf(customMeasure.getId()))
+      .setParam(PARAM_VALUE, "new-text-measure-value")
+      .execute();
+
+    CustomMeasureDto updatedCustomMeasure = dbClient.customMeasureDao().selectById(dbSession, customMeasure.getId());
+    assertThat(updatedCustomMeasure.getTextValue()).isEqualTo("new-text-measure-value");
+    assertThat(updatedCustomMeasure.getDescription()).isEqualTo("custom-measure-description");
+    assertThat(updatedCustomMeasure.getUpdatedAt()).isEqualTo(123_456_789L);
+    assertThat(customMeasure.getCreatedAt()).isEqualTo(updatedCustomMeasure.getCreatedAt());
+  }
+
+  @Test
+  public void fail_if_get_request() throws Exception {
+    expectedException.expect(ServerException.class);
+
+    ws.newGetRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION)
+      .setParam(PARAM_ID, "42")
+      .setParam(PARAM_DESCRIPTION, "new-custom-measure-description")
+      .setParam(PARAM_VALUE, "1984")
+      .execute();
+  }
+
+  @Test
+  public void fail_if_not_in_db() throws Exception {
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("Custom measure '42' not found.");
+
+    ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION)
+      .setParam(PARAM_ID, "42")
+      .setParam(PARAM_DESCRIPTION, "new-custom-measure-description")
+      .setParam(PARAM_VALUE, "1984")
+      .execute();
+  }
+
+  @Test
+  public void fail_if_insufficient_privileges() throws Exception {
+    userSessionRule.login("login").setGlobalPermissions(GlobalPermissions.SCAN_EXECUTION);
+    expectedException.expect(ForbiddenException.class);
+    MetricDto metric = MetricTesting.newMetricDto().setEnabled(true).setValueType(ValueType.STRING.name());
+    dbClient.metricDao().insert(dbSession, metric);
+    ComponentDto component = ComponentTesting.newProjectDto("project-uuid");
+    dbClient.componentDao().insert(dbSession, component);
+    CustomMeasureDto customMeasure = newCustomMeasureDto()
+      .setMetricId(metric.getId())
+      .setComponentId(component.getId())
+      .setComponentUuid(component.uuid())
+      .setCreatedAt(system.now())
+      .setDescription("custom-measure-description")
+      .setTextValue("text-measure-value");
+    dbClient.customMeasureDao().insert(dbSession, customMeasure);
+    dbSession.commit();
+
+    ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION)
+      .setParam(PARAM_ID, String.valueOf(customMeasure.getId()))
+      .setParam(PARAM_DESCRIPTION, "new-custom-measure-description")
+      .setParam(PARAM_VALUE, "1984")
+      .execute();
+  }
+
+  @Test
+  public void fail_if_custom_measure_id_is_missing_in_request() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+
+    ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION)
+      .setParam(PARAM_DESCRIPTION, "new-custom-measure-description")
+      .setParam(PARAM_VALUE, "1984")
+      .execute();
+  }
+
+  @Test
+  public void fail_if_custom_measure_value_and_description_are_missing_in_request() throws Exception {
+    expectedException.expect(IllegalArgumentException.class);
+
+    ws.newPostRequest(CustomMeasuresWs.ENDPOINT, UpdateAction.ACTION)
+      .setParam(PARAM_ID, "42")
+      .execute();
+  }
+
+  private MetricDto insertNewMetric(ValueType metricType) {
+    MetricDto metric = MetricTesting.newMetricDto().setEnabled(true).setValueType(metricType.name());
+    dbClient.metricDao().insert(dbSession, metric);
+
+    return metric;
+  }
+
+  private ComponentDto insertNewProject(String uuid) {
+    ComponentDto component = ComponentTesting.newProjectDto(uuid);
+    dbClient.componentDao().insert(dbSession, component);
+    return component;
+  }
+
+  private CustomMeasureDto newCustomMeasure(ComponentDto project, MetricDto metric) {
+    return newCustomMeasureDto()
+      .setMetricId(metric.getId())
+      .setComponentId(project.getId())
+      .setComponentUuid(project.uuid())
+      .setCreatedAt(system.now());
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/util/TypeValidationsTesting.java b/server/sonar-server/src/test/java/org/sonar/server/util/TypeValidationsTesting.java
new file mode 100644 (file)
index 0000000..a4e31d7
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.util;
+
+import java.util.Arrays;
+
+public class TypeValidationsTesting {
+  private TypeValidationsTesting() {
+    // utility class
+  }
+
+  public static TypeValidations newFullTypeValidations() {
+    return new TypeValidations(Arrays.asList(
+      new BooleanTypeValidation(),
+      new IntegerTypeValidation(),
+      new LongTypeValidation(),
+      new FloatTypeValidation(),
+      new StringTypeValidation(),
+      new StringListTypeValidation(),
+      new MetricLevelTypeValidation()
+      ));
+  }
+}
index 97ddaa1775ffd07f0055d9cd5b6e2d17b03b85d8..ab5e1c1200349a47e9ffe626e35f102d227173e9 100644 (file)
@@ -6,5 +6,11 @@
     "type": "STRING"
   },
   "value": "custom-measure-free-text",
-  "description": "custom-measure-description"
+  "description": "custom-measure-description",
+  "user": {
+    "active": true,
+    "email": "login@login.com",
+    "login": "login",
+    "name": "Login"
+  }
 }
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/UpdateActionTest/custom-measure.json b/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/UpdateActionTest/custom-measure.json
new file mode 100644 (file)
index 0000000..a66612b
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "projectId": "project-uuid",
+  "projectKey": "project-key",
+  "metric": {
+    "key": "metric-key",
+    "type": "STRING"
+  },
+  "value": "new-text-measure-value",
+  "description": "new-custom-measure-description",
+  "user": {
+    "active": true,
+    "email": "login@login.com",
+    "login": "login",
+    "name": "Login"
+  }
+}
index bfc7eb929ea4010b68447b85aa622c7693f44379..444753996da777e57909f82f4440f401857e4760 100644 (file)
@@ -24,7 +24,11 @@ import java.util.List;
 import org.apache.ibatis.annotations.Param;
 
 public interface CustomMeasureMapper {
-  void insert(CustomMeasureDto customMeasureDto);
+  void insert(CustomMeasureDto customMeasure);
+
+  void update(CustomMeasureDto customMeasure);
+
+  void delete(long id);
 
   void deleteByMetricIds(@Param("metricIds") List<Integer> metricIds);
 
@@ -34,7 +38,5 @@ public interface CustomMeasureMapper {
 
   List<CustomMeasureDto> selectByComponentUuid(String s);
 
-  void delete(long id);
-
   int countByComponentIdAndMetricId(@Param("componentUuid") String componentUuid, @Param("metricId") int metricId);
 }
index 12a61bdf35917b09cffe148aa13f819886b42abe..f9062d192d6bfe67243429f567148c4d26c1fb31 100644 (file)
     )
   </insert>
 
+  <update id="update" parameterType="CustomMeasure">
+    update manual_measures
+    set value = #{value, jdbcType=DOUBLE},
+    text_value = #{textValue, jdbcType=VARCHAR},
+    description = #{description, jdbcType=VARCHAR},
+    user_login = #{userLogin, jdbcType=VARCHAR},
+    updated_at = #{updatedAt, jdbcType=BIGINT}
+    where id = #{id}
+  </update>
+
   <delete id="deleteByMetricIds">
     delete from manual_measures
     where metric_id in