]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8466 Create WS api/project_analyses/update_event 1446/head
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 9 Dec 2016 08:27:24 +0000 (09:27 +0100)
committerTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Fri, 9 Dec 2016 15:26:45 +0000 (16:26 +0100)
18 files changed:
server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ProjectAnalysisModule.java
server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/CreateEventAction.java
server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/DeleteEventAction.java
server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/EventValidator.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/UpdateEventAction.java [new file with mode: 0644]
server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/update_event-example.json [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ProjectAnalysisModuleTest.java
server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/CreateEventActionTest.java
server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/DeleteEventActionTest.java
server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/UpdateEventActionTest.java [new file with mode: 0644]
sonar-db/src/main/java/org/sonar/db/event/EventDao.java
sonar-db/src/main/java/org/sonar/db/event/EventMapper.java
sonar-db/src/main/java/org/sonar/db/event/EventValidator.java
sonar-db/src/main/resources/org/sonar/db/event/EventMapper.xml
sonar-db/src/test/java/org/sonar/db/event/EventDaoTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/UpdateEventRequest.java [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-projectanalyses.proto
sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/UpdateEventRequestTest.java [new file with mode: 0644]

index 61f5ecd3052b274b954c4706f8d8d3b7c5222ae3..e4c43303e225b688ee38bdb37003f91fbeb4270a 100644 (file)
@@ -24,6 +24,7 @@ import org.sonar.core.platform.Module;
 import org.sonar.server.projectanalysis.ws.CreateEventAction;
 import org.sonar.server.projectanalysis.ws.DeleteEventAction;
 import org.sonar.server.projectanalysis.ws.ProjectAnalysesWs;
+import org.sonar.server.projectanalysis.ws.UpdateEventAction;
 
 public class ProjectAnalysisModule extends Module {
 
@@ -33,6 +34,7 @@ public class ProjectAnalysisModule extends Module {
       ProjectAnalysesWs.class,
       // actions
       CreateEventAction.class,
+      UpdateEventAction.class,
       DeleteEventAction.class);
   }
 
index 96d068e8c276913ec2b9b28516ecd8699d0f5aa1..cc5a422051f8471f4ab969e1c74c8d7be383d098 100644 (file)
@@ -205,7 +205,7 @@ public class CreateEventAction implements ProjectAnalysesWsAction {
         };
       case OTHER:
         return dbEvent -> {
-          throw new IllegalArgumentException(format("An 'other' event with the same name already exists on analysis '%s'", request.getAnalysis()));
+          throw new IllegalArgumentException(format("An '%s' event with the same name already exists on analysis '%s'", OTHER.getLabel(), request.getAnalysis()));
         };
       default:
         throw new IllegalStateException("Event category not handled: " + request.getCategory());
index ac6be518af132e6d75e284e0139074a8b88d5178..1fe95314717b9cea9ab5477fba37602f1f1ed988 100644 (file)
@@ -20,9 +20,6 @@
 
 package org.sonar.server.projectanalysis.ws;
 
-import com.google.common.base.Joiner;
-import com.google.common.collect.ImmutableSet;
-import java.util.Set;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
@@ -37,14 +34,11 @@ import org.sonarqube.ws.client.projectanalysis.DeleteEventRequest;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.String.format;
-import static org.sonarqube.ws.client.projectanalysis.EventCategory.OTHER;
+import static org.sonar.server.projectanalysis.ws.EventValidator.checkModifiable;
 import static org.sonarqube.ws.client.projectanalysis.EventCategory.VERSION;
 import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_EVENT;
 
 public class DeleteEventAction implements ProjectAnalysesWsAction {
-  private static final Set<String> AUTHORIZED_CATEGORIES = ImmutableSet.of(VERSION.getLabel(), OTHER.getLabel());
-  private static final String AUTHORIZED_CATEGORIES_INLINED = Joiner.on(", ").join(AUTHORIZED_CATEGORIES);
-
   private final DbClient dbClient;
   private final UserSession userSession;
 
@@ -83,8 +77,7 @@ public class DeleteEventAction implements ProjectAnalysesWsAction {
       EventDto dbEvent = dbClient.eventDao().selectByUuid(dbSession, request.getEvent())
         .orElseThrow(() -> new NotFoundException(format("Event '%s' not found", request.getEvent())));
       checkPermissions(dbEvent);
-      checkArgument(AUTHORIZED_CATEGORIES.contains(dbEvent.getCategory()), "Event of category '%s' cannot be deleted. Authorized categories: %s", dbEvent.getCategory(),
-        AUTHORIZED_CATEGORIES_INLINED);
+      checkModifiable().accept(dbEvent);
 
       deleteEvent(dbSession, dbEvent);
     }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/EventValidator.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/EventValidator.java
new file mode 100644 (file)
index 0000000..52604c0
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.projectanalysis.ws;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import java.util.Set;
+import java.util.function.Consumer;
+import org.sonar.db.event.EventDto;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+class EventValidator {
+  private static final Set<String> AUTHORIZED_CATEGORIES = ImmutableSet.of("Version", "Other");
+  private static final String AUTHORIZED_CATEGORIES_INLINED = Joiner.on(", ").join(AUTHORIZED_CATEGORIES);
+
+  static Consumer<EventDto> checkModifiable() {
+    return event -> checkArgument(AUTHORIZED_CATEGORIES.contains(event.getCategory()),
+      "Event of category '%s' cannot be modified. Authorized categories: %s",
+      event.getCategory(), AUTHORIZED_CATEGORIES_INLINED);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/UpdateEventAction.java b/server/sonar-server/src/main/java/org/sonar/server/projectanalysis/ws/UpdateEventAction.java
new file mode 100644 (file)
index 0000000..823c057
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.projectanalysis.ws;
+
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+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.web.UserRole;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.event.EventDto;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.user.UserSession;
+import org.sonarqube.ws.ProjectAnalyses.Event;
+import org.sonarqube.ws.ProjectAnalyses.UpdateEventResponse;
+import org.sonarqube.ws.client.projectanalysis.UpdateEventRequest;
+
+import static java.lang.String.format;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.server.projectanalysis.ws.EventValidator.checkModifiable;
+import static org.sonar.server.ws.WsUtils.writeProtobuf;
+import static org.sonarqube.ws.client.projectanalysis.EventCategory.VERSION;
+import static org.sonarqube.ws.client.projectanalysis.EventCategory.fromLabel;
+import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_DESCRIPTION;
+import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_EVENT;
+import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_NAME;
+
+public class UpdateEventAction implements ProjectAnalysesWsAction {
+  private final DbClient dbClient;
+  private final UserSession userSession;
+
+  public UpdateEventAction(DbClient dbClient, UserSession userSession) {
+    this.dbClient = dbClient;
+    this.userSession = userSession;
+  }
+
+  @Override
+  public void define(WebService.NewController context) {
+    WebService.NewAction action = context.createAction("update_event")
+      .setDescription("Update a project analysis event.<br>" +
+        "Requires one of the following permissions:" +
+        "<ul>" +
+        "  <li>'Administer System'</li>" +
+        "  <li>'Administer' rights on the specified project</li>" +
+        "</ul>")
+      .setSince("6.3")
+      .setPost(true)
+      .setResponseExample(getClass().getResource("update_event-example.json"))
+      .setHandler(this);
+
+    action.createParam(PARAM_EVENT)
+      .setDescription("Event key")
+      .setRequired(true);
+
+    action.createParam(PARAM_NAME)
+      .setDescription("New name")
+      .setExampleValue("5.6");
+
+    action.createParam(PARAM_DESCRIPTION)
+      .setDescription("New description")
+      .setExampleValue("Version released");
+  }
+
+  @Override
+  public void handle(Request httpRequest, Response httpResponse) throws Exception {
+    Stream.of(httpRequest)
+      .map(toUpdateEventRequest())
+      .map(this::doHandle)
+      .forEach(wsResponse -> writeProtobuf(wsResponse, httpRequest, httpResponse));
+  }
+
+  private UpdateEventResponse doHandle(UpdateEventRequest request) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      return Stream
+        .of(getDbEvent(dbSession, request))
+        .peek(checkPermissions())
+        .peek(checkModifiable())
+        .map(updateNameAndDescription(request))
+        .peek(checkNonConflictingOtherEvents(dbSession))
+        .peek(updateInDb(dbSession))
+        .map(toWsResponse())
+        .findAny()
+        .orElseThrow(() -> new IllegalStateException("Event not found"));
+    }
+  }
+
+  private Consumer<EventDto> updateInDb(DbSession dbSession) {
+    return event -> {
+      dbClient.eventDao().update(dbSession, event.getUuid(), event.getName(), event.getDescription());
+      if (VERSION.getLabel().equals(event.getCategory())) {
+        SnapshotDto analysis = getAnalysis(dbSession, event);
+        dbClient.snapshotDao().updateVersion(dbSession, analysis.getUuid(), event.getName());
+      }
+      dbSession.commit();
+    };
+  }
+
+  private EventDto getDbEvent(DbSession dbSession, UpdateEventRequest request) {
+    return dbClient.eventDao().selectByUuid(dbSession, request.getEvent())
+      .orElseThrow(() -> new NotFoundException(format("Event '%s' not found", request.getEvent())));
+  }
+
+  private Consumer<EventDto> checkPermissions() {
+    return event -> userSession.checkComponentUuidPermission(UserRole.ADMIN, event.getComponentUuid());
+  }
+
+  private Consumer<EventDto> checkNonConflictingOtherEvents(DbSession dbSession) {
+    return candidateEvent -> {
+      List<EventDto> dbEvents = dbClient.eventDao().selectByAnalysisUuid(dbSession, candidateEvent.getAnalysisUuid());
+      Predicate<EventDto> otherEventWithSameName = otherEvent -> !candidateEvent.getUuid().equals(otherEvent.getUuid()) && otherEvent.getName().equals(candidateEvent.getName());
+      dbEvents.stream()
+        .filter(otherEventWithSameName)
+        .findAny()
+        .ifPresent(event -> {
+          throw new IllegalArgumentException(format("An '%s' event with the same name already exists on analysis '%s'",
+            candidateEvent.getCategory(),
+            candidateEvent.getAnalysisUuid()));
+        });
+    };
+  }
+
+  private SnapshotDto getAnalysis(DbSession dbSession, EventDto event) {
+    return dbClient.snapshotDao().selectByUuid(dbSession, event.getAnalysisUuid())
+      .orElseThrow(() -> new IllegalStateException(format("Analysis '%s' is not found", event.getAnalysisUuid())));
+  }
+
+  private static Function<EventDto, EventDto> updateNameAndDescription(UpdateEventRequest request) {
+    return event -> {
+      setNullable(request.getName(), event::setName);
+      setNullable(request.getDescription(), event::setDescription);
+      return event;
+    };
+  }
+
+  private static Function<EventDto, UpdateEventResponse> toWsResponse() {
+    return dbEvent -> {
+      Event.Builder wsEvent = Event.newBuilder()
+        .setKey(dbEvent.getUuid())
+        .setCategory(fromLabel(dbEvent.getCategory()).name())
+        .setAnalysis(dbEvent.getAnalysisUuid());
+      setNullable(dbEvent.getName(), wsEvent::setName);
+      setNullable(dbEvent.getDescription(), wsEvent::setDescription);
+
+      return UpdateEventResponse.newBuilder().setEvent(wsEvent).build();
+    };
+  }
+
+  private static Function<Request, UpdateEventRequest> toUpdateEventRequest() {
+    return request -> new UpdateEventRequest(
+      request.mandatoryParam(PARAM_EVENT),
+      request.param(PARAM_NAME),
+      request.param(PARAM_DESCRIPTION));
+  }
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/update_event-example.json b/server/sonar-server/src/main/resources/org/sonar/server/projectanalysis/ws/update_event-example.json
new file mode 100644 (file)
index 0000000..14e486d
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "event": {
+    "analysis": "A2",
+    "key": "E1",
+    "category": "OTHER",
+    "name": "My Custom Event",
+    "description": "event description"
+  }
+}
index 1f81537fee83a365e05f117d8cd89a78c28b9e58..bd1018524f6b4043251fa2dd456f765a89008e56 100644 (file)
@@ -31,6 +31,6 @@ public class ProjectAnalysisModuleTest {
   public void verify_count_of_added_components() {
     ComponentContainer container = new ComponentContainer();
     new ProjectAnalysisModule().configure(container);
-    assertThat(container.size()).isEqualTo(2 + 3);
+    assertThat(container.size()).isEqualTo(2 + 4);
   }
 }
index dd6ecaad45d6537358f5ac5f646eb05c82f58626..3070200a4012fbedaa12b836a2ac6c1972389ef1 100644 (file)
@@ -257,7 +257,7 @@ public class CreateEventActionTest {
     call(request);
 
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("An 'other' event with the same name already exists on analysis '" + analysis.getUuid() + "'");
+    expectedException.expectMessage("An 'Other' event with the same name already exists on analysis '" + analysis.getUuid() + "'");
 
     call(request.setName("Project Import"));
   }
index dcd4ffd4f46d581fd050712b6d0566abc22324d5..10cd80f445b43dc1c15c0e1ee066673d9a0dc342 100644 (file)
@@ -113,7 +113,7 @@ public class DeleteEventActionTest {
     db.events().insertEvent(newEvent(analysis).setUuid("E1").setCategory("Profile"));
 
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Event of category 'Profile' cannot be deleted. Authorized categories: Version, Other");
+    expectedException.expectMessage("Event of category 'Profile' cannot be modified. Authorized categories: Version, Other");
 
     call("E1");
   }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/UpdateEventActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/projectanalysis/ws/UpdateEventActionTest.java
new file mode 100644 (file)
index 0000000..f9ec5d1
--- /dev/null
@@ -0,0 +1,259 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.projectanalysis.ws;
+
+import com.google.common.base.Throwables;
+import java.io.IOException;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.api.utils.System2;
+import org.sonar.api.web.UserRole;
+import org.sonar.core.permission.GlobalPermissions;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.event.EventDto;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.MediaTypes;
+import org.sonarqube.ws.ProjectAnalyses;
+import org.sonarqube.ws.ProjectAnalyses.UpdateEventResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.core.util.Protobuf.setNullable;
+import static org.sonar.db.component.ComponentTesting.newProjectDto;
+import static org.sonar.db.component.SnapshotTesting.newAnalysis;
+import static org.sonar.db.event.EventTesting.newEvent;
+import static org.sonar.test.JsonAssert.assertJson;
+import static org.sonarqube.ws.client.WsRequest.Method.POST;
+import static org.sonarqube.ws.client.projectanalysis.EventCategory.OTHER;
+import static org.sonarqube.ws.client.projectanalysis.EventCategory.VERSION;
+import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_DESCRIPTION;
+import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_EVENT;
+import static org.sonarqube.ws.client.projectanalysis.ProjectAnalysesWsParameters.PARAM_NAME;
+
+public class UpdateEventActionTest {
+  @Rule
+  public UserSessionRule userSession = UserSessionRule.standalone().setGlobalPermissions(GlobalPermissions.SYSTEM_ADMIN);
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+  @Rule
+  public DbTester db = DbTester.create(System2.INSTANCE);
+  private DbClient dbClient = db.getDbClient();
+  private DbSession dbSession = db.getSession();
+
+  private WsActionTester ws = new WsActionTester(new UpdateEventAction(dbClient, userSession));
+
+  @Test
+  public void json_example() {
+    ComponentDto project = db.components().insertProject();
+    SnapshotDto analysis = db.components().insertSnapshot(newAnalysis(project).setUuid("A2"));
+    db.events().insertEvent(newEvent(analysis)
+      .setUuid("E1")
+      .setCategory(OTHER.getLabel())
+      .setName("Original Name")
+      .setDescription("Original Description"));
+
+    String result = ws.newRequest()
+      .setParam(PARAM_EVENT, "E1")
+      .setParam(PARAM_NAME, "My Custom Event")
+      .setParam(PARAM_DESCRIPTION, "event description")
+      .execute().getInput();
+
+    assertJson(result).isSimilarTo(getClass().getResource("update_event-example.json"));
+  }
+
+  @Test
+  public void update_name_and_description_in_db() {
+    SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto());
+    EventDto originalEvent = db.events().insertEvent(newEvent(analysis).setUuid("E1").setName("Original Name").setDescription("Original Description"));
+
+    call("E1", "name", "description");
+
+    EventDto newEvent = dbClient.eventDao().selectByUuid(dbSession, "E1").get();
+    assertThat(newEvent.getName()).isEqualTo("name");
+    assertThat(newEvent.getDescription()).isEqualTo("description");
+    assertThat(newEvent.getCategory()).isEqualTo(originalEvent.getCategory());
+    assertThat(newEvent.getDate()).isEqualTo(originalEvent.getDate());
+    assertThat(newEvent.getCreatedAt()).isEqualTo(originalEvent.getCreatedAt());
+  }
+
+  @Test
+  public void ws_response_with_updated_name_and_description() {
+    SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto());
+    EventDto originalEvent = db.events().insertEvent(newEvent(analysis).setUuid("E1").setName("Original Name").setDescription("Original Description"));
+
+    ProjectAnalyses.Event result = call("E1", "name", "description").getEvent();
+
+    assertThat(result.getName()).isEqualTo("name");
+    assertThat(result.getDescription()).isEqualTo("description");
+    assertThat(result.getCategory()).isEqualTo(OTHER.name());
+    assertThat(result.getAnalysis()).isEqualTo(originalEvent.getAnalysisUuid());
+    assertThat(result.getKey()).isEqualTo("E1");
+  }
+
+  @Test
+  public void update_VERSION_event_update_analysis_version() {
+    ComponentDto project = db.components().insertProject();
+    SnapshotDto analysis = db.components().insertSnapshot(newAnalysis(project).setVersion("5.6"));
+    db.events().insertEvent(newEvent(analysis).setUuid("E1").setCategory(VERSION.getLabel()));
+
+    call("E1", "6.3", null);
+
+    SnapshotDto updatedAnalysis = dbClient.snapshotDao().selectByUuid(dbSession, analysis.getUuid()).get();
+    assertThat(updatedAnalysis.getVersion()).isEqualTo("6.3");
+  }
+
+  @Test
+  public void update_OTHER_event_does_not_update_analysis_version() {
+    ComponentDto project = db.components().insertProject();
+    SnapshotDto analysis = db.components().insertSnapshot(newAnalysis(project).setVersion("5.6"));
+    db.events().insertEvent(newEvent(analysis).setUuid("E1").setCategory(OTHER.getLabel()));
+
+    call("E1", "6.3", null);
+
+    SnapshotDto updatedAnalysis = dbClient.snapshotDao().selectByUuid(dbSession, analysis.getUuid()).get();
+    assertThat(updatedAnalysis.getVersion()).isEqualTo("5.6");
+  }
+
+  @Test
+  public void update_name_only_in_db() {
+    SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto());
+    EventDto originalEvent = db.events().insertEvent(newEvent(analysis).setUuid("E1").setName("Original Name").setDescription("Original Description"));
+
+    call("E1", "name", null);
+
+    EventDto newEvent = dbClient.eventDao().selectByUuid(dbSession, "E1").get();
+    assertThat(newEvent.getName()).isEqualTo("name");
+    assertThat(newEvent.getDescription()).isEqualTo(originalEvent.getDescription());
+  }
+
+  @Test
+  public void update_as_project_admin() {
+    SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto("P1"));
+    db.events().insertEvent(newEvent(analysis).setUuid("E1").setName("Original Name").setDescription("Original Description"));
+    userSession.anonymous().addProjectUuidPermissions(UserRole.ADMIN, "P1");
+
+    call("E1", "name", "description");
+
+    EventDto newEvent = dbClient.eventDao().selectByUuid(dbSession, "E1").get();
+    assertThat(newEvent.getName()).isEqualTo("name");
+    assertThat(newEvent.getDescription()).isEqualTo("description");
+  }
+
+  @Test
+  public void update_description_only_in_db() {
+    SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto());
+    EventDto originalEvent = db.events().insertEvent(newEvent(analysis).setUuid("E1").setName("Original Name").setDescription("Original Description"));
+
+    call("E1", null, "description");
+
+    EventDto newEvent = dbClient.eventDao().selectByUuid(dbSession, "E1").get();
+    assertThat(newEvent.getName()).isEqualTo(originalEvent.getName());
+    assertThat(newEvent.getDescription()).isEqualTo("description");
+  }
+
+  @Test
+  public void ws_definition() {
+    WebService.Action definition = ws.getDef();
+
+    assertThat(definition.key()).isEqualTo("update_event");
+    assertThat(definition.responseExampleAsString()).isNotEmpty();
+    assertThat(definition.isPost()).isTrue();
+    assertThat(definition.since()).isEqualTo("6.3");
+    assertThat(definition.param(PARAM_EVENT).isRequired()).isTrue();
+  }
+
+  @Test
+  public void fail_if_insufficient_permissions() {
+    SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto());
+    db.events().insertEvent(newEvent(analysis).setUuid("E1"));
+    userSession.anonymous();
+
+    expectedException.expect(ForbiddenException.class);
+
+    call("E1", "name", "description");
+  }
+
+  @Test
+  public void fail_if_event_is_not_found() {
+    expectedException.expect(NotFoundException.class);
+    expectedException.expectMessage("Event 'E42' not found");
+
+    call("E42", "name", "description");
+  }
+
+  @Test
+  public void fail_if_no_name_nor_description() {
+    SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto());
+    db.events().insertEvent(newEvent(analysis).setUuid("E1"));
+
+    expectedException.expect(IllegalArgumentException.class);
+
+    call("E1", null, null);
+  }
+
+  @Test
+  public void fail_if_category_other_than_other_or_version() {
+    SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto());
+    db.events().insertEvent(newEvent(analysis).setUuid("E1").setCategory("Profile"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Event of category 'Profile' cannot be modified. Authorized categories: Version, Other");
+
+    call("E1", "name", "description");
+  }
+
+  @Test
+  public void fail_if_other_event_with_same_name_on_same_analysis() {
+    SnapshotDto analysis = db.components().insertProjectAndSnapshot(newProjectDto());
+    db.events().insertEvent(newEvent(analysis).setUuid("E1").setCategory(OTHER.getLabel()).setName("E1 name"));
+    db.events().insertEvent(newEvent(analysis).setUuid("E2").setCategory(OTHER.getLabel()).setName("E2 name"));
+
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("An 'Other' event with the same name already exists on analysis '" + analysis.getUuid() + "'");
+
+    call("E2", "E1 name", "description");
+  }
+
+  private UpdateEventResponse call(@Nullable String eventUuid, @Nullable String name, @Nullable String description) {
+    TestRequest request = ws.newRequest()
+      .setMethod(POST.name())
+      .setMediaType(MediaTypes.PROTOBUF);
+    setNullable(eventUuid, e -> request.setParam(PARAM_EVENT, e));
+    setNullable(name, n -> request.setParam(PARAM_NAME, n));
+    setNullable(description, d -> request.setParam(PARAM_DESCRIPTION, d));
+
+    try {
+      return UpdateEventResponse.parseFrom(request.execute().getInputStream());
+    } catch (IOException e) {
+      throw Throwables.propagate(e);
+    }
+  }
+}
index 86a99504abd3e1cc11503f20dcbfc992c34b44b8..b62416907189b1a151e49d323cd83a3feeab8de2 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.db.event;
 
 import java.util.List;
 import java.util.Optional;
+import javax.annotation.Nullable;
 import org.sonar.db.Dao;
 import org.sonar.db.DbSession;
 
@@ -44,6 +45,10 @@ public class EventDao implements Dao {
     return dto;
   }
 
+  public void update(DbSession dbSession, String uuid, @Nullable String name, @Nullable String description) {
+    mapper(dbSession).update(uuid, name, description);
+  }
+
   public void delete(DbSession session, Long id) {
     mapper(session).deleteById(id);
   }
index 5c2f503035636c0f90e69f7d10419e73e31ed771..3a6db52c352f5d97059d240e703daca09db4be73 100644 (file)
@@ -20,6 +20,8 @@
 package org.sonar.db.event;
 
 import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.ibatis.annotations.Param;
 
 public interface EventMapper {
 
@@ -31,6 +33,8 @@ public interface EventMapper {
 
   void insert(EventDto dto);
 
+  void update(@Param("uuid") String uuid, @Param("name") @Nullable String name, @Param("description") @Nullable String description);
+
   void deleteById(long id);
 
   void deleteByUuid(String uuid);
index d69684950db5473c561d62d6d02fd81ac62760de..0044f29aad16b0d9af03548d826e546b4b56c273 100644 (file)
@@ -25,7 +25,7 @@ import javax.annotation.Nullable;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
-public class EventValidator {
+class EventValidator {
   private static final int MAX_NAME_LENGTH = 400;
   private static final int MAX_CATEGORY_LENGTH = 50;
   private static final int MAX_DESCRIPTION_LENGTH = 4000;
@@ -35,7 +35,7 @@ public class EventValidator {
   }
 
   @CheckForNull
-  public static String checkEventName(@Nullable String name) {
+  static String checkEventName(@Nullable String name) {
     if (name == null) {
       return null;
     }
@@ -45,7 +45,7 @@ public class EventValidator {
   }
 
   @CheckForNull
-  public static String checkEventCategory(@Nullable String category) {
+  static String checkEventCategory(@Nullable String category) {
     if (category == null) {
       return null;
     }
@@ -55,7 +55,7 @@ public class EventValidator {
   }
 
   @CheckForNull
-  public static String checkEventDescription(@Nullable String description) {
+  static String checkEventDescription(@Nullable String description) {
     if (description == null) {
       return null;
     }
@@ -63,5 +63,4 @@ public class EventValidator {
       description.length(), MAX_DESCRIPTION_LENGTH, description);
     return description;
   }
-
 }
index e9241e67c663685b957eccebbf095bed567226b7..bffc0b4b240d23eb3d21b98f9b6d7e36768f966a 100644 (file)
       #{createdAt, jdbcType=BIGINT})
   </insert>
 
+  <update id="update" parameterType="map">
+    update events
+    set name = #{name, jdbcType=VARCHAR},
+        description = #{description, jdbcType=VARCHAR}
+    where uuid = #{uuid}
+  </update>
+
   <delete id="deleteById" parameterType="Long">
     DELETE FROM events WHERE id=#{id}
   </delete>
index b5e2850b1ea9550043cc57499e0ea7a74292a327..120d44932788313deadb854e5559b5c03db8ea0e 100644 (file)
@@ -129,6 +129,18 @@ public class EventDaoTest {
     dbTester.assertDbUnit(getClass(), "insert-result.xml", new String[] {"id"}, "events");
   }
 
+  @Test
+  public void update_name_and_description() {
+    SnapshotDto analysis = dbTester.components().insertProjectAndSnapshot(newProjectDto());
+    dbTester.events().insertEvent(newEvent(analysis).setUuid("E1"));
+
+    underTest.update(dbSession, "E1", "New Name", "New Description");
+
+    EventDto result = dbClient.eventDao().selectByUuid(dbSession, "E1").get();
+    assertThat(result.getName()).isEqualTo("New Name");
+    assertThat(result.getDescription()).isEqualTo("New Description");
+  }
+
   @Test
   public void delete_by_id() {
     dbTester.prepareDbUnit(getClass(), "delete.xml");
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/UpdateEventRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/projectanalysis/UpdateEventRequest.java
new file mode 100644 (file)
index 0000000..9afeb37
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.projectanalysis;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public class UpdateEventRequest {
+  private final String event;
+  private final String name;
+  private final String description;
+
+  public UpdateEventRequest(String event, @Nullable String name, @Nullable String description) {
+    checkArgument(name != null || description != null, "Name or description is required");
+    this.event = requireNonNull(event, "Event key is required");
+    this.name = name;
+    this.description = description;
+  }
+
+  public String getEvent() {
+    return event;
+  }
+
+  @CheckForNull
+  public String getName() {
+    return name;
+  }
+
+  @CheckForNull
+  public String getDescription() {
+    return description;
+  }
+}
index 686027ad229e450583c57b0f4acb30d8df3c6042..b06aca0771372f9cacf8e582cabca90ad174a613 100644 (file)
@@ -29,6 +29,11 @@ message CreateEventResponse {
   optional Event event = 1;
 }
 
+// WS api/project_analyses/update_event
+message UpdateEventResponse {
+  optional Event event = 1;
+}
+
 message Event {
   optional string key = 1;
   optional string analysis = 2;
diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/UpdateEventRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/projectanalysis/UpdateEventRequestTest.java
new file mode 100644 (file)
index 0000000..43e05ad
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonarqube.ws.client.projectanalysis;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class UpdateEventRequestTest {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private UpdateEventRequest underTest;
+
+  @Test
+  public void request_with_name_only() {
+    underTest = new UpdateEventRequest("E1", "name", null);
+
+    assertThat(underTest.getEvent()).isEqualTo("E1");
+    assertThat(underTest.getName()).isEqualTo("name");
+    assertThat(underTest.getDescription()).isNull();
+  }
+
+  @Test
+  public void request_with_description_only() {
+    underTest = new UpdateEventRequest("E1", null , "description");
+
+    assertThat(underTest.getEvent()).isEqualTo("E1");
+    assertThat(underTest.getName()).isNull();
+    assertThat(underTest.getDescription()).isEqualTo("description");
+  }
+
+  @Test
+  public void request_with_all_parameters() {
+    underTest = new UpdateEventRequest("E1", "name", "description");
+
+    assertThat(underTest.getEvent()).isEqualTo("E1");
+    assertThat(underTest.getName()).isEqualTo("name");
+    assertThat(underTest.getDescription()).isEqualTo("description");
+  }
+
+  @Test
+  public void fail_if_null_event() {
+    expectedException.expect(NullPointerException.class);
+    expectedException.expectMessage("Event key is required");
+
+    new UpdateEventRequest(null, "name", "description");
+  }
+
+  @Test
+  public void fail_if_name_and_description_not_provided() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Name or description is required");
+
+    new UpdateEventRequest("E1", null, null);
+  }
+}