diff options
author | Alain Kermis <alain.kermis@sonarsource.com> | 2023-05-23 10:46:57 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-05-30 20:02:52 +0000 |
commit | 716a2667e6392f29f9c2d651eb7a7a9fbbc1fccb (patch) | |
tree | a9f4110e1e1b26cc6794fc06f43fa43f4aae8e87 | |
parent | 1998f37bb4c45c5f3160705a9b70ad73d9cc04cc (diff) | |
download | sonarqube-716a2667e6392f29f9c2d651eb7a7a9fbbc1fccb.tar.gz sonarqube-716a2667e6392f29f9c2d651eb7a7a9fbbc1fccb.zip |
SONAR-19340 Raise event whenever hotspots change in WS
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, |