summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/custom/persistence/CustomMeasureDao.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CreateAction.java139
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java20
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValidator.java98
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValueDescription.java71
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModule.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/UpdateAction.java143
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java35
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasureValidatorTest.java116
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModuleTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsTest.java18
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java321
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/util/TypeValidationsTesting.java41
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/CreateActionTest/custom-measure.json8
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/UpdateActionTest/custom-measure.json16
-rw-r--r--sonar-core/src/main/java/org/sonar/core/measure/custom/db/CustomMeasureMapper.java8
-rw-r--r--sonar-core/src/main/resources/org/sonar/core/measure/custom/db/CustomMeasureMapper.xml10
17 files changed, 906 insertions, 150 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/persistence/CustomMeasureDao.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/persistence/CustomMeasureDao.java
index ea62814d830..6a454ee2720 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/persistence/CustomMeasureDao.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/persistence/CustomMeasureDao.java
@@ -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;
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CreateAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CreateAction.java
index 4586689a37e..af5c91f22ef 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CreateAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CreateAction.java
@@ -20,10 +20,7 @@
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());
- }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java
index cbb6211c5fb..0f359ff9bc1 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java
@@ -20,17 +20,22 @@
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
index 00000000000..2d38586da01
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValidator.java
@@ -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
index 00000000000..2970556b81e
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureValueDescription.java
@@ -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());
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModule.java
index ae49ecf5007..51270b190fd 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModule.java
@@ -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
index 00000000000..3dbf8a4e4c9
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/custom/ws/UpdateAction.java
@@ -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());
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java
index 6fccdd1153d..fdc8581c9f0 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CreateActionTest.java
@@ -20,16 +20,17 @@
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
index 00000000000..c546022eee8
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasureValidatorTest.java
@@ -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);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModuleTest.java
index 098819b9cb0..7a65f3a7b07 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsModuleTest.java
@@ -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);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsTest.java
index 3737f9236ed..7ed41878f21 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/CustomMeasuresWsTest.java
@@ -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
index 00000000000..188dd68ea11
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/custom/ws/UpdateActionTest.java
@@ -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
index 00000000000..a4e31d7d8f2
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/util/TypeValidationsTesting.java
@@ -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()
+ ));
+ }
+}
diff --git a/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/CreateActionTest/custom-measure.json b/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/CreateActionTest/custom-measure.json
index 97ddaa1775f..ab5e1c12003 100644
--- a/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/CreateActionTest/custom-measure.json
+++ b/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/CreateActionTest/custom-measure.json
@@ -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
index 00000000000..a66612bb9b0
--- /dev/null
+++ b/server/sonar-server/src/test/resources/org/sonar/server/measure/custom/ws/UpdateActionTest/custom-measure.json
@@ -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"
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/measure/custom/db/CustomMeasureMapper.java b/sonar-core/src/main/java/org/sonar/core/measure/custom/db/CustomMeasureMapper.java
index bfc7eb929ea..444753996da 100644
--- a/sonar-core/src/main/java/org/sonar/core/measure/custom/db/CustomMeasureMapper.java
+++ b/sonar-core/src/main/java/org/sonar/core/measure/custom/db/CustomMeasureMapper.java
@@ -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);
}
diff --git a/sonar-core/src/main/resources/org/sonar/core/measure/custom/db/CustomMeasureMapper.xml b/sonar-core/src/main/resources/org/sonar/core/measure/custom/db/CustomMeasureMapper.xml
index 12a61bdf359..f9062d192d6 100644
--- a/sonar-core/src/main/resources/org/sonar/core/measure/custom/db/CustomMeasureMapper.xml
+++ b/sonar-core/src/main/resources/org/sonar/core/measure/custom/db/CustomMeasureMapper.xml
@@ -47,6 +47,16 @@
)
</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