Skip to content

Commit

Permalink
SONAR-9720 Issue tracking for long branches
Browse files Browse the repository at this point in the history
  • Loading branch information
dbmeneses authored and Janos Gyerik committed Sep 12, 2017
1 parent fe946b3 commit 6affc40
Show file tree
Hide file tree
Showing 21 changed files with 419 additions and 35 deletions.
Expand Up @@ -67,6 +67,7 @@
import org.sonar.server.computation.task.projectanalysis.issue.IssueVisitors; import org.sonar.server.computation.task.projectanalysis.issue.IssueVisitors;
import org.sonar.server.computation.task.projectanalysis.issue.IssuesRepositoryVisitor; import org.sonar.server.computation.task.projectanalysis.issue.IssuesRepositoryVisitor;
import org.sonar.server.computation.task.projectanalysis.issue.LoadComponentUuidsHavingOpenIssuesVisitor; import org.sonar.server.computation.task.projectanalysis.issue.LoadComponentUuidsHavingOpenIssuesVisitor;
import org.sonar.server.computation.task.projectanalysis.issue.MergeBranchTrackerExecution;
import org.sonar.server.computation.task.projectanalysis.issue.MovedIssueVisitor; import org.sonar.server.computation.task.projectanalysis.issue.MovedIssueVisitor;
import org.sonar.server.computation.task.projectanalysis.issue.NewEffortAggregator; import org.sonar.server.computation.task.projectanalysis.issue.NewEffortAggregator;
import org.sonar.server.computation.task.projectanalysis.issue.NewEffortCalculator; import org.sonar.server.computation.task.projectanalysis.issue.NewEffortCalculator;
Expand Down Expand Up @@ -239,6 +240,7 @@ private static List<Object> componentClasses() {
Tracker.class, Tracker.class,
TrackerExecution.class, TrackerExecution.class,
ShortBranchTrackerExecution.class, ShortBranchTrackerExecution.class,
MergeBranchTrackerExecution.class,
ComponentIssuesLoader.class, ComponentIssuesLoader.class,
BaseIssuesLoader.class, BaseIssuesLoader.class,
IssueTrackingDelegator.class, IssueTrackingDelegator.class,
Expand Down
Expand Up @@ -26,7 +26,6 @@
import java.util.Map; import java.util.Map;


import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracking;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.component.Component; import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.Component.Status; import org.sonar.server.computation.task.projectanalysis.component.Component.Status;
Expand Down Expand Up @@ -60,13 +59,15 @@ public void visitAny(Component component) {
issueVisitors.beforeComponent(component); issueVisitors.beforeComponent(component);


if (isIncremental(component)) { if (isIncremental(component)) {
// no tracking needed, simply re-use existing issues
List<DefaultIssue> issues = issuesLoader.loadForComponentUuid(component.getUuid()); List<DefaultIssue> issues = issuesLoader.loadForComponentUuid(component.getUuid());
fillIncrementalOpenIssues(component, issues, cacheAppender); reuseOpenIssues(component, issues, cacheAppender);
} else { } else {
Tracking<DefaultIssue, DefaultIssue> tracking = issueTracking.track(component); TrackingResult tracking = issueTracking.track(component);
fillNewOpenIssues(component, tracking.getUnmatchedRaws(), cacheAppender); fillNewOpenIssues(component, tracking.newIssues(), cacheAppender);
fillExistingOpenIssues(component, tracking.getMatchedRaws(), cacheAppender); fillExistingOpenIssues(component, tracking.issuesToMerge(), cacheAppender);
closeUnmatchedBaseIssues(component, tracking.getUnmatchedBases(), cacheAppender); closeIssues(component, tracking.issuesToClose(), cacheAppender);
copyIssues(component, tracking.issuesToCopy(), cacheAppender);
} }
issueVisitors.afterComponent(component); issueVisitors.afterComponent(component);
} catch (Exception e) { } catch (Exception e) {
Expand All @@ -85,7 +86,16 @@ private void fillNewOpenIssues(Component component, Iterable<DefaultIssue> issue
} }
} }


private void fillIncrementalOpenIssues(Component component, Collection<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) { private void copyIssues(Component component, Map<DefaultIssue, DefaultIssue> matched, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
for (Map.Entry<DefaultIssue, DefaultIssue> entry : matched.entrySet()) {
DefaultIssue raw = entry.getKey();
DefaultIssue base = entry.getValue();
issueLifecycle.copyExistingOpenIssue(raw, base);
process(component, raw, cacheAppender);
}
}

private void reuseOpenIssues(Component component, Collection<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
for (DefaultIssue issue : issues) { for (DefaultIssue issue : issues) {
process(component, issue, cacheAppender); process(component, issue, cacheAppender);
} }
Expand All @@ -100,7 +110,7 @@ private void fillExistingOpenIssues(Component component, Map<DefaultIssue, Defau
} }
} }


private void closeUnmatchedBaseIssues(Component component, Iterable<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) { private void closeIssues(Component component, Iterable<DefaultIssue> issues, DiskCache<DefaultIssue>.DiskAppender cacheAppender) {
for (DefaultIssue issue : issues) { for (DefaultIssue issue : issues) {
// TODO should replace flag "beingClosed" by express call to transition "automaticClose" // TODO should replace flag "beingClosed" by express call to transition "automaticClose"
issue.setBeingClosed(true); issue.setBeingClosed(true);
Expand Down
Expand Up @@ -65,6 +65,29 @@ public void initNewOpenIssue(DefaultIssue issue) {
issue.setEffort(debtCalculator.calculate(issue)); issue.setEffort(debtCalculator.calculate(issue));
} }


public void copyExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
raw.setKey(Uuids.create());
raw.setNew(false);
raw.setCopied(true);
raw.setType(base.type());
raw.setCreationDate(base.creationDate());
raw.setUpdateDate(base.updateDate());
raw.setCloseDate(base.closeDate());
raw.setResolution(base.resolution());
raw.setStatus(base.status());
raw.setAssignee(base.assignee());
raw.setAuthorLogin(base.authorLogin());
raw.setTags(base.tags());
raw.setAttributes(base.attributes());
raw.setEffort(debtCalculator.calculate(raw));
raw.setOnDisabledRule(base.isOnDisabledRule());
if (base.manualSeverity()) {
raw.setManualSeverity(true);
raw.setSeverity(base.severity());
}
raw.setSelectedAt(base.selectedAt());
}

public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) { public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) {
raw.setNew(false); raw.setNew(false);
raw.setKey(base.key()); raw.setKey(base.key());
Expand Down
Expand Up @@ -19,28 +19,57 @@
*/ */
package org.sonar.server.computation.task.projectanalysis.issue; package org.sonar.server.computation.task.projectanalysis.issue;


import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;

import java.util.Optional;


import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracking; import org.sonar.core.issue.tracking.Tracking;
import org.sonar.db.component.BranchType;
import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.server.computation.task.projectanalysis.analysis.Branch;
import org.sonar.server.computation.task.projectanalysis.component.Component; import org.sonar.server.computation.task.projectanalysis.component.Component;


public class IssueTrackingDelegator { public class IssueTrackingDelegator {
private final ShortBranchTrackerExecution shortBranchTracker; private final ShortBranchTrackerExecution shortBranchTracker;
private final TrackerExecution tracker; private final TrackerExecution tracker;
private final AnalysisMetadataHolder analysisMetadataHolder; private final AnalysisMetadataHolder analysisMetadataHolder;
private final MergeBranchTrackerExecution mergeBranchTracker;


public IssueTrackingDelegator(ShortBranchTrackerExecution shortBranchTracker, TrackerExecution tracker, AnalysisMetadataHolder analysisMetadataHolder) { public IssueTrackingDelegator(ShortBranchTrackerExecution shortBranchTracker, MergeBranchTrackerExecution longBranchTracker,
TrackerExecution tracker, AnalysisMetadataHolder analysisMetadataHolder) {
this.shortBranchTracker = shortBranchTracker; this.shortBranchTracker = shortBranchTracker;
this.mergeBranchTracker = longBranchTracker;
this.tracker = tracker; this.tracker = tracker;
this.analysisMetadataHolder = analysisMetadataHolder; this.analysisMetadataHolder = analysisMetadataHolder;
} }


public Tracking<DefaultIssue, DefaultIssue> track(Component component) { public TrackingResult track(Component component) {
if (analysisMetadataHolder.isShortLivingBranch()) { if (analysisMetadataHolder.isShortLivingBranch()) {
return shortBranchTracker.track(component); return standardResult(shortBranchTracker.track(component));
} else if (isFirstAnalysisSecondaryLongLivingBranch()) {
Tracking<DefaultIssue, DefaultIssue> tracking = mergeBranchTracker.track(component);
return new TrackingResult(tracking.getMatchedRaws(), emptyMap(), emptyList(), tracking.getUnmatchedRaws());
} else { } else {
return tracker.track(component); return standardResult(tracker.track(component));
}
}

private static TrackingResult standardResult(Tracking<DefaultIssue, DefaultIssue> tracking) {
return new TrackingResult(emptyMap(), tracking.getMatchedRaws(), tracking.getUnmatchedBases(), tracking.getUnmatchedRaws());
}

/**
* Special case where we want to do the issue tracking with the merge branch, and copy matched issue to the current branch.
*/
private boolean isFirstAnalysisSecondaryLongLivingBranch() {
if (analysisMetadataHolder.isFirstAnalysis()) {
Optional<Branch> branch = analysisMetadataHolder.getBranch();
if (branch.isPresent()) {
return !branch.get().isMain() && branch.get().getType() == BranchType.LONG;
}
} }
return false;
} }
} }
@@ -0,0 +1,42 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.computation.task.projectanalysis.issue;

import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Tracker;
import org.sonar.core.issue.tracking.Tracking;
import org.sonar.server.computation.task.projectanalysis.component.Component;

public class MergeBranchTrackerExecution {
private final TrackerRawInputFactory rawInputFactory;
private final TrackerMergeBranchInputFactory mergeInputFactory;
private final Tracker<DefaultIssue, DefaultIssue> tracker;

public MergeBranchTrackerExecution(TrackerRawInputFactory rawInputFactory, TrackerMergeBranchInputFactory mergeInputFactory,
Tracker<DefaultIssue, DefaultIssue> tracker) {
this.rawInputFactory = rawInputFactory;
this.mergeInputFactory = mergeInputFactory;
this.tracker = tracker;
}

public Tracking<DefaultIssue, DefaultIssue> track(Component component) {
return tracker.track(rawInputFactory.create(component), mergeInputFactory.create(component));
}
}
Expand Up @@ -29,10 +29,10 @@
import org.sonar.server.computation.task.projectanalysis.component.Component; import org.sonar.server.computation.task.projectanalysis.component.Component;


public class ShortBranchTrackerExecution { public class ShortBranchTrackerExecution {
private TrackerBaseInputFactory baseInputFactory; private final TrackerBaseInputFactory baseInputFactory;
private TrackerRawInputFactory rawInputFactory; private final TrackerRawInputFactory rawInputFactory;
private TrackerMergeBranchInputFactory mergeInputFactory; private final TrackerMergeBranchInputFactory mergeInputFactory;
private Tracker<DefaultIssue, DefaultIssue> tracker; private final Tracker<DefaultIssue, DefaultIssue> tracker;


public ShortBranchTrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory, TrackerMergeBranchInputFactory mergeInputFactory, public ShortBranchTrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory, TrackerMergeBranchInputFactory mergeInputFactory,
Tracker<DefaultIssue, DefaultIssue> tracker) { Tracker<DefaultIssue, DefaultIssue> tracker) {
Expand Down
@@ -0,0 +1,55 @@
/*
* SonarQube
* Copyright (C) 2009-2017 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.computation.task.projectanalysis.issue;

import java.util.Map;

import org.sonar.core.issue.DefaultIssue;

public class TrackingResult {
private final Map<DefaultIssue, DefaultIssue> issuesToCopy;
private final Map<DefaultIssue, DefaultIssue> issuesToMerge;
private final Iterable<DefaultIssue> issuesToClose;
private final Iterable<DefaultIssue> newIssues;

public TrackingResult(Map<DefaultIssue, DefaultIssue> issuesToCopy, Map<DefaultIssue, DefaultIssue> issuesToMerge,
Iterable<DefaultIssue> issuesToClose, Iterable<DefaultIssue> newIssues) {
this.issuesToCopy = issuesToCopy;
this.issuesToMerge = issuesToMerge;
this.issuesToClose = issuesToClose;
this.newIssues = newIssues;
}

public Map<DefaultIssue, DefaultIssue> issuesToCopy() {
return issuesToCopy;
}

public Map<DefaultIssue, DefaultIssue> issuesToMerge() {
return issuesToMerge;
}

public Iterable<DefaultIssue> issuesToClose() {
return issuesToClose;
}

public Iterable<DefaultIssue> newIssues() {
return newIssues;
}
}
Expand Up @@ -73,10 +73,11 @@ public void execute() {
} }


private boolean persistIssueIfRequired(IssueMapper mapper, DefaultIssue issue) { private boolean persistIssueIfRequired(IssueMapper mapper, DefaultIssue issue) {
if (issue.isNew()) { if (issue.isNew() || issue.isCopied()) {
persistNewIssue(mapper, issue); persistNewIssue(mapper, issue);
return true; return true;
} }

if (issue.isChanged()) { if (issue.isChanged()) {
persistChangedIssue(mapper, issue); persistChangedIssue(mapper, issue);
return true; return true;
Expand Down
Expand Up @@ -119,6 +119,7 @@ public class IntegrateIssuesVisitorTest {
IssueTrackingDelegator trackingDelegator; IssueTrackingDelegator trackingDelegator;
TrackerExecution tracker; TrackerExecution tracker;
ShortBranchTrackerExecution shortBranchTracker; ShortBranchTrackerExecution shortBranchTracker;
MergeBranchTrackerExecution mergeBranchTracker;
IssueCache issueCache; IssueCache issueCache;


TypeAwareVisitor underTest; TypeAwareVisitor underTest;
Expand All @@ -135,8 +136,9 @@ public void setUp() throws Exception {
TrackerMergeBranchInputFactory mergeInputFactory = new TrackerMergeBranchInputFactory(issuesLoader, analysisMetadataHolder, dbTester.getDbClient()); TrackerMergeBranchInputFactory mergeInputFactory = new TrackerMergeBranchInputFactory(issuesLoader, analysisMetadataHolder, dbTester.getDbClient());
tracker = new TrackerExecution(baseInputFactory, rawInputFactory, new Tracker<>()); tracker = new TrackerExecution(baseInputFactory, rawInputFactory, new Tracker<>());
shortBranchTracker = new ShortBranchTrackerExecution(baseInputFactory, rawInputFactory, mergeInputFactory, new Tracker<>()); shortBranchTracker = new ShortBranchTrackerExecution(baseInputFactory, rawInputFactory, mergeInputFactory, new Tracker<>());
mergeBranchTracker = new MergeBranchTrackerExecution(rawInputFactory, mergeInputFactory, new Tracker<>());


trackingDelegator = new IssueTrackingDelegator(shortBranchTracker, tracker, analysisMetadataHolder); trackingDelegator = new IssueTrackingDelegator(shortBranchTracker, mergeBranchTracker, tracker, analysisMetadataHolder);
treeRootHolder.setRoot(PROJECT); treeRootHolder.setRoot(PROJECT);
issueCache = new IssueCache(temp.newFile(), System2.INSTANCE); issueCache = new IssueCache(temp.newFile(), System2.INSTANCE);
when(analysisMetadataHolder.isIncrementalAnalysis()).thenReturn(false); when(analysisMetadataHolder.isIncrementalAnalysis()).thenReturn(false);
Expand Down Expand Up @@ -165,11 +167,6 @@ public void process_issues_on_incremental_mode() {
assertThat(newArrayList(issueCache.traverse())).hasSize(1); assertThat(newArrayList(issueCache.traverse())).hasSize(1);
} }


@Test
public void process_short_branch_issues() {
//TODO
}

@Test @Test
public void process_new_issue() throws Exception { public void process_new_issue() throws Exception {
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder() ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
Expand Down Expand Up @@ -268,10 +265,6 @@ public void remove_uuid_of_original_file_from_componentsWithUnprocessedIssues_if
underTest.visitAny(FILE); underTest.visitAny(FILE);
} }


private void addMergeIssue(RuleKey ruleKey) {

}

private void addBaseIssue(RuleKey ruleKey) { private void addBaseIssue(RuleKey ruleKey) {
ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), PROJECT_UUID).setDbKey(PROJECT_KEY); ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), PROJECT_UUID).setDbKey(PROJECT_KEY);
ComponentDto file = ComponentTesting.newFileDto(project, null, FILE_UUID).setDbKey(FILE_KEY); ComponentDto file = ComponentTesting.newFileDto(project, null, FILE_UUID).setDbKey(FILE_KEY);
Expand Down
Expand Up @@ -20,6 +20,7 @@
package org.sonar.server.computation.task.projectanalysis.issue; package org.sonar.server.computation.task.projectanalysis.issue;


import org.junit.Test; import org.junit.Test;
import org.junit.Rule;
import org.sonar.api.rules.RuleType; import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.log.LogTester; import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel; import org.sonar.api.utils.log.LoggerLevel;
Expand All @@ -40,13 +41,13 @@ public class IssueAssignerTest {
static final int FILE_REF = 1; static final int FILE_REF = 1;
static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build(); static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build();


@org.junit.Rule @Rule
public LogTester logTester = new LogTester(); public LogTester logTester = new LogTester();


@org.junit.Rule @Rule
public ScmInfoRepositoryRule scmInfoRepository = new ScmInfoRepositoryRule(); public ScmInfoRepositoryRule scmInfoRepository = new ScmInfoRepositoryRule();


@org.junit.Rule @Rule
public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(123456789L); public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(123456789L);


ScmAccountToUser scmAccountToUser = mock(ScmAccountToUser.class); ScmAccountToUser scmAccountToUser = mock(ScmAccountToUser.class);
Expand Down Expand Up @@ -185,7 +186,7 @@ public void display_warning_when_line_is_above_max_size() throws Exception {
"moduleUuid=<null>,moduleUuidPath=<null>,projectUuid=<null>,projectKey=<null>,ruleKey=<null>,language=<null>,severity=<null>," + "moduleUuid=<null>,moduleUuidPath=<null>,projectUuid=<null>,projectKey=<null>,ruleKey=<null>,language=<null>,severity=<null>," +
"manualSeverity=false,message=<null>,line=2,gap=<null>,effort=<null>,status=<null>,resolution=<null>," + "manualSeverity=false,message=<null>,line=2,gap=<null>,effort=<null>,status=<null>,resolution=<null>," +
"assignee=<null>,checksum=<null>,attributes=<null>,authorLogin=<null>,comments=<null>,tags=<null>," + "assignee=<null>,checksum=<null>,attributes=<null>,authorLogin=<null>,comments=<null>,tags=<null>," +
"locations=<null>,creationDate=<null>,updateDate=<null>,closeDate=<null>,currentChange=<null>,changes=<null>,isNew=true," + "locations=<null>,creationDate=<null>,updateDate=<null>,closeDate=<null>,currentChange=<null>,changes=<null>,isNew=true,isCopied=false," +
"beingClosed=false,onDisabledRule=false,isChanged=false,sendNotifications=false,selectedAt=<null>]"); "beingClosed=false,onDisabledRule=false,isChanged=false,sendNotifications=false,selectedAt=<null>]");
} }


Expand Down

0 comments on commit 6affc40

Please sign in to comment.