@@ -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 |
@@ -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; |
@@ -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, |
@@ -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); | |||
} |
@@ -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); |
@@ -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 |
@@ -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(); |
@@ -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); |
@@ -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 + | |||
'}'; | |||
} | |||
} |
@@ -53,6 +53,10 @@ public interface QGChangeEventListener { | |||
default boolean isVulnerability() { | |||
return getType() == VULNERABILITY; | |||
} | |||
default boolean fromAlm() { | |||
return false; | |||
} | |||
} | |||
enum Status { |
@@ -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); | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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) { |
@@ -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 |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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) { |
@@ -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, |
@@ -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; |
@@ -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, |
@@ -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); | |||
} |
@@ -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()); |
@@ -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(); |
@@ -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; | |||
@@ -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()); |
@@ -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; | |||
@@ -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); | |||
@@ -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(); |
@@ -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); | |||
} |
@@ -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 |
@@ -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"); | |||
} |
@@ -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") |
@@ -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); |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |