]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19340 Raise event whenever hotspots change in WS
authorAlain Kermis <alain.kermis@sonarsource.com>
Tue, 23 May 2023 08:46:57 +0000 (10:46 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 30 May 2023 20:02:52 +0000 (20:02 +0000)
15 files changed:
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventService.java [new file with mode: 0644]
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventServiceImpl.java [new file with mode: 0644]
server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangedEvent.java [new file with mode: 0644]
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventServiceImplTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/AssignActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/ChangeStatusActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ChangeStatusAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java

index e93840411901728bdda5437a0dc6d09778887617..956359ff38cb8fb8064a080cd7cc3f15e12c1c8f 100644 (file)
 
   <select id="selectByKey" parameterType="String" resultType="Issue">
     select
-    <include refid="issueColumns"/>
+    <include refid="issueColumns"/>,
+    u.login as assigneeLogin
     from issues i
     inner join rules r on r.uuid=i.rule_uuid
     inner join components p on p.uuid=i.component_uuid
     inner join components root on root.uuid=i.project_uuid
+    left join users u on i.assignee = u.uuid
     left join new_code_reference_issues n on i.kee = n.issue_key
     where i.kee=#{kee,jdbcType=VARCHAR}
   </select>
index 977e644dbe1d264d38aa4486ad2d6657cf99934f..9949cb0188aaf861419be075a59dd18b577d79f5 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.db.issue;
 
+import com.google.protobuf.InvalidProtocolBufferException;
 import java.time.temporal.ChronoUnit;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -47,7 +48,7 @@ public class IssueDtoTest {
     .build();
 
   @Test
-  public void toDefaultIssue_set_issue_fields() {
+  public void toDefaultIssue_ShouldSetIssueFields() throws InvalidProtocolBufferException {
     Date createdAt = DateUtils.addDays(new Date(), -5);
     Date updatedAt = DateUtils.addDays(new Date(), -3);
     Date closedAt = DateUtils.addDays(new Date(), -1);
@@ -78,32 +79,38 @@ public class IssueDtoTest {
       .setIssueCloseDate(closedAt)
       .setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
 
+    DefaultIssue expected = new DefaultIssue()
+      .setKey("100")
+      .setType(RuleType.VULNERABILITY)
+      .setRuleKey(RuleKey.of("java", "AvoidCycle"))
+      .setLanguage("xoo")
+      .setComponentUuid("CDEF")
+      .setProjectUuid("GHIJ")
+      .setComponentKey("org.sonar.sample:Sample")
+      .setProjectKey("org.sonar.sample")
+      .setStatus(Issue.STATUS_CLOSED)
+      .setResolution(Issue.RESOLUTION_FALSE_POSITIVE)
+      .setGap(15.0)
+      .setEffort(Duration.create(10L))
+      .setLine(6)
+      .setSeverity("BLOCKER")
+      .setMessage("message")
+      .setMessageFormattings(DbIssues.MessageFormattings.parseFrom(EXAMPLE_MESSAGE_FORMATTINGS.toByteArray()))
+      .setManualSeverity(true)
+      .setAssigneeUuid("perceval")
+      .setAuthorLogin("pierre")
+      .setCreationDate(DateUtils.truncate(createdAt, Calendar.SECOND))
+      .setUpdateDate(DateUtils.truncate(updatedAt, Calendar.SECOND))
+      .setCloseDate(DateUtils.truncate(closedAt, Calendar.SECOND))
+      .setNew(false)
+      .setIsNewCodeReferenceIssue(false)
+      .setRuleDescriptionContextKey(TEST_CONTEXT_KEY)
+      .setCodeVariants(Set.of())
+      .setTags(Set.of());
+
     DefaultIssue issue = dto.toDefaultIssue();
-    assertThat(issue.key()).isEqualTo("100");
-    assertThat(issue.type()).isEqualTo(RuleType.VULNERABILITY);
-    assertThat(issue.ruleKey()).hasToString("java:AvoidCycle");
-    assertThat(issue.language()).isEqualTo("xoo");
-    assertThat(issue.componentUuid()).isEqualTo("CDEF");
-    assertThat(issue.projectUuid()).isEqualTo("GHIJ");
-    assertThat(issue.componentKey()).isEqualTo("org.sonar.sample:Sample");
-    assertThat(issue.projectKey()).isEqualTo("org.sonar.sample");
-    assertThat(issue.status()).isEqualTo(Issue.STATUS_CLOSED);
-    assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FALSE_POSITIVE);
-    assertThat(issue.gap()).isEqualTo(15.0);
-    assertThat(issue.effort()).isEqualTo(Duration.create(10L));
-    assertThat(issue.line()).isEqualTo(6);
-    assertThat(issue.severity()).isEqualTo("BLOCKER");
-    assertThat(issue.message()).isEqualTo("message");
-    assertThat((DbIssues.MessageFormattings) issue.getMessageFormattings()).isEqualTo(EXAMPLE_MESSAGE_FORMATTINGS);
-    assertThat(issue.manualSeverity()).isTrue();
-    assertThat(issue.assignee()).isEqualTo("perceval");
-    assertThat(issue.authorLogin()).isEqualTo("pierre");
-    assertThat(issue.creationDate()).isEqualTo(DateUtils.truncate(createdAt, Calendar.SECOND));
-    assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(updatedAt, Calendar.SECOND));
-    assertThat(issue.closeDate()).isEqualTo(DateUtils.truncate(closedAt, Calendar.SECOND));
-    assertThat(issue.isNew()).isFalse();
-    assertThat(issue.isNewCodeReferenceIssue()).isFalse();
-    assertThat(issue.getRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY);
+
+    assertThat(issue).usingRecursiveComparison().isEqualTo(expected);
   }
 
   @Test
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventService.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventService.java
new file mode 100644 (file)
index 0000000..bd6cf1a
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.pushapi.hotspots;
+
+public interface HotspotChangeEventService {
+  void distributeHotspotChangedEvent(String projectUuid, HotspotChangedEvent hotspotEvent);
+
+}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventServiceImpl.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventServiceImpl.java
new file mode 100644 (file)
index 0000000..06317d1
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.pushapi.hotspots;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.pushevent.PushEventDto;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+@ServerSide
+public class HotspotChangeEventServiceImpl implements HotspotChangeEventService {
+  private static final Gson GSON = new GsonBuilder().create();
+
+  private static final String EVENT_NAME = "SecurityHotspotChanged";
+
+  private final DbClient dbClient;
+
+  public HotspotChangeEventServiceImpl(DbClient dbClient) {
+    this.dbClient = dbClient;
+  }
+
+  @Override
+  public void distributeHotspotChangedEvent(String projectUuid, HotspotChangedEvent hotspotEvent) {
+    persistEvent(projectUuid, hotspotEvent);
+  }
+
+  private void persistEvent(String projectUuid, HotspotChangedEvent hotspotEvent) {
+    try (DbSession dbSession = dbClient.openSession(false)) {
+      PushEventDto eventDto = new PushEventDto()
+        .setName(EVENT_NAME)
+        .setProjectUuid(projectUuid)
+        .setPayload(serializeIssueToPushEvent(hotspotEvent));
+      dbClient.pushEventDao().insert(dbSession, eventDto);
+      dbSession.commit();
+    }
+  }
+
+  private static byte[] serializeIssueToPushEvent(HotspotChangedEvent event) {
+    return GSON.toJson(event).getBytes(UTF_8);
+  }
+
+
+}
diff --git a/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangedEvent.java b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangedEvent.java
new file mode 100644 (file)
index 0000000..bcc08a3
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.pushapi.hotspots;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.annotation.CheckForNull;
+
+public class HotspotChangedEvent implements Serializable {
+
+  private final String key;
+  private final String projectKey;
+  private final Long updateDate;
+  private final String status;
+  private final String assignee;
+  private final String resolution;
+  private final String filePath;
+
+  private HotspotChangedEvent(Builder builder) {
+    this.key = builder.getKey();
+    this.projectKey = builder.getProjectKey();
+    this.updateDate = builder.getUpdateDate() == null ? null : builder.getUpdateDate().getTime();
+    this.status = builder.getStatus();
+    this.filePath = builder.getFilePath();
+    this.resolution = builder.getResolution();
+    this.assignee = builder.getAssignee();
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public String getProjectKey() {
+    return projectKey;
+  }
+
+  public Long getUpdateDate() {
+    return updateDate;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public String getFilePath() {
+    return filePath;
+  }
+
+  @CheckForNull
+  public String getResolution() {
+    return resolution;
+  }
+
+  @CheckForNull
+  public String getAssignee() {
+    return assignee;
+  }
+
+  public static class Builder {
+    private String key;
+    private String projectKey;
+    private Date updateDate;
+    private String status;
+    private String assignee;
+    private String resolution;
+    private String filePath;
+
+    public Builder setKey(String key) {
+      this.key = key;
+      return this;
+    }
+
+    public Builder setProjectKey(String projectKey) {
+      this.projectKey = projectKey;
+      return this;
+    }
+
+    public Builder setUpdateDate(Date updateDate) {
+      this.updateDate = updateDate;
+      return this;
+    }
+
+    public Builder setStatus(String status) {
+      this.status = status;
+      return this;
+    }
+
+    public Builder setAssignee(String assignee) {
+      this.assignee = assignee;
+      return this;
+    }
+
+    public Builder setResolution(String resolution) {
+      this.resolution = resolution;
+      return this;
+    }
+
+    public Builder setFilePath(String filePath) {
+      this.filePath = filePath;
+      return this;
+    }
+
+    public String getKey() {
+      return key;
+    }
+
+    public String getProjectKey() {
+      return projectKey;
+    }
+
+    public Date getUpdateDate() {
+      return updateDate;
+    }
+
+    public String getStatus() {
+      return status;
+    }
+
+    public String getAssignee() {
+      return assignee;
+    }
+
+    public String getResolution() {
+      return resolution;
+    }
+
+    public String getFilePath() {
+      return filePath;
+    }
+
+    public HotspotChangedEvent build() {
+      return new HotspotChangedEvent(this);
+    }
+  }
+}
diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventServiceImplTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventServiceImplTest.java
new file mode 100644 (file)
index 0000000..ae2a6dd
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.pushapi.hotspots;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.Deque;
+import java.util.Set;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.DbTester;
+import org.sonar.db.pushevent.PushEventDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.test.JsonAssert.assertJson;
+
+public class HotspotChangeEventServiceImplTest {
+
+  @Rule
+  public DbTester db = DbTester.create();
+
+  public final HotspotChangeEventServiceImpl underTest = new HotspotChangeEventServiceImpl(db.getDbClient());
+
+  @Test
+  public void distributeHotspotChangedEvent_whenCalled_shouldPersistCorrectEventData() {
+    HotspotChangedEvent hotspotChangedEvent = new HotspotChangedEvent.Builder()
+      .setKey("key")
+      .setProjectKey("project-key")
+      .setUpdateDate(new Date(1L))
+      .setStatus("REVIEWED")
+      .setResolution("ACKNOWLEDGED")
+      .setAssignee("assignee")
+      .setFilePath("path/to/file")
+      .build();
+    assertPushEventIsPersisted(hotspotChangedEvent);
+  }
+
+  private void assertPushEventIsPersisted(HotspotChangedEvent hotspotChangedEvent) {
+    underTest.distributeHotspotChangedEvent("project-uuid", hotspotChangedEvent);
+
+    Deque<PushEventDto> events = db.getDbClient().pushEventDao()
+      .selectChunkByProjectUuids(db.getSession(), Set.of("project-uuid"), 1L, null, 1);
+    assertThat(events).isNotEmpty();
+    assertThat(events).extracting(PushEventDto::getName, PushEventDto::getProjectUuid)
+      .contains(tuple("SecurityHotspotChanged", "project-uuid"));
+
+    String payload = new String(events.getLast().getPayload(), StandardCharsets.UTF_8);
+    assertJson(payload).isSimilarTo("{" +
+      "\"key\": \"" + hotspotChangedEvent.getKey() + "\"," +
+      "\"projectKey\": \"" + hotspotChangedEvent.getProjectKey() + "\"," +
+      "\"updateDate\": " + hotspotChangedEvent.getUpdateDate() + "," +
+      "\"status\": \"" + hotspotChangedEvent.getStatus() + "\"," +
+      "\"filePath\": \"" + hotspotChangedEvent.getFilePath() + "\"," +
+      "\"assignee\": \"" + hotspotChangedEvent.getAssignee() + "\"," +
+      "\"resolution\": \"" + hotspotChangedEvent.getResolution() + "\"" +
+      "}");
+  }
+
+}
index 019bda572a7b64a82b787c6634ee9b02ac50a4f0..bab03f3c1dffb9a48709922d42bd4880fca56107 100644 (file)
@@ -30,6 +30,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 import org.assertj.core.api.Condition;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,6 +45,8 @@ import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.rule.RuleDto;
@@ -53,6 +56,8 @@ import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.issue.IssueFieldsSetter;
 import org.sonar.server.issue.ws.IssueUpdater;
+import org.sonar.server.pushapi.hotspots.HotspotChangeEventService;
+import org.sonar.server.pushapi.hotspots.HotspotChangedEvent;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
@@ -68,6 +73,7 @@ import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 import static org.sonar.api.issue.Issue.RESOLUTION_ACKNOWLEDGED;
@@ -90,15 +96,22 @@ public class AssignActionIT {
 
   private final DbClient dbClient = dbTester.getDbClient();
   private final IssueUpdater issueUpdater = mock(IssueUpdater.class);
+  private HotspotChangeEventService hotspotChangeEventService = mock(HotspotChangeEventService.class);
   private final System2 system2 = mock(System2.class);
   private final IssueFieldsSetter issueFieldsSetter = mock(IssueFieldsSetter.class);
   private final HotspotWsSupport hotspotWsSupport = new HotspotWsSupport(dbClient, userSessionRule, system2);
 
-  private final AssignAction underTest = new AssignAction(dbClient, hotspotWsSupport, issueFieldsSetter, issueUpdater);
+  private final AssignAction underTest = new AssignAction(dbClient, hotspotWsSupport, issueFieldsSetter, issueUpdater, hotspotChangeEventService);
   private final WsActionTester actionTester = new WsActionTester(underTest);
+  private BranchDto branchDto = mock(BranchDto.class);
+
+  @Before
+  public void setMock() {
+    when(issueUpdater.getBranch(any(), any())).thenReturn(branchDto);
+  }
 
   @Test
-  public void ws_definition_check() {
+  public void wsExecution_whenDefined() {
     WebService.Action wsDefinition = actionTester.getDef();
 
     assertThat(wsDefinition.isPost()).isTrue();
@@ -120,7 +133,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void assign_hotspot_to_someone_for_public_project() {
+  public void wsExecution_whenAssignedForPublicProject() {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
@@ -137,7 +150,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void unassign_hotspot_for_public_project() {
+  public void wsExecution_whenUnassignedForPublicProject() {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     UserDto assignee = insertUser(randomAlphanumeric(15));
@@ -154,7 +167,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void assign_hotspot_to_me_for_public_project() {
+  public void wsExecution_whenMyselfAssignedForPublicProject() {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
@@ -170,7 +183,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void unassign_hotspot_myself_for_public_project() {
+  public void wsExecution_whenMyselfUnassignedForPublicProject() {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     UserDto me = insertUser(randomAlphanumeric(10));
@@ -185,7 +198,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void assign_hotspot_to_someone_for_private_project() {
+  public void wsExecution_whenAssigneeForPrivateProject() {
     ComponentDto project = dbTester.components().insertPrivateProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
@@ -201,7 +214,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void unassign_hotspot_for_private_project() {
+  public void wsExecution_whenUnassignedForPrivateProject() {
     ComponentDto project = dbTester.components().insertPrivateProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     UserDto assignee = insertUser(randomAlphanumeric(15));
@@ -217,7 +230,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void assign_hotspot_to_someone_for_private_project_branch() {
+  public void wsExecution_whenAssigneeForPrivateProjectBranch() {
     ComponentDto project = dbTester.components().insertPrivateProject().getMainBranchComponent();
     ComponentDto branch = dbTester.components().insertProjectBranch(project);
     ComponentDto file = dbTester.components().insertComponent(newFileDto(branch, project.uuid()));
@@ -235,7 +248,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void fail_if_assignee_does_not_have_access_for_private_project() {
+  public void wsExecution_whenAssigneeDoesNotHaveAccessToPrivateProject_shouldFail() {
     ComponentDto project = dbTester.components().insertPrivateProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
@@ -252,7 +265,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void fail_if_assignee_does_not_have_access_for_private_project_branch() {
+  public void wsExecution_whenAssigneeDoesNotHaveAccessToPrivateProjectBranch_shouldFail() {
     ComponentDto project = dbTester.components().insertPrivateProject().getMainBranchComponent();
     ComponentDto branch = dbTester.components().insertProjectBranch(project);
     ComponentDto file = dbTester.components().insertComponent(newFileDto(branch, project.uuid()));
@@ -271,7 +284,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void assign_hotspot_to_me_for_private_project() {
+  public void wsExecution_whenAssignHotspotToMeForPrivateProject() {
     ComponentDto project = dbTester.components().insertPrivateProject().getMainBranchComponent();
 
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
@@ -287,7 +300,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void assign_hotspot_with_comment() {
+  public void wsExecution_whenAssignHotspotWithComment() {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
 
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
@@ -307,7 +320,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void assign_twice_same_user_to_hotspot_does_not_reload() {
+  public void wsExecution_whenAssignTwiceSameUserHotspotDoesNotReload_shouldFail() {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
@@ -326,7 +339,42 @@ public class AssignActionIT {
   }
 
   @Test
-  public void fail_if_assigning_to_not_existing_user() {
+  public void wsExecution_whenBranchTypeIsBranch_shouldDistributeEvents() {
+    ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
+
+    UserDto userDto = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn(userDto).registerComponents(project);
+
+    UserDto assignee = insertUser(randomAlphanumeric(15));
+    when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH);
+    when(issueFieldsSetter.assign(eq(hotspot.toDefaultIssue()), userMatcher(assignee), any(IssueChangeContext.class))).thenReturn(true);
+
+    executeRequest(hotspot, assignee.getLogin(), null);
+    verify(hotspotChangeEventService).distributeHotspotChangedEvent(eq(project.uuid()), any(HotspotChangedEvent.class));
+  }
+
+  @Test
+  public void wsExecution_whenBranchIsPullRequest_shouldNotDistributeEvents() {
+    ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
+
+    UserDto userDto = insertUser(randomAlphanumeric(10));
+    userSessionRule.logIn(userDto).registerComponents(project);
+
+    UserDto assignee = insertUser(randomAlphanumeric(15));
+    when(branchDto.getBranchType()).thenReturn(BranchType.PULL_REQUEST);
+    when(issueFieldsSetter.assign(eq(hotspot.toDefaultIssue()), userMatcher(assignee), any(IssueChangeContext.class))).thenReturn(true);
+
+    executeRequest(hotspot, assignee.getLogin(), null);
+    verifyNoInteractions(hotspotChangeEventService);
+  }
+
+
+  @Test
+  public void wsExecution_whenAssigningToNonExistingUser_shouldFail() {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
@@ -343,7 +391,7 @@ public class AssignActionIT {
 
   @Test
   @UseDataProvider("allIssueStatusesAndResolutionsThatThrowOnAssign")
-  public void fail_if_assign_user_to_hotspot_for_which_it_is_forbidden(String status, String resolution) {
+  public void wsExecution_whenAssigningToUserIfForbidden_shouldFail(String status, String resolution) {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
 
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
@@ -373,7 +421,7 @@ public class AssignActionIT {
 
   @Test
   @UseDataProvider("allIssueStatusesAndResolutionsThatDoNotThrowOnAssign")
-  public void fail_if_assign_user_to_hotspot_for_which_it_is_allowed(String status, String resolution) {
+  public void wsExecution_whenAssigningToUserIfAllowed_shouldNotFail(String status, String resolution) {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     IssueDto hotspot = dbTester.issues().insertHotspot(project, file, h -> {
@@ -397,7 +445,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void fail_if_not_authenticated() {
+  public void wsExecution_whenNotAuthenticated_shouldFail() {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
@@ -413,7 +461,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void fail_if_missing_browse_permission() {
+  public void wsExecution_whenMissingBrowserAthentication_shouldFail() {
     ComponentDto project = dbTester.components().insertPrivateProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
@@ -429,7 +477,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void fail_if_hotspot_does_not_exist() {
+  public void wsExecution_whenHotspotDoesNotExist_shouldFail() {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
 
     UserDto me = insertUser(randomAlphanumeric(10));
@@ -444,7 +492,7 @@ public class AssignActionIT {
 
   @Test
   @UseDataProvider("allRuleTypesWithStatusesExceptHotspot")
-  public void fail_if_trying_to_assign_issue(RuleType ruleType, String status) {
+  public void wsExecution_whenAssigningToNonexistantIssue_shouldFail(RuleType ruleType, String status) {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     RuleDto rule = newRule(ruleType);
@@ -478,7 +526,7 @@ public class AssignActionIT {
   }
 
   @Test
-  public void fail_with_NotFoundException_if_hotspot_is_closed() {
+  public void wsExecution_whenHotspotIsClosed_shouldFailWithNotFoundException() {
     ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
     RuleDto rule = newRule(SECURITY_HOTSPOT);
index 9249030980e1b2204118d168ef4603163913bc58..b0b02683772236a901c0cf06bbe33a55138c0d7a 100644 (file)
@@ -29,6 +29,7 @@ import java.util.Random;
 import java.util.function.Consumer;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,9 +44,13 @@ import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.issue.IssueTesting;
+import org.sonar.db.project.ProjectDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleTesting;
 import org.sonar.server.exceptions.ForbiddenException;
@@ -54,6 +59,8 @@ import org.sonar.server.exceptions.UnauthorizedException;
 import org.sonar.server.issue.IssueFieldsSetter;
 import org.sonar.server.issue.TransitionService;
 import org.sonar.server.issue.ws.IssueUpdater;
+import org.sonar.server.pushapi.hotspots.HotspotChangeEventService;
+import org.sonar.server.pushapi.hotspots.HotspotChangedEvent;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
@@ -92,11 +99,19 @@ public class ChangeStatusActionIT {
   private DbClient dbClient = dbTester.getDbClient();
   private TransitionService transitionService = mock(TransitionService.class);
   private IssueUpdater issueUpdater = mock(IssueUpdater.class);
+  private HotspotChangeEventService hotspotChangeEventService = mock(HotspotChangeEventService.class);
   private System2 system2 = mock(System2.class);
   private IssueFieldsSetter issueFieldsSetter = mock(IssueFieldsSetter.class);
   private HotspotWsSupport hotspotWsSupport = new HotspotWsSupport(dbClient, userSessionRule, system2);
-  private ChangeStatusAction underTest = new ChangeStatusAction(dbClient, hotspotWsSupport, transitionService, issueFieldsSetter, issueUpdater);
+  private ChangeStatusAction underTest = new ChangeStatusAction(dbClient, hotspotWsSupport, transitionService, issueFieldsSetter, issueUpdater, hotspotChangeEventService);
   private WsActionTester actionTester = new WsActionTester(underTest);
+  private final ComponentDbTester componentDbTester = new ComponentDbTester(dbTester);
+  private BranchDto branchDto = mock(BranchDto.class);
+
+  @Before
+  public void setMock() {
+    when(issueUpdater.getBranch(any(), any())).thenReturn(branchDto);
+  }
 
   @Test
   public void define_shouldMarkWebServiceAsPublic() {
@@ -426,6 +441,55 @@ public class ChangeStatusActionIT {
     }
   }
 
+  @Test
+  public void wsExecution_whenOnMainBranch_shouldDistributeEvents() {
+    ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
+    userSessionRule.logIn().registerComponents(project)
+      .addProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN, project);
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH);
+    IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
+    when(transitionService.doTransition(any(), any(), any())).thenReturn(true);
+
+    newRequest(hotspot, STATUS_REVIEWED, RESOLUTION_FIXED, NO_COMMENT).execute();
+
+    verify(hotspotChangeEventService).distributeHotspotChangedEvent(eq(project.uuid()), any(HotspotChangedEvent.class));
+  }
+
+  @Test
+  public void wsExecution_whenOnNonMainBranch_shouldDistributeEvents() {
+    ProjectDto project = dbTester.components().insertPublicProject().getProjectDto();
+    ComponentDto projectComponentDto = dbTester.components().getComponentDto(project);
+    BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey("develop"));
+    ComponentDto branchComponentDto = dbTester.components().getComponentDto(branch);
+
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(branchComponentDto));
+    when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH);
+    IssueDto hotspot = dbTester.issues().insertHotspot(branchComponentDto, file);
+    when(transitionService.doTransition(any(), any(), any())).thenReturn(true);
+
+    userSessionRule.logIn().registerComponents(projectComponentDto)
+      .addProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN, projectComponentDto, branchComponentDto);
+    newRequest(hotspot, STATUS_REVIEWED, RESOLUTION_FIXED, NO_COMMENT).execute();
+
+    verify(hotspotChangeEventService).distributeHotspotChangedEvent(eq(branchComponentDto.uuid()), any(HotspotChangedEvent.class));
+  }
+
+  @Test
+  public void wsExecution_whenBranchTypeIsPullRequest_shouldNotDistributeEvents() {
+    ComponentDto project = dbTester.components().insertPublicProject().getMainBranchComponent();
+    userSessionRule.logIn().registerComponents(project)
+      .addProjectPermission(UserRole.SECURITYHOTSPOT_ADMIN, project);
+    ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
+    when(branchDto.getBranchType()).thenReturn(BranchType.PULL_REQUEST);
+    IssueDto hotspot = dbTester.issues().insertHotspot(project, file);
+    when(transitionService.doTransition(any(), any(), any())).thenReturn(true);
+
+    newRequest(hotspot, STATUS_REVIEWED, RESOLUTION_FIXED, NO_COMMENT).execute();
+
+    verifyNoInteractions(hotspotChangeEventService);
+  }
+
   @DataProvider
   public static Object[][] reviewedResolutionsAndExpectedTransitionKey() {
     return new Object[][] {
index 31b961e38e0379d7344953526b47482ac097da03..24f141d7efbf70b56807f0ed22480f6c5106e6bf 100644 (file)
@@ -30,15 +30,19 @@ import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.db.project.ProjectDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.issue.IssueFieldsSetter;
 import org.sonar.server.issue.ws.IssueUpdater;
+import org.sonar.server.pushapi.hotspots.HotspotChangeEventService;
+import org.sonar.server.pushapi.hotspots.HotspotChangedEvent;
 
 import static com.google.common.base.Strings.isNullOrEmpty;
 import static org.sonar.api.issue.Issue.RESOLUTION_ACKNOWLEDGED;
 import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
+import static org.sonar.db.component.BranchType.BRANCH;
 import static org.sonar.server.exceptions.NotFoundException.checkFound;
 import static org.sonar.server.exceptions.NotFoundException.checkFoundWithOptional;
 
@@ -53,13 +57,15 @@ public class AssignAction implements HotspotsWsAction {
   private final HotspotWsSupport hotspotWsSupport;
   private final IssueFieldsSetter issueFieldsSetter;
   private final IssueUpdater issueUpdater;
+  private final HotspotChangeEventService hotspotChangeEventService;
 
   public AssignAction(DbClient dbClient, HotspotWsSupport hotspotWsSupport, IssueFieldsSetter issueFieldsSetter,
-    IssueUpdater issueUpdater) {
+    IssueUpdater issueUpdater, HotspotChangeEventService hotspotChangeEventService) {
     this.dbClient = dbClient;
     this.hotspotWsSupport = hotspotWsSupport;
     this.issueFieldsSetter = issueFieldsSetter;
     this.issueUpdater = issueUpdater;
+    this.hotspotChangeEventService = hotspotChangeEventService;
   }
 
   @Override
@@ -121,6 +127,12 @@ public class AssignAction implements HotspotsWsAction {
 
       if (issueFieldsSetter.assign(defaultIssue, assignee, context)) {
         issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context);
+
+        BranchDto branch = issueUpdater.getBranch(dbSession, defaultIssue);
+        if (BRANCH.equals(branch.getBranchType())) {
+          HotspotChangedEvent hotspotChangedEvent = buildEventData(defaultIssue, assignee, hotspotDto.getFilePath());
+          hotspotChangeEventService.distributeHotspotChangedEvent(defaultIssue.projectUuid(), hotspotChangedEvent);
+        }
       }
     }
   }
@@ -147,4 +159,16 @@ public class AssignAction implements HotspotsWsAction {
   private boolean hasProjectPermission(DbSession dbSession, String userUuid, String projectUuid) {
     return dbClient.authorizationDao().selectProjectPermissions(dbSession, projectUuid, userUuid).contains(UserRole.USER);
   }
+
+  private static HotspotChangedEvent buildEventData(DefaultIssue defaultIssue, @Nullable UserDto assignee, String filePath) {
+    return new HotspotChangedEvent.Builder()
+      .setKey(defaultIssue.key())
+      .setProjectKey(defaultIssue.projectKey())
+      .setStatus(defaultIssue.status())
+      .setResolution(defaultIssue.resolution())
+      .setUpdateDate(defaultIssue.updateDate())
+      .setAssignee(assignee == null ? null : assignee.getLogin())
+      .setFilePath(filePath)
+      .build();
+  }
 }
index 8a7cf079fb79eb6b8ea3d0dabafe13310bb512fe..98cb5a079a0ca5a6038503d26f288c913195dd82 100644 (file)
@@ -33,10 +33,13 @@ import org.sonar.core.issue.IssueChangeContext;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
 import org.sonar.db.issue.IssueDto;
 import org.sonar.server.issue.IssueFieldsSetter;
 import org.sonar.server.issue.TransitionService;
 import org.sonar.server.issue.ws.IssueUpdater;
+import org.sonar.server.pushapi.hotspots.HotspotChangeEventService;
+import org.sonar.server.pushapi.hotspots.HotspotChangedEvent;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.apache.commons.lang.StringUtils.trimToNull;
@@ -45,6 +48,7 @@ import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
 import static org.sonar.api.issue.Issue.SECURITY_HOTSPOT_RESOLUTIONS;
 import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
 import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
+import static org.sonar.db.component.BranchType.BRANCH;
 
 public class ChangeStatusAction implements HotspotsWsAction {
 
@@ -58,14 +62,16 @@ public class ChangeStatusAction implements HotspotsWsAction {
   private final TransitionService transitionService;
   private final IssueFieldsSetter issueFieldsSetter;
   private final IssueUpdater issueUpdater;
+  private final HotspotChangeEventService hotspotChangeEventService;
 
   public ChangeStatusAction(DbClient dbClient, HotspotWsSupport hotspotWsSupport, TransitionService transitionService,
-    IssueFieldsSetter issueFieldsSetter, IssueUpdater issueUpdater) {
+    IssueFieldsSetter issueFieldsSetter, IssueUpdater issueUpdater, HotspotChangeEventService hotspotChangeEventService) {
     this.dbClient = dbClient;
     this.hotspotWsSupport = hotspotWsSupport;
     this.transitionService = transitionService;
     this.issueFieldsSetter = issueFieldsSetter;
     this.issueUpdater = issueUpdater;
+    this.hotspotChangeEventService = hotspotChangeEventService;
   }
 
   @Override
@@ -155,8 +161,27 @@ public class ChangeStatusAction implements HotspotsWsAction {
       if (comment != null) {
         issueFieldsSetter.addComment(defaultIssue, comment, context);
       }
+
       issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context);
+
+      BranchDto branch = issueUpdater.getBranch(session, defaultIssue);
+      if (BRANCH.equals(branch.getBranchType())) {
+        HotspotChangedEvent hotspotChangedEvent = buildEventData(defaultIssue, issueDto);
+        hotspotChangeEventService.distributeHotspotChangedEvent(defaultIssue.projectUuid(), hotspotChangedEvent);
+      }
     }
   }
 
+  private static HotspotChangedEvent buildEventData(DefaultIssue defaultIssue, IssueDto issueDto) {
+    return new HotspotChangedEvent.Builder()
+      .setKey(defaultIssue.key())
+      .setProjectKey(defaultIssue.projectKey())
+      .setStatus(defaultIssue.status())
+      .setResolution(defaultIssue.resolution())
+      .setUpdateDate(defaultIssue.updateDate())
+      .setAssignee(issueDto.getAssigneeLogin())
+      .setFilePath(issueDto.getFilePath())
+      .build();
+  }
+
 }
index f9db25c4734f8fdc9d12164ff39183e88a1cc3e7..134e68f7e21e758fb6d3196e24a61393e11bcf1d 100644 (file)
@@ -119,7 +119,7 @@ public class DoTransitionAction implements IssuesWsAction {
     IssueChangeContext context = issueChangeContextByUserBuilder(new Date(system2.now()), userSession.getUuid()).withRefreshMeasures().build();
     transitionService.checkTransitionPermission(transitionKey, defaultIssue);
     if (transitionService.doTransition(defaultIssue, context, transitionKey)) {
-      BranchDto branch = issueUpdater.getBranch(session, defaultIssue, defaultIssue.projectUuid());
+      BranchDto branch = issueUpdater.getBranch(session, defaultIssue);
       SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context, branch);
 
       if (branch.getBranchType().equals(BRANCH) && response.getComponentByUuid(defaultIssue.projectUuid()) != null) {
index 85669dfdf5710cbf9c6b61d6b0ca3c024e5739ec..680f3912abf4bc02b00e2c6f96aae3f564501bb4 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.issue.ws;
 
+import java.util.Date;
 import java.util.List;
 import java.util.Optional;
 import javax.annotation.Nullable;
@@ -70,7 +71,7 @@ public class IssueUpdater {
   }
 
   public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue, IssueChangeContext context) {
-    BranchDto branch = getBranch(dbSession, issue, issue.projectUuid());
+    BranchDto branch = getBranch(dbSession, issue);
     return saveIssueAndPreloadSearchResponseData(dbSession, issue, context, branch);
   }
 
@@ -94,20 +95,24 @@ public class IssueUpdater {
     return result;
   }
 
-  protected BranchDto getBranch(DbSession dbSession, DefaultIssue issue, @Nullable String projectUuid) {
+  public BranchDto getBranch(DbSession dbSession, DefaultIssue issue) {
     String issueKey = issue.key();
-    checkState(projectUuid != null, "Issue '%s' has no project", issueKey);
-    BranchDto component = dbClient.branchDao().selectByUuid(dbSession, projectUuid).orElse(null);
-    checkState(component != null, "Branch uuid '%s' for issue key '%s' cannot be found", projectUuid, issueKey);
-    return component;
+    String componentUuid = issue.componentUuid();
+    checkState(componentUuid != null, "Component uuid for issue key '%s' cannot be null", issueKey);
+    Optional<ComponentDto> componentDto = dbClient.componentDao().selectByUuid(dbSession, componentUuid);
+    checkState(componentDto.isPresent(), "Component not found for issue with key '%s'", issueKey);
+    BranchDto branchDto = dbClient.branchDao().selectByUuid(dbSession, componentDto.get().branchUuid()).orElse(null);
+    checkState(branchDto != null, "Branch not found for issue with key '%s'", issueKey);
+    return branchDto;
   }
 
   private IssueDto doSaveIssue(DbSession session, DefaultIssue issue, IssueChangeContext context,
     @Nullable RuleDto ruleDto, ComponentDto project, BranchDto branchDto) {
     IssueDto issueDto = issueStorage.save(session, singletonList(issue)).iterator().next();
+    Date updateDate = issue.updateDate();
     if (
       // since this method is called after an update of the issue, date should never be null
-      issue.updateDate() == null
+      updateDate == null
         // name of rule is displayed in notification, rule must therefor be present
         || ruleDto == null
         // notification are not supported on PRs
@@ -133,7 +138,7 @@ public class IssueUpdater {
           .setBranchName(branchDto.isMain() ? null : branchDto.getKey())
           .build())
         .build()),
-      new UserChange(issue.updateDate().getTime(), new User(author.getUuid(), author.getLogin(), author.getName())));
+      new UserChange(updateDate.getTime(), new User(author.getUuid(), author.getLogin(), author.getName())));
     notificationService.scheduleForSending(notificationSerializer.serialize(notificationBuilder));
     return issueDto;
   }
index a00dd2afa7d7d9fd7fc83ff7a13eb0d03143d23e..d5ce9ebe9e1ee9ba4f05770e0071c1e56b9cc1b9 100644 (file)
@@ -115,7 +115,7 @@ public class SetSeverityAction implements IssuesWsAction {
 
     IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), userSession.getUuid()).withRefreshMeasures().build();
     if (issueFieldsSetter.setManualSeverity(issue, severity, context)) {
-      BranchDto branch = issueUpdater.getBranch(session, issue, issue.projectUuid());
+      BranchDto branch = issueUpdater.getBranch(session, issue);
       SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, branch);
 
       if (branch.getBranchType().equals(BRANCH) && response.getComponentByUuid(issue.projectUuid()) != null) {
index 6807f29186d5bb005882eaa74a7b7163b7b3ba69..dbaf40cfd1f62c0e5b59b4bf6b121165d5232665 100644 (file)
@@ -120,7 +120,7 @@ public class SetTypeAction implements IssuesWsAction {
 
     IssueChangeContext context = issueChangeContextByUserBuilder(new Date(system2.now()), userSession.getUuid()).withRefreshMeasures().build();
     if (issueFieldsSetter.setType(issue, ruleType, context)) {
-      BranchDto branch = issueUpdater.getBranch(session, issue, issue.projectUuid());
+      BranchDto branch = issueUpdater.getBranch(session, issue);
       SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, branch);
       if (branch.getBranchType().equals(BRANCH) && response.getComponentByUuid(issue.projectUuid()) != null) {
         issueChangeEventService.distributeIssueChangeEvent(issue, null, ruleType.name(), null, branch,
index f47242c2847ec92d2b787c8e9af356f7628174d3..d89f0e2280f78fc416562538c9c4b13defac1959 100644 (file)
@@ -191,6 +191,7 @@ import org.sonar.server.projectlink.ws.ProjectLinksModule;
 import org.sonar.server.projecttag.ws.ProjectTagsWsModule;
 import org.sonar.server.property.InternalPropertiesImpl;
 import org.sonar.server.pushapi.ServerPushModule;
+import org.sonar.server.pushapi.hotspots.HotspotChangeEventServiceImpl;
 import org.sonar.server.pushapi.issues.IssueChangeEventServiceImpl;
 import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventServiceImpl;
 import org.sonar.server.qualitygate.ProjectsInWarningModule;
@@ -458,6 +459,7 @@ public class PlatformLevel4 extends PlatformLevel {
       MyNewIssuesNotificationHandler.class,
       MyNewIssuesNotificationHandler.newMetadata(),
       IssueChangeEventServiceImpl.class,
+      HotspotChangeEventServiceImpl.class,
 
       // issues actions
       AssignAction.class,