Browse Source

SONAR-17271 - Add origin on issue update from ALM

tags/9.7.0.61563
Antoine Vinot 1 year ago
parent
commit
9d1fe387d1
41 changed files with 481 additions and 257 deletions
  1. 2
    2
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueAssigner.java
  2. 2
    2
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCreationDateCalculator.java
  3. 2
    1
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
  4. 2
    1
      server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInput.java
  5. 2
    1
      server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
  6. 2
    1
      server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueFieldsSetterTest.java
  7. 8
    6
      server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java
  8. 19
    18
      server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java
  9. 117
    0
      server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/ChangedIssueImpl.java
  10. 4
    0
      server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListener.java
  11. 7
    1
      server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListeners.java
  12. 33
    106
      server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java
  13. 51
    18
      server/sonar-webserver-api/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java
  14. 7
    6
      server/sonar-webserver-api/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventTest.java
  15. 2
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AddCommentAction.java
  16. 2
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java
  17. 2
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ChangeStatusAction.java
  18. 7
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsSupport.java
  19. 1
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/IssueChangePostProcessor.java
  20. 2
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/IssueChangePostProcessorImpl.java
  21. 3
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java
  22. 3
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/AssignAction.java
  23. 3
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java
  24. 3
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
  25. 5
    5
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java
  26. 3
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
  27. 3
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java
  28. 3
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
  29. 3
    3
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AddCommentActionTest.java
  30. 1
    2
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AssignActionTest.java
  31. 7
    9
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ChangeStatusActionTest.java
  32. 2
    1
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/AssignActionTest.java
  33. 2
    2
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java
  34. 2
    2
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/SetTypeActionTest.java
  35. 1
    1
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/TestIssueChangePostProcessor.java
  36. 2
    2
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/TransitionActionTest.java
  37. 4
    2
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/TransitionServiceTest.java
  38. 2
    2
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/WebIssueStorageTest.java
  39. 19
    18
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/IssueUpdaterTest.java
  40. 69
    8
      sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java
  41. 67
    12
      sonar-core/src/test/java/org/sonar/core/issue/IssueChangeContextTest.java

+ 2
- 2
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueAssigner.java View File

@@ -36,7 +36,7 @@ import org.sonar.db.issue.IssueDto;
import org.sonar.server.issue.IssueFieldsSetter;

import static org.apache.commons.lang.StringUtils.defaultIfEmpty;
import static org.sonar.core.issue.IssueChangeContext.createScan;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByScanBuilder;

/**
* Detect the SCM author and SQ assignee.
@@ -62,7 +62,7 @@ public class IssueAssigner extends IssueVisitor {
this.scmAccountToUser = scmAccountToUser;
this.defaultAssignee = defaultAssignee;
this.issueUpdater = issueUpdater;
this.changeContext = createScan(new Date(analysisMetadataHolder.getAnalysisDate()));
this.changeContext = issueChangeContextByScanBuilder(new Date(analysisMetadataHolder.getAnalysisDate())).build();
}

@Override

+ 2
- 2
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueCreationDateCalculator.java View File

@@ -42,7 +42,7 @@ import org.sonar.core.issue.IssueChangeContext;
import org.sonar.server.issue.IssueFieldsSetter;

import static org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepository.Status.UNCHANGED;
import static org.sonar.core.issue.IssueChangeContext.createScan;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByScanBuilder;

/**
* Calculates the creation date of an issue. Takes into account, that the issue
@@ -66,7 +66,7 @@ public class IssueCreationDateCalculator extends IssueVisitor {
this.issueUpdater = issueUpdater;
this.analysisMetadataHolder = analysisMetadataHolder;
this.ruleRepository = ruleRepository;
this.changeContext = createScan(new Date(analysisMetadataHolder.getAnalysisDate()));
this.changeContext = issueChangeContextByScanBuilder(new Date(analysisMetadataHolder.getAnalysisDate())).build();
this.activeRulesHolder = activeRulesHolder;
this.addedFileRepository = addedFileRepository;
this.qProfileStatusRepository = qProfileStatusRepository;

+ 2
- 1
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java View File

@@ -37,6 +37,7 @@ import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.workflow.IssueWorkflow;

import static java.util.Objects.requireNonNull;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByScanBuilder;

/**
* Sets the appropriate fields when an issue is :
@@ -58,7 +59,7 @@ public class IssueLifecycle {
@Inject
public IssueLifecycle(AnalysisMetadataHolder analysisMetadataHolder, IssueWorkflow workflow, IssueFieldsSetter updater, DebtCalculator debtCalculator,
RuleRepository ruleRepository) {
this(analysisMetadataHolder, IssueChangeContext.createScan(new Date(analysisMetadataHolder.getAnalysisDate())), workflow, updater, debtCalculator, ruleRepository);
this(analysisMetadataHolder, issueChangeContextByScanBuilder(new Date(analysisMetadataHolder.getAnalysisDate())).build(), workflow, updater, debtCalculator, ruleRepository);
}

@VisibleForTesting IssueLifecycle(AnalysisMetadataHolder analysisMetadataHolder, IssueChangeContext changeContext, IssueWorkflow workflow, IssueFieldsSetter updater,

+ 2
- 1
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInput.java View File

@@ -42,6 +42,7 @@ import org.sonar.db.component.ComponentDto;
import org.sonar.server.issue.IssueFieldsSetter;

import static org.apache.commons.lang.StringUtils.trimToEmpty;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;

class ProjectTrackerBaseLazyInput extends BaseInputFactory.BaseLazyInput {

@@ -104,7 +105,7 @@ class ProjectTrackerBaseLazyInput extends BaseInputFactory.BaseLazyInput {
private Collection<? extends DefaultIssue> migrateIssuesToTheRoot(List<DefaultIssue> issuesOnModule, String modulePath) {
for (DefaultIssue i : issuesOnModule) {
// changes the issue's component uuid, add a change and set issue as changed to enforce it is persisted to DB
IssueChangeContext context = IssueChangeContext.createUser(new Date(analysisMetadataHolder.getAnalysisDate()), null);
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(analysisMetadataHolder.getAnalysisDate()), null).build();
if (StringUtils.isNotBlank(modulePath)) {
issueUpdater.setMessage(i, "[" + modulePath + "] " + i.getMessage(), context);
}

+ 2
- 1
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java View File

@@ -54,6 +54,7 @@ import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
import static org.sonar.api.rule.Severity.BLOCKER;
import static org.sonar.api.utils.DateUtils.parseDate;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.rule.RuleTesting.XOO_X1;

public class IssueLifecycleTest {
@@ -68,7 +69,7 @@ public class IssueLifecycleTest {
@Rule
public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();

private final IssueChangeContext issueChangeContext = IssueChangeContext.createUser(DEFAULT_DATE, "default_user_uuid");
private final IssueChangeContext issueChangeContext = issueChangeContextByUserBuilder(DEFAULT_DATE, "default_user_uuid").build();
private final IssueWorkflow workflow = mock(IssueWorkflow.class);
private final IssueFieldsSetter updater = mock(IssueFieldsSetter.class);
private final DebtCalculator debtCalculator = mock(DebtCalculator.class);

+ 2
- 1
server/sonar-server-common/src/test/java/org/sonar/server/issue/IssueFieldsSetterTest.java View File

@@ -34,6 +34,7 @@ import org.sonar.db.user.UserDto;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.user.UserTesting.newUserDto;
import static org.sonar.server.issue.IssueFieldsSetter.ASSIGNEE;
import static org.sonar.server.issue.IssueFieldsSetter.RESOLUTION;
@@ -47,7 +48,7 @@ public class IssueFieldsSetterTest {
private final String DEFAULT_RULE_DESCRIPTION_CONTEXT_KEY = "spring";

private final DefaultIssue issue = new DefaultIssue();
private final IssueChangeContext context = IssueChangeContext.createUser(new Date(), "user_uuid");
private final IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), "user_uuid").build();
private final IssueFieldsSetter underTest = new IssueFieldsSetter();

@Test

+ 8
- 6
server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java View File

@@ -53,12 +53,14 @@ import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByScanBuilder;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.rule.RuleTesting.XOO_X1;
import static org.sonar.server.issue.workflow.IssueWorkflowTest.emptyIfNull;

@RunWith(DataProviderRunner.class)
public class IssueWorkflowForSecurityHotspotsTest {
private static final IssueChangeContext SOME_CHANGE_CONTEXT = IssueChangeContext.createUser(new Date(), "USER1");
private static final IssueChangeContext SOME_CHANGE_CONTEXT = issueChangeContextByUserBuilder(new Date(), "USER1").build();
private static final List<String> RESOLUTION_TYPES = List.of(RESOLUTION_FIXED, RESOLUTION_SAFE, RESOLUTION_ACKNOWLEDGED);

private final IssueFieldsSetter updater = new IssueFieldsSetter();
@@ -221,7 +223,7 @@ public class IssueWorkflowForSecurityHotspotsTest {
.setBeingClosed(true);
Date now = new Date();

underTest.doAutomaticTransition(hotspot, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(hotspot, issueChangeContextByScanBuilder(now).build());

assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_FIXED);
assertThat(hotspot.status()).isEqualTo(STATUS_CLOSED);
@@ -238,7 +240,7 @@ public class IssueWorkflowForSecurityHotspotsTest {
.setBeingClosed(true);
Date now = new Date();

underTest.doAutomaticTransition(hotspot, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(hotspot, issueChangeContextByScanBuilder(now).build());

assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_FIXED);
assertThat(hotspot.status()).isEqualTo(STATUS_CLOSED);
@@ -265,8 +267,8 @@ public class IssueWorkflowForSecurityHotspotsTest {
Date now = new Date();
underTest.start();

underTest.doAutomaticTransition(hotspot1, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(hotspot2, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(hotspot1, issueChangeContextByScanBuilder(now).build());
underTest.doAutomaticTransition(hotspot2, issueChangeContextByScanBuilder(now).build());

assertThat(hotspot1.updateDate()).isNotNull();
assertThat(hotspot1.status()).isEqualTo(STATUS_REVIEWED);
@@ -284,7 +286,7 @@ public class IssueWorkflowForSecurityHotspotsTest {
.setRuleKey(XOO_X1);

underTest.start();
underTest.doAutomaticTransition(hotspot, IssueChangeContext.createScan(new Date()));
underTest.doAutomaticTransition(hotspot, issueChangeContextByScanBuilder(new Date()).build());

assertThat(hotspot.status()).isEqualTo(STATUS_TO_REVIEW);
assertThat(hotspot.resolution()).isNull();

+ 19
- 18
server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java View File

@@ -44,6 +44,7 @@ import org.sonar.server.issue.IssueFieldsSetter;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.commons.lang.time.DateUtils.addDays;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.fail;
import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
@@ -56,6 +57,7 @@ import static org.sonar.api.issue.Issue.STATUS_REOPENED;
import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByScanBuilder;

@RunWith(DataProviderRunner.class)
public class IssueWorkflowTest {
@@ -147,7 +149,7 @@ public class IssueWorkflowTest {
.setNew(false)
.setBeingClosed(true);
Date now = new Date();
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(now).build());
assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
assertThat(issue.closeDate()).isNotNull();
@@ -168,7 +170,7 @@ public class IssueWorkflowTest {
underTest.start();

Arrays.stream(issues).forEach(issue -> {
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(now).build());

assertThat(issue.status()).isEqualTo(previousStatus);
assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
@@ -194,7 +196,7 @@ public class IssueWorkflowTest {
underTest.start();

Arrays.stream(issues).forEach(issue -> {
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(now).build());

assertThat(issue.status()).isEqualTo(previousStatus);
assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
@@ -218,7 +220,7 @@ public class IssueWorkflowTest {
underTest.start();

Arrays.stream(issues).forEach(issue -> {
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(now).build());

assertThat(issue.status()).isEqualTo(randomPreviousStatus);
assertThat(issue.resolution()).isEqualTo(resolutionBeforeClosed);
@@ -242,7 +244,7 @@ public class IssueWorkflowTest {
underTest.start();

Arrays.stream(issues).forEach(issue -> {
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(now).build());

assertThat(issue.status()).isEqualTo(randomPreviousStatus);
assertThat(issue.resolution()).isNull();
@@ -270,7 +272,7 @@ public class IssueWorkflowTest {
underTest.start();

Arrays.stream(issues).forEach(issue -> {
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(now).build());

assertThat(issue.status()).isEqualTo(randomPreviousStatus);
assertThat(issue.resolution()).isEqualTo(resolutionBeforeClosed);
@@ -296,7 +298,7 @@ public class IssueWorkflowTest {
underTest.start();

Arrays.stream(issues).forEach(issue -> {
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(now).build());

assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
assertThat(issue.updateDate()).isNull();
@@ -331,7 +333,7 @@ public class IssueWorkflowTest {
.setNew(false)
.setBeingClosed(true);
Date now = new Date();
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(now).build());
assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
assertThat(issue.closeDate()).isNotNull();
@@ -349,7 +351,7 @@ public class IssueWorkflowTest {
.setNew(false)
.setBeingClosed(true);
Date now = new Date();
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(now).build());
assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
assertThat(issue.closeDate()).isNotNull();
@@ -367,7 +369,7 @@ public class IssueWorkflowTest {
.setNew(false)
.setBeingClosed(true);
Date now = new Date();
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(now).build());
assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
assertThat(issue.closeDate()).isNotNull();
@@ -384,12 +386,11 @@ public class IssueWorkflowTest {
.setStatus("xxx")
.setNew(false)
.setBeingClosed(true);
try {
underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date()));
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Unknown status: xxx [issue=ABCDE]");
}

IssueChangeContext issueChangeContext = issueChangeContextByScanBuilder(new Date()).build();
assertThatThrownBy(() -> underTest.doAutomaticTransition(issue, issueChangeContext))
.isInstanceOf(IllegalStateException.class)
.hasMessage("Unknown status: xxx [issue=ABCDE]");
}

@Test
@@ -401,7 +402,7 @@ public class IssueWorkflowTest {
.setAssigneeUuid("morgan");

underTest.start();
underTest.doManualTransition(issue, DefaultTransitions.FALSE_POSITIVE, IssueChangeContext.createScan(new Date()));
underTest.doManualTransition(issue, DefaultTransitions.FALSE_POSITIVE, issueChangeContextByScanBuilder(new Date()).build());

assertThat(issue.resolution()).isEqualTo(RESOLUTION_FALSE_POSITIVE);
assertThat(issue.status()).isEqualTo(STATUS_RESOLVED);
@@ -419,7 +420,7 @@ public class IssueWorkflowTest {
.setAssigneeUuid("morgan");

underTest.start();
underTest.doManualTransition(issue, DefaultTransitions.WONT_FIX, IssueChangeContext.createScan(new Date()));
underTest.doManualTransition(issue, DefaultTransitions.WONT_FIX, issueChangeContextByScanBuilder(new Date()).build());

assertThat(issue.resolution()).isEqualTo(RESOLUTION_WONT_FIX);
assertThat(issue.status()).isEqualTo(STATUS_RESOLVED);

+ 117
- 0
server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/ChangedIssueImpl.java View File

@@ -0,0 +1,117 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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.qualitygate.changeevent;

import java.util.Objects;
import org.sonar.api.issue.Issue;
import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.DefaultIssue;

class ChangedIssueImpl implements QGChangeEventListener.ChangedIssue {
private final String key;
private final QGChangeEventListener.Status status;
private final RuleType type;
private final String severity;
private final boolean fromAlm;

ChangedIssueImpl(DefaultIssue issue) {
this(issue, false);
}

ChangedIssueImpl(DefaultIssue issue, boolean fromAlm) {
this.key = issue.key();
this.status = statusOf(issue);
this.type = issue.type();
this.severity = issue.severity();
this.fromAlm = fromAlm;
}

static QGChangeEventListener.Status statusOf(DefaultIssue issue) {
switch (issue.status()) {
case Issue.STATUS_OPEN:
return QGChangeEventListener.Status.OPEN;
case Issue.STATUS_CONFIRMED:
return QGChangeEventListener.Status.CONFIRMED;
case Issue.STATUS_REOPENED:
return QGChangeEventListener.Status.REOPENED;
case Issue.STATUS_TO_REVIEW:
return QGChangeEventListener.Status.TO_REVIEW;
case Issue.STATUS_IN_REVIEW:
return QGChangeEventListener.Status.IN_REVIEW;
case Issue.STATUS_REVIEWED:
return QGChangeEventListener.Status.REVIEWED;
case Issue.STATUS_RESOLVED:
return statusOfResolved(issue);
default:
throw new IllegalStateException("Unexpected status: " + issue.status());
}
}

private static QGChangeEventListener.Status statusOfResolved(DefaultIssue issue) {
String resolution = issue.resolution();
Objects.requireNonNull(resolution, "A resolved issue should have a resolution");
switch (resolution) {
case Issue.RESOLUTION_FALSE_POSITIVE:
return QGChangeEventListener.Status.RESOLVED_FP;
case Issue.RESOLUTION_WONT_FIX:
return QGChangeEventListener.Status.RESOLVED_WF;
case Issue.RESOLUTION_FIXED:
return QGChangeEventListener.Status.RESOLVED_FIXED;
default:
throw new IllegalStateException("Unexpected resolution for a resolved issue: " + resolution);
}
}

@Override
public String getKey() {
return key;
}

@Override
public QGChangeEventListener.Status getStatus() {
return status;
}

@Override
public RuleType getType() {
return type;
}

@Override
public String getSeverity() {
return severity;
}

@Override
public boolean fromAlm() {
return fromAlm;
}

@Override
public String toString() {
return "ChangedIssueImpl{" +
"key='" + key + '\'' +
", status=" + status +
", type=" + type +
", severity=" + severity +
", fromAlm=" + fromAlm +
'}';
}
}

+ 4
- 0
server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListener.java View File

@@ -53,6 +53,10 @@ public interface QGChangeEventListener {
default boolean isVulnerability() {
return getType() == VULNERABILITY;
}

default boolean fromAlm() {
return false;
}
}

enum Status {

+ 7
- 1
server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListeners.java View File

@@ -25,5 +25,11 @@ import org.sonar.core.issue.DefaultIssue;

public interface QGChangeEventListeners {

void broadcastOnIssueChange(List<DefaultIssue> changedIssues, Collection<QGChangeEvent> qgChangeEvents);
/**
* Broadcast events after issues were updated
*
* @param fromAlm: true if issues changes were initiated by an ALM.
*/
void broadcastOnIssueChange(List<DefaultIssue> changedIssues, Collection<QGChangeEvent> qgChangeEvents, boolean fromAlm);

}

+ 33
- 106
server/sonar-webserver-api/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java View File

@@ -19,14 +19,11 @@
*/
package org.sonar.server.qualitygate.changeevent;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.api.issue.Issue;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.issue.DefaultIssue;
@@ -46,127 +43,57 @@ import static org.sonar.core.util.stream.MoreCollectors.toSet;
public class QGChangeEventListenersImpl implements QGChangeEventListeners {
private static final Logger LOG = Loggers.get(QGChangeEventListenersImpl.class);

private final QGChangeEventListener[] listeners;
private final Set<QGChangeEventListener> listeners;

public QGChangeEventListenersImpl(@Nullable QGChangeEventListener[] listeners) {
this.listeners = listeners != null ? listeners : new QGChangeEventListener[0];
public QGChangeEventListenersImpl(Set<QGChangeEventListener> listeners) {
this.listeners = listeners;
}

@Override
public void broadcastOnIssueChange(List<DefaultIssue> issues, Collection<QGChangeEvent> changeEvents) {
if (listeners.length == 0 || issues.isEmpty() || changeEvents.isEmpty()) {
public void broadcastOnIssueChange(List<DefaultIssue> issues, Collection<QGChangeEvent> changeEvents, boolean fromAlm) {
if (listeners.isEmpty() || issues.isEmpty() || changeEvents.isEmpty()) {
return;
}

try {
Multimap<String, QGChangeEvent> eventsByBranchUuid = changeEvents.stream()
.collect(MoreCollectors.index(t -> t.getBranch().getUuid()));
Multimap<String, DefaultIssue> issueByBranchUuid = issues.stream()
.collect(MoreCollectors.index(DefaultIssue::projectUuid));

issueByBranchUuid.asMap().forEach((branchUuid, branchIssues) -> {
Collection<QGChangeEvent> qgChangeEvents = eventsByBranchUuid.get(branchUuid);
if (qgChangeEvents.isEmpty()) {
return;
}
Set<ChangedIssue> changedIssues = branchIssues.stream().map(ChangedIssueImpl::new).collect(toSet());
for (QGChangeEvent changeEvent : qgChangeEvents) {
for (QGChangeEventListener listener : listeners) {
broadcastTo(changedIssues, changeEvent, listener);
}
}
});
broadcastChangeEventsToBranches(issues, changeEvents, fromAlm);
} catch (Error e) {
LOG.warn(format("Broadcasting to listeners failed for %s events", changeEvents.size()), e);
}
}

private static void broadcastTo(Set<ChangedIssue> changedIssues, QGChangeEvent changeEvent, QGChangeEventListener listener) {
try {
LOG.trace("calling onChange() on listener {} for events {}...", listener.getClass().getName(), changeEvent);
listener.onIssueChanges(changeEvent, changedIssues);
} catch (Exception e) {
LOG.warn(format("onChange() call failed on listener %s for events %s", listener.getClass().getName(), changeEvent), e);
}
}

static class ChangedIssueImpl implements ChangedIssue {
private final String key;
private final QGChangeEventListener.Status status;
private final RuleType type;
private final String severity;

ChangedIssueImpl(DefaultIssue issue) {
this.key = issue.key();
this.status = statusOf(issue);
this.type = issue.type();
this.severity = issue.severity();
}
private void broadcastChangeEventsToBranches(List<DefaultIssue> issues, Collection<QGChangeEvent> changeEvents, boolean fromAlm) {
Multimap<String, QGChangeEvent> eventsByBranchUuid = changeEvents.stream()
.collect(MoreCollectors.index(qgChangeEvent -> qgChangeEvent.getBranch().getUuid()));

static QGChangeEventListener.Status statusOf(DefaultIssue issue) {
switch (issue.status()) {
case Issue.STATUS_OPEN:
return QGChangeEventListener.Status.OPEN;
case Issue.STATUS_CONFIRMED:
return QGChangeEventListener.Status.CONFIRMED;
case Issue.STATUS_REOPENED:
return QGChangeEventListener.Status.REOPENED;
case Issue.STATUS_TO_REVIEW:
return QGChangeEventListener.Status.TO_REVIEW;
case Issue.STATUS_IN_REVIEW:
return QGChangeEventListener.Status.IN_REVIEW;
case Issue.STATUS_REVIEWED:
return QGChangeEventListener.Status.REVIEWED;
case Issue.STATUS_RESOLVED:
return statusOfResolved(issue);
default:
throw new IllegalStateException("Unexpected status: " + issue.status());
}
}

private static QGChangeEventListener.Status statusOfResolved(DefaultIssue issue) {
String resolution = issue.resolution();
Objects.requireNonNull(resolution, "A resolved issue should have a resolution");
switch (resolution) {
case Issue.RESOLUTION_FALSE_POSITIVE:
return QGChangeEventListener.Status.RESOLVED_FP;
case Issue.RESOLUTION_WONT_FIX:
return QGChangeEventListener.Status.RESOLVED_WF;
case Issue.RESOLUTION_FIXED:
return QGChangeEventListener.Status.RESOLVED_FIXED;
default:
throw new IllegalStateException("Unexpected resolution for a resolved issue: " + resolution);
}
}
Multimap<String, DefaultIssue> issueByBranchUuid = issues.stream()
.collect(MoreCollectors.index(DefaultIssue::projectUuid));

@Override
public String getKey() {
return key;
}
issueByBranchUuid.asMap().forEach(
(branchUuid, branchIssues) -> broadcastChangeEventsToBranch(branchIssues, eventsByBranchUuid.get(branchUuid), fromAlm));
}

@Override
public QGChangeEventListener.Status getStatus() {
return status;
}
private void broadcastChangeEventsToBranch(Collection<DefaultIssue> branchIssues, Collection<QGChangeEvent> branchQgChangeEvents, boolean fromAlm) {
Set<ChangedIssue> changedIssues = toChangedIssues(branchIssues, fromAlm);
branchQgChangeEvents.forEach(changeEvent -> broadcastChangeEventToListeners(changedIssues, changeEvent));
}

@Override
public RuleType getType() {
return type;
}
private static ImmutableSet<ChangedIssue> toChangedIssues(Collection<DefaultIssue> defaultIssues, boolean fromAlm) {
return defaultIssues.stream()
.map(defaultIssue -> new ChangedIssueImpl(defaultIssue, fromAlm))
.collect(toSet());
}

@Override
public String getSeverity() {
return severity;
}
private void broadcastChangeEventToListeners(Set<ChangedIssue> changedIssues, QGChangeEvent changeEvent) {
listeners.forEach(listener -> broadcastChangeEventToListener(changedIssues, changeEvent, listener));
}

@Override
public String toString() {
return "ChangedIssueImpl{" +
"key='" + key + '\'' +
", status=" + status +
", type=" + type +
", severity=" + severity +
'}';
private static void broadcastChangeEventToListener(Set<ChangedIssue> changedIssues, QGChangeEvent changeEvent, QGChangeEventListener listener) {
try {
LOG.trace("calling onChange() on listener {} for events {}...", listener.getClass().getName(), changeEvent);
listener.onIssueChanges(changeEvent, changedIssues);
} catch (Exception e) {
LOG.warn(format("onChange() call failed on listener %s for events %s", listener.getClass().getName(), changeEvent), e);
}
}


+ 51
- 18
server/sonar-webserver-api/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java View File

@@ -24,6 +24,7 @@ import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
@@ -43,7 +44,6 @@ import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.db.component.BranchDto;
import org.sonar.server.qualitygate.changeevent.QGChangeEventListener.ChangedIssue;
import org.sonar.server.qualitygate.changeevent.QGChangeEventListenersImpl.ChangedIssueImpl;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
@@ -77,25 +77,25 @@ public class QGChangeEventListenersImplTest {

private final InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3);

private final QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(new QGChangeEventListener[] {listener1, listener2, listener3});
private final QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(new LinkedHashSet<>(List.of(listener1, listener2, listener3)));

@Test
public void broadcastOnIssueChange_has_no_effect_when_issues_are_empty() {
underTest.broadcastOnIssueChange(emptyList(), singletonList(component1QGChangeEvent));
underTest.broadcastOnIssueChange(emptyList(), singletonList(component1QGChangeEvent), false);

verifyNoInteractions(listener1, listener2, listener3);
}

@Test
public void broadcastOnIssueChange_has_no_effect_when_no_changeEvent() {
underTest.broadcastOnIssueChange(oneIssueOnComponent1, emptySet());
underTest.broadcastOnIssueChange(oneIssueOnComponent1, emptySet(), false);

verifyNoInteractions(listener1, listener2, listener3);
}

@Test
public void broadcastOnIssueChange_passes_same_arguments_to_all_listeners_in_order_of_addition_to_constructor() {
underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent));
underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent), false);

ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor();
inOrder.verify(listener1).onIssueChanges(same(component1QGChangeEvent), changedIssuesCaptor.capture());
@@ -112,7 +112,7 @@ public class QGChangeEventListenersImplTest {
.when(failingListener)
.onIssueChanges(any(), any());

underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent));
underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent), false);

ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor();
inOrder.verify(listener1).onIssueChanges(same(component1QGChangeEvent), changedIssuesCaptor.capture());
@@ -130,7 +130,7 @@ public class QGChangeEventListenersImplTest {
.when(listener2)
.onIssueChanges(any(), any());

underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent));
underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent), false);

ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor();
inOrder.verify(listener1).onIssueChanges(same(component1QGChangeEvent), changedIssuesCaptor.capture());
@@ -143,7 +143,7 @@ public class QGChangeEventListenersImplTest {

@Test
public void broadcastOnIssueChange_logs_each_listener_call_at_TRACE_level() {
underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent));
underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent), false);

assertThat(logTester.logs()).hasSize(3);
List<String> traceLogs = logTester.logs(LoggerLevel.TRACE);
@@ -156,9 +156,9 @@ public class QGChangeEventListenersImplTest {

@Test
public void broadcastOnIssueChange_passes_immutable_set_of_ChangedIssues() {
QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(new QGChangeEventListener[] {listener1});
QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(Set.of(listener1));

underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent));
underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent), false);

ArgumentCaptor<Set<ChangedIssue>> changedIssuesCaptor = newSetCaptor();
inOrder.verify(listener1).onIssueChanges(same(component1QGChangeEvent), changedIssuesCaptor.capture());
@@ -167,9 +167,9 @@ public class QGChangeEventListenersImplTest {

@Test
public void broadcastOnIssueChange_has_no_effect_when_no_listener() {
QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(null);
QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(Set.of());

underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent));
underTest.broadcastOnIssueChange(oneIssueOnComponent1, singletonList(component1QGChangeEvent), false);

verifyNoInteractions(listener1, listener2, listener3);
}
@@ -213,7 +213,7 @@ public class QGChangeEventListenersImplTest {
.flatMap(s -> s)
.collect(Collectors.toList());

underTest.broadcastOnIssueChange(changedIssues, randomizedList(qgChangeEvents));
underTest.broadcastOnIssueChange(changedIssues, randomizedList(qgChangeEvents), false);

listeners.forEach(listener -> {
verifyListenerCalled(listener, component1QGChangeEvent, component1Issue);
@@ -274,6 +274,41 @@ public class QGChangeEventListenersImplTest {
assertThat(changedIssue.isVulnerability()).isFalse();
}

@Test
public void fromAlm_returns_false_by_default() {
DefaultIssue defaultIssue = new DefaultIssue();
defaultIssue.setStatus(Issue.STATUS_OPEN);

ChangedIssue changedIssue = new ChangedIssueImpl(defaultIssue);

assertThat(changedIssue.fromAlm()).isFalse();
}

@Test
public void getSeverity_should_returns_default_issue_severity() {
DefaultIssue defaultIssue = new DefaultIssue();
defaultIssue.setStatus(Issue.STATUS_OPEN);
defaultIssue.setSeverity("BLOCKER");

ChangedIssue changedIssue = new ChangedIssueImpl(defaultIssue);

assertThat(changedIssue.getSeverity()).isEqualTo(defaultIssue.severity());
}

@Test
public void test_ChangedIssueImpl_toString() {
DefaultIssue defaultIssue = new DefaultIssue();
defaultIssue.setStatus(Issue.STATUS_CONFIRMED);
defaultIssue.setKey("abc");
defaultIssue.setType(RuleType.BUG);
defaultIssue.setSeverity("BLOCKER");
String expected = "ChangedIssueImpl{key='abc', status=" + Issue.STATUS_CONFIRMED + ", type=" + RuleType.BUG + ", severity=BLOCKER, fromAlm=false}";

ChangedIssue changedIssue = new ChangedIssueImpl(defaultIssue);

assertThat(changedIssue).hasToString(expected);
}

@Test
public void test_status_mapping() {
assertThat(ChangedIssueImpl.statusOf(new DefaultIssue().setStatus(Issue.STATUS_OPEN))).isEqualTo(QGChangeEventListener.Status.OPEN);
@@ -345,12 +380,10 @@ public class QGChangeEventListenersImplTest {
}

private static String[] possibleResolutions(String status) {
switch (status) {
case Issue.STATUS_RESOLVED:
return new String[] {Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_WONT_FIX};
default:
return new String[0];
if (Issue.STATUS_RESOLVED.equals(status)) {
return new String[]{Issue.RESOLUTION_FALSE_POSITIVE, Issue.RESOLUTION_WONT_FIX};
}
return new String[0];
}

private static BranchDto newBranchDto(String uuid) {

+ 7
- 6
server/sonar-webserver-api/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventTest.java View File

@@ -33,23 +33,24 @@ import org.sonar.db.project.ProjectDto;
import org.sonar.server.qualitygate.EvaluatedQualityGate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

public class QGChangeEventTest {

private ProjectDto project = new ProjectDto()
private final ProjectDto project = new ProjectDto()
.setKey("foo")
.setUuid("bar");
private BranchDto branch = new BranchDto()
private final BranchDto branch = new BranchDto()
.setBranchType(BranchType.BRANCH)
.setUuid("bar")
.setProjectUuid("doh")
.setMergeBranchUuid("zop");
private SnapshotDto analysis = new SnapshotDto()
private final SnapshotDto analysis = new SnapshotDto()
.setUuid("pto")
.setCreatedAt(8_999_999_765L);
private Configuration configuration = Mockito.mock(Configuration.class);
private Metric.Level previousStatus = Metric.Level.values()[new Random().nextInt(Metric.Level.values().length)];
private final Configuration configuration = Mockito.mock(Configuration.class);
private final Metric.Level previousStatus = Metric.Level.values()[new Random().nextInt(Metric.Level.values().length)];
private Supplier<Optional<EvaluatedQualityGate>> supplier = Optional::empty;

@Test
@@ -82,7 +83,7 @@ public class QGChangeEventTest {

@Test
public void constructor_does_not_fail_with_NPE_if_previousStatus_is_null() {
new QGChangeEvent(project, branch, analysis, configuration, null, supplier);
assertThatCode(() -> new QGChangeEvent(project, branch, analysis, configuration, null, supplier)).doesNotThrowAnyException();
}

@Test

+ 2
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AddCommentAction.java View File

@@ -83,9 +83,9 @@ public class AddCommentAction implements HotspotsWsAction {
hotspotWsSupport.loadAndCheckProject(dbSession, hotspot, UserRole.USER);

DefaultIssue defaultIssue = hotspot.toDefaultIssue();
IssueChangeContext context = hotspotWsSupport.newIssueChangeContext();
IssueChangeContext context = hotspotWsSupport.newIssueChangeContextWithoutMeasureRefresh();
issueFieldsSetter.addComment(defaultIssue, comment, context);
issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context, false);
issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context);
response.noContent();
}
}

+ 2
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/AssignAction.java View File

@@ -107,7 +107,7 @@ public class AssignAction implements HotspotsWsAction {
hotspotWsSupport.loadAndCheckProject(dbSession, hotspotDto, UserRole.USER);
UserDto assignee = isNullOrEmpty(login) ? null : getAssignee(dbSession, login);

IssueChangeContext context = hotspotWsSupport.newIssueChangeContext();
IssueChangeContext context = hotspotWsSupport.newIssueChangeContextWithoutMeasureRefresh();

DefaultIssue defaultIssue = hotspotDto.toDefaultIssue();

@@ -120,7 +120,7 @@ public class AssignAction implements HotspotsWsAction {
}

if (issueFieldsSetter.assign(defaultIssue, assignee, context)) {
issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context, false);
issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context);
}
}
}

+ 2
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/ChangeStatusAction.java View File

@@ -147,13 +147,13 @@ public class ChangeStatusAction implements HotspotsWsAction {

private void doTransition(DbSession session, IssueDto issueDto, String transitionKey, @Nullable String comment) {
DefaultIssue defaultIssue = issueDto.toDefaultIssue();
IssueChangeContext context = hotspotWsSupport.newIssueChangeContext();
IssueChangeContext context = hotspotWsSupport.newIssueChangeContextWithMeasureRefresh();
transitionService.checkTransitionPermission(transitionKey, defaultIssue);
if (transitionService.doTransition(defaultIssue, context, transitionKey)) {
if (comment != null) {
issueFieldsSetter.addComment(defaultIssue, comment, context);
}
issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context, true);
issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context);
}
}


+ 7
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/HotspotWsSupport.java View File

@@ -34,6 +34,7 @@ import org.sonar.server.user.UserSession;

import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;

public class HotspotWsSupport {
private final DbClient dbClient;
@@ -77,7 +78,11 @@ public class HotspotWsSupport {
return userSession.hasComponentPermission(UserRole.SECURITYHOTSPOT_ADMIN, project);
}

IssueChangeContext newIssueChangeContext() {
return IssueChangeContext.createUser(new Date(system2.now()), checkLoggedIn());
IssueChangeContext newIssueChangeContextWithoutMeasureRefresh() {
return issueChangeContextByUserBuilder(new Date(system2.now()), checkLoggedIn()).build();
}

IssueChangeContext newIssueChangeContextWithMeasureRefresh() {
return issueChangeContextByUserBuilder(new Date(system2.now()), checkLoggedIn()).withRefreshMeasures().build();
}
}

+ 1
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/IssueChangePostProcessor.java View File

@@ -34,6 +34,6 @@ public interface IssueChangePostProcessor {
*
* @param components the components of changed issues
*/
void process(DbSession dbSession, List<DefaultIssue> changedIssues, Collection<ComponentDto> components);
void process(DbSession dbSession, List<DefaultIssue> changedIssues, Collection<ComponentDto> components, boolean fromAlm);

}

+ 2
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/IssueChangePostProcessorImpl.java View File

@@ -39,8 +39,8 @@ public class IssueChangePostProcessorImpl implements IssueChangePostProcessor {
}

@Override
public void process(DbSession dbSession, List<DefaultIssue> changedIssues, Collection<ComponentDto> components) {
public void process(DbSession dbSession, List<DefaultIssue> changedIssues, Collection<ComponentDto> components, boolean fromAlm) {
List<QGChangeEvent> gateChangeEvents = liveMeasureComputer.refresh(dbSession, components);
qualityGateListeners.broadcastOnIssueChange(changedIssues, gateChangeEvents);
qualityGateListeners.broadcastOnIssueChange(changedIssues, gateChangeEvents, fromAlm);
}
}

+ 3
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java View File

@@ -39,6 +39,7 @@ import org.sonarqube.ws.client.issue.IssuesWsParameters;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Objects.requireNonNull;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TEXT;
@@ -95,10 +96,10 @@ public class AddCommentAction implements IssuesWsAction {
AddCommentRequest wsRequest = toWsRequest(request);
try (DbSession dbSession = dbClient.openSession(false)) {
IssueDto issueDto = issueFinder.getByKey(dbSession, wsRequest.getIssue());
IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getUuid());
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(system2.now()), userSession.getUuid()).build();
DefaultIssue defaultIssue = issueDto.toDefaultIssue();
issueFieldsSetter.addComment(defaultIssue, wsRequest.getText(), context);
SearchResponseData preloadedSearchResponseData = issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context, false);
SearchResponseData preloadedSearchResponseData = issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context);
responseWriter.write(defaultIssue.key(), preloadedSearchResponseData, request, response);
}
}

+ 3
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/AssignAction.java View File

@@ -41,6 +41,7 @@ import org.sonar.server.issue.IssueFinder;
import org.sonar.server.user.UserSession;

import static com.google.common.base.Strings.emptyToNull;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.server.exceptions.NotFoundException.checkFound;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_ASSIGN;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEE;
@@ -104,9 +105,9 @@ public class AssignAction implements IssuesWsAction {
IssueDto issueDto = issueFinder.getByKey(dbSession, issueKey);
DefaultIssue issue = issueDto.toDefaultIssue();
UserDto user = getUser(dbSession, login);
IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getUuid());
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(system2.now()), userSession.getUuid()).build();
if (issueFieldsSetter.assign(issue, user, context)) {
return issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, issue, context, false);
return issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, issue, context);
}
return new SearchResponseData(issueDto);
}

+ 3
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java View File

@@ -88,6 +88,7 @@ import static org.sonar.api.issue.DefaultTransitions.SET_AS_IN_REVIEW;
import static org.sonar.api.rule.Severity.BLOCKER;
import static org.sonar.api.rules.RuleType.BUG;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02;
import static org.sonar.core.util.stream.MoreCollectors.toSet;
@@ -207,7 +208,7 @@ public class BulkChangeAction implements IssuesWsAction {
private BulkChangeResult executeBulkChange(DbSession dbSession, Request request) {
BulkChangeData bulkChangeData = new BulkChangeData(dbSession, request);
BulkChangeResult result = new BulkChangeResult(bulkChangeData.issues.size());
IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(system2.now()), userSession.getUuid());
IssueChangeContext issueChangeContext = issueChangeContextByUserBuilder(new Date(system2.now()), userSession.getUuid()).build();

List<DefaultIssue> items = bulkChangeData.issues.stream()
.filter(bulkChange(issueChangeContext, bulkChangeData, result))
@@ -239,7 +240,7 @@ public class BulkChangeAction implements IssuesWsAction {
.collect(MoreCollectors.toList(touchedComponentUuids.size()));

List<DefaultIssue> changedIssues = data.issues.stream().filter(result.success::contains).collect(MoreCollectors.toList());
issueChangePostProcessor.process(dbSession, changedIssues, touchedComponents);
issueChangePostProcessor.process(dbSession, changedIssues, touchedComponents, false);
}

private static Predicate<DefaultIssue> bulkChange(IssueChangeContext issueChangeContext, BulkChangeData bulkChangeData, BulkChangeResult result) {

+ 3
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java View File

@@ -44,6 +44,7 @@ import static org.sonar.api.issue.DefaultTransitions.OPEN_AS_VULNERABILITY;
import static org.sonar.api.issue.DefaultTransitions.RESET_AS_TO_REVIEW;
import static org.sonar.api.issue.DefaultTransitions.RESOLVE_AS_REVIEWED;
import static org.sonar.api.issue.DefaultTransitions.SET_AS_IN_REVIEW;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.BranchType.BRANCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_DO_TRANSITION;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
@@ -114,11 +115,11 @@ public class DoTransitionAction implements IssuesWsAction {

private SearchResponseData doTransition(DbSession session, IssueDto issueDto, String transitionKey) {
DefaultIssue defaultIssue = issueDto.toDefaultIssue();
IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getUuid());
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());
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context, true, branch);
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context, branch);

if (branch.getBranchType().equals(BRANCH) && response.getComponentByUuid(defaultIssue.projectUuid()) != null) {
issueChangeEventService.distributeIssueChangeEvent(defaultIssue, null, null, transitionKey, branch,

+ 5
- 5
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/IssueUpdater.java View File

@@ -68,12 +68,12 @@ public class IssueUpdater {
this.notificationSerializer = notificationSerializer;
}

public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue, IssueChangeContext context, boolean refreshMeasures) {
public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue, IssueChangeContext context) {
BranchDto branch = getBranch(dbSession, issue, issue.projectUuid());
return saveIssueAndPreloadSearchResponseData(dbSession, issue, context, refreshMeasures, branch);
return saveIssueAndPreloadSearchResponseData(dbSession, issue, context, branch);
}

public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue, IssueChangeContext context, boolean refreshMeasures, BranchDto branch) {
public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue, IssueChangeContext context, BranchDto branch) {
Optional<RuleDto> rule = getRuleByKey(dbSession, issue.getRuleKey());
ComponentDto project = dbClient.componentDao().selectOrFailByUuid(dbSession, issue.projectUuid());
ComponentDto component = getComponent(dbSession, issue, issue.componentUuid());
@@ -84,9 +84,9 @@ public class IssueUpdater {
result.addComponents(singleton(project));
result.addComponents(singleton(component));

if (refreshMeasures) {
if (context.refreshMeasures()) {
List<DefaultIssue> changedIssues = result.getIssues().stream().map(IssueDto::toDefaultIssue).collect(MoreCollectors.toList(result.getIssues().size()));
issueChangePostProcessor.process(dbSession, changedIssues, singleton(component));
issueChangePostProcessor.process(dbSession, changedIssues, singleton(component), context.fromAlm());
}

return result;

+ 3
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java View File

@@ -39,6 +39,7 @@ import org.sonar.server.pushapi.issues.IssueChangeEventService;
import org.sonar.server.user.UserSession;

import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.BranchType.BRANCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SET_SEVERITY;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
@@ -111,10 +112,10 @@ public class SetSeverityAction implements IssuesWsAction {
DefaultIssue issue = issueDto.toDefaultIssue();
userSession.checkComponentUuidPermission(ISSUE_ADMIN, issue.projectUuid());

IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.getUuid());
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), userSession.getUuid()).withRefreshMeasures().build();
if (issueFieldsSetter.setManualSeverity(issue, severity, context)) {
BranchDto branch = issueUpdater.getBranch(session, issue, issue.projectUuid());
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, true, branch);
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, branch);

if (branch.getBranchType().equals(BRANCH) && response.getComponentByUuid(issue.projectUuid()) != null) {
issueChangeEventService.distributeIssueChangeEvent(issue, severity, null, null,

+ 3
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java View File

@@ -39,6 +39,7 @@ import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.user.UserSession;

import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SET_TAGS;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TAGS;
@@ -102,9 +103,9 @@ public class SetTagsAction implements IssuesWsAction {
try (DbSession session = dbClient.openSession(false)) {
IssueDto issueDto = issueFinder.getByKey(session, issueKey);
DefaultIssue issue = issueDto.toDefaultIssue();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.getUuid());
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), userSession.getUuid()).build();
if (issueFieldsSetter.setTags(issue, tags, context)) {
return issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, false);
return issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context);
}
return new SearchResponseData(issueDto);
}

+ 3
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java View File

@@ -41,6 +41,7 @@ import org.sonar.server.pushapi.issues.IssueChangeEventService;
import org.sonar.server.user.UserSession;

import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.BranchType.BRANCH;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SET_TYPE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
@@ -116,10 +117,10 @@ public class SetTypeAction implements IssuesWsAction {

userSession.checkComponentUuidPermission(ISSUE_ADMIN, issue.projectUuid());

IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getUuid());
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());
SearchResponseData response = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, true, branch);
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,
response.getComponentByUuid(issue.projectUuid()).getKey());

+ 3
- 3
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AddCommentActionTest.java View File

@@ -64,6 +64,7 @@ import static org.sonar.api.issue.Issue.RESOLUTION_SAFE;
import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.ComponentTesting.newFileDto;

@RunWith(DataProviderRunner.class)
@@ -226,14 +227,13 @@ public class AddCommentActionTest {

newRequest(hotspot, comment).execute().assertNoContent();

IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(now), userSessionRule.getUuid());
IssueChangeContext issueChangeContext = issueChangeContextByUserBuilder(new Date(now), userSessionRule.getUuid()).build();
ArgumentCaptor<DefaultIssue> defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
verify(issueFieldsSetter).addComment(defaultIssueCaptor.capture(), eq(comment), eq(issueChangeContext));
verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
any(DbSession.class),
defaultIssueCaptor.capture(),
eq(issueChangeContext),
eq(false));
eq(issueChangeContext));

// because it is mutated by FieldSetter and IssueUpdater, the same object must be passed to all methods
List<DefaultIssue> capturedDefaultIssues = defaultIssueCaptor.getAllValues();

+ 1
- 2
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/AssignActionTest.java View File

@@ -502,8 +502,7 @@ public class AssignActionTest {
verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
any(DbSession.class),
defaultIssueCaptor.capture(),
any(IssueChangeContext.class),
eq(false));
any(IssueChangeContext.class));

capturedArgsCount += 2;


+ 7
- 9
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ChangeStatusActionTest.java View File

@@ -75,6 +75,7 @@ import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.ComponentTesting.newFileDto;

@RunWith(DataProviderRunner.class)
@@ -404,7 +405,7 @@ public class ChangeStatusActionTest {

newRequest(hotspot, STATUS_REVIEWED, resolution, NO_COMMENT).execute().assertNoContent();

IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(now), userSessionRule.getUuid());
IssueChangeContext issueChangeContext = issueChangeContextByUserBuilder(new Date(now), userSessionRule.getUuid()).withRefreshMeasures().build();
ArgumentCaptor<DefaultIssue> defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
verify(transitionService).checkTransitionPermission(eq(expectedTransitionKey), defaultIssueCaptor.capture());
verify(transitionService).doTransition(
@@ -415,8 +416,7 @@ public class ChangeStatusActionTest {
verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
any(DbSession.class),
defaultIssueCaptor.capture(),
eq(issueChangeContext),
eq(true));
eq(issueChangeContext));

// because it is mutated by FieldSetter and IssueUpdater, the same object must be passed to all methods
verifyAllSame3Objects(defaultIssueCaptor.getAllValues());
@@ -453,7 +453,7 @@ public class ChangeStatusActionTest {

newRequest(hotspot, STATUS_TO_REVIEW, null, NO_COMMENT).execute().assertNoContent();

IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(now), userSessionRule.getUuid());
IssueChangeContext issueChangeContext = issueChangeContextByUserBuilder(new Date(now), userSessionRule.getUuid()).withRefreshMeasures().build();
ArgumentCaptor<DefaultIssue> defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
verify(transitionService).checkTransitionPermission(eq(DefaultTransitions.RESET_AS_TO_REVIEW), defaultIssueCaptor.capture());
verify(transitionService).doTransition(
@@ -464,8 +464,7 @@ public class ChangeStatusActionTest {
verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
any(DbSession.class),
defaultIssueCaptor.capture(),
eq(issueChangeContext),
eq(true));
eq(issueChangeContext));

// because it is mutated by FieldSetter and IssueUpdater, the same object must be passed to all methods
verifyAllSame3Objects(defaultIssueCaptor.getAllValues());
@@ -504,7 +503,7 @@ public class ChangeStatusActionTest {

newRequest(hotspot, newStatus, newResolution, comment).execute().assertNoContent();

IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(now), userSessionRule.getUuid());
IssueChangeContext issueChangeContext = issueChangeContextByUserBuilder(new Date(now), userSessionRule.getUuid()).withRefreshMeasures().build();
ArgumentCaptor<DefaultIssue> defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class);
verify(transitionService).doTransition(defaultIssueCaptor.capture(), eq(issueChangeContext), anyString());
if (transitionDone) {
@@ -512,8 +511,7 @@ public class ChangeStatusActionTest {
verify(issueUpdater).saveIssueAndPreloadSearchResponseData(
any(DbSession.class),
defaultIssueCaptor.capture(),
eq(issueChangeContext),
eq(true));
eq(issueChangeContext));

// because it is mutated by FieldSetter and IssueUpdater, the same object must be passed to all methods
verifyAllSame3Objects(defaultIssueCaptor.getAllValues());

+ 2
- 1
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/AssignActionTest.java View File

@@ -40,6 +40,7 @@ import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.server.tester.UserSessionRule.standalone;

public class AssignActionTest {
@@ -53,7 +54,7 @@ public class AssignActionTest {
@Rule
public DbTester db = DbTester.create();

private final IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(), "user_uuid");
private final IssueChangeContext issueChangeContext = issueChangeContextByUserBuilder(new Date(), "user_uuid").build();
private final DefaultIssue issue = new DefaultIssue().setKey("ABC").setAssigneeUuid(ISSUE_CURRENT_ASSIGNEE_UUID);
private Action.Context context;


+ 2
- 2
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/SetSeverityActionTest.java View File

@@ -29,7 +29,6 @@ import org.sonar.api.issue.Issue;
import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.IssueDto;
@@ -43,6 +42,7 @@ import static org.sonar.api.rule.Severity.MAJOR;
import static org.sonar.api.rule.Severity.MINOR;
import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.issue.IssueTesting.newDto;
import static org.sonar.db.rule.RuleTesting.newRuleDto;
@@ -68,7 +68,7 @@ public class SetSeverityActionTest {
IssueDto issueDto = newIssue().setSeverity(MAJOR);
DefaultIssue issue = issueDto.toDefaultIssue();
setUserWithBrowseAndAdministerIssuePermission(issueDto);
Action.Context context = new ActionContext(issue, IssueChangeContext.createUser(NOW, userSession.getUuid()), null);
Action.Context context = new ActionContext(issue, issueChangeContextByUserBuilder(NOW, userSession.getUuid()).build(), null);

action.execute(ImmutableMap.of("severity", MINOR), context);


+ 2
- 2
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/SetTypeActionTest.java View File

@@ -28,7 +28,6 @@ import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.issue.IssueDto;
@@ -41,6 +40,7 @@ import static org.sonar.api.rules.RuleType.BUG;
import static org.sonar.api.rules.RuleType.VULNERABILITY;
import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.issue.IssueTesting.newDto;
import static org.sonar.db.rule.RuleTesting.newRuleDto;
@@ -68,7 +68,7 @@ public class SetTypeActionTest {
setUserWithBrowseAndAdministerIssuePermission(issueDto);

action.execute(ImmutableMap.of("type", VULNERABILITY.name()),
new ActionContext(issue, IssueChangeContext.createUser(NOW, userSession.getUuid()), null));
new ActionContext(issue, issueChangeContextByUserBuilder(NOW, userSession.getUuid()).build(), null));

assertThat(issue.type()).isEqualTo(VULNERABILITY);
assertThat(issue.isChanged()).isTrue();

+ 1
- 1
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/TestIssueChangePostProcessor.java View File

@@ -32,7 +32,7 @@ public class TestIssueChangePostProcessor implements IssueChangePostProcessor {
private final List<ComponentDto> calledComponents = new ArrayList<>();

@Override
public void process(DbSession dbSession, List<DefaultIssue> changedIssues, Collection<ComponentDto> components) {
public void process(DbSession dbSession, List<DefaultIssue> changedIssues, Collection<ComponentDto> components, boolean fromAlm) {
called = true;
calledComponents.addAll(components);
}

+ 2
- 2
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/TransitionActionTest.java View File

@@ -28,7 +28,6 @@ import org.junit.Test;
import org.sonar.api.issue.Issue;
import org.sonar.api.rules.RuleType;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.util.Uuids;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
@@ -46,6 +45,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.issue.Issue.STATUS_CLOSED;
import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.rule.RuleTesting.newRule;

@@ -65,7 +65,7 @@ public class TransitionActionTest {
public void setUp() {
workflow.start();
when(context.issue()).thenReturn(issue);
when(context.issueChangeContext()).thenReturn(IssueChangeContext.createUser(new Date(), "user_uuid"));
when(context.issueChangeContext()).thenReturn(issueChangeContextByUserBuilder(new Date(), "user_uuid").build());
}

@Test

+ 4
- 2
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/TransitionServiceTest.java View File

@@ -41,6 +41,7 @@ import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.rules.RuleType.CODE_SMELL;
import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.ComponentTesting.newFileDto;

public class TransitionServiceTest {
@@ -119,7 +120,7 @@ public class TransitionServiceTest {
IssueDto issue = db.issues().insert(rule, project, file, i -> i.setStatus(STATUS_OPEN).setResolution(null).setType(CODE_SMELL));

DefaultIssue defaultIssue = issue.toDefaultIssue();
boolean result = underTest.doTransition(defaultIssue, IssueChangeContext.createUser(new Date(), "user_uuid"), "confirm");
boolean result = underTest.doTransition(defaultIssue, issueChangeContextByUserBuilder(new Date(), "user_uuid").build(), "confirm");

assertThat(result).isTrue();
assertThat(defaultIssue.status()).isEqualTo(STATUS_CONFIRMED);
@@ -133,7 +134,8 @@ public class TransitionServiceTest {
IssueDto externalIssue = db.issues().insert(externalRule, project, file, i -> i.setStatus(STATUS_OPEN).setResolution(null).setType(CODE_SMELL));
DefaultIssue defaultIssue = externalIssue.toDefaultIssue();

assertThatThrownBy(() -> underTest.doTransition(defaultIssue, IssueChangeContext.createUser(new Date(), "user_uuid"), "confirm"))
IssueChangeContext issueChangeContext = issueChangeContextByUserBuilder(new Date(), "user_uuid").build();
assertThatThrownBy(() -> underTest.doTransition(defaultIssue, issueChangeContext, "confirm"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Transition is not allowed on issues imported from external rule engines");
}

+ 2
- 2
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/WebIssueStorageTest.java View File

@@ -31,7 +31,6 @@ import org.sonar.api.utils.Duration;
import org.sonar.api.utils.System2;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.util.SequenceUuidFactory;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
@@ -46,6 +45,7 @@ import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.sonar.api.rule.RuleStatus.REMOVED;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.issue.IssueTesting.newIssue;
@@ -173,7 +173,7 @@ public class WebIssueStorageTest {
.setChecksum("FFFFF")
.setAuthorLogin("simon")
.setAssigneeUuid("loic")
.setFieldChange(IssueChangeContext.createUser(new Date(), "user_uuid"), "severity", "INFO", "BLOCKER")
.setFieldChange(issueChangeContextByUserBuilder(new Date(), "user_uuid").build(), "severity", "INFO", "BLOCKER")
.addComment(DefaultIssueComment.create("ABCDE", "user_uuid", "the comment"))
.setResolution("FIXED")
.setStatus("RESOLVED")

+ 19
- 18
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/IssueUpdaterTest.java View File

@@ -57,6 +57,7 @@ import static org.mockito.Mockito.verifyNoInteractions;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.rule.Severity.BLOCKER;
import static org.sonar.api.rule.Severity.MAJOR;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
import static org.sonar.db.component.BranchType.BRANCH;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.projectBranchOf;
@@ -91,10 +92,10 @@ public class IssueUpdaterTest {
public void update_issue() {
DefaultIssue issue = db.issues().insertIssue(i -> i.setSeverity(MAJOR)).toDefaultIssue();
UserDto user = db.users().insertUser();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), user.getUuid());
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), user.getUuid()).build();
issueFieldsSetter.setSeverity(issue, BLOCKER, context);

underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context, false);
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context);

IssueDto issueReloaded = dbClient.issueDao().selectByKey(db.getSession(), issue.key()).get();
assertThat(issueReloaded.getSeverity()).isEqualTo(BLOCKER);
@@ -110,10 +111,10 @@ public class IssueUpdaterTest {
t -> t.setSeverity(MAJOR).setAssigneeUuid(assignee.getUuid()))
.toDefaultIssue();
UserDto changeAuthor = db.users().insertUser();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), changeAuthor.getUuid());
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), changeAuthor.getUuid()).build();
issueFieldsSetter.setSeverity(issue, BLOCKER, context);

underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context, false);
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context);

verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
IssuesChangesNotification issueChangeNotification = notificationArgumentCaptor.getValue();
@@ -139,10 +140,10 @@ public class IssueUpdaterTest {
t -> t.setSeverity(MAJOR).setAssigneeUuid(assignee.getUuid()))
.toDefaultIssue();
UserDto changeAuthor = db.users().insertUser();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), changeAuthor.getUuid());
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), changeAuthor.getUuid()).build();
issueFieldsSetter.setResolution(issue, RESOLUTION_FIXED, context);

underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context, false);
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context);

verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
IssuesChangesNotification issueChangeNotification = notificationArgumentCaptor.getValue();
@@ -167,10 +168,10 @@ public class IssueUpdaterTest {
DefaultIssue issue = db.issues().insertIssue(rule, branch, file,
t -> t.setSeverity(MAJOR)).toDefaultIssue();
UserDto changeAuthor = db.users().insertUser();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), changeAuthor.getUuid());
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), changeAuthor.getUuid()).build();
issueFieldsSetter.setSeverity(issue, BLOCKER, context);

underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context, false);
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context);

verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
IssuesChangesNotification issueChangeNotification = notificationArgumentCaptor.getValue();
@@ -193,10 +194,10 @@ public class IssueUpdaterTest {
ComponentDto branch = db.components().insertProjectBranch(project, t -> t.setBranchType(BranchType.PULL_REQUEST));
ComponentDto file = db.components().insertComponent(newFileDto(branch));
DefaultIssue issue = db.issues().insertIssue(rule, branch, file, t -> t.setSeverity(MAJOR)).toDefaultIssue();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), "user_uuid");
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), "user_uuid").build();
issueFieldsSetter.setSeverity(issue, BLOCKER, context);

underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context, false);
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context);

verifyNoInteractions(notificationManager);
}
@@ -207,10 +208,10 @@ public class IssueUpdaterTest {
ComponentDto project = db.components().insertPublicProject();
ComponentDto file = db.components().insertComponent(newFileDto(project));
DefaultIssue issue = db.issues().insertIssue(rule, project, file, t -> t.setSeverity(MAJOR)).toDefaultIssue();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), "user_uuid");
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), "user_uuid").build();
issueFieldsSetter.setSeverity(issue, BLOCKER, context);

underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context, false);
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context);

verifyNoInteractions(notificationManager);
}
@@ -224,11 +225,11 @@ public class IssueUpdaterTest {
DefaultIssue issue = db.issues().insertIssue(rule, project, file, t -> t.setAssigneeUuid(oldAssignee.getUuid()))
.toDefaultIssue();
UserDto changeAuthor = db.users().insertUser();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), changeAuthor.getUuid());
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), changeAuthor.getUuid()).build();
UserDto newAssignee = db.users().insertUser();
issueFieldsSetter.assign(issue, newAssignee, context);

underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context, false);
underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context);

verify(notificationManager).scheduleForSending(notificationArgumentCaptor.capture());
IssuesChangesNotification issueChangeNotification = notificationArgumentCaptor.getValue();
@@ -252,10 +253,10 @@ public class IssueUpdaterTest {
IssueDto issueDto = db.issues().insertIssue(rule, project, file);
DefaultIssue issue = issueDto.setSeverity(MAJOR).toDefaultIssue();
UserDto changeAuthor = db.users().insertUser();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), changeAuthor.getUuid());
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), changeAuthor.getUuid()).withRefreshMeasures().build();
issueFieldsSetter.setSeverity(issue, BLOCKER, context);

SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context, true);
SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context);

assertThat(preloadedSearchResponseData.getIssues())
.hasSize(1);
@@ -277,10 +278,10 @@ public class IssueUpdaterTest {
ComponentDto file = db.components().insertComponent(newFileDto(project));
IssueDto issueDto = db.issues().insertIssue(rule, project, file);
DefaultIssue issue = issueDto.setSeverity(MAJOR).toDefaultIssue();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), "user_uuid");
IssueChangeContext context = issueChangeContextByUserBuilder(new Date(), "user_uuid").build();
issueFieldsSetter.setSeverity(issue, BLOCKER, context);

SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context, false);
SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(db.getSession(), issue, context);

assertThat(preloadedSearchResponseData.getIssues())
.hasSize(1);

+ 69
- 8
sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java View File

@@ -25,16 +25,22 @@ import java.util.Objects;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;

import static java.util.Objects.requireNonNull;

public class IssueChangeContext implements Serializable {

private final String userUuid;
private final Date date;
private final boolean scan;
private final boolean refreshMeasures;
private final boolean fromAlm;

private IssueChangeContext(@Nullable String userUuid, Date date, boolean scan) {
private IssueChangeContext(@Nullable String userUuid, Date date, boolean scan, boolean refreshMeasures, boolean fromAlm) {
this.userUuid = userUuid;
this.date = date;
this.date = requireNonNull(date);
this.scan = scan;
this.refreshMeasures = refreshMeasures;
this.fromAlm = fromAlm;
}

@CheckForNull
@@ -50,12 +56,12 @@ public class IssueChangeContext implements Serializable {
return scan;
}

public static IssueChangeContext createScan(Date date) {
return new IssueChangeContext(null, date, true);
public boolean refreshMeasures() {
return refreshMeasures;
}

public static IssueChangeContext createUser(Date date, @Nullable String userUuid) {
return new IssueChangeContext(userUuid, date, false);
public boolean fromAlm() {
return fromAlm;
}

@Override
@@ -69,12 +75,13 @@ public class IssueChangeContext implements Serializable {
IssueChangeContext that = (IssueChangeContext) o;
return scan == that.scan &&
Objects.equals(userUuid, that.userUuid) &&
Objects.equals(date, that.date);
Objects.equals(date, that.date) &&
refreshMeasures == that.refreshMeasures;
}

@Override
public int hashCode() {
return Objects.hash(userUuid, date, scan);
return Objects.hash(userUuid, date, scan, refreshMeasures, fromAlm);
}

@Override
@@ -83,6 +90,60 @@ public class IssueChangeContext implements Serializable {
"userUuid='" + userUuid + '\'' +
", date=" + date +
", scan=" + scan +
", refreshMeasures=" + refreshMeasures +
", fromAlm=" + fromAlm +
'}';
}

public static IssueChangeContextBuilder newBuilder() {
return new IssueChangeContextBuilder();
}

public static IssueChangeContextBuilder issueChangeContextByScanBuilder(Date date) {
return newBuilder().withScan().setUserUuid(null).setDate(date);
}

public static IssueChangeContextBuilder issueChangeContextByUserBuilder(Date date, @Nullable String userUuid) {
return newBuilder().setUserUuid(userUuid).setDate(date);
}

public static final class IssueChangeContextBuilder {
private String userUuid;
private Date date;
private boolean scan = false;
private boolean refreshMeasures = false;
private boolean fromAlm = false;

private IssueChangeContextBuilder() {
}

public IssueChangeContextBuilder setUserUuid(@Nullable String userUuid) {
this.userUuid = userUuid;
return this;
}

public IssueChangeContextBuilder setDate(Date date) {
this.date = date;
return this;
}

public IssueChangeContextBuilder withScan() {
this.scan = true;
return this;
}

public IssueChangeContextBuilder withRefreshMeasures() {
this.refreshMeasures = true;
return this;
}

public IssueChangeContextBuilder withFromAlm() {
this.fromAlm = true;
return this;
}

public IssueChangeContext build() {
return new IssueChangeContext(userUuid, date, scan, refreshMeasures, fromAlm);
}
}
}

+ 67
- 12
sonar-core/src/test/java/org/sonar/core/issue/IssueChangeContextTest.java View File

@@ -20,28 +20,83 @@
package org.sonar.core.issue;

import java.util.Date;
import java.util.Objects;
import javax.annotation.Nullable;
import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByScanBuilder;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;

public class IssueChangeContextTest {

private static final Date NOW = new Date();
private static final String USER_UUID = "user_uuid";

private IssueChangeContext context;

@Test
public void test_issueChangeContextByScanBuilder() {
context = issueChangeContextByScanBuilder(NOW).build();

verifyContext(null, true, false, false);
}

@Test
public void test_issueChangeContextByUserBuilder() {
context = issueChangeContextByUserBuilder(NOW, USER_UUID).build();

verifyContext(USER_UUID, false, false, false);
}

@Test
public void test_scan_context() {
Date now = new Date();
IssueChangeContext context = IssueChangeContext.createScan(now);
public void test_newBuilder() {
context = IssueChangeContext.newBuilder()
.withScan()
.withRefreshMeasures()
.setUserUuid(USER_UUID)
.setDate(NOW)
.withFromAlm()
.build();

assertThat(context.scan()).isTrue();
assertThat(context.userUuid()).isNull();
assertThat(context.date()).isEqualTo(now);
verifyContext(USER_UUID, true, true, true);
}

@Test
public void test_end_user_context() {
Date now = new Date();
IssueChangeContext context = IssueChangeContext.createUser(now, "user_uuid");
public void test_equal() {
context = IssueChangeContext.newBuilder().setUserUuid(USER_UUID).setDate(NOW).build();
IssueChangeContext equalContext = IssueChangeContext.newBuilder().setUserUuid(USER_UUID).setDate(NOW).build();
IssueChangeContext notEqualContext = IssueChangeContext.newBuilder().setUserUuid("other_user_uuid").setDate(NOW).build();

assertThat(context.scan()).isFalse();
assertThat(context.userUuid()).isEqualTo("user_uuid");
assertThat(context.date()).isEqualTo(now);
assertThat(context).isEqualTo(context)
.isEqualTo(equalContext)
.isNotEqualTo(notEqualContext)
.isNotEqualTo(null)
.isNotEqualTo(new Object());
}

@Test
public void test_hashCode() {
context = IssueChangeContext.newBuilder().setUserUuid(USER_UUID).setDate(NOW).build();

assertThat(context.hashCode()).isEqualTo(Objects.hash(USER_UUID, NOW, false, false, false));
}

@Test
public void test_toString() {
context = IssueChangeContext.newBuilder().setUserUuid(USER_UUID).setDate(NOW).build();
String expected = "IssueChangeContext{userUuid='user_uuid', date=" + NOW + ", scan=false, refreshMeasures=false, fromAlm=false}";

assertThat(context).hasToString(expected);
}

private void verifyContext(@Nullable String userUuid, boolean scan, boolean refreshMeasures, boolean fromAlm) {
assertThat(context.userUuid()).isEqualTo(userUuid);
assertThat(context.date()).isEqualTo(NOW);
assertThat(context.scan()).isEqualTo(scan);
assertThat(context.refreshMeasures()).isEqualTo(refreshMeasures);
assertThat(context.fromAlm()).isEqualTo(fromAlm);
}


}

Loading…
Cancel
Save