aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlain Kermis <alain.kermis@sonarsource.com>2023-05-23 10:46:57 +0200
committersonartech <sonartech@sonarsource.com>2023-05-30 20:02:52 +0000
commit716a2667e6392f29f9c2d651eb7a7a9fbbc1fccb (patch)
treea9f4110e1e1b26cc6794fc06f43fa43f4aae8e87
parent1998f37bb4c45c5f3160705a9b70ad73d9cc04cc (diff)
downloadsonarqube-716a2667e6392f29f9c2d651eb7a7a9fbbc1fccb.tar.gz
sonarqube-716a2667e6392f29f9c2d651eb7a7a9fbbc1fccb.zip
SONAR-19340 Raise event whenever hotspots change in WS
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml4
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java59
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventService.java25
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventServiceImpl.java64
-rw-r--r--server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangedEvent.java152
-rw-r--r--server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventServiceImplTest.java77
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/AssignActionIT.java92
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/ChangeStatusActionIT.java66
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java26
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ChangeStatusAction.java27
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java21
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java2
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java2
15 files changed, 558 insertions, 63 deletions
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
index e9384041190..956359ff38c 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
@@ -212,11 +212,13 @@
<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>
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java
index 977e644dbe1..9949cb0188a 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDtoTest.java
@@ -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
index 00000000000..bd6cf1addae
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventService.java
@@ -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
index 00000000000..06317d1a725
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventServiceImpl.java
@@ -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
index 00000000000..bcc08a30807
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/main/java/org/sonar/server/pushapi/hotspots/HotspotChangedEvent.java
@@ -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
index 00000000000..ae2a6dd52bd
--- /dev/null
+++ b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/hotspots/HotspotChangeEventServiceImplTest.java
@@ -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() + "\"" +
+ "}");
+ }
+
+}
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/AssignActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/AssignActionIT.java
index 019bda572a7..bab03f3c1df 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/AssignActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/AssignActionIT.java
@@ -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);
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/ChangeStatusActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/ChangeStatusActionIT.java
index 9249030980e..b0b02683772 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/ChangeStatusActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/hotspot/ws/ChangeStatusActionIT.java
@@ -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[][] {
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java
index 31b961e38e0..24f141d7efb 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java
@@ -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();
+ }
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ChangeStatusAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ChangeStatusAction.java
index 8a7cf079fb7..98cb5a079a0 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ChangeStatusAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ChangeStatusAction.java
@@ -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();
+ }
+
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
index f9db25c4734..134e68f7e21 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
@@ -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) {
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java
index 85669dfdf57..680f3912abf 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java
@@ -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;
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
index a00dd2afa7d..d5ce9ebe9e1 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
@@ -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) {
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
index 6807f29186d..dbaf40cfd1f 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
@@ -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,
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index f47242c2847..d89f0e2280f 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -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,