@@ -36,8 +36,6 @@ public interface BatchReportReader { | |||
List<BatchReport.Issue> readComponentIssues(int componentRef); | |||
BatchReport.Issues readDeletedComponentIssues(int deletedComponentRef); | |||
List<BatchReport.Duplication> readComponentDuplications(int componentRef); | |||
List<BatchReport.Symbols.Symbol> readComponentSymbols(int componentRef); |
@@ -73,11 +73,6 @@ public class BatchReportReaderImpl implements BatchReportReader { | |||
return delegate.readComponentIssues(componentRef); | |||
} | |||
@Override | |||
public BatchReport.Issues readDeletedComponentIssues(int deletedComponentRef) { | |||
return delegate.readDeletedComponentIssues(deletedComponentRef); | |||
} | |||
@Override | |||
public List<BatchReport.Duplication> readComponentDuplications(int componentRef) { | |||
return delegate.readComponentDuplications(componentRef); |
@@ -23,6 +23,16 @@ public interface ComponentVisitor { | |||
void visit(Component tree); | |||
enum Order { | |||
PRE_ORDER, POST_ORDER | |||
/** | |||
* Each component is visited BEFORE its children. Top-down traversal of | |||
* tree of components. | |||
*/ | |||
PRE_ORDER, | |||
/** | |||
* Each component is visited AFTER its children. Bottom-up traversal of | |||
* tree of components. | |||
*/ | |||
POST_ORDER | |||
} | |||
} |
@@ -26,7 +26,7 @@ import org.sonar.server.properties.ProjectSettingsFactory; | |||
/** | |||
* Lazy loading of project settings. | |||
* | |||
* Should be refactored to be able to load settings from any components. | |||
* TODO Should be refactored to be able to load settings from any components. | |||
*/ | |||
public class ProjectSettingsRepository { | |||
@@ -31,6 +31,7 @@ import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.api.utils.log.Profiler; | |||
import org.sonar.core.component.Module; | |||
import org.sonar.core.issue.db.UpdateConflictResolver; | |||
import org.sonar.core.issue.tracking.Tracker; | |||
import org.sonar.core.platform.ComponentContainer; | |||
import org.sonar.server.computation.ComputationService; | |||
import org.sonar.server.computation.ReportQueue; | |||
@@ -43,13 +44,21 @@ import org.sonar.server.computation.component.TreeRootHolderImpl; | |||
import org.sonar.server.computation.debt.DebtModelHolderImpl; | |||
import org.sonar.server.computation.event.EventRepositoryImpl; | |||
import org.sonar.server.computation.formula.CoreFormulaRepositoryImpl; | |||
import org.sonar.server.computation.issue.BaseIssuesLoader; | |||
import org.sonar.server.computation.issue.DebtCalculator; | |||
import org.sonar.server.computation.issue.DefaultAssignee; | |||
import org.sonar.server.computation.issue.IssueAssigner; | |||
import org.sonar.server.computation.issue.IssueCache; | |||
import org.sonar.server.computation.issue.IssueComputation; | |||
import org.sonar.server.computation.issue.IssueLifecycle; | |||
import org.sonar.server.computation.issue.IssueListeners; | |||
import org.sonar.server.computation.issue.RuleCache; | |||
import org.sonar.server.computation.issue.RuleCacheLoader; | |||
import org.sonar.server.computation.issue.ScmAccountCache; | |||
import org.sonar.server.computation.issue.ScmAccountCacheLoader; | |||
import org.sonar.server.computation.issue.SourceLinesCache; | |||
import org.sonar.server.computation.issue.RuleTagsCopier; | |||
import org.sonar.server.computation.issue.ScmAccountToUser; | |||
import org.sonar.server.computation.issue.ScmAccountToUserLoader; | |||
import org.sonar.server.computation.issue.TrackerBaseInputFactory; | |||
import org.sonar.server.computation.issue.TrackerExecution; | |||
import org.sonar.server.computation.issue.TrackerRawInputFactory; | |||
import org.sonar.server.computation.language.LanguageRepositoryImpl; | |||
import org.sonar.server.computation.measure.MeasureRepositoryImpl; | |||
import org.sonar.server.computation.measure.newcoverage.NewCoverageMetricKeysModule; | |||
@@ -156,14 +165,23 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co | |||
NewCoverageMetricKeysModule.class, | |||
// issues | |||
ScmAccountCacheLoader.class, | |||
ScmAccountCache.class, | |||
SourceLinesCache.class, | |||
IssueComputation.class, | |||
ScmAccountToUserLoader.class, | |||
ScmAccountToUser.class, | |||
IssueAssigner.class, | |||
RuleTagsCopier.class, | |||
RuleCache.class, | |||
RuleCacheLoader.class, | |||
IssueCache.class, | |||
DefaultAssignee.class, | |||
DebtCalculator.class, | |||
IssueListeners.class, | |||
IssueLifecycle.class, | |||
UpdateConflictResolver.class, | |||
TrackerBaseInputFactory.class, | |||
TrackerRawInputFactory.class, | |||
Tracker.class, | |||
TrackerExecution.class, | |||
BaseIssuesLoader.class, | |||
// views | |||
ViewIndex.class, |
@@ -0,0 +1,82 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import com.google.common.collect.ImmutableMap; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import org.apache.ibatis.session.ResultContext; | |||
import org.apache.ibatis.session.ResultHandler; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.db.IssueDto; | |||
import org.sonar.core.issue.db.IssueMapper; | |||
import org.sonar.core.persistence.DbSession; | |||
import org.sonar.core.persistence.MyBatis; | |||
import org.sonar.server.computation.component.TreeRootHolder; | |||
import org.sonar.server.db.DbClient; | |||
/** | |||
* Loads all the project open issues from database, including manual issues. | |||
*/ | |||
public class BaseIssuesLoader { | |||
private final TreeRootHolder treeRootHolder; | |||
private final DbClient dbClient; | |||
private final RuleCache ruleCache; | |||
public BaseIssuesLoader(TreeRootHolder treeRootHolder, DbClient dbClient, RuleCache ruleCache) { | |||
this.treeRootHolder = treeRootHolder; | |||
this.dbClient = dbClient; | |||
this.ruleCache = ruleCache; | |||
} | |||
public List<DefaultIssue> loadForComponentUuid(String componentUuid) { | |||
DbSession session = dbClient.openSession(false); | |||
final List<DefaultIssue> result = new ArrayList<>(); | |||
try { | |||
Map<String, String> params = ImmutableMap.of("componentUuid", componentUuid); | |||
session.select(IssueMapper.class.getName() + ".selectOpenByComponentUuid", params, new ResultHandler() { | |||
@Override | |||
public void handleResult(ResultContext resultContext) { | |||
DefaultIssue issue = ((IssueDto) resultContext.getResultObject()).toDefaultIssue(); | |||
issue.setOnDisabledRule(ruleCache.getNullable(issue.ruleKey()) == null); | |||
result.add(issue); | |||
} | |||
}); | |||
return result; | |||
} finally { | |||
MyBatis.closeQuietly(session); | |||
} | |||
} | |||
/** | |||
* Uuids of all the components that have open issues on this project. | |||
*/ | |||
public Set<String> loadComponentUuids() { | |||
DbSession session = dbClient.openSession(false); | |||
try { | |||
return dbClient.issueDao().selectComponentUuidsOfOpenIssuesForProjectUuid(session, treeRootHolder.getRoot().getUuid()); | |||
} finally { | |||
MyBatis.closeQuietly(session); | |||
} | |||
} | |||
} |
@@ -0,0 +1,33 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.server.computation.component.Component; | |||
public class DebtCalculator extends IssueListener { | |||
@Override | |||
public void beforeIssue(Component component, DefaultIssue issue) { | |||
if (issue.resolution() == null) { | |||
// TODO | |||
} | |||
} | |||
} |
@@ -0,0 +1,73 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import com.google.common.base.Strings; | |||
import javax.annotation.CheckForNull; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.utils.log.Logger; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.server.computation.component.ProjectSettingsRepository; | |||
import org.sonar.server.computation.component.TreeRootHolder; | |||
import org.sonar.server.user.index.UserDoc; | |||
import org.sonar.server.user.index.UserIndex; | |||
/** | |||
* The user who is optionally declared as being the assignee | |||
* of all the issues which SCM author is not associated with any SonarQube user. | |||
*/ | |||
public class DefaultAssignee { | |||
private static final Logger LOG = Loggers.get(DefaultAssignee.class); | |||
private final TreeRootHolder treeRootHolder; | |||
private final UserIndex userIndex; | |||
private final ProjectSettingsRepository settings; | |||
private boolean loaded = false; | |||
private String login = null; | |||
public DefaultAssignee(TreeRootHolder treeRootHolder, UserIndex userIndex, ProjectSettingsRepository settings) { | |||
this.treeRootHolder = treeRootHolder; | |||
this.userIndex = userIndex; | |||
this.settings = settings; | |||
} | |||
@CheckForNull | |||
public String getLogin() { | |||
if (!loaded) { | |||
String configuredLogin = settings.getProjectSettings(treeRootHolder.getRoot().getKey()).getString(CoreProperties.DEFAULT_ISSUE_ASSIGNEE); | |||
if (!Strings.isNullOrEmpty(configuredLogin) && isValidLogin(configuredLogin)) { | |||
this.login = configuredLogin; | |||
} | |||
loaded = true; | |||
} | |||
return login; | |||
} | |||
private boolean isValidLogin(String s) { | |||
UserDoc user = userIndex.getNullableByLogin(s); | |||
if (user == null) { | |||
LOG.info("the {} property was set with an unknown login: {}", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, s); | |||
return false; | |||
} | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,149 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import com.google.common.collect.Sets; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.tracking.Tracking; | |||
import org.sonar.server.computation.component.Component; | |||
import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor; | |||
import org.sonar.server.computation.component.TreeRootHolder; | |||
import org.sonar.server.computation.step.ComputationStep; | |||
import org.sonar.server.util.cache.DiskCache; | |||
import static org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor.Order.POST_ORDER; | |||
public class IntegrateIssuesStep implements ComputationStep { | |||
private final TreeRootHolder treeRootHolder; | |||
private final TrackerExecution tracker; | |||
private final IssueCache issueCache; | |||
private final BaseIssuesLoader baseIssuesLoader; | |||
private final IssueLifecycle issueLifecycle; | |||
private final IssueListeners issueListeners; | |||
public IntegrateIssuesStep(TreeRootHolder treeRootHolder, TrackerExecution tracker, IssueCache issueCache, | |||
BaseIssuesLoader baseIssuesLoader, IssueLifecycle issueLifecycle, | |||
IssueListeners issueListeners) { | |||
this.treeRootHolder = treeRootHolder; | |||
this.tracker = tracker; | |||
this.issueCache = issueCache; | |||
this.baseIssuesLoader = baseIssuesLoader; | |||
this.issueLifecycle = issueLifecycle; | |||
this.issueListeners = issueListeners; | |||
} | |||
@Override | |||
public void execute() { | |||
// all the components that had issues before this analysis | |||
final Set<String> unprocessedComponentUuids = Sets.newHashSet(baseIssuesLoader.loadComponentUuids()); | |||
new DepthTraversalTypeAwareVisitor(Component.Type.FILE, POST_ORDER) { | |||
@Override | |||
public void visitAny(Component component) { | |||
processIssues(component); | |||
unprocessedComponentUuids.remove(component.getUuid()); | |||
} | |||
}.visit(treeRootHolder.getRoot()); | |||
closeIssuesForDeletedComponentUuids(unprocessedComponentUuids); | |||
} | |||
private void processIssues(Component component) { | |||
Tracking<DefaultIssue, DefaultIssue> tracking = tracker.track(component); | |||
DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender(); | |||
try { | |||
issueListeners.beforeComponent(component, tracking); | |||
fillNewOpenIssues(component, tracking, cacheAppender); | |||
fillExistingOpenIssues(component, tracking, cacheAppender); | |||
closeUnmatchedBaseIssues(component, tracking, cacheAppender); | |||
issueListeners.afterComponent(component); | |||
} finally { | |||
cacheAppender.close(); | |||
} | |||
} | |||
private void fillNewOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) { | |||
Set<DefaultIssue> issues = tracking.getUnmatchedRaws(); | |||
for (DefaultIssue issue : issues) { | |||
issueLifecycle.initNewOpenIssue(issue); | |||
issueListeners.beforeIssue(component, issue); | |||
process(component, issue, cacheAppender); | |||
} | |||
} | |||
private void fillExistingOpenIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) { | |||
for (Map.Entry<DefaultIssue, DefaultIssue> entry : tracking.getMatchedRaws().entrySet()) { | |||
DefaultIssue raw = entry.getKey(); | |||
DefaultIssue base = entry.getValue(); | |||
issueListeners.beforeIssue(component, raw); | |||
issueLifecycle.mergeExistingOpenIssue(raw, base); | |||
process(component, raw, cacheAppender); | |||
} | |||
for (Map.Entry<Integer, DefaultIssue> entry : tracking.getOpenManualIssuesByLine().entries()) { | |||
int line = entry.getKey(); | |||
DefaultIssue manualIssue = entry.getValue(); | |||
manualIssue.setLine(line == 0 ? null : line); | |||
issueListeners.beforeIssue(component, manualIssue); | |||
process(component, manualIssue, cacheAppender); | |||
} | |||
} | |||
private void closeUnmatchedBaseIssues(Component component, Tracking<DefaultIssue, DefaultIssue> tracking, DiskCache<DefaultIssue>.DiskAppender cacheAppender) { | |||
for (DefaultIssue issue : tracking.getUnmatchedBases()) { | |||
// TODO should replace flag "beingClosed" by express call to transition "automaticClose" | |||
issue.setBeingClosed(true); | |||
// TODO manual issues -> was updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext);. Is it a problem ? | |||
process(component, issue, cacheAppender); | |||
} | |||
} | |||
private void process(Component component, DefaultIssue issue, DiskCache<DefaultIssue>.DiskAppender cacheAppender) { | |||
issueLifecycle.doAutomaticTransition(issue); | |||
issueListeners.onIssue(component, issue); | |||
cacheAppender.append(issue); | |||
} | |||
private void closeIssuesForDeletedComponentUuids(Set<String> deletedComponentUuids) { | |||
DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender(); | |||
try { | |||
for (String deletedComponentUuid : deletedComponentUuids) { | |||
List<DefaultIssue> issues = baseIssuesLoader.loadForComponentUuid(deletedComponentUuid); | |||
for (DefaultIssue issue : issues) { | |||
issue.setBeingClosed(true); | |||
issueLifecycle.doAutomaticTransition(issue); | |||
// TODO execute listeners ? Component is currently missing. | |||
cacheAppender.append(issue); | |||
} | |||
} | |||
} finally { | |||
cacheAppender.close(); | |||
} | |||
} | |||
@Override | |||
public String getDescription() { | |||
return "Integrate issues"; | |||
} | |||
} |
@@ -27,102 +27,103 @@ import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.batch.protocol.output.BatchReport; | |||
import org.sonar.batch.protocol.output.BatchReport.Changesets.Changeset; | |||
import org.sonar.batch.protocol.output.BatchReport.Changesets.Changeset.Builder; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.tracking.Tracking; | |||
import org.sonar.server.computation.batch.BatchReportReader; | |||
import org.sonar.server.computation.component.Component; | |||
import org.sonar.server.source.index.SourceLineDoc; | |||
import org.sonar.server.source.index.SourceLineIndex; | |||
/** | |||
* Cache of the lines of the currently processed file. Only a <strong>single</strong> file | |||
* is kept in memory at a time. Data is loaded <strong>on demand</strong> (to avoid non necessary | |||
* loading).<br /> | |||
* Detect the SCM author and SQ assignee. | |||
* <p/> | |||
* It relies on: | |||
* <ul> | |||
* <li>the SCM information sent in the report for modified files</li> | |||
* <li>the source line index for non-modified files</li> | |||
* <li>the Elasticsearch index of source lines for non-modified files</li> | |||
* </ul> | |||
* | |||
*/ | |||
public class SourceLinesCache { | |||
private final SourceLineIndex index; | |||
private BatchReportReader reportReader; | |||
public class IssueAssigner extends IssueListener { | |||
private boolean loaded = false; | |||
private BatchReport.Changesets scm; | |||
private String currentFileUuid; | |||
private Integer currentFileReportRef; | |||
private final SourceLineIndex sourceLineIndex; | |||
private final BatchReportReader reportReader; | |||
private final DefaultAssignee defaultAssigne; | |||
private final ScmAccountToUser scmAccountToUser; | |||
private long lastCommitDate = 0L; | |||
private String lastCommitAuthor = null; | |||
private BatchReport.Changesets scmChangesets = null; | |||
public SourceLinesCache(SourceLineIndex index) { | |||
this.index = index; | |||
public IssueAssigner(SourceLineIndex sourceLineIndex, BatchReportReader reportReader, | |||
ScmAccountToUser scmAccountToUser, DefaultAssignee defaultAssigne) { | |||
this.sourceLineIndex = sourceLineIndex; | |||
this.reportReader = reportReader; | |||
this.scmAccountToUser = scmAccountToUser; | |||
this.defaultAssigne = defaultAssigne; | |||
} | |||
/** | |||
* Marks the currently processed component | |||
*/ | |||
void init(String fileUuid, @Nullable Integer fileReportRef, BatchReportReader reportReader) { | |||
this.loaded = false; | |||
this.currentFileUuid = fileUuid; | |||
this.currentFileReportRef = fileReportRef; | |||
this.lastCommitDate = 0L; | |||
this.lastCommitAuthor = null; | |||
this.reportReader = reportReader; | |||
clear(); | |||
@Override | |||
public void beforeComponent(Component component, Tracking tracking) { | |||
// optimization - do not load data if there are no new issues | |||
if (!tracking.getUnmatchedRaws().isEmpty()) { | |||
scmChangesets = loadScmChangesetsFromReport(component); | |||
if (scmChangesets == null) { | |||
scmChangesets = loadScmChangesetsFromIndex(component); | |||
} | |||
computeLastCommitDateAndAuthor(); | |||
} | |||
} | |||
@Override | |||
public void onIssue(Component component, DefaultIssue issue) { | |||
if (issue.isNew()) { | |||
String scmAuthor = guessScmAuthor(issue.line()); | |||
issue.setAuthorLogin(scmAuthor); | |||
if (scmAuthor != null) { | |||
String assigneeLogin = scmAccountToUser.getNullable(scmAuthor); | |||
if (assigneeLogin == null) { | |||
issue.setAssignee(defaultAssigne.getLogin()); | |||
} else { | |||
issue.setAssignee(assigneeLogin); | |||
} | |||
} | |||
} | |||
} | |||
@Override | |||
public void afterComponent(Component component) { | |||
lastCommitDate = 0L; | |||
lastCommitAuthor = null; | |||
scmChangesets = null; | |||
} | |||
/** | |||
* Last committer of the line, can be null. | |||
* Get the SCM login of the last committer on the line. When line is zero, | |||
* then get the last committer on the file. | |||
*/ | |||
@CheckForNull | |||
public String lineAuthor(@Nullable Integer lineNumber) { | |||
loadIfNeeded(); | |||
if (lineNumber == null) { | |||
// issue on file, approximately estimate that author is the last committer on the file | |||
return lastCommitAuthor; | |||
} | |||
private String guessScmAuthor(@Nullable Integer line) { | |||
String author = null; | |||
if (lineNumber <= scm.getChangesetIndexByLineCount()) { | |||
BatchReport.Changesets.Changeset changeset = scm.getChangeset(scm.getChangesetIndexByLine(lineNumber - 1)); | |||
if (line != null && line <= scmChangesets.getChangesetIndexByLineCount()) { | |||
BatchReport.Changesets.Changeset changeset = scmChangesets.getChangeset(scmChangesets.getChangesetIndexByLine(line - 1)); | |||
author = changeset.hasAuthor() ? changeset.getAuthor() : null; | |||
} | |||
return StringUtils.defaultIfEmpty(author, lastCommitAuthor); | |||
} | |||
private void loadIfNeeded() { | |||
checkState(); | |||
if (!loaded) { | |||
scm = loadScmFromReport(); | |||
loaded = scm != null; | |||
} | |||
if (!loaded) { | |||
scm = loadLinesFromIndexAndBuildScm(); | |||
loaded = true; | |||
} | |||
computeLastCommitDateAndAuthor(); | |||
} | |||
private BatchReport.Changesets loadScmFromReport() { | |||
return reportReader.readChangesets(currentFileReportRef); | |||
private BatchReport.Changesets loadScmChangesetsFromReport(Component component) { | |||
return reportReader.readChangesets(component.getRef()); | |||
} | |||
private BatchReport.Changesets loadLinesFromIndexAndBuildScm() { | |||
List<SourceLineDoc> lines = index.getLines(currentFileUuid); | |||
private BatchReport.Changesets loadScmChangesetsFromIndex(Component component) { | |||
List<SourceLineDoc> lines = sourceLineIndex.getLines(component.getUuid()); | |||
Map<String, BatchReport.Changesets.Changeset> changesetByRevision = new HashMap<>(); | |||
BatchReport.Changesets.Builder scmBuilder = BatchReport.Changesets.newBuilder() | |||
.setComponentRef(currentFileReportRef); | |||
.setComponentRef(component.getRef()); | |||
for (SourceLineDoc sourceLine : lines) { | |||
String scmRevision = sourceLine.scmRevision(); | |||
if (scmRevision == null || changesetByRevision.get(scmRevision) == null) { | |||
Builder changeSetBuilder = BatchReport.Changesets.Changeset.newBuilder(); | |||
BatchReport.Changesets.Changeset.Builder changeSetBuilder = BatchReport.Changesets.Changeset.newBuilder(); | |||
String scmAuthor = sourceLine.scmAuthor(); | |||
if (scmAuthor != null) { | |||
changeSetBuilder.setAuthor(scmAuthor); | |||
@@ -135,7 +136,7 @@ public class SourceLinesCache { | |||
changeSetBuilder.setRevision(scmRevision); | |||
} | |||
Changeset changeset = changeSetBuilder.build(); | |||
BatchReport.Changesets.Changeset changeset = changeSetBuilder.build(); | |||
scmBuilder.addChangeset(changeset); | |||
scmBuilder.addChangesetIndexByLine(scmBuilder.getChangesetCount() - 1); | |||
if (scmRevision != null) { | |||
@@ -149,24 +150,11 @@ public class SourceLinesCache { | |||
} | |||
private void computeLastCommitDateAndAuthor() { | |||
for (BatchReport.Changesets.Changeset changeset : scm.getChangesetList()) { | |||
for (BatchReport.Changesets.Changeset changeset : scmChangesets.getChangesetList()) { | |||
if (changeset.hasAuthor() && changeset.hasDate() && changeset.getDate() > lastCommitDate) { | |||
lastCommitDate = changeset.getDate(); | |||
lastCommitAuthor = changeset.getAuthor(); | |||
} | |||
} | |||
} | |||
private void checkState() { | |||
if (currentFileReportRef == null) { | |||
throw new IllegalStateException("Report component reference must not be null to use the cache"); | |||
} | |||
} | |||
/** | |||
* Makes cache eligible to GC | |||
*/ | |||
public void clear() { | |||
scm = null; | |||
} | |||
} |
@@ -19,14 +19,13 @@ | |||
*/ | |||
package org.sonar.server.computation.issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.utils.TempFolder; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.server.util.cache.DiskCache; | |||
import java.io.File; | |||
import java.io.IOException; | |||
/** | |||
* Cache of all the issues involved in the analysis. Their state is as it will be | |||
* persisted in database (after issue tracking, auto-assignment, ...) |
@@ -1,166 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import com.google.common.base.Strings; | |||
import com.google.common.collect.Sets; | |||
import java.util.Date; | |||
import javax.annotation.Nullable; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.utils.Duration; | |||
import org.sonar.api.utils.KeyValueFormat; | |||
import org.sonar.api.utils.log.Logger; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.batch.protocol.output.BatchReport; | |||
import org.sonar.core.rule.RuleDto; | |||
import org.sonar.server.computation.batch.BatchReportReader; | |||
import org.sonar.server.computation.component.ProjectSettingsRepository; | |||
import org.sonar.server.user.index.UserDoc; | |||
import org.sonar.server.user.index.UserIndex; | |||
import org.sonar.server.util.cache.DiskCache; | |||
public class IssueComputation { | |||
private static final Logger LOG = Loggers.get(IssueComputation.class); | |||
private final RuleCache ruleCache; | |||
private final ScmAccountCache scmAccountCache; | |||
private final SourceLinesCache linesCache; | |||
private final DiskCache<DefaultIssue>.DiskAppender diskIssuesAppender; | |||
private final UserIndex userIndex; | |||
private final ProjectSettingsRepository projectSettingsRepository; | |||
private final BatchReportReader reportReader; | |||
private boolean hasAssigneeBeenComputed = false; | |||
private String defaultAssignee = null; | |||
public IssueComputation(RuleCache ruleCache, SourceLinesCache linesCache, ScmAccountCache scmAccountCache, | |||
IssueCache issueCache, UserIndex userIndex, ProjectSettingsRepository projectSettingsRepository, BatchReportReader reportReader) { | |||
this.ruleCache = ruleCache; | |||
this.linesCache = linesCache; | |||
this.scmAccountCache = scmAccountCache; | |||
this.userIndex = userIndex; | |||
this.reportReader = reportReader; | |||
this.projectSettingsRepository = projectSettingsRepository; | |||
this.diskIssuesAppender = issueCache.newAppender(); | |||
} | |||
public void processComponentIssues(Iterable<BatchReport.Issue> issues, String componentUuid, @Nullable Integer componentReportRef, | |||
String projectKey, String projectUuid) { | |||
linesCache.init(componentUuid, componentReportRef, reportReader); | |||
computeDefaultAssignee(projectSettingsRepository.getProjectSettings(projectKey).getString(CoreProperties.DEFAULT_ISSUE_ASSIGNEE)); | |||
for (BatchReport.Issue reportIssue : issues) { | |||
DefaultIssue issue = toDefaultIssue(componentUuid, reportIssue, projectKey, projectUuid); | |||
if (issue.isNew()) { | |||
guessAuthor(issue); | |||
autoAssign(issue, defaultAssignee); | |||
copyRuleTags(issue); | |||
} | |||
diskIssuesAppender.append(issue); | |||
} | |||
linesCache.clear(); | |||
} | |||
private DefaultIssue toDefaultIssue(String componentUuid, BatchReport.Issue issue, String projectKey, String projectUuid) { | |||
DefaultIssue target = new DefaultIssue(); | |||
target.setKey(issue.getUuid()); | |||
target.setComponentUuid(componentUuid); | |||
target.setRuleKey(RuleKey.of(issue.getRuleRepository(), issue.getRuleKey())); | |||
target.setSeverity(issue.getSeverity().name()); | |||
target.setManualSeverity(issue.getManualSeverity()); | |||
target.setMessage(issue.hasMsg() ? issue.getMsg() : null); | |||
target.setLine(issue.hasLine() ? issue.getLine() : null); | |||
target.setProjectUuid(projectUuid); | |||
target.setProjectKey(projectKey); | |||
target.setEffortToFix(issue.hasEffortToFix() ? issue.getEffortToFix() : null); | |||
target.setDebt(issue.hasDebtInMinutes() ? Duration.create(issue.getDebtInMinutes()) : null); | |||
if (issue.hasDiffFields()) { | |||
FieldDiffs fieldDiffs = FieldDiffs.parse(issue.getDiffFields()); | |||
fieldDiffs.setCreationDate(new Date(reportReader.readMetadata().getAnalysisDate())); | |||
target.setCurrentChange(fieldDiffs); | |||
} | |||
target.setStatus(issue.getStatus()); | |||
target.setTags(issue.getTagList()); | |||
target.setResolution(issue.hasResolution() ? issue.getResolution() : null); | |||
target.setReporter(issue.hasReporter() ? issue.getReporter() : null); | |||
target.setAssignee(issue.hasAssignee() ? issue.getAssignee() : null); | |||
target.setChecksum(issue.hasChecksum() ? issue.getChecksum() : null); | |||
target.setAttributes(issue.hasAttributes() ? KeyValueFormat.parse(issue.getAttributes()) : null); | |||
target.setAuthorLogin(issue.hasAuthorLogin() ? issue.getAuthorLogin() : null); | |||
target.setActionPlanKey(issue.hasActionPlanKey() ? issue.getActionPlanKey() : null); | |||
target.setCreationDate(issue.hasCreationDate() ? new Date(issue.getCreationDate()) : null); | |||
target.setUpdateDate(issue.hasUpdateDate() ? new Date(issue.getUpdateDate()) : null); | |||
target.setCloseDate(issue.hasCloseDate() ? new Date(issue.getCloseDate()) : null); | |||
target.setChanged(issue.getIsChanged()); | |||
target.setNew(issue.getIsNew()); | |||
target.setSelectedAt(issue.hasSelectedAt() ? issue.getSelectedAt() : null); | |||
target.setSendNotifications(issue.getMustSendNotification()); | |||
return target; | |||
} | |||
public void afterReportProcessing() { | |||
diskIssuesAppender.close(); | |||
} | |||
private void guessAuthor(DefaultIssue issue) { | |||
// issue.authorLogin() can be not-null when old developer cockpit plugin (or other plugin) | |||
// is still installed and executed during analysis | |||
if (issue.authorLogin() == null) { | |||
issue.setAuthorLogin(linesCache.lineAuthor(issue.line())); | |||
} | |||
} | |||
private void autoAssign(DefaultIssue issue, @Nullable String defaultAssignee) { | |||
// issue.assignee() can be not-null if the issue-assign-plugin is | |||
// still installed and executed during analysis | |||
if (issue.assignee() == null) { | |||
String scmAccount = issue.authorLogin(); | |||
if (scmAccount != null) { | |||
issue.setAssignee(scmAccountCache.getNullable(scmAccount)); | |||
} | |||
if (issue.assignee() == null && defaultAssignee != null) { | |||
issue.setAssignee(defaultAssignee); | |||
} | |||
} | |||
} | |||
private void copyRuleTags(DefaultIssue issue) { | |||
RuleDto rule = ruleCache.get(issue.ruleKey()); | |||
issue.setTags(Sets.union(rule.getTags(), rule.getSystemTags())); | |||
} | |||
private void computeDefaultAssignee(@Nullable String login) { | |||
if (hasAssigneeBeenComputed) { | |||
return; | |||
} | |||
hasAssigneeBeenComputed = true; | |||
if (!Strings.isNullOrEmpty(login)) { | |||
UserDoc user = userIndex.getNullableByLogin(login); | |||
if (user == null) { | |||
LOG.info("the {} property was set with an unknown login: {}", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, login); | |||
} else { | |||
defaultAssignee = login; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,81 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import java.util.Date; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.utils.internal.Uuids; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.core.issue.workflow.IssueWorkflow; | |||
import org.sonar.server.computation.batch.BatchReportReader; | |||
public class IssueLifecycle { | |||
private final IssueWorkflow workflow; | |||
private final IssueChangeContext changeContext; | |||
private final IssueUpdater updater; | |||
public IssueLifecycle(BatchReportReader reportReader, IssueWorkflow workflow, IssueUpdater updater) { | |||
this.workflow = workflow; | |||
this.updater = updater; | |||
this.changeContext = IssueChangeContext.createScan(new Date(reportReader.readMetadata().getAnalysisDate())); | |||
} | |||
public void initNewOpenIssue(DefaultIssue issue) { | |||
issue.setKey(Uuids.create()); | |||
issue.setCreationDate(changeContext.date()); | |||
issue.setUpdateDate(changeContext.date()); | |||
issue.setStatus(Issue.STATUS_OPEN); | |||
} | |||
public void mergeExistingOpenIssue(DefaultIssue raw, DefaultIssue base) { | |||
raw.setNew(false); | |||
raw.setKey(base.key()); | |||
raw.setCreationDate(base.creationDate()); | |||
raw.setUpdateDate(base.updateDate()); | |||
raw.setCloseDate(base.closeDate()); | |||
raw.setActionPlanKey(base.actionPlanKey()); | |||
raw.setResolution(base.resolution()); | |||
raw.setStatus(base.status()); | |||
raw.setAssignee(base.assignee()); | |||
raw.setAuthorLogin(base.authorLogin()); | |||
raw.setTags(base.tags()); | |||
if (base.manualSeverity()) { | |||
raw.setManualSeverity(true); | |||
raw.setSeverity(base.severity()); | |||
} else { | |||
updater.setPastSeverity(raw, base.severity(), changeContext); | |||
} | |||
// TODO attributes + changelog | |||
// fields coming from raw | |||
updater.setPastLine(raw, base.getLine()); | |||
updater.setPastMessage(raw, base.getMessage(), changeContext); | |||
updater.setPastEffortToFix(raw, base.effortToFix(), changeContext); | |||
updater.setPastTechnicalDebt(raw, base.debt(), changeContext); | |||
} | |||
public void doAutomaticTransition(DefaultIssue issue) { | |||
workflow.doAutomaticTransition(issue, changeContext); | |||
} | |||
} |
@@ -17,34 +17,31 @@ | |||
* 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.batch.phases.event; | |||
package org.sonar.server.computation.issue; | |||
import org.sonar.api.batch.events.EventHandler; | |||
import org.sonar.batch.index.ScanPersister; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.tracking.Tracking; | |||
import org.sonar.server.computation.component.Component; | |||
import java.util.List; | |||
public abstract class IssueListener { | |||
public interface PersistersPhaseHandler extends EventHandler { | |||
public void beforeComponent(Component component, Tracking tracking) { | |||
/** | |||
* This interface is not intended to be implemented by clients. | |||
*/ | |||
interface PersistersPhaseEvent { | |||
/** | |||
* @return list of Persisters in the order of execution | |||
*/ | |||
List<ScanPersister> getPersisters(); | |||
boolean isStart(); | |||
} | |||
boolean isEnd(); | |||
public void beforeIssue(Component component, DefaultIssue issue) { | |||
} | |||
/** | |||
* Called before and after execution of all {@link ScanPersister}s. | |||
* This method is called when tracking is done and issue is initialized. That means that the following fields | |||
* are set: resolution, status, line, creation date, uuid and all the fields merged from base issues. | |||
*/ | |||
void onPersistersPhase(PersistersPhaseEvent event); | |||
public void onIssue(Component component, DefaultIssue issue) { | |||
} | |||
public void afterComponent(Component component) { | |||
} | |||
} |
@@ -0,0 +1,57 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.tracking.Tracking; | |||
import org.sonar.server.computation.component.Component; | |||
public class IssueListeners { | |||
private final IssueListener[] listeners; | |||
public IssueListeners(IssueListener[] listeners) { | |||
this.listeners = listeners; | |||
} | |||
public void beforeComponent(Component component, Tracking tracking) { | |||
for (IssueListener listener : listeners) { | |||
listener.beforeComponent(component, tracking); | |||
} | |||
} | |||
public void beforeIssue(Component component, DefaultIssue issue) { | |||
for (IssueListener listener : listeners) { | |||
listener.beforeIssue(component, issue); | |||
} | |||
} | |||
public void onIssue(Component component, DefaultIssue issue) { | |||
for (IssueListener listener : listeners) { | |||
listener.onIssue(component, issue); | |||
} | |||
} | |||
public void afterComponent(Component component) { | |||
for (IssueListener listener : listeners) { | |||
listener.afterComponent(component); | |||
} | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.server.computation.component.Component; | |||
public class NewDebtCalculator extends IssueListener { | |||
@Override | |||
public void onIssue(Component component, DefaultIssue issue) { | |||
// TODO | |||
} | |||
} |
@@ -17,34 +17,30 @@ | |||
* 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.batch.phases; | |||
package org.sonar.server.computation.issue; | |||
import org.sonar.batch.index.ScanPersister; | |||
import org.sonar.batch.phases.event.PersisterExecutionHandler; | |||
import java.util.Set; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.rule.RuleDto; | |||
import org.sonar.server.computation.component.Component; | |||
class PersisterExecutionEvent extends AbstractPhaseEvent<PersisterExecutionHandler> | |||
implements PersisterExecutionHandler.PersisterExecutionEvent { | |||
import static com.google.common.collect.Sets.union; | |||
private final ScanPersister persister; | |||
public class RuleTagsCopier extends IssueListener { | |||
PersisterExecutionEvent(ScanPersister persister, boolean start) { | |||
super(start); | |||
this.persister = persister; | |||
} | |||
@Override | |||
public ScanPersister getPersister() { | |||
return persister; | |||
} | |||
private final RuleCache ruleCache; | |||
@Override | |||
public void dispatch(PersisterExecutionHandler handler) { | |||
handler.onPersisterExecution(this); | |||
public RuleTagsCopier(RuleCache ruleCache) { | |||
this.ruleCache = ruleCache; | |||
} | |||
@Override | |||
public Class getType() { | |||
return PersisterExecutionHandler.class; | |||
public void onIssue(Component component, DefaultIssue issue) { | |||
if (issue.isNew()) { | |||
// analyzer can provide some tags. They must be merged with rule tags | |||
RuleDto rule = ruleCache.get(issue.ruleKey()); | |||
Set<String> ruleTags = union(rule.getTags(), rule.getSystemTags()); | |||
issue.setTags(union(issue.tags(), ruleTags)); | |||
} | |||
} | |||
} |
@@ -22,10 +22,11 @@ package org.sonar.server.computation.issue; | |||
import org.sonar.server.util.cache.MemoryCache; | |||
/** | |||
* Cache of dictionary {SCM account -> SQ user login} | |||
* Cache of dictionary {SCM account -> SQ user login}. Kept in memory | |||
* during the execution of Compute Engine. | |||
*/ | |||
public class ScmAccountCache extends MemoryCache<String,String> { | |||
public ScmAccountCache(ScmAccountCacheLoader loader) { | |||
public class ScmAccountToUser extends MemoryCache<String,String> { | |||
public ScmAccountToUser(ScmAccountToUserLoader loader) { | |||
super(loader); | |||
} | |||
} |
@@ -36,17 +36,17 @@ import java.util.Map; | |||
/** | |||
* Loads the association between a SCM account and a SQ user | |||
*/ | |||
public class ScmAccountCacheLoader implements CacheLoader<String, String> { | |||
public class ScmAccountToUserLoader implements CacheLoader<String, String> { | |||
private final Logger log; | |||
private final UserIndex index; | |||
public ScmAccountCacheLoader(UserIndex index) { | |||
this(index, Loggers.get(ScmAccountCacheLoader.class)); | |||
public ScmAccountToUserLoader(UserIndex index) { | |||
this(index, Loggers.get(ScmAccountToUserLoader.class)); | |||
} | |||
@VisibleForTesting | |||
ScmAccountCacheLoader(UserIndex index, Logger log) { | |||
ScmAccountToUserLoader(UserIndex index, Logger log) { | |||
this.log = log; | |||
this.index = index; | |||
} |
@@ -0,0 +1,70 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import java.util.List; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.tracking.Input; | |||
import org.sonar.core.issue.tracking.LazyInput; | |||
import org.sonar.core.persistence.DbSession; | |||
import org.sonar.core.persistence.MyBatis; | |||
import org.sonar.server.computation.component.Component; | |||
import org.sonar.server.db.DbClient; | |||
/** | |||
* Factory of {@link Input} of base data for issue tracking. Data are lazy-loaded. | |||
*/ | |||
public class TrackerBaseInputFactory { | |||
private final BaseIssuesLoader baseIssuesLoader; | |||
private final DbClient dbClient; | |||
public TrackerBaseInputFactory(BaseIssuesLoader baseIssuesLoader, DbClient dbClient) { | |||
this.baseIssuesLoader = baseIssuesLoader; | |||
this.dbClient = dbClient; | |||
} | |||
public Input<DefaultIssue> create(Component component) { | |||
return new BaseLazyInput(component); | |||
} | |||
private class BaseLazyInput extends LazyInput<DefaultIssue> { | |||
private final Component component; | |||
private BaseLazyInput(Component component) { | |||
this.component = component; | |||
} | |||
@Override | |||
protected Iterable<String> loadSourceLines() { | |||
DbSession session = dbClient.openSession(false); | |||
try { | |||
return dbClient.fileSourceDao().selectLineHashes(session, component.getUuid()); | |||
} finally { | |||
MyBatis.closeQuietly(session); | |||
} | |||
} | |||
@Override | |||
protected List<DefaultIssue> loadIssues() { | |||
return baseIssuesLoader.loadForComponentUuid(component.getUuid()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,43 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.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.component.Component; | |||
public class TrackerExecution { | |||
private final TrackerBaseInputFactory baseInputFactory; | |||
private final TrackerRawInputFactory rawInputFactory; | |||
private final Tracker<DefaultIssue, DefaultIssue> tracker; | |||
public TrackerExecution(TrackerBaseInputFactory baseInputFactory, TrackerRawInputFactory rawInputFactory, | |||
Tracker<DefaultIssue, DefaultIssue> tracker) { | |||
this.baseInputFactory = baseInputFactory; | |||
this.rawInputFactory = rawInputFactory; | |||
this.tracker = tracker; | |||
} | |||
public Tracking<DefaultIssue, DefaultIssue> track(Component component) { | |||
return tracker.track(rawInputFactory.create(component), baseInputFactory.create(component)); | |||
} | |||
} |
@@ -0,0 +1,125 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import com.google.common.collect.Lists; | |||
import com.google.common.collect.Sets; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.utils.Duration; | |||
import org.sonar.batch.protocol.output.BatchReport; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.tracking.Input; | |||
import org.sonar.core.issue.tracking.LazyInput; | |||
import org.sonar.core.issue.tracking.LineHashSequence; | |||
import org.sonar.server.computation.batch.BatchReportReader; | |||
import org.sonar.server.computation.component.Component; | |||
import org.sonar.server.computation.component.TreeRootHolder; | |||
public class TrackerRawInputFactory { | |||
private final TreeRootHolder treeRootHolder; | |||
private final BatchReportReader reportReader; | |||
private final RuleCache ruleCache; | |||
public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader, RuleCache ruleCache) { | |||
this.treeRootHolder = treeRootHolder; | |||
this.reportReader = reportReader; | |||
this.ruleCache = ruleCache; | |||
} | |||
public Input<DefaultIssue> create(Component component) { | |||
return new RawLazyInput(component); | |||
} | |||
private class RawLazyInput extends LazyInput<DefaultIssue> { | |||
private final Component component; | |||
private RawLazyInput(Component component) { | |||
this.component = component; | |||
} | |||
@Override | |||
protected Iterable<String> loadSourceLines() { | |||
return Lists.newArrayList(reportReader.readFileSource(component.getRef())); | |||
} | |||
@Override | |||
protected List<DefaultIssue> loadIssues() { | |||
List<BatchReport.Issue> reportIssues = reportReader.readComponentIssues(component.getRef()); | |||
List<DefaultIssue> issues = new ArrayList<>(); | |||
if (!reportIssues.isEmpty()) { | |||
// optimization - do not load line hashes if there are no issues | |||
LineHashSequence lineHashSeq = getLineHashSequence(); | |||
for (BatchReport.Issue reportIssue : reportIssues) { | |||
DefaultIssue issue = toIssue(lineHashSeq, reportIssue); | |||
if (isValid(issue, lineHashSeq)) { | |||
issues.add(issue); | |||
} | |||
} | |||
} | |||
return issues; | |||
} | |||
private DefaultIssue toIssue(LineHashSequence lineHashSeq, BatchReport.Issue reportIssue) { | |||
DefaultIssue issue = new DefaultIssue(); | |||
issue.setRuleKey(RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey())); | |||
issue.setStatus(Issue.STATUS_OPEN); | |||
issue.setComponentUuid(component.getUuid()); | |||
issue.setComponentKey(component.getKey()); | |||
issue.setProjectUuid(treeRootHolder.getRoot().getUuid()); | |||
if (reportIssue.hasLine()) { | |||
issue.setLine(reportIssue.getLine()); | |||
issue.setChecksum(lineHashSeq.getHashForLine(reportIssue.getLine())); | |||
} else { | |||
issue.setChecksum(""); | |||
} | |||
if (reportIssue.hasMsg()) { | |||
issue.setMessage(reportIssue.getMsg()); | |||
} | |||
if (reportIssue.hasSeverity()) { | |||
issue.setSeverity(reportIssue.getSeverity().name()); | |||
} | |||
if (reportIssue.hasEffortToFix()) { | |||
issue.setEffortToFix(reportIssue.getEffortToFix()); | |||
} | |||
if (reportIssue.hasDebtInMinutes()) { | |||
issue.setDebt(Duration.create(reportIssue.getDebtInMinutes())); | |||
} | |||
issue.setTags(Sets.newHashSet(reportIssue.getTagList())); | |||
// TODO issue attributes | |||
return issue; | |||
} | |||
} | |||
private boolean isValid(DefaultIssue issue, LineHashSequence lineHashSeq) { | |||
// TODO log debug when invalid ? | |||
if (ruleCache.getNullable(issue.ruleKey()) == null) { | |||
return false; | |||
} | |||
if (issue.getLine() != null && !lineHashSeq.hasLine(issue.getLine())) { | |||
return false; | |||
} | |||
return true; | |||
} | |||
} |
@@ -24,6 +24,7 @@ import com.google.common.collect.Iterables; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import org.sonar.server.computation.container.ComputeEngineContainer; | |||
import org.sonar.server.computation.issue.IntegrateIssuesStep; | |||
/** | |||
* Ordered list of steps to be executed | |||
@@ -46,10 +47,8 @@ public class ComputationSteps { | |||
FeedDebtModelStep.class, | |||
// Read report | |||
ParseReportStep.class, | |||
// load project related stuffs | |||
IntegrateIssuesStep.class, | |||
QualityGateLoadingStep.class, | |||
FeedPeriodsStep.class, | |||
@@ -1,100 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.step; | |||
import java.util.List; | |||
import org.sonar.batch.protocol.output.BatchReport; | |||
import org.sonar.server.computation.batch.BatchReportReader; | |||
import org.sonar.server.computation.component.Component; | |||
import org.sonar.server.computation.component.DepthTraversalTypeAwareVisitor; | |||
import org.sonar.server.computation.component.TreeRootHolder; | |||
import org.sonar.server.computation.issue.IssueComputation; | |||
public class ParseReportStep implements ComputationStep { | |||
private final IssueComputation issueComputation; | |||
private final BatchReportReader reportReader; | |||
private final TreeRootHolder treeRootHolder; | |||
public ParseReportStep(IssueComputation issueComputation, BatchReportReader reportReader, TreeRootHolder treeRootHolder) { | |||
this.issueComputation = issueComputation; | |||
this.reportReader = reportReader; | |||
this.treeRootHolder = treeRootHolder; | |||
} | |||
@Override | |||
public void execute() { | |||
IssueDepthTraversalTypeAwareVisitor visitor = new IssueDepthTraversalTypeAwareVisitor(); | |||
visitor.visit(treeRootHolder.getRoot()); | |||
processDeletedComponents(visitor); | |||
issueComputation.afterReportProcessing(); | |||
} | |||
private void processDeletedComponents(IssueDepthTraversalTypeAwareVisitor visitor) { | |||
int deletedComponentsCount = reportReader.readMetadata().getDeletedComponentsCount(); | |||
for (int componentRef = 1; componentRef <= deletedComponentsCount; componentRef++) { | |||
BatchReport.Issues issues = reportReader.readDeletedComponentIssues(componentRef); | |||
issueComputation.processComponentIssues(issues.getIssueList(), issues.getComponentUuid(), null, visitor.projectKey, visitor.projectUuid); | |||
} | |||
} | |||
@Override | |||
public String getDescription() { | |||
return "Digest analysis report"; | |||
} | |||
private class IssueDepthTraversalTypeAwareVisitor extends DepthTraversalTypeAwareVisitor { | |||
private String projectKey; | |||
private String projectUuid; | |||
public IssueDepthTraversalTypeAwareVisitor() { | |||
super(Component.Type.FILE, Order.PRE_ORDER); | |||
} | |||
@Override | |||
public void visitProject(Component tree) { | |||
projectKey = tree.getKey(); | |||
projectUuid = tree.getUuid(); | |||
executeForComponent(tree); | |||
} | |||
@Override | |||
public void visitModule(Component module) { | |||
executeForComponent(module); | |||
} | |||
@Override | |||
public void visitDirectory(Component directory) { | |||
executeForComponent(directory); | |||
} | |||
@Override | |||
public void visitFile(Component file) { | |||
executeForComponent(file); | |||
} | |||
private void executeForComponent(Component component) { | |||
int componentRef = component.getRef(); | |||
List<BatchReport.Issue> issues = reportReader.readComponentIssues(componentRef); | |||
issueComputation.processComponentIssues(issues, component.getUuid(), componentRef, projectKey, projectUuid); | |||
} | |||
} | |||
} |
@@ -21,10 +21,10 @@ package org.sonar.server.computation.step; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.IssueComment; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.core.issue.db.IssueChangeDto; | |||
import org.sonar.core.issue.db.IssueChangeMapper; | |||
import org.sonar.core.issue.db.IssueDto; |
@@ -23,7 +23,7 @@ import com.google.common.collect.ImmutableSet; | |||
import java.util.Date; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.server.computation.batch.BatchReportReader; | |||
import org.sonar.server.computation.component.Component; | |||
import org.sonar.server.computation.component.TreeRootHolder; |
@@ -26,7 +26,7 @@ import com.google.common.collect.Sets; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.condition.IsUnResolved; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.server.rule.RuleTagFormat; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.server.user.UserSession; |
@@ -26,7 +26,7 @@ import com.google.common.collect.ImmutableList; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.condition.Condition; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.server.user.UserSession; | |||
import java.util.Collection; |
@@ -31,8 +31,8 @@ import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.action.Action; | |||
import org.sonar.api.issue.action.Actions; | |||
import org.sonar.api.issue.action.Function; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.core.issue.db.IssueStorage; | |||
import org.sonar.core.persistence.DbSession; |
@@ -24,7 +24,7 @@ import com.google.common.base.Strings; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.condition.IsUnResolved; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.user.User; | |||
import org.sonar.api.user.UserFinder; | |||
import org.sonar.core.issue.IssueUpdater; |
@@ -24,7 +24,7 @@ import com.google.common.base.Strings; | |||
import java.util.Collection; | |||
import java.util.Map; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.server.user.UserSession; |
@@ -45,9 +45,9 @@ import org.sonar.api.issue.ActionPlan; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.IssueComment; | |||
import org.sonar.api.issue.action.Action; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.user.User; |
@@ -35,8 +35,8 @@ import java.util.Set; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rules.Rule; | |||
import org.sonar.api.utils.log.Logger; |
@@ -20,7 +20,7 @@ | |||
package org.sonar.server.issue; | |||
import com.google.common.collect.Maps; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.api.user.User; | |||
import javax.annotation.CheckForNull; |
@@ -25,7 +25,7 @@ import java.util.Locale; | |||
import java.util.Map; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.i18n.I18n; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.api.utils.Duration; | |||
import org.sonar.api.utils.Durations; | |||
import org.sonar.core.issue.IssueUpdater; |
@@ -23,7 +23,7 @@ import java.util.Collection; | |||
import java.util.List; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.api.user.User; | |||
import org.sonar.api.user.UserFinder; | |||
import org.sonar.core.issue.db.IssueChangeDao; |
@@ -24,9 +24,9 @@ import com.google.common.base.Strings; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.issue.IssueComment; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.core.issue.db.IssueChangeDto; |
@@ -24,8 +24,8 @@ import com.google.common.base.Strings; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.issue.ActionPlan; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.server.notification.NotificationManager; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rule.Severity; |
@@ -25,7 +25,7 @@ import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.issue.ActionPlan; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.condition.IsUnResolved; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.server.issue.actionplan.ActionPlanService; | |||
import org.sonar.server.user.UserSession; |
@@ -20,7 +20,7 @@ | |||
package org.sonar.server.issue; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.rules.RuleFinder; | |||
import org.sonar.core.component.ComponentDto; | |||
import org.sonar.core.issue.db.IssueDto; |
@@ -26,7 +26,7 @@ import java.util.Map; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.condition.Condition; | |||
import org.sonar.api.issue.condition.IsUnResolved; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.core.issue.IssueUpdater; |
@@ -27,7 +27,7 @@ import java.util.Collection; | |||
import java.util.Map; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.core.issue.workflow.IssueWorkflow; | |||
import org.sonar.core.issue.workflow.Transition; |
@@ -28,8 +28,8 @@ import java.util.Date; | |||
import java.util.List; | |||
import javax.annotation.CheckForNull; | |||
import org.sonar.api.issue.ActionPlan; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.core.issue.ActionPlanDeadlineComparator; |
@@ -19,7 +19,9 @@ | |||
*/ | |||
package org.sonar.server.issue.db; | |||
import org.apache.ibatis.session.ResultHandler; | |||
import java.util.List; | |||
import java.util.Set; | |||
import javax.annotation.CheckForNull; | |||
import org.sonar.core.issue.db.IssueDto; | |||
import org.sonar.core.issue.db.IssueMapper; | |||
import org.sonar.core.persistence.DaoComponent; | |||
@@ -27,9 +29,6 @@ import org.sonar.core.persistence.DbSession; | |||
import org.sonar.core.persistence.MyBatis; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import javax.annotation.CheckForNull; | |||
import java.util.List; | |||
public class IssueDao extends org.sonar.core.issue.db.IssueDao implements DaoComponent { | |||
public IssueDao(MyBatis mybatis) { | |||
@@ -57,12 +56,8 @@ public class IssueDao extends org.sonar.core.issue.db.IssueDao implements DaoCom | |||
return mapper(session).selectByKeys(keys); | |||
} | |||
public void selectNonClosedIssuesByModuleUuid(DbSession session, String moduleUuid, ResultHandler handler) { | |||
session.select("org.sonar.core.issue.db.IssueMapper.selectNonClosedIssuesByModuleUuid", moduleUuid, handler); | |||
} | |||
public void selectNonClosedIssuesByProjectUuid(DbSession session, String projectUuid, ResultHandler handler) { | |||
session.select("org.sonar.core.issue.db.IssueMapper.selectNonClosedIssuesByProjectUuid", projectUuid, handler); | |||
public Set<String> selectComponentUuidsOfOpenIssuesForProjectUuid(DbSession session, String projectUuid) { | |||
return mapper(session).selectComponentUuidsOfOpenIssuesForProjectUuid(projectUuid); | |||
} | |||
public void insert(DbSession session, IssueDto dto) { |
@@ -21,8 +21,8 @@ package org.sonar.server.issue.notification; | |||
import com.google.common.base.Strings; | |||
import org.sonar.api.component.Component; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.api.notifications.Notification; | |||
import javax.annotation.CheckForNull; |
@@ -35,13 +35,13 @@ import javax.annotation.Nullable; | |||
import org.sonar.api.issue.ActionPlan; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.IssueComment; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.api.user.User; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.api.utils.Duration; | |||
import org.sonar.api.utils.Durations; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import org.sonar.core.component.ComponentDto; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.markdown.Markdown; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonar.server.ws.JsonWriterUtils; |
@@ -38,7 +38,6 @@ import org.apache.commons.lang.BooleanUtils; | |||
import org.sonar.api.i18n.I18n; | |||
import org.sonar.api.issue.ActionPlan; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.api.resources.Language; | |||
import org.sonar.api.resources.Languages; | |||
import org.sonar.api.rule.RuleKey; | |||
@@ -52,6 +51,7 @@ import org.sonar.api.user.UserFinder; | |||
import org.sonar.api.utils.DateUtils; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import org.sonar.core.component.ComponentDto; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.core.persistence.DbSession; | |||
import org.sonar.server.component.ws.ComponentJsonWriter; | |||
import org.sonar.server.db.DbClient; |
@@ -29,8 +29,8 @@ import org.sonar.api.i18n.I18n; | |||
import org.sonar.api.issue.ActionPlan; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.IssueComment; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.api.server.debt.DebtCharacteristic; | |||
import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic; | |||
import org.sonar.api.server.ws.Request; |
@@ -21,6 +21,7 @@ | |||
package org.sonar.server.source.db; | |||
import com.google.common.base.Function; | |||
import com.google.common.base.Splitter; | |||
import java.io.Reader; | |||
import java.sql.Connection; | |||
import java.sql.PreparedStatement; | |||
@@ -40,6 +41,7 @@ import org.sonar.core.source.db.FileSourceMapper; | |||
@ServerSide | |||
public class FileSourceDao implements DaoComponent { | |||
private static final Splitter END_OF_LINE_SPLITTER = Splitter.on('\n'); | |||
private final MyBatis mybatis; | |||
public FileSourceDao(MyBatis myBatis) { | |||
@@ -66,6 +68,27 @@ public class FileSourceDao implements DaoComponent { | |||
} | |||
} | |||
@CheckForNull | |||
public Iterable<String> selectLineHashes(DbSession dbSession, String fileUuid) { | |||
Connection connection = dbSession.getConnection(); | |||
PreparedStatement pstmt = null; | |||
ResultSet rs = null; | |||
try { | |||
pstmt = connection.prepareStatement("SELECT line_hashes FROM file_sources WHERE file_uuid=? AND data_type=?"); | |||
pstmt.setString(1, fileUuid); | |||
pstmt.setString(2, Type.SOURCE); | |||
rs = pstmt.executeQuery(); | |||
if (rs.next()) { | |||
return END_OF_LINE_SPLITTER.split(rs.getString(1)); | |||
} | |||
return null; | |||
} catch (SQLException e) { | |||
throw new IllegalStateException("Fail to read FILE_SOURCES.LINE_HASHES of file " + fileUuid, e); | |||
} finally { | |||
DbUtils.closeQuietly(connection, pstmt, rs); | |||
} | |||
} | |||
public <T> void readLineHashesStream(DbSession dbSession, String fileUuid, Function<Reader, T> function) { | |||
Connection connection = dbSession.getConnection(); | |||
PreparedStatement pstmt = null; |
@@ -42,7 +42,7 @@ public class BatchReportReaderImplTest { | |||
private static final BatchReport.Measure MEASURE = BatchReport.Measure.newBuilder().build(); | |||
private static final BatchReport.Component COMPONENT = BatchReport.Component.newBuilder().setRef(COMPONENT_REF).build(); | |||
private static final BatchReport.Issue ISSUE = BatchReport.Issue.newBuilder().build(); | |||
private static final BatchReport.Issues ISSUES = BatchReport.Issues.newBuilder().setComponentRef(COMPONENT_REF).setComponentUuid(COMPONENT_UUID).addIssue(ISSUE).build(); | |||
private static final BatchReport.Issues ISSUES = BatchReport.Issues.newBuilder().setComponentRef(COMPONENT_REF).addIssue(ISSUE).build(); | |||
private static final BatchReport.Duplication DUPLICATION = BatchReport.Duplication.newBuilder().build(); | |||
private static final BatchReport.Symbols.Symbol SYMBOL = BatchReport.Symbols.Symbol.newBuilder().build(); | |||
private static final BatchReport.SyntaxHighlighting SYNTAX_HIGHLIGHTING_1 = BatchReport.SyntaxHighlighting.newBuilder().build(); | |||
@@ -166,25 +166,6 @@ public class BatchReportReaderImplTest { | |||
assertThat(underTest.readComponentIssues(COMPONENT_REF)).isNotSameAs(underTest.readComponentIssues(COMPONENT_REF)); | |||
} | |||
@Test(expected = IllegalStateException.class) | |||
public void readDeletedComponentIssues_throws_ISE_if_file_does_not_exist() { | |||
underTest.readDeletedComponentIssues(COMPONENT_REF); | |||
} | |||
@Test | |||
public void verify_readDeletedComponentIssues_returns_Issues() { | |||
writer.writeDeletedComponentIssues(COMPONENT_REF, COMPONENT_UUID, of(ISSUE)); | |||
assertThat(underTest.readDeletedComponentIssues(COMPONENT_REF)).isEqualTo(ISSUES); | |||
} | |||
@Test | |||
public void readDeletedComponentIssues_it_not_cached() { | |||
writer.writeDeletedComponentIssues(COMPONENT_REF, COMPONENT_UUID, of(ISSUE)); | |||
assertThat(underTest.readDeletedComponentIssues(COMPONENT_REF)).isNotSameAs(underTest.readDeletedComponentIssues(COMPONENT_REF)); | |||
} | |||
@Test | |||
public void readComponentDuplications_returns_empty_list_if_file_does_not_exist() { | |||
assertThat(underTest.readComponentDuplications(COMPONENT_REF)).isEmpty(); |
@@ -39,7 +39,6 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader { | |||
private Map<Integer, BatchReport.Changesets> changesets = new HashMap<>(); | |||
private Map<Integer, BatchReport.Component> components = new HashMap<>(); | |||
private Map<Integer, List<BatchReport.Issue>> issues = new HashMap<>(); | |||
private Map<Integer, BatchReport.Issues> deletedIssues = new HashMap<>(); | |||
private Map<Integer, List<BatchReport.Duplication>> duplications = new HashMap<>(); | |||
private Map<Integer, List<BatchReport.Symbols.Symbol>> symbols = new HashMap<>(); | |||
private Map<Integer, List<BatchReport.SyntaxHighlighting>> syntaxHighlightings = new HashMap<>(); | |||
@@ -55,8 +54,7 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader { | |||
public void evaluate() throws Throwable { | |||
try { | |||
statement.evaluate(); | |||
} | |||
finally { | |||
} finally { | |||
clear(); | |||
} | |||
} | |||
@@ -69,7 +67,6 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader { | |||
this.changesets.clear(); | |||
this.components.clear(); | |||
this.issues.clear(); | |||
this.deletedIssues.clear(); | |||
this.duplications.clear(); | |||
this.symbols.clear(); | |||
this.syntaxHighlightings.clear(); | |||
@@ -132,19 +129,6 @@ public class BatchReportReaderRule implements TestRule, BatchReportReader { | |||
this.issues.put(componentRef, issue); | |||
} | |||
@Override | |||
public BatchReport.Issues readDeletedComponentIssues(int deletedComponentRef) { | |||
BatchReport.Issues issues = this.deletedIssues.get(deletedComponentRef); | |||
if (issues == null) { | |||
throw new IllegalStateException("Unable to issues for deleted component #" + deletedComponentRef); | |||
} | |||
return issues; | |||
} | |||
public void putDeletedIssues(int componentRef, BatchReport.Issues issues) { | |||
this.deletedIssues.put(componentRef, issues); | |||
} | |||
@Override | |||
public List<BatchReport.Duplication> readComponentDuplications(int componentRef) { | |||
return nonNull(this.duplications.get(componentRef)); |
@@ -17,210 +17,229 @@ | |||
* 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.issue; | |||
import com.google.common.collect.ImmutableSet; | |||
import com.google.common.collect.Iterators; | |||
import java.io.IOException; | |||
import java.util.Arrays; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.api.CoreProperties; | |||
import org.sonar.api.config.Settings; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.api.utils.log.LogTester; | |||
import org.sonar.batch.protocol.output.BatchReport; | |||
import org.sonar.core.rule.RuleDto; | |||
import org.sonar.server.computation.batch.BatchReportReaderRule; | |||
import org.sonar.server.computation.component.ProjectSettingsRepository; | |||
import org.sonar.server.user.index.UserDoc; | |||
import org.sonar.server.user.index.UserIndex; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verifyZeroInteractions; | |||
import static org.mockito.Mockito.when; | |||
public class IssueComputationTest { | |||
private static final RuleKey RULE_KEY = RuleKey.of("squid", "R1"); | |||
private static final String PROJECT_KEY = "PROJECT_KEY"; | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@Rule | |||
public LogTester logTester = new LogTester(); | |||
@Rule | |||
public BatchReportReaderRule reportReader = new BatchReportReaderRule(); | |||
IssueComputation sut; | |||
// inputs | |||
RuleCache ruleCache = mock(RuleCache.class); | |||
SourceLinesCache lineCache = mock(SourceLinesCache.class); | |||
ScmAccountCache scmAccountCache = mock(ScmAccountCache.class); | |||
RuleDto rule = new RuleDto().setRepositoryKey(RULE_KEY.repository()).setRuleKey(RULE_KEY.rule()); | |||
BatchReport.Issue.Builder inputIssue = BatchReport.Issue.newBuilder() | |||
.setUuid("ISSUE_A") | |||
.setRuleRepository(RULE_KEY.repository()) | |||
.setRuleKey(RULE_KEY.rule()) | |||
.setStatus(Issue.STATUS_OPEN); | |||
Settings projectSettings; | |||
ProjectSettingsRepository projectSettingsRepository = mock(ProjectSettingsRepository.class); | |||
UserIndex userIndex = mock(UserIndex.class); | |||
// output | |||
IssueCache outputIssues; | |||
@Before | |||
public void setUp() throws IOException { | |||
when(ruleCache.get(RULE_KEY)).thenReturn(rule); | |||
outputIssues = new IssueCache(temp.newFile(), System2.INSTANCE); | |||
projectSettings = new Settings(); | |||
when(projectSettingsRepository.getProjectSettings(PROJECT_KEY)).thenReturn(projectSettings); | |||
sut = new IssueComputation(ruleCache, lineCache, scmAccountCache, outputIssues, userIndex, projectSettingsRepository, reportReader); | |||
} | |||
@After | |||
public void after() { | |||
sut.afterReportProcessing(); | |||
} | |||
@Test | |||
public void store_issues_on_disk() { | |||
process(); | |||
assertThat(Iterators.getOnlyElement(outputIssues.traverse()).key()).isEqualTo("ISSUE_A"); | |||
} | |||
@Test | |||
public void copy_rule_tags_on_new_issues() { | |||
inputIssue.setIsNew(true); | |||
rule.setTags(ImmutableSet.of("bug", "performance")); | |||
rule.setSystemTags(ImmutableSet.of("blocker")); | |||
process(); | |||
assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).containsOnly("blocker", "bug", "performance"); | |||
} | |||
@Test | |||
public void do_not_copy_rule_tags_on_existing_issues() { | |||
inputIssue.setIsNew(false); | |||
rule.setTags(ImmutableSet.of("bug", "performance")); | |||
rule.setSystemTags(ImmutableSet.of("blocker")); | |||
process(); | |||
assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).isEmpty(); | |||
} | |||
@Test | |||
public void guess_author_of_new_issues() { | |||
inputIssue.setIsNew(true); | |||
inputIssue.setLine(3); | |||
when(lineCache.lineAuthor(3)).thenReturn("charlie"); | |||
process(); | |||
assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isEqualTo("charlie"); | |||
} | |||
@Test | |||
public void do_not_fail_if_missing_author_for_new_issues() { | |||
inputIssue.setIsNew(true); | |||
inputIssue.setLine(3); | |||
when(lineCache.lineAuthor(3)).thenReturn(null); | |||
process(); | |||
assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull(); | |||
} | |||
@Test | |||
public void do_not_guess_author_of_existing_issues() { | |||
inputIssue.setIsNew(false); | |||
inputIssue.setLine(3); | |||
when(lineCache.lineAuthor(3)).thenReturn("charlie"); | |||
process(); | |||
assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull(); | |||
} | |||
@Test | |||
public void auto_assign_new_issues() { | |||
inputIssue.setIsNew(true); | |||
inputIssue.setAuthorLogin("charlie"); | |||
when(scmAccountCache.getNullable("charlie")).thenReturn("char.lie"); | |||
process(); | |||
assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo("char.lie"); | |||
} | |||
@Test | |||
public void do_not_auto_assign_existing_issues() { | |||
inputIssue.setIsNew(false); | |||
inputIssue.setAuthorLogin("charlie"); | |||
when(scmAccountCache.getNullable("charlie")).thenReturn("char.lie"); | |||
process(); | |||
assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull(); | |||
} | |||
@Test | |||
public void do_not_override_author_and_assignee_set_by_old_batch_plugins() { | |||
inputIssue.setIsNew(true); | |||
// these fields were provided during project analysis, for instance | |||
// by developer cockpit or issue-assign plugins | |||
inputIssue.setAuthorLogin("charlie"); | |||
inputIssue.setAssignee("cabu"); | |||
process(); | |||
// keep the values, without trying to update them | |||
DefaultIssue cachedIssue = Iterators.getOnlyElement(outputIssues.traverse()); | |||
assertThat(cachedIssue.assignee()).isEqualTo("cabu"); | |||
assertThat(cachedIssue.authorLogin()).isEqualTo("charlie"); | |||
verifyZeroInteractions(scmAccountCache); | |||
} | |||
@Test | |||
public void assign_default_assignee_when_available() { | |||
inputIssue.setIsNew(true); | |||
String wolinski = "wolinski"; | |||
projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski); | |||
when(userIndex.getNullableByLogin(wolinski)).thenReturn(new UserDoc()); | |||
process(); | |||
assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo(wolinski); | |||
assertThat(logTester.logs()).doesNotContain(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski)); | |||
} | |||
@Test | |||
public void do_not_assign_default_assignee_when_not_found_in_index() { | |||
inputIssue.setIsNew(true); | |||
String wolinski = "wolinski"; | |||
projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski); | |||
when(userIndex.getNullableByLogin(wolinski)).thenReturn(null); | |||
process(); | |||
assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull(); | |||
assertThat(logTester.logs()).contains(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski)); | |||
} | |||
private void process() { | |||
sut.processComponentIssues(Arrays.asList(inputIssue.build()), "FILE_A", 1, PROJECT_KEY, "PROJECT_UUID"); | |||
} | |||
} | |||
///* | |||
// * SonarQube, open source software quality management tool. | |||
// * Copyright (C) 2008-2014 SonarSource | |||
// * mailto:contact AT sonarsource DOT com | |||
// * | |||
// * SonarQube 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. | |||
// * | |||
// * SonarQube 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.issue; | |||
// | |||
//import com.google.common.collect.ImmutableSet; | |||
//import com.google.common.collect.Iterators; | |||
//import java.io.IOException; | |||
//import java.util.Arrays; | |||
//import org.junit.After; | |||
//import org.junit.Before; | |||
//import org.junit.Rule; | |||
//import org.junit.Test; | |||
//import org.junit.rules.TemporaryFolder; | |||
//import org.sonar.api.CoreProperties; | |||
//import org.sonar.api.config.Settings; | |||
//import org.sonar.api.issue.Issue; | |||
//import org.sonar.core.issue.DefaultIssue; | |||
//import org.sonar.api.rule.RuleKey; | |||
//import org.sonar.api.utils.System2; | |||
//import org.sonar.api.utils.log.LogTester; | |||
//import org.sonar.batch.protocol.output.BatchReport; | |||
//import org.sonar.core.rule.RuleDto; | |||
//import org.sonar.server.computation.batch.BatchReportReaderRule; | |||
//import org.sonar.server.computation.component.ProjectSettingsRepository; | |||
//import org.sonar.server.user.index.UserDoc; | |||
//import org.sonar.server.user.index.UserIndex; | |||
// | |||
//import static org.assertj.core.api.Assertions.assertThat; | |||
//import static org.mockito.Mockito.mock; | |||
//import static org.mockito.Mockito.verifyZeroInteractions; | |||
//import static org.mockito.Mockito.when; | |||
// | |||
//public class IssueComputationTest { | |||
// | |||
// private static final RuleKey RULE_KEY = RuleKey.of("squid", "R1"); | |||
// private static final String PROJECT_KEY = "PROJECT_KEY"; | |||
// | |||
// @Rule | |||
// public TemporaryFolder temp = new TemporaryFolder(); | |||
// @Rule | |||
// public LogTester logTester = new LogTester(); | |||
// @Rule | |||
// public BatchReportReaderRule reportReader = new BatchReportReaderRule(); | |||
// | |||
// IssueComputation sut; | |||
// | |||
// // inputs | |||
// RuleCache ruleCache = mock(RuleCache.class); | |||
// SourceAuthorsHolder lineCache = mock(SourceAuthorsHolder.class); | |||
// ScmAccountToUser scmAccountToUser = mock(ScmAccountToUser.class); | |||
// RuleDto rule = new RuleDto().setRepositoryKey(RULE_KEY.repository()).setRuleKey(RULE_KEY.rule()); | |||
// BatchReport.Issue.Builder inputIssue = BatchReport.Issue.newBuilder() | |||
// .setUuid("ISSUE_A") | |||
// .setRuleRepository(RULE_KEY.repository()) | |||
// .setRuleKey(RULE_KEY.rule()) | |||
// .setStatus(Issue.STATUS_OPEN); | |||
// Settings projectSettings; | |||
// ProjectSettingsRepository projectSettingsRepository = mock(ProjectSettingsRepository.class); | |||
// UserIndex userIndex = mock(UserIndex.class); | |||
// | |||
// // output | |||
// DeprecatedIssueCache outputIssues; | |||
// | |||
// @Before | |||
// public void setUp() throws IOException { | |||
// when(ruleCache.get(RULE_KEY)).thenReturn(rule); | |||
// outputIssues = new DeprecatedIssueCache(temp.newFile(), System2.INSTANCE); | |||
// projectSettings = new Settings(); | |||
// when(projectSettingsRepository.getProjectSettings(PROJECT_KEY)).thenReturn(projectSettings); | |||
// sut = new IssueComputation(ruleCache, lineCache, scmAccountToUser, outputIssues, userIndex, projectSettingsRepository, reportReader); | |||
// } | |||
// | |||
// @After | |||
// public void after() { | |||
// sut.afterReportProcessing(); | |||
// } | |||
// | |||
// @Test | |||
// public void store_issues_on_disk() { | |||
// process(); | |||
// | |||
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).key()).isEqualTo("ISSUE_A"); | |||
// } | |||
// | |||
// @Test | |||
// public void copy_rule_tags_on_new_issues() { | |||
// inputIssue.setIsNew(true); | |||
// rule.setTags(ImmutableSet.of("bug", "performance")); | |||
// rule.setSystemTags(ImmutableSet.of("blocker")); | |||
// | |||
// process(); | |||
// | |||
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).containsOnly("blocker", "bug", "performance"); | |||
// } | |||
// | |||
// @Test | |||
// public void do_not_copy_rule_tags_on_existing_issues() { | |||
// inputIssue.setIsNew(false); | |||
// rule.setTags(ImmutableSet.of("bug", "performance")); | |||
// rule.setSystemTags(ImmutableSet.of("blocker")); | |||
// | |||
// process(); | |||
// | |||
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).tags()).isEmpty(); | |||
// } | |||
// | |||
// @Test | |||
// public void guess_author_of_new_issues() { | |||
// inputIssue.setIsNew(true); | |||
// inputIssue.setLine(3); | |||
// when(lineCache.lineAuthor(3)).thenReturn("charlie"); | |||
// | |||
// process(); | |||
// | |||
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isEqualTo("charlie"); | |||
// } | |||
// | |||
// @Test | |||
// public void do_not_fail_if_missing_author_for_new_issues() { | |||
// inputIssue.setIsNew(true); | |||
// inputIssue.setLine(3); | |||
// when(lineCache.lineAuthor(3)).thenReturn(null); | |||
// | |||
// process(); | |||
// | |||
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull(); | |||
// } | |||
// | |||
// @Test | |||
// public void do_not_guess_author_of_existing_issues() { | |||
// inputIssue.setIsNew(false); | |||
// inputIssue.setLine(3); | |||
// when(lineCache.lineAuthor(3)).thenReturn("charlie"); | |||
// | |||
// process(); | |||
// | |||
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).authorLogin()).isNull(); | |||
// } | |||
// | |||
// @Test | |||
// public void auto_assign_new_issues() { | |||
// inputIssue.setIsNew(true); | |||
// inputIssue.setAuthorLogin("charlie"); | |||
// when(scmAccountToUser.getNullable("charlie")).thenReturn("char.lie"); | |||
// | |||
// process(); | |||
// | |||
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo("char.lie"); | |||
// } | |||
// | |||
// @Test | |||
// public void do_not_auto_assign_existing_issues() { | |||
// inputIssue.setIsNew(false); | |||
// inputIssue.setAuthorLogin("charlie"); | |||
// when(scmAccountToUser.getNullable("charlie")).thenReturn("char.lie"); | |||
// | |||
// process(); | |||
// | |||
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull(); | |||
// } | |||
// | |||
// @Test | |||
// public void do_not_override_author_and_assignee_set_by_old_batch_plugins() { | |||
// inputIssue.setIsNew(true); | |||
// | |||
// // these fields were provided during project analysis, for instance | |||
// // by developer cockpit or issue-assign plugins | |||
// inputIssue.setAuthorLogin("charlie"); | |||
// inputIssue.setAssignee("cabu"); | |||
// | |||
// process(); | |||
// | |||
// // keep the values, without trying to update them | |||
// DefaultIssue cachedIssue = Iterators.getOnlyElement(outputIssues.traverse()); | |||
// assertThat(cachedIssue.assignee()).isEqualTo("cabu"); | |||
// assertThat(cachedIssue.authorLogin()).isEqualTo("charlie"); | |||
// verifyZeroInteractions(scmAccountToUser); | |||
// } | |||
// | |||
// @Test | |||
// public void assign_default_assignee_when_available() { | |||
// inputIssue.setIsNew(true); | |||
// String wolinski = "wolinski"; | |||
// projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski); | |||
// when(userIndex.getNullableByLogin(wolinski)).thenReturn(new UserDoc()); | |||
// | |||
// process(); | |||
// | |||
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isEqualTo(wolinski); | |||
// assertThat(logTester.logs()).doesNotContain(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski)); | |||
// } | |||
// | |||
// @Test | |||
// public void do_not_assign_default_assignee_when_not_found_in_index() { | |||
// inputIssue.setIsNew(true); | |||
// String wolinski = "wolinski"; | |||
// projectSettings.setProperty(CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski); | |||
// when(userIndex.getNullableByLogin(wolinski)).thenReturn(null); | |||
// | |||
// process(); | |||
// | |||
// assertThat(Iterators.getOnlyElement(outputIssues.traverse()).assignee()).isNull(); | |||
// assertThat(logTester.logs()).contains(String.format("the %s property was set with an unknown login: %s", CoreProperties.DEFAULT_ISSUE_ASSIGNEE, wolinski)); | |||
// } | |||
// | |||
// private void process() { | |||
// sut.processComponentIssues(Arrays.asList(inputIssue.build()), "FILE_A", 1, PROJECT_KEY, "PROJECT_UUID"); | |||
// } | |||
//} |
@@ -19,6 +19,7 @@ | |||
*/ | |||
package org.sonar.server.computation.issue; | |||
import java.util.Collections; | |||
import org.junit.Before; | |||
import org.junit.ClassRule; | |||
import org.junit.Test; | |||
@@ -28,14 +29,12 @@ import org.sonar.server.es.EsTester; | |||
import org.sonar.server.user.index.UserIndex; | |||
import org.sonar.server.user.index.UserIndexDefinition; | |||
import java.util.Collections; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.junit.Assert.fail; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
public class ScmAccountCacheLoaderTest { | |||
public class ScmAccountToUserLoaderTest { | |||
@ClassRule | |||
public static EsTester esTester = new EsTester().addDefinitions(new UserIndexDefinition(new Settings())); | |||
@@ -49,7 +48,7 @@ public class ScmAccountCacheLoaderTest { | |||
public void load_login_for_scm_account() throws Exception { | |||
esTester.putDocuments("users", "user", getClass(), "charlie.json"); | |||
UserIndex index = new UserIndex(esTester.client()); | |||
ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index); | |||
ScmAccountToUserLoader loader = new ScmAccountToUserLoader(index); | |||
assertThat(loader.load("missing")).isNull(); | |||
assertThat(loader.load("jesuis@charlie.com")).isEqualTo("charlie"); | |||
@@ -60,7 +59,7 @@ public class ScmAccountCacheLoaderTest { | |||
esTester.putDocuments("users", "user", getClass(), "charlie.json", "charlie_conflict.json"); | |||
UserIndex index = new UserIndex(esTester.client()); | |||
Logger log = mock(Logger.class); | |||
ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index, log); | |||
ScmAccountToUserLoader loader = new ScmAccountToUserLoader(index, log); | |||
assertThat(loader.load("charlie")).isNull(); | |||
verify(log).warn("Multiple users share the SCM account 'charlie': charlie, another.charlie"); | |||
@@ -69,7 +68,7 @@ public class ScmAccountCacheLoaderTest { | |||
@Test | |||
public void load_by_multiple_scm_accounts_is_not_supported_yet() { | |||
UserIndex index = new UserIndex(esTester.client()); | |||
ScmAccountCacheLoader loader = new ScmAccountCacheLoader(index); | |||
ScmAccountToUserLoader loader = new ScmAccountToUserLoader(index); | |||
try { | |||
loader.loadAll(Collections.<String>emptyList()); | |||
fail(); |
@@ -0,0 +1,137 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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. | |||
*/ | |||
///* | |||
// * SonarQube, open source software quality management tool. | |||
// * Copyright (C) 2008-2014 SonarSource | |||
// * mailto:contact AT sonarsource DOT com | |||
// * | |||
// * SonarQube 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. | |||
// * | |||
// * SonarQube 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.issue; | |||
// | |||
//import java.util.Date; | |||
//import org.junit.Before; | |||
//import org.junit.ClassRule; | |||
//import org.junit.Rule; | |||
//import org.junit.Test; | |||
//import org.junit.experimental.categories.Category; | |||
//import org.sonar.api.config.Settings; | |||
//import org.sonar.batch.protocol.output.BatchReport; | |||
//import org.sonar.server.computation.batch.BatchReportReaderRule; | |||
//import org.sonar.server.es.EsTester; | |||
//import org.sonar.server.source.index.SourceLineDoc; | |||
//import org.sonar.server.source.index.SourceLineIndex; | |||
//import org.sonar.server.source.index.SourceLineIndexDefinition; | |||
//import org.sonar.test.DbTests; | |||
// | |||
//import static org.assertj.core.api.Assertions.assertThat; | |||
// | |||
//@Category(DbTests.class) | |||
//public class SourceAuthorsHolderTest { | |||
// | |||
// @ClassRule | |||
// public static EsTester esTester = new EsTester().addDefinitions(new SourceLineIndexDefinition(new Settings())); | |||
// @Rule | |||
// public BatchReportReaderRule reportReader = new BatchReportReaderRule(); | |||
// | |||
// SourceAuthorsHolder sut; | |||
// | |||
// @Before | |||
// public void setUp() throws Exception { | |||
// esTester.truncateIndices(); | |||
// sut = new SourceAuthorsHolder(new SourceLineIndex(esTester.client()), reportReader); | |||
// } | |||
// | |||
// @Test | |||
// public void line_author_from_report() { | |||
// reportReader.putChangesets(BatchReport.Changesets.newBuilder() | |||
// .setComponentRef(123_456_789) | |||
// .addChangeset(newChangeset("charb", "123-456-789", 123_456_789L)) | |||
// .addChangeset(newChangeset("wolinski", "987-654-321", 987_654_321L)) | |||
// .addChangesetIndexByLine(0) | |||
// .addChangesetIndexByLine(0) | |||
// .addChangesetIndexByLine(1) | |||
// .build()); | |||
// | |||
// sut.init("ANY_UUID", 123_456_789, reportReader); | |||
// | |||
// assertThat(sut.lineAuthor(1)).isEqualTo("charb"); | |||
// assertThat(sut.lineAuthor(2)).isEqualTo("charb"); | |||
// assertThat(sut.lineAuthor(3)).isEqualTo("wolinski"); | |||
// // compute last author | |||
// assertThat(sut.lineAuthor(4)).isEqualTo("wolinski"); | |||
// assertThat(sut.lineAuthor(null)).isEqualTo("wolinski"); | |||
// } | |||
// | |||
// @Test | |||
// public void line_author_from_index() throws Exception { | |||
// esTester.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE, | |||
// newSourceLine("cabu", "123-456-789", 123_456_789, 1), | |||
// newSourceLine("cabu", "123-456-789", 123_456_789, 2), | |||
// newSourceLine("cabu", "123-123-789", 123_456_789, 3), | |||
// newSourceLine("wolinski", "987-654-321", 987_654_321, 4), | |||
// newSourceLine("cabu", "123-456-789", 123_456_789, 5) | |||
// ); | |||
// | |||
// sut.init("DEFAULT_UUID", 123, reportReader); | |||
// | |||
// assertThat(sut.lineAuthor(1)).isEqualTo("cabu"); | |||
// assertThat(sut.lineAuthor(2)).isEqualTo("cabu"); | |||
// assertThat(sut.lineAuthor(3)).isEqualTo("cabu"); | |||
// assertThat(sut.lineAuthor(4)).isEqualTo("wolinski"); | |||
// assertThat(sut.lineAuthor(5)).isEqualTo("cabu"); | |||
// assertThat(sut.lineAuthor(6)).isEqualTo("wolinski"); | |||
// } | |||
// | |||
// @Test(expected = IllegalStateException.class) | |||
// public void fail_when_component_ref_is_not_filled() { | |||
// sut.init("ANY_UUID", null, reportReader); | |||
// sut.lineAuthor(0); | |||
// } | |||
// | |||
// private BatchReport.Changesets.Changeset.Builder newChangeset(String author, String revision, long date) { | |||
// return BatchReport.Changesets.Changeset.newBuilder() | |||
// .setAuthor(author) | |||
// .setRevision(revision) | |||
// .setDate(date); | |||
// } | |||
// | |||
// private SourceLineDoc newSourceLine(String author, String revision, long date, int lineNumber) { | |||
// return new SourceLineDoc() | |||
// .setScmAuthor(author) | |||
// .setScmRevision(revision) | |||
// .setScmDate(new Date(date)) | |||
// .setLine(lineNumber) | |||
// .setProjectUuid("PROJECT_UUID") | |||
// .setFileUuid("DEFAULT_UUID"); | |||
// } | |||
//} |
@@ -1,118 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.issue; | |||
import java.util.Date; | |||
import org.junit.Before; | |||
import org.junit.ClassRule; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.experimental.categories.Category; | |||
import org.sonar.api.config.Settings; | |||
import org.sonar.batch.protocol.output.BatchReport; | |||
import org.sonar.server.computation.batch.BatchReportReaderRule; | |||
import org.sonar.server.es.EsTester; | |||
import org.sonar.server.source.index.SourceLineDoc; | |||
import org.sonar.server.source.index.SourceLineIndex; | |||
import org.sonar.server.source.index.SourceLineIndexDefinition; | |||
import org.sonar.test.DbTests; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
@Category(DbTests.class) | |||
public class SourceLinesCacheTest { | |||
@ClassRule | |||
public static EsTester esTester = new EsTester().addDefinitions(new SourceLineIndexDefinition(new Settings())); | |||
@Rule | |||
public BatchReportReaderRule reportReader = new BatchReportReaderRule(); | |||
SourceLinesCache sut; | |||
@Before | |||
public void setUp() throws Exception { | |||
esTester.truncateIndices(); | |||
sut = new SourceLinesCache(new SourceLineIndex(esTester.client())); | |||
} | |||
@Test | |||
public void line_author_from_report() { | |||
reportReader.putChangesets(BatchReport.Changesets.newBuilder() | |||
.setComponentRef(123_456_789) | |||
.addChangeset(newChangeset("charb", "123-456-789", 123_456_789L)) | |||
.addChangeset(newChangeset("wolinski", "987-654-321", 987_654_321L)) | |||
.addChangesetIndexByLine(0) | |||
.addChangesetIndexByLine(0) | |||
.addChangesetIndexByLine(1) | |||
.build()); | |||
sut.init("ANY_UUID", 123_456_789, reportReader); | |||
assertThat(sut.lineAuthor(1)).isEqualTo("charb"); | |||
assertThat(sut.lineAuthor(2)).isEqualTo("charb"); | |||
assertThat(sut.lineAuthor(3)).isEqualTo("wolinski"); | |||
// compute last author | |||
assertThat(sut.lineAuthor(4)).isEqualTo("wolinski"); | |||
assertThat(sut.lineAuthor(null)).isEqualTo("wolinski"); | |||
} | |||
@Test | |||
public void line_author_from_index() throws Exception { | |||
esTester.putDocuments(SourceLineIndexDefinition.INDEX, SourceLineIndexDefinition.TYPE, | |||
newSourceLine("cabu", "123-456-789", 123_456_789, 1), | |||
newSourceLine("cabu", "123-456-789", 123_456_789, 2), | |||
newSourceLine("cabu", "123-123-789", 123_456_789, 3), | |||
newSourceLine("wolinski", "987-654-321", 987_654_321, 4), | |||
newSourceLine("cabu", "123-456-789", 123_456_789, 5) | |||
); | |||
sut.init("DEFAULT_UUID", 123, reportReader); | |||
assertThat(sut.lineAuthor(1)).isEqualTo("cabu"); | |||
assertThat(sut.lineAuthor(2)).isEqualTo("cabu"); | |||
assertThat(sut.lineAuthor(3)).isEqualTo("cabu"); | |||
assertThat(sut.lineAuthor(4)).isEqualTo("wolinski"); | |||
assertThat(sut.lineAuthor(5)).isEqualTo("cabu"); | |||
assertThat(sut.lineAuthor(6)).isEqualTo("wolinski"); | |||
} | |||
@Test(expected = IllegalStateException.class) | |||
public void fail_when_component_ref_is_not_filled() { | |||
sut.init("ANY_UUID", null, reportReader); | |||
sut.lineAuthor(0); | |||
} | |||
private BatchReport.Changesets.Changeset.Builder newChangeset(String author, String revision, long date) { | |||
return BatchReport.Changesets.Changeset.newBuilder() | |||
.setAuthor(author) | |||
.setRevision(revision) | |||
.setDate(date); | |||
} | |||
private SourceLineDoc newSourceLine(String author, String revision, long date, int lineNumber) { | |||
return new SourceLineDoc() | |||
.setScmAuthor(author) | |||
.setScmRevision(revision) | |||
.setScmDate(new Date(date)) | |||
.setLine(lineNumber) | |||
.setProjectUuid("PROJECT_UUID") | |||
.setFileUuid("DEFAULT_UUID"); | |||
} | |||
} |
@@ -1,122 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.step; | |||
import java.io.IOException; | |||
import java.util.Arrays; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import org.junit.ClassRule; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.batch.protocol.Constants; | |||
import org.sonar.batch.protocol.output.BatchReport; | |||
import org.sonar.core.persistence.DbTester; | |||
import org.sonar.server.computation.batch.BatchReportReaderRule; | |||
import org.sonar.server.computation.batch.TreeRootHolderRule; | |||
import org.sonar.server.computation.component.Component; | |||
import org.sonar.server.computation.component.DumbComponent; | |||
import org.sonar.server.computation.issue.IssueComputation; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
public class ParseReportStepTest extends BaseStepTest { | |||
private static final String PROJECT_KEY = "PROJECT_KEY"; | |||
private static final List<BatchReport.Issue> ISSUES_ON_DELETED_COMPONENT = Arrays.asList(BatchReport.Issue.newBuilder() | |||
.setUuid("DELETED_ISSUE_UUID") | |||
.build()); | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@Rule | |||
public BatchReportReaderRule reportReader = new BatchReportReaderRule(); | |||
@Rule | |||
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); | |||
@ClassRule | |||
public static DbTester dbTester = new DbTester(); | |||
IssueComputation issueComputation = mock(IssueComputation.class); | |||
ParseReportStep sut = new ParseReportStep(issueComputation, reportReader, treeRootHolder); | |||
@Test | |||
public void extract_report_from_db_and_browse_components() throws Exception { | |||
DumbComponent root = DumbComponent.builder(Component.Type.PROJECT, 1).setUuid("PROJECT_UUID").setKey(PROJECT_KEY).addChildren( | |||
DumbComponent.builder(Component.Type.FILE, 2).setUuid("FILE1_UUID").setKey("PROJECT_KEY:file1").build(), | |||
DumbComponent.builder(Component.Type.FILE, 3).setUuid("FILE2_UUID").setKey("PROJECT_KEY:file2").build()) | |||
.build(); | |||
generateReport(); | |||
treeRootHolder.setRoot(root); | |||
sut.execute(); | |||
assertThat(reportReader.readMetadata().getRootComponentRef()).isEqualTo(1); | |||
assertThat(reportReader.readMetadata().getDeletedComponentsCount()).isEqualTo(1); | |||
// verify that all components are processed (currently only for issues) | |||
verify(issueComputation).processComponentIssues(Collections.<BatchReport.Issue>emptyList(), "PROJECT_UUID", 1, PROJECT_KEY, "PROJECT_UUID"); | |||
verify(issueComputation).processComponentIssues(Collections.<BatchReport.Issue>emptyList(), "FILE1_UUID", 2, PROJECT_KEY, "PROJECT_UUID"); | |||
verify(issueComputation).processComponentIssues(Collections.<BatchReport.Issue>emptyList(), "FILE2_UUID", 3, PROJECT_KEY, "PROJECT_UUID"); | |||
verify(issueComputation).processComponentIssues(ISSUES_ON_DELETED_COMPONENT, "DELETED_UUID", null, PROJECT_KEY, "PROJECT_UUID"); | |||
verify(issueComputation).afterReportProcessing(); | |||
} | |||
private void generateReport() throws IOException { | |||
// project and 2 files | |||
reportReader.setMetadata(BatchReport.Metadata.newBuilder() | |||
.setRootComponentRef(1) | |||
.setDeletedComponentsCount(1) | |||
.build()); | |||
reportReader.putComponent(BatchReport.Component.newBuilder() | |||
.setRef(1) | |||
.setType(Constants.ComponentType.PROJECT) | |||
.addChildRef(2) | |||
.addChildRef(3) | |||
.build()); | |||
reportReader.putComponent(BatchReport.Component.newBuilder() | |||
.setRef(2) | |||
.setType(Constants.ComponentType.FILE) | |||
.build()); | |||
reportReader.putComponent(BatchReport.Component.newBuilder() | |||
.setRef(3) | |||
.setType(Constants.ComponentType.FILE) | |||
.build()); | |||
// deleted components | |||
BatchReport.Issues.Builder issuesBuilder = BatchReport.Issues.newBuilder(); | |||
issuesBuilder.setComponentRef(1); | |||
issuesBuilder.setComponentUuid("DELETED_UUID"); | |||
issuesBuilder.addAllIssue(ISSUES_ON_DELETED_COMPONENT); | |||
reportReader.putDeletedIssues(1, issuesBuilder.build()); | |||
} | |||
@Override | |||
protected ComputationStep step() { | |||
return sut; | |||
} | |||
} |
@@ -27,12 +27,12 @@ import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rule.Severity; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.core.issue.db.UpdateConflictResolver; | |||
import org.sonar.core.persistence.DbSession; | |||
import org.sonar.core.persistence.DbTester; |
@@ -25,7 +25,7 @@ import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.mockito.Mockito; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.notifications.Notification; | |||
import org.sonar.api.rule.Severity; | |||
import org.sonar.api.utils.System2; |
@@ -28,8 +28,8 @@ import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.action.Actions; | |||
import org.sonar.api.issue.action.Function; | |||
import org.sonar.api.issue.condition.Condition; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.component.ComponentDto; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.core.issue.db.IssueDto; |
@@ -26,8 +26,8 @@ import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.mockito.Matchers; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import java.util.Collection; |
@@ -25,8 +25,8 @@ import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.api.user.User; | |||
import org.sonar.api.user.UserFinder; | |||
import org.sonar.core.issue.IssueUpdater; |
@@ -25,8 +25,8 @@ import java.util.Map; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.server.tester.AnonymousMockUserSession; | |||
@@ -33,8 +33,8 @@ import org.mockito.ArgumentCaptor; | |||
import org.sonar.api.issue.ActionPlan; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.action.Action; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.api.user.User; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.core.issue.DefaultActionPlan; |
@@ -28,7 +28,7 @@ import org.junit.runner.RunWith; | |||
import org.mockito.Mock; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.i18n.I18n; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.api.utils.Duration; | |||
import org.sonar.api.utils.Durations; | |||
import org.sonar.server.tester.UserSessionRule; |
@@ -27,8 +27,8 @@ import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.mockito.Mock; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.api.user.User; | |||
import org.sonar.api.user.UserFinder; | |||
import org.sonar.core.issue.db.IssueChangeDao; |
@@ -27,7 +27,7 @@ import org.junit.ClassRule; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rule.RuleStatus; | |||
import org.sonar.api.security.DefaultGroups; |
@@ -28,9 +28,9 @@ import org.junit.rules.ExpectedException; | |||
import org.junit.runner.RunWith; | |||
import org.mockito.Mock; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.core.issue.db.IssueChangeDao; | |||
import org.sonar.core.issue.db.IssueChangeDto; |
@@ -26,8 +26,8 @@ import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.api.issue.ActionPlan; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultActionPlan; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.server.issue.actionplan.ActionPlanService; |
@@ -26,8 +26,8 @@ import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.mockito.Matchers; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import java.util.Collection; |
@@ -23,9 +23,9 @@ package org.sonar.server.issue; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rules.Rule; | |||
import org.sonar.api.rules.RuleFinder; |
@@ -26,8 +26,8 @@ import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.server.tester.UserSessionRule; |
@@ -26,8 +26,8 @@ import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.core.issue.workflow.IssueWorkflow; | |||
import org.sonar.core.issue.workflow.Transition; | |||
import org.sonar.server.tester.UserSessionRule; |
@@ -28,8 +28,8 @@ import org.mockito.Mock; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.issue.ActionPlan; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.api.web.UserRole; | |||
import org.sonar.core.issue.ActionPlanStats; | |||
import org.sonar.core.issue.DefaultActionPlan; |
@@ -20,8 +20,8 @@ | |||
package org.sonar.server.issue.notification; | |||
import org.junit.Test; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.core.component.ComponentDto; | |||
import static org.assertj.core.api.Assertions.assertThat; |
@@ -24,7 +24,7 @@ import com.google.common.collect.ImmutableMap; | |||
import com.google.common.collect.Lists; | |||
import org.junit.Test; | |||
import org.mockito.Mockito; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rule.Severity; | |||
import org.sonar.api.utils.DateUtils; |
@@ -22,7 +22,7 @@ package org.sonar.server.issue.notification; | |||
import com.google.common.collect.Lists; | |||
import org.junit.Test; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rule.Severity; | |||
import org.sonar.api.utils.Duration; |
@@ -29,7 +29,7 @@ import org.mockito.Mock; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.action.Action; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.utils.text.JsonWriter; | |||
import org.sonar.api.web.UserRole; |
@@ -31,9 +31,9 @@ import org.mockito.Mock; | |||
import org.mockito.runners.MockitoJUnitRunner; | |||
import org.sonar.api.i18n.I18n; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.DefaultIssueComment; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssueComment; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.server.debt.internal.DefaultDebtCharacteristic; | |||
import org.sonar.api.user.User; |
@@ -79,15 +79,6 @@ public class BatchReportReader { | |||
return Collections.emptyList(); | |||
} | |||
public Issues readDeletedComponentIssues(int deletedComponentRef) { | |||
File file = fileStructure.fileFor(FileStructure.Domain.ISSUES_ON_DELETED, deletedComponentRef); | |||
if (!doesFileExists(file)) { | |||
throw new IllegalStateException("Unable to find report for deleted component #" + deletedComponentRef); | |||
} | |||
// all the issues are loaded in memory | |||
return ProtobufUtil.readFile(file, Issues.PARSER); | |||
} | |||
public List<BatchReport.Duplication> readComponentDuplications(int componentRef) { | |||
File file = fileStructure.fileFor(FileStructure.Domain.DUPLICATIONS, componentRef); | |||
if (doesFileExists(file)) { |
@@ -80,20 +80,6 @@ public class BatchReportWriter { | |||
return file; | |||
} | |||
/** | |||
* Issues on components which have been deleted are stored in another location. | |||
* Temporary hack, waiting for computation stack | |||
*/ | |||
public File writeDeletedComponentIssues(int componentRef, String componentUuid, Iterable<BatchReport.Issue> issues) { | |||
BatchReport.Issues.Builder issuesBuilder = BatchReport.Issues.newBuilder(); | |||
issuesBuilder.setComponentRef(componentRef); | |||
issuesBuilder.setComponentUuid(componentUuid); | |||
issuesBuilder.addAllIssue(issues); | |||
File file = fileStructure.fileFor(FileStructure.Domain.ISSUES_ON_DELETED, componentRef); | |||
ProtobufUtil.writeToFile(issuesBuilder.build(), file); | |||
return file; | |||
} | |||
public File writeComponentDuplications(int componentRef, Iterable<BatchReport.Duplication> duplications) { | |||
BatchReport.Duplications.Builder builder = BatchReport.Duplications.newBuilder(); | |||
builder.setComponentRef(componentRef); |
@@ -45,9 +45,6 @@ message Metadata { | |||
optional string project_key = 2; | |||
optional string branch = 3; | |||
optional int32 root_component_ref = 4; | |||
// temporary fields used during development of computation stack | |||
optional int32 deleted_components_count = 5; | |||
} | |||
message ComponentLink { | |||
@@ -111,36 +108,16 @@ message Issue { | |||
optional string msg = 4; | |||
optional Severity severity = 5; | |||
repeated string tag = 6; | |||
// temporary fields during development of computation stack | |||
optional double effort_to_fix = 7; | |||
optional bool is_new = 8; | |||
optional string uuid = 9; | |||
optional int64 debt_in_minutes = 10; | |||
optional string resolution = 11; | |||
optional string status = 12; | |||
optional string checksum = 13; | |||
optional bool manual_severity = 14; | |||
optional string reporter = 15; | |||
optional string assignee = 16; | |||
optional string action_plan_key = 17; | |||
optional string attributes = 18; | |||
optional string author_login = 19; | |||
optional int64 creation_date = 20; | |||
optional int64 close_date = 21; | |||
optional int64 update_date = 22; | |||
optional int64 selected_at = 23; | |||
optional string diff_fields = 24; | |||
optional bool is_changed = 25; | |||
optional bool must_send_notification = 26; | |||
optional string attributes = 8; | |||
// TODO should it be moved to compute engine? | |||
optional int64 debt_in_minutes = 9; | |||
} | |||
message Issues { | |||
optional int32 component_ref = 1; | |||
repeated Issue issue = 2; | |||
// Temporary field for issues on deleted components | |||
optional string component_uuid = 3; | |||
} | |||
message Changesets { |
@@ -56,14 +56,12 @@ public class BatchReportReaderTest { | |||
BatchReport.Metadata.Builder metadata = BatchReport.Metadata.newBuilder() | |||
.setAnalysisDate(15000000L) | |||
.setProjectKey("PROJECT_A") | |||
.setRootComponentRef(1) | |||
.setDeletedComponentsCount(10); | |||
.setRootComponentRef(1); | |||
writer.writeMetadata(metadata.build()); | |||
BatchReport.Metadata readMetadata = sut.readMetadata(); | |||
assertThat(readMetadata.getAnalysisDate()).isEqualTo(15000000L); | |||
assertThat(readMetadata.getProjectKey()).isEqualTo("PROJECT_A"); | |||
assertThat(readMetadata.getDeletedComponentsCount()).isEqualTo(10); | |||
assertThat(readMetadata.getRootComponentRef()).isEqualTo(1); | |||
} | |||
@@ -92,23 +90,12 @@ public class BatchReportReaderTest { | |||
public void read_issues() { | |||
BatchReportWriter writer = new BatchReportWriter(dir); | |||
BatchReport.Issue issue = BatchReport.Issue.newBuilder() | |||
.setUuid("ISSUE_A") | |||
.setLine(50) | |||
.build(); | |||
writer.writeComponentIssues(1, Arrays.asList(issue)); | |||
writer.writeDeletedComponentIssues(1, "compUuid", Arrays.asList(issue)); | |||
assertThat(sut.readComponentIssues(1)).hasSize(1); | |||
assertThat(sut.readComponentIssues(200)).isEmpty(); | |||
BatchReport.Issues deletedComponentIssues = sut.readDeletedComponentIssues(1); | |||
assertThat(deletedComponentIssues.getComponentUuid()).isEqualTo("compUuid"); | |||
assertThat(deletedComponentIssues.getIssueList()).hasSize(1); | |||
} | |||
@Test(expected = IllegalStateException.class) | |||
public void fail_if_missing_file_on_deleted_component() { | |||
sut.readDeletedComponentIssues(UNKNOWN_COMPONENT_REF); | |||
} | |||
@Test |
@@ -101,7 +101,6 @@ public class BatchReportWriterTest { | |||
// write data | |||
BatchReport.Issue issue = BatchReport.Issue.newBuilder() | |||
.setUuid("ISSUE_A") | |||
.setLine(50) | |||
.setMsg("the message") | |||
.build(); | |||
@@ -113,30 +112,6 @@ public class BatchReportWriterTest { | |||
assertThat(file).exists().isFile(); | |||
BatchReport.Issues read = ProtobufUtil.readFile(file, BatchReport.Issues.PARSER); | |||
assertThat(read.getComponentRef()).isEqualTo(1); | |||
assertThat(read.hasComponentUuid()).isFalse(); | |||
assertThat(read.getIssueCount()).isEqualTo(1); | |||
} | |||
@Test | |||
public void write_issues_of_deleted_component() { | |||
// no data yet | |||
assertThat(sut.hasComponentData(FileStructure.Domain.ISSUES_ON_DELETED, 1)).isFalse(); | |||
// write data | |||
BatchReport.Issue issue = BatchReport.Issue.newBuilder() | |||
.setUuid("ISSUE_A") | |||
.setLine(50) | |||
.setMsg("the message") | |||
.build(); | |||
sut.writeDeletedComponentIssues(1, "componentUuid", Arrays.asList(issue)); | |||
assertThat(sut.hasComponentData(FileStructure.Domain.ISSUES_ON_DELETED, 1)).isTrue(); | |||
File file = sut.getFileStructure().fileFor(FileStructure.Domain.ISSUES_ON_DELETED, 1); | |||
assertThat(file).exists().isFile(); | |||
BatchReport.Issues read = ProtobufUtil.readFile(file, BatchReport.Issues.PARSER); | |||
assertThat(read.getComponentRef()).isEqualTo(1); | |||
assertThat(read.getComponentUuid()).isEqualTo("componentUuid"); | |||
assertThat(read.getIssueCount()).isEqualTo(1); | |||
} | |||
@@ -40,10 +40,7 @@ import org.sonar.batch.cpd.CpdComponents; | |||
import org.sonar.batch.debt.DebtDecorator; | |||
import org.sonar.batch.debt.IssueChangelogDebtCalculator; | |||
import org.sonar.batch.debt.NewDebtDecorator; | |||
import org.sonar.batch.issue.tracking.InitialOpenIssuesSensor; | |||
import org.sonar.batch.issue.tracking.IssueHandlers; | |||
import org.sonar.batch.issue.tracking.IssueTracking; | |||
import org.sonar.batch.issue.tracking.IssueTrackingDecorator; | |||
import org.sonar.batch.language.LanguageDistributionDecorator; | |||
import org.sonar.batch.scan.report.ConsoleReport; | |||
import org.sonar.batch.scan.report.HtmlReport; | |||
@@ -92,11 +89,6 @@ public class BatchComponents { | |||
DebtDecorator.class, | |||
NewDebtDecorator.class, | |||
// Issue tracking | |||
IssueTrackingDecorator.class, | |||
IssueHandlers.class, | |||
InitialOpenIssuesSensor.class, | |||
// to be moved to compute engine | |||
UnitTestDecorator.class, | |||
LineCoverageDecorator.class, |
@@ -38,7 +38,7 @@ import org.sonar.api.batch.rule.Rules; | |||
import org.sonar.api.component.ResourcePerspectives; | |||
import org.sonar.api.issue.Issuable; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.measures.CoreMetrics; | |||
import org.sonar.api.measures.Measure; | |||
import org.sonar.api.measures.MeasuresFilters; |
@@ -34,8 +34,8 @@ import javax.annotation.Nullable; | |||
import org.apache.commons.lang.time.DateUtils; | |||
import org.sonar.api.batch.BatchSide; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.FieldDiffs; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.FieldDiffs; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import static com.google.common.collect.Lists.newArrayList; |
@@ -23,7 +23,7 @@ import com.google.common.collect.Lists; | |||
import java.util.List; | |||
import org.sonar.api.issue.Issuable; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.batch.index.BatchComponent; | |||
import org.sonar.core.issue.DefaultIssueBuilder; |
@@ -23,7 +23,7 @@ import com.google.common.base.Predicate; | |||
import com.google.common.collect.Iterables; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.ProjectIssues; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import javax.annotation.Nullable; | |||
@@ -20,7 +20,7 @@ | |||
package org.sonar.batch.issue; | |||
import org.sonar.api.batch.BatchSide; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.batch.index.Cache; | |||
import org.sonar.batch.index.Caches; | |||
@@ -21,7 +21,7 @@ package org.sonar.batch.issue; | |||
import org.sonar.api.batch.BatchSide; | |||
import org.sonar.api.issue.batch.IssueFilter; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
@BatchSide | |||
public class IssueFilters { |
@@ -28,7 +28,7 @@ import org.sonar.api.batch.rule.ActiveRules; | |||
import org.sonar.api.batch.rule.Rule; | |||
import org.sonar.api.batch.rule.Rules; | |||
import org.sonar.api.batch.rule.internal.DefaultActiveRule; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.rule.RuleKey; | |||
import org.sonar.api.rules.Violation; |
@@ -1,93 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.batch.issue.tracking; | |||
import java.util.Calendar; | |||
import java.util.Date; | |||
import org.apache.commons.lang.time.DateUtils; | |||
import org.apache.ibatis.session.ResultContext; | |||
import org.apache.ibatis.session.ResultHandler; | |||
import org.sonar.api.batch.RequiresDB; | |||
import org.sonar.api.batch.Sensor; | |||
import org.sonar.api.batch.SensorContext; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.core.issue.db.IssueChangeDao; | |||
import org.sonar.core.issue.db.IssueChangeDto; | |||
import org.sonar.core.issue.db.IssueDao; | |||
import org.sonar.core.issue.db.IssueDto; | |||
import org.sonar.core.resource.ResourceDao; | |||
import org.sonar.core.resource.ResourceDto; | |||
import org.sonar.core.resource.ResourceQuery; | |||
/** | |||
* Load all the issues referenced during the previous scan. | |||
*/ | |||
@RequiresDB | |||
public class InitialOpenIssuesSensor implements Sensor { | |||
private final InitialOpenIssuesStack initialOpenIssuesStack; | |||
private final IssueDao issueDao; | |||
private final IssueChangeDao issueChangeDao; | |||
private final ResourceDao resourceDao; | |||
public InitialOpenIssuesSensor(InitialOpenIssuesStack initialOpenIssuesStack, IssueDao issueDao, IssueChangeDao issueChangeDao, ResourceDao resourceDao) { | |||
this.initialOpenIssuesStack = initialOpenIssuesStack; | |||
this.issueDao = issueDao; | |||
this.issueChangeDao = issueChangeDao; | |||
this.resourceDao = resourceDao; | |||
} | |||
@Override | |||
public boolean shouldExecuteOnProject(Project project) { | |||
return true; | |||
} | |||
@Override | |||
public void analyse(Project project, SensorContext context) { | |||
ResourceDto module = resourceDao.getResource(ResourceQuery.create().setKey(project.getEffectiveKey())); | |||
if (module != null) { | |||
long moduleId = module.getId(); | |||
// Adding one second is a hack for resolving conflicts with concurrent user | |||
// changes during issue persistence | |||
final Date now = DateUtils.addSeconds(DateUtils.truncate(new Date(), Calendar.MILLISECOND), 1); | |||
issueDao.selectNonClosedIssuesByModule(moduleId, new ResultHandler() { | |||
@Override | |||
public void handleResult(ResultContext rc) { | |||
IssueDto dto = (IssueDto) rc.getResultObject(); | |||
dto.setSelectedAt(now.getTime()); | |||
initialOpenIssuesStack.addIssue(dto); | |||
} | |||
}); | |||
issueChangeDao.selectChangelogOnNonClosedIssuesByModuleAndType(moduleId, new ResultHandler() { | |||
@Override | |||
public void handleResult(ResultContext rc) { | |||
IssueChangeDto dto = (IssueChangeDto) rc.getResultObject(); | |||
initialOpenIssuesStack.addChangelog(dto); | |||
} | |||
}); | |||
} | |||
} | |||
@Override | |||
public String toString() { | |||
return getClass().getSimpleName(); | |||
} | |||
} |
@@ -1,86 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.batch.issue.tracking; | |||
import org.sonar.api.batch.BatchSide; | |||
import org.sonar.api.batch.InstantiationStrategy; | |||
import org.sonar.batch.index.Cache; | |||
import org.sonar.batch.index.Caches; | |||
import org.sonar.core.issue.db.IssueChangeDto; | |||
import org.sonar.core.issue.db.IssueDto; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import static com.google.common.collect.Lists.newArrayList; | |||
@BatchSide | |||
@InstantiationStrategy(InstantiationStrategy.PER_BATCH) | |||
public class InitialOpenIssuesStack { | |||
private final Cache<IssueDto> issuesCache; | |||
private final Cache<ArrayList<IssueChangeDto>> issuesChangelogCache; | |||
public InitialOpenIssuesStack(Caches caches) { | |||
issuesCache = caches.createCache("last-open-issues"); | |||
issuesChangelogCache = caches.createCache("issues-changelog"); | |||
} | |||
public InitialOpenIssuesStack addIssue(IssueDto issueDto) { | |||
issuesCache.put(issueDto.getComponentKey(), issueDto.getKee(), issueDto); | |||
return this; | |||
} | |||
public List<ServerIssue> selectAndRemoveIssues(String componentKey) { | |||
Iterable<IssueDto> issues = issuesCache.values(componentKey); | |||
List<ServerIssue> result = newArrayList(); | |||
for (IssueDto issue : issues) { | |||
result.add(new ServerIssueFromDb(issue)); | |||
} | |||
issuesCache.clear(componentKey); | |||
return result; | |||
} | |||
public Iterable<IssueDto> selectAllIssues() { | |||
return issuesCache.values(); | |||
} | |||
public InitialOpenIssuesStack addChangelog(IssueChangeDto issueChangeDto) { | |||
List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueChangeDto.getIssueKey()); | |||
if (changeDtos == null) { | |||
changeDtos = newArrayList(); | |||
} | |||
changeDtos.add(issueChangeDto); | |||
issuesChangelogCache.put(issueChangeDto.getIssueKey(), newArrayList(changeDtos)); | |||
return this; | |||
} | |||
public List<IssueChangeDto> selectChangelog(String issueKey) { | |||
List<IssueChangeDto> changeDtos = issuesChangelogCache.get(issueKey); | |||
return changeDtos != null ? changeDtos : Collections.<IssueChangeDto>emptyList(); | |||
} | |||
public void clear() { | |||
issuesCache.clear(); | |||
issuesChangelogCache.clear(); | |||
} | |||
} |
@@ -1,141 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.batch.issue.tracking; | |||
import org.sonar.api.batch.BatchSide; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.IssueHandler; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.api.user.User; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.core.user.DefaultUser; | |||
import javax.annotation.Nullable; | |||
@BatchSide | |||
public class IssueHandlers { | |||
private final IssueHandler[] handlers; | |||
private final DefaultContext context; | |||
public IssueHandlers(IssueUpdater updater, IssueHandler[] handlers) { | |||
this.handlers = handlers; | |||
this.context = new DefaultContext(updater); | |||
} | |||
public IssueHandlers(IssueUpdater updater) { | |||
this(updater, new IssueHandler[0]); | |||
} | |||
public void execute(DefaultIssue issue, IssueChangeContext changeContext) { | |||
context.reset(issue, changeContext); | |||
for (IssueHandler handler : handlers) { | |||
handler.onIssue(context); | |||
} | |||
} | |||
static class DefaultContext implements IssueHandler.Context { | |||
private final IssueUpdater updater; | |||
private DefaultIssue issue; | |||
private IssueChangeContext changeContext; | |||
private DefaultContext(IssueUpdater updater) { | |||
this.updater = updater; | |||
} | |||
private void reset(DefaultIssue i, IssueChangeContext changeContext) { | |||
this.issue = i; | |||
this.changeContext = changeContext; | |||
} | |||
@Override | |||
public Issue issue() { | |||
return issue; | |||
} | |||
@Override | |||
public boolean isNew() { | |||
return issue.isNew(); | |||
} | |||
@Override | |||
public boolean isEndOfLife() { | |||
return issue.isEndOfLife(); | |||
} | |||
@Override | |||
public IssueHandler.Context setLine(@Nullable Integer line) { | |||
updater.setLine(issue, line); | |||
return this; | |||
} | |||
@Override | |||
public IssueHandler.Context setMessage(@Nullable String s) { | |||
updater.setMessage(issue, s, changeContext); | |||
return this; | |||
} | |||
@Override | |||
public IssueHandler.Context setSeverity(String severity) { | |||
updater.setSeverity(issue, severity, changeContext); | |||
return this; | |||
} | |||
@Override | |||
public IssueHandler.Context setAuthorLogin(@Nullable String login) { | |||
updater.setAuthorLogin(issue, login, changeContext); | |||
return this; | |||
} | |||
@Override | |||
public IssueHandler.Context setEffortToFix(@Nullable Double d) { | |||
updater.setEffortToFix(issue, d, changeContext); | |||
return this; | |||
} | |||
@Override | |||
public IssueHandler.Context setAttribute(String key, @Nullable String value) { | |||
throw new UnsupportedOperationException("TODO"); | |||
} | |||
@Override | |||
public IssueHandler.Context assign(@Nullable String assignee) { | |||
User user = null; | |||
if (assignee != null) { | |||
user = new DefaultUser().setLogin(assignee).setName(assignee); | |||
} | |||
updater.assign(issue, user, changeContext); | |||
return this; | |||
} | |||
@Override | |||
public IssueHandler.Context assign(@Nullable User user) { | |||
updater.assign(issue, user, changeContext); | |||
return this; | |||
} | |||
@Override | |||
public IssueHandler.Context addComment(String text) { | |||
updater.addComment(issue, text, changeContext); | |||
return this; | |||
} | |||
} | |||
} |
@@ -29,7 +29,7 @@ import com.google.common.collect.Maps; | |||
import com.google.common.collect.Multimap; | |||
import org.sonar.api.batch.BatchSide; | |||
import org.sonar.api.batch.InstantiationStrategy; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import javax.annotation.Nullable; | |||
@@ -1,279 +0,0 @@ | |||
/* | |||
* SonarQube, open source software quality management tool. | |||
* Copyright (C) 2008-2014 SonarSource | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* SonarQube 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. | |||
* | |||
* SonarQube 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.batch.issue.tracking; | |||
import com.google.common.annotations.VisibleForTesting; | |||
import com.google.common.base.Strings; | |||
import com.google.common.collect.Lists; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.api.batch.Decorator; | |||
import org.sonar.api.batch.DecoratorBarriers; | |||
import org.sonar.api.batch.DecoratorContext; | |||
import org.sonar.api.batch.DependedUpon; | |||
import org.sonar.api.batch.DependsUpon; | |||
import org.sonar.api.batch.RequiresDB; | |||
import org.sonar.api.batch.fs.InputFile; | |||
import org.sonar.api.batch.fs.internal.DefaultInputFile; | |||
import org.sonar.api.component.ResourcePerspectives; | |||
import org.sonar.api.issue.Issuable; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.api.profiles.RulesProfile; | |||
import org.sonar.api.resources.File; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.resources.Resource; | |||
import org.sonar.api.resources.ResourceUtils; | |||
import org.sonar.api.rules.ActiveRule; | |||
import org.sonar.api.rules.Rule; | |||
import org.sonar.api.rules.RuleFinder; | |||
import org.sonar.api.utils.Duration; | |||
import org.sonar.api.utils.KeyValueFormat; | |||
import org.sonar.batch.issue.IssueCache; | |||
import org.sonar.batch.scan.filesystem.InputPathCache; | |||
import org.sonar.core.issue.IssueUpdater; | |||
import org.sonar.core.issue.db.IssueChangeDto; | |||
import org.sonar.core.issue.db.IssueDto; | |||
import org.sonar.core.issue.workflow.IssueWorkflow; | |||
import java.util.Collection; | |||
@DependsUpon(DecoratorBarriers.ISSUES_ADDED) | |||
@DependedUpon(DecoratorBarriers.ISSUES_TRACKED) | |||
@RequiresDB | |||
public class IssueTrackingDecorator implements Decorator { | |||
private static final Logger LOG = LoggerFactory.getLogger(IssueTrackingDecorator.class); | |||
private final IssueCache issueCache; | |||
private final InitialOpenIssuesStack initialOpenIssues; | |||
private final IssueTracking tracking; | |||
private final ServerLineHashesLoader lastLineHashes; | |||
private final IssueHandlers handlers; | |||
private final IssueWorkflow workflow; | |||
private final IssueUpdater updater; | |||
private final IssueChangeContext changeContext; | |||
private final ResourcePerspectives perspectives; | |||
private final RulesProfile rulesProfile; | |||
private final RuleFinder ruleFinder; | |||
private final InputPathCache inputPathCache; | |||
private final Project project; | |||
public IssueTrackingDecorator(IssueCache issueCache, InitialOpenIssuesStack initialOpenIssues, IssueTracking tracking, | |||
ServerLineHashesLoader lastLineHashes, | |||
IssueHandlers handlers, IssueWorkflow workflow, | |||
IssueUpdater updater, | |||
Project project, | |||
ResourcePerspectives perspectives, | |||
RulesProfile rulesProfile, | |||
RuleFinder ruleFinder, InputPathCache inputPathCache) { | |||
this.issueCache = issueCache; | |||
this.initialOpenIssues = initialOpenIssues; | |||
this.tracking = tracking; | |||
this.lastLineHashes = lastLineHashes; | |||
this.handlers = handlers; | |||
this.workflow = workflow; | |||
this.updater = updater; | |||
this.project = project; | |||
this.inputPathCache = inputPathCache; | |||
this.changeContext = IssueChangeContext.createScan(project.getAnalysisDate()); | |||
this.perspectives = perspectives; | |||
this.rulesProfile = rulesProfile; | |||
this.ruleFinder = ruleFinder; | |||
} | |||
@Override | |||
public boolean shouldExecuteOnProject(Project project) { | |||
return true; | |||
} | |||
@Override | |||
public void decorate(Resource resource, DecoratorContext context) { | |||
Issuable issuable = perspectives.as(Issuable.class, resource); | |||
if (issuable != null) { | |||
doDecorate(resource); | |||
} | |||
} | |||
@VisibleForTesting | |||
void doDecorate(Resource resource) { | |||
Collection<DefaultIssue> issues = Lists.newArrayList(); | |||
for (Issue issue : issueCache.byComponent(resource.getEffectiveKey())) { | |||
issues.add((DefaultIssue) issue); | |||
} | |||
issueCache.clear(resource.getEffectiveKey()); | |||
// issues = all the issues created by rule engines during this module scan and not excluded by filters | |||
// all the issues that are not closed in db before starting this module scan, including manual issues | |||
Collection<ServerIssue> dbOpenIssues = initialOpenIssues.selectAndRemoveIssues(resource.getEffectiveKey()); | |||
SourceHashHolder sourceHashHolder = null; | |||
if (ResourceUtils.isFile(resource)) { | |||
File sonarFile = (File) resource; | |||
InputFile file = inputPathCache.getFile(project.getEffectiveKey(), sonarFile.getPath()); | |||
if (file == null) { | |||
throw new IllegalStateException("File " + resource + " was not found in InputPath cache"); | |||
} | |||
sourceHashHolder = new SourceHashHolder((DefaultInputFile) file, lastLineHashes); | |||
} | |||
IssueTrackingResult trackingResult = tracking.track(sourceHashHolder, dbOpenIssues, issues); | |||
// unmatched = issues that have been resolved + issues on disabled/removed rules + manual issues | |||
addUnmatched(trackingResult.unmatched(), sourceHashHolder, issues); | |||
mergeMatched(trackingResult); | |||
if (ResourceUtils.isProject(resource)) { | |||
// issues that relate to deleted components | |||
addIssuesOnDeletedComponents(issues); | |||
} | |||
for (DefaultIssue issue : issues) { | |||
workflow.doAutomaticTransition(issue, changeContext); | |||
handlers.execute(issue, changeContext); | |||
issueCache.put(issue); | |||
} | |||
} | |||
@VisibleForTesting | |||
protected void mergeMatched(IssueTrackingResult result) { | |||
for (DefaultIssue issue : result.matched()) { | |||
IssueDto ref = ((ServerIssueFromDb) result.matching(issue)).getDto(); | |||
// invariant fields | |||
issue.setKey(ref.getKee()); | |||
issue.setCreationDate(ref.getIssueCreationDate()); | |||
issue.setUpdateDate(ref.getIssueUpdateDate()); | |||
issue.setCloseDate(ref.getIssueCloseDate()); | |||
// non-persisted fields | |||
issue.setNew(false); | |||
issue.setEndOfLife(false); | |||
issue.setOnDisabledRule(false); | |||
issue.setSelectedAt(ref.getSelectedAt()); | |||
// fields to update with old values | |||
issue.setActionPlanKey(ref.getActionPlanKey()); | |||
issue.setResolution(ref.getResolution()); | |||
issue.setStatus(ref.getStatus()); | |||
issue.setAssignee(ref.getAssignee()); | |||
issue.setAuthorLogin(ref.getAuthorLogin()); | |||
issue.setTags(ref.getTags()); | |||
if (ref.getIssueAttributes() != null) { | |||
issue.setAttributes(KeyValueFormat.parse(ref.getIssueAttributes())); | |||
} | |||
// populate existing changelog | |||
Collection<IssueChangeDto> issueChangeDtos = initialOpenIssues.selectChangelog(issue.key()); | |||
for (IssueChangeDto issueChangeDto : issueChangeDtos) { | |||
issue.addChange(issueChangeDto.toFieldDiffs()); | |||
} | |||
// fields to update with current values | |||
if (ref.isManualSeverity()) { | |||
issue.setManualSeverity(true); | |||
issue.setSeverity(ref.getSeverity()); | |||
} else { | |||
updater.setPastSeverity(issue, ref.getSeverity(), changeContext); | |||
} | |||
updater.setPastLine(issue, ref.getLine()); | |||
updater.setPastMessage(issue, ref.getMessage(), changeContext); | |||
updater.setPastEffortToFix(issue, ref.getEffortToFix(), changeContext); | |||
Long debtInMinutes = ref.getDebt(); | |||
Duration previousTechnicalDebt = debtInMinutes != null ? Duration.create(debtInMinutes) : null; | |||
updater.setPastTechnicalDebt(issue, previousTechnicalDebt, changeContext); | |||
updater.setPastProject(issue, ref.getProjectKey(), changeContext); | |||
} | |||
} | |||
private void addUnmatched(Collection<ServerIssue> unmatchedIssues, SourceHashHolder sourceHashHolder, Collection<DefaultIssue> issues) { | |||
for (ServerIssue unmatchedIssue : unmatchedIssues) { | |||
IssueDto unmatchedDto = ((ServerIssueFromDb) unmatchedIssue).getDto(); | |||
DefaultIssue unmatched = unmatchedDto.toDefaultIssue(); | |||
if (StringUtils.isNotBlank(unmatchedDto.getReporter()) && !Issue.STATUS_CLOSED.equals(unmatchedDto.getStatus())) { | |||
relocateManualIssue(unmatched, unmatchedDto, sourceHashHolder); | |||
} | |||
updateUnmatchedIssue(unmatched, false /* manual issues can be kept open */); | |||
issues.add(unmatched); | |||
} | |||
} | |||
private void addIssuesOnDeletedComponents(Collection<DefaultIssue> issues) { | |||
for (IssueDto deadDto : initialOpenIssues.selectAllIssues()) { | |||
DefaultIssue dead = deadDto.toDefaultIssue(); | |||
updateUnmatchedIssue(dead, true); | |||
issues.add(dead); | |||
} | |||
initialOpenIssues.clear(); | |||
} | |||
private void updateUnmatchedIssue(DefaultIssue issue, boolean forceEndOfLife) { | |||
issue.setNew(false); | |||
boolean manualIssue = !Strings.isNullOrEmpty(issue.reporter()); | |||
Rule rule = ruleFinder.findByKey(issue.ruleKey()); | |||
if (manualIssue) { | |||
// Manual rules are not declared in Quality profiles, so no need to check ActiveRule | |||
boolean isRemovedRule = rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus()); | |||
issue.setEndOfLife(forceEndOfLife || isRemovedRule); | |||
issue.setOnDisabledRule(isRemovedRule); | |||
} else { | |||
ActiveRule activeRule = rulesProfile.getActiveRule(issue.ruleKey().repository(), issue.ruleKey().rule()); | |||
issue.setEndOfLife(true); | |||
issue.setOnDisabledRule(activeRule == null || rule == null || Rule.STATUS_REMOVED.equals(rule.getStatus())); | |||
} | |||
} | |||
private void relocateManualIssue(DefaultIssue newIssue, IssueDto oldIssue, SourceHashHolder sourceHashHolder) { | |||
LOG.debug("Trying to relocate manual issue {}", oldIssue.getKee()); | |||
Integer previousLine = oldIssue.getLine(); | |||
if (previousLine == null) { | |||
LOG.debug("Cannot relocate issue at resource level"); | |||
return; | |||
} | |||
Collection<Integer> newLinesWithSameHash = sourceHashHolder.getNewLinesMatching(previousLine); | |||
LOG.debug("Found the following lines with same hash: {}", newLinesWithSameHash); | |||
if (newLinesWithSameHash.isEmpty()) { | |||
if (previousLine > sourceHashHolder.getHashedSource().length()) { | |||
LOG.debug("Old issue line {} is out of new source, closing and removing line number", previousLine); | |||
newIssue.setLine(null); | |||
updater.setStatus(newIssue, Issue.STATUS_CLOSED, changeContext); | |||
updater.setResolution(newIssue, Issue.RESOLUTION_REMOVED, changeContext); | |||
updater.setPastLine(newIssue, previousLine); | |||
updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext); | |||
updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext); | |||
} | |||
} else if (newLinesWithSameHash.size() == 1) { | |||
Integer newLine = newLinesWithSameHash.iterator().next(); | |||
LOG.debug("Relocating issue to line {}", newLine); | |||
newIssue.setLine(newLine); | |||
updater.setPastLine(newIssue, previousLine); | |||
updater.setPastMessage(newIssue, oldIssue.getMessage(), changeContext); | |||
updater.setPastEffortToFix(newIssue, oldIssue.getEffortToFix(), changeContext); | |||
} | |||
} | |||
} |
@@ -23,7 +23,7 @@ import com.google.common.collect.HashMultimap; | |||
import com.google.common.collect.Maps; | |||
import com.google.common.collect.Multimap; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.api.rule.RuleKey; | |||
import javax.annotation.Nullable; |
@@ -29,8 +29,8 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile; | |||
import org.sonar.api.batch.rule.ActiveRule; | |||
import org.sonar.api.batch.rule.ActiveRules; | |||
import org.sonar.api.issue.Issue; | |||
import org.sonar.api.issue.internal.DefaultIssue; | |||
import org.sonar.api.issue.internal.IssueChangeContext; | |||
import org.sonar.core.issue.DefaultIssue; | |||
import org.sonar.core.issue.IssueChangeContext; | |||
import org.sonar.api.resources.Project; | |||
import org.sonar.api.resources.ResourceUtils; | |||
import org.sonar.api.rule.RuleKey; | |||
@@ -166,7 +166,7 @@ public class LocalIssueTracking { | |||
// non-persisted fields | |||
issue.setNew(false); | |||
issue.setEndOfLife(false); | |||
issue.setBeingClosed(false); | |||
issue.setOnDisabledRule(false); | |||
// fields to update with old values | |||
@@ -226,9 +226,9 @@ public class LocalIssueTracking { | |||
boolean manualIssue = issue.ruleKey().isManual(); | |||
boolean isRemovedRule = activeRule == null; | |||
if (manualIssue) { | |||
issue.setEndOfLife(forceEndOfLife || isRemovedRule); | |||
issue.setBeingClosed(forceEndOfLife || isRemovedRule); | |||
} else { | |||
issue.setEndOfLife(true); | |||
issue.setBeingClosed(true); | |||
} | |||
issue.setOnDisabledRule(isRemovedRule); | |||
} |