aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2018-12-10 19:22:44 +0100
committersonartech <sonartech@sonarsource.com>2019-01-16 09:43:08 +0100
commit6067a4689902828f7331e2f0a45db02e654a1a06 (patch)
treecf2813a9bf002fff0a9523734af4c30fe0e0e269
parenteae7151086efcdbcd91945e7cbe28e1a76e186ef (diff)
downloadsonarqube-6067a4689902828f7331e2f0a45db02e654a1a06.tar.gz
sonarqube-6067a4689902828f7331e2f0a45db02e654a1a06.zip
SONAR-11459 Migrate module and folder issues to the root
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java1
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryWithMigration.java3
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImpl.java6
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java1
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java4
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInput.java119
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactory.java42
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImplTest.java5
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java4
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java17
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputTest.java164
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactoryTest.java20
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java2
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml7
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/SettingsLoader.java3
17 files changed, 393 insertions, 13 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java
index 76545a33367..1992fabf8f8 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java
@@ -20,6 +20,7 @@
package org.sonar.ce.task.projectanalysis.component;
public interface ComponentUuidFactory {
+
/**
* Get UUID from database if it exists, otherwise generate a new one.
*/
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryWithMigration.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryWithMigration.java
index 732f278d20a..5ffc8b1c360 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryWithMigration.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryWithMigration.java
@@ -30,7 +30,6 @@ import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
-import org.sonar.core.component.ComponentKeys;
import org.sonar.core.util.Uuids;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
@@ -105,7 +104,7 @@ public class ComponentUuidFactoryWithMigration implements ComponentUuidFactory {
private static Map<String, String> loadModulePathsByUuid(DbClient dbClient, DbSession dbSession, String rootKey, Map<String, String> pathByModuleKey) {
List<ComponentDto> moduleDtos = dbClient.componentDao()
- .selectModulesFromProjectKey(dbSession, rootKey, false).stream()
+ .selectProjectAndModulesFromProjectKey(dbSession, rootKey, false).stream()
.filter(c -> Qualifiers.MODULE.equals(c.qualifier()))
.collect(Collectors.toList());
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImpl.java
index 6426ce28fb4..b137dcf409f 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImpl.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImpl.java
@@ -19,11 +19,11 @@
*/
package org.sonar.ce.task.projectanalysis.issue;
+import java.util.Collections;
import java.util.List;
import javax.annotation.CheckForNull;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.core.issue.DefaultIssue;
-import org.sonar.ce.task.projectanalysis.component.Component;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
@@ -45,6 +45,10 @@ public class ComponentIssuesRepositoryImpl implements MutableComponentIssuesRepo
@Override
public List<DefaultIssue> getIssues(Component component) {
+ if (component.getType() == Component.Type.DIRECTORY) {
+ // No issues on directories
+ return Collections.emptyList();
+ }
checkState(this.component != null && this.issues != null, "Issues have not been initialized");
checkArgument(component.equals(this.component),
"Only issues from component '%s' are available, but wanted component is '%s'.",
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java
index 213b747ac96..bf04ad5d4a0 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitor.java
@@ -61,7 +61,6 @@ public class IntegrateIssuesVisitor extends TypeAwareVisitorAdapter {
public void visitAny(Component component) {
try (DiskCache<DefaultIssue>.DiskAppender cacheAppender = issueCache.newAppender()) {
issueVisitors.beforeComponent(component);
-
TrackingResult tracking = issueTracking.track(component);
fillNewOpenIssues(component, tracking.newIssues(), cacheAppender);
fillExistingOpenIssues(component, tracking.issuesToMerge(), cacheAppender);
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
index b4d8c344fa2..e8f046e8f06 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycle.java
@@ -147,6 +147,10 @@ public class IssueLifecycle {
Preconditions.checkArgument(raw.isFromExternalRuleEngine() != (raw.type() == null), "At this stage issue type should be set for and only for external issues");
raw.setKey(base.key());
raw.setNew(false);
+ if (base.isChanged()) {
+ // In case issue was moved from module or folder to the root project
+ raw.setChanged(true);
+ }
setType(raw);
copyFields(raw, base);
base.changes().forEach(raw::addChange);
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInput.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInput.java
new file mode 100644
index 00000000000..958c6ad3864
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInput.java
@@ -0,0 +1,119 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.ce.task.projectanalysis.issue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReportModulesPath;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.issue.IssueFieldsSetter;
+
+import static org.apache.commons.lang.StringUtils.trimToEmpty;
+
+class ProjectTrackerBaseLazyInput extends BaseInputFactory.BaseLazyInput {
+
+ private AnalysisMetadataHolder analysisMetadataHolder;
+ private ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues;
+ private DbClient dbClient;
+ private IssueFieldsSetter issueUpdater;
+ private ComponentIssuesLoader issuesLoader;
+ private ReportModulesPath reportModulesPath;
+
+ ProjectTrackerBaseLazyInput(AnalysisMetadataHolder analysisMetadataHolder, ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues, DbClient dbClient,
+ IssueFieldsSetter issueUpdater, ComponentIssuesLoader issuesLoader, ReportModulesPath reportModulesPath, Component component) {
+ super(dbClient, component, null);
+ this.analysisMetadataHolder = analysisMetadataHolder;
+ this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues;
+ this.dbClient = dbClient;
+ this.issueUpdater = issueUpdater;
+ this.issuesLoader = issuesLoader;
+ this.reportModulesPath = reportModulesPath;
+ }
+
+ @Override
+ protected List<DefaultIssue> loadIssues() {
+ List<DefaultIssue> result = new ArrayList<>();
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ Set<String> dirOrModulesUuidsWithIssues = dbClient.issueDao().selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(dbSession, component.getUuid());
+ if (!dirOrModulesUuidsWithIssues.isEmpty()) {
+ Map<String, String> pathByModuleKey = reportModulesPath.get();
+ // Migrate issues that were previously on modules or directories to the root project
+ Map<String, ComponentDto> modulesByUuid = dbClient.componentDao().selectProjectAndModulesFromProjectKey(dbSession, component.getDbKey(), true)
+ .stream().collect(Collectors.toMap(ComponentDto::uuid, Function.identity()));
+ List<ComponentDto> dirOrModulesWithIssues = dbClient.componentDao().selectByUuids(dbSession, dirOrModulesUuidsWithIssues);
+ dirOrModulesWithIssues.forEach(c -> {
+ List<DefaultIssue> issuesOnModuleOrDir = issuesLoader.loadOpenIssues(c.uuid());
+ String moduleOrDirProjectRelativePath = c.qualifier().equals(Qualifiers.MODULE) ? buildModuleProjectRelativePath(pathByModuleKey, c)
+ : buildDirectoryProjectRelativePath(pathByModuleKey, c, modulesByUuid.get(c.moduleUuid()));
+ result.addAll(migrateIssuesToTheRoot(issuesOnModuleOrDir, moduleOrDirProjectRelativePath));
+ componentsWithUnprocessedIssues.remove(c.uuid());
+ });
+ }
+ result.addAll(issuesLoader.loadOpenIssues(effectiveUuid));
+ return result;
+ }
+ }
+
+ private static String buildDirectoryProjectRelativePath(Map<String, String> pathByModuleKey, ComponentDto c, ComponentDto parentModule) {
+ String moduleProjectRelativePath = buildModuleProjectRelativePath(pathByModuleKey, parentModule);
+ return Stream.of(moduleProjectRelativePath, c.path())
+ .map(StringUtils::trimToNull)
+ .filter(s -> s != null && !"/".equals(s))
+ .collect(Collectors.joining("/"));
+ }
+
+ private static String buildModuleProjectRelativePath(Map<String, String> pathByModuleKey, ComponentDto parentModule) {
+ return Optional.ofNullable(pathByModuleKey.get(parentModule.getKey()))
+ // If module is not in the scanner report, we can't guess the path accurately. Fallback on what we have in DB
+ .orElse(trimToEmpty(parentModule.path()));
+ }
+
+ private Collection<? extends DefaultIssue> migrateIssuesToTheRoot(List<DefaultIssue> issuesOnModule, String modulePath) {
+ for (DefaultIssue i : issuesOnModule) {
+ // changes the issue's component uuid, add a change and set issue as changed to enforce it is persisted to DB
+ IssueChangeContext context = IssueChangeContext.createUser(new Date(analysisMetadataHolder.getAnalysisDate()), null);
+ if (StringUtils.isNotBlank(modulePath)) {
+ issueUpdater.setMessage(i, "[" + modulePath + "] " + i.getMessage(), context);
+ }
+ issueUpdater.setIssueMoved(i, component.getUuid(), context);
+ // other fields (such as module, modulePath, componentKey) are read-only and set/reset for consistency only
+ i.setComponentKey(component.getKey());
+ i.setModuleUuid(null);
+ i.setModuleUuidPath(null);
+ }
+ return issuesOnModule;
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactory.java
index 757711cc3bb..844854ff0e4 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactory.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactory.java
@@ -19,14 +19,18 @@
*/
package org.sonar.ce.task.projectanalysis.issue;
+import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder;
import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReportModulesPath;
import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository;
import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository.OriginalFile;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Input;
import org.sonar.db.DbClient;
+import org.sonar.server.issue.IssueFieldsSetter;
/**
* Factory of {@link Input} of base data for issue tracking. Data are lazy-loaded.
@@ -36,20 +40,35 @@ public class TrackerBaseInputFactory extends BaseInputFactory {
private final ComponentIssuesLoader issuesLoader;
private final DbClient dbClient;
private final MovedFilesRepository movedFilesRepository;
+ private final ReportModulesPath reportModulesPath;
+ private final AnalysisMetadataHolder analysisMetadataHolder;
+ private final IssueFieldsSetter issueUpdater;
+ private final ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues;
- public TrackerBaseInputFactory(ComponentIssuesLoader issuesLoader, DbClient dbClient, MovedFilesRepository movedFilesRepository) {
+ public TrackerBaseInputFactory(ComponentIssuesLoader issuesLoader, DbClient dbClient, MovedFilesRepository movedFilesRepository, ReportModulesPath reportModulesPath,
+ AnalysisMetadataHolder analysisMetadataHolder, IssueFieldsSetter issueUpdater, ComponentsWithUnprocessedIssues componentsWithUnprocessedIssues) {
this.issuesLoader = issuesLoader;
this.dbClient = dbClient;
this.movedFilesRepository = movedFilesRepository;
+ this.reportModulesPath = reportModulesPath;
+ this.analysisMetadataHolder = analysisMetadataHolder;
+ this.issueUpdater = issueUpdater;
+ this.componentsWithUnprocessedIssues = componentsWithUnprocessedIssues;
}
public Input<DefaultIssue> create(Component component) {
- return new TrackerBaseLazyInput(dbClient, component, movedFilesRepository.getOriginalFile(component).orNull());
+ if (component.getType() == Component.Type.PROJECT) {
+ return new ProjectTrackerBaseLazyInput(analysisMetadataHolder, componentsWithUnprocessedIssues, dbClient, issueUpdater, issuesLoader, reportModulesPath, component);
+ } else if (component.getType() == Component.Type.DIRECTORY) {
+ // Folders have no issues
+ return new EmptyTrackerBaseLazyInput(dbClient, component);
+ }
+ return new FileTrackerBaseLazyInput(dbClient, component, movedFilesRepository.getOriginalFile(component).orNull());
}
- private class TrackerBaseLazyInput extends BaseLazyInput {
+ private class FileTrackerBaseLazyInput extends BaseLazyInput {
- private TrackerBaseLazyInput(DbClient dbClient, Component component, @Nullable OriginalFile originalFile) {
+ private FileTrackerBaseLazyInput(DbClient dbClient, Component component, @Nullable OriginalFile originalFile) {
super(dbClient, component, originalFile);
}
@@ -57,5 +76,20 @@ public class TrackerBaseInputFactory extends BaseInputFactory {
protected List<DefaultIssue> loadIssues() {
return issuesLoader.loadOpenIssues(effectiveUuid);
}
+
+ }
+
+ private class EmptyTrackerBaseLazyInput extends BaseLazyInput {
+
+ private EmptyTrackerBaseLazyInput(DbClient dbClient, Component component) {
+ super(dbClient, component, null);
+ }
+
+ @Override
+ protected List<DefaultIssue> loadIssues() {
+ return Collections.emptyList();
+ }
+
}
+
}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImplTest.java
index c6d3083a1f4..85437b81742 100644
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImplTest.java
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesRepositoryImplTest.java
@@ -49,6 +49,11 @@ public class ComponentIssuesRepositoryImplTest {
}
@Test
+ public void no_issues_on_dir() {
+ assertThat(sut.getIssues(builder(Component.Type.DIRECTORY, 1).build())).isEmpty();
+ }
+
+ @Test
public void set_empty_issues() {
sut.setIssues(FILE_1, Collections.emptyList());
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
index deda0b2abaa..a1600f1a4c8 100644
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java
@@ -38,6 +38,7 @@ import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.MergeBranchComponentUuids;
import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.ReportModulesPath;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitor;
import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository;
@@ -61,6 +62,7 @@ import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleTesting;
import org.sonar.scanner.protocol.Constants;
import org.sonar.scanner.protocol.output.ScannerReport;
+import org.sonar.server.issue.IssueFieldsSetter;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Arrays.asList;
@@ -141,7 +143,7 @@ public class IntegrateIssuesVisitorTest {
DbClient dbClient = dbTester.getDbClient();
TrackerRawInputFactory rawInputFactory = new TrackerRawInputFactory(treeRootHolder, reportReader, sourceLinesHash, new CommonRuleEngineImpl(),
issueFilter, ruleRepositoryRule, activeRulesHolder, issueRelocationToRoot);
- TrackerBaseInputFactory baseInputFactory = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository);
+ TrackerBaseInputFactory baseInputFactory = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository, mock(ReportModulesPath.class), analysisMetadataHolder, new IssueFieldsSetter(), mock(ComponentsWithUnprocessedIssues.class));
TrackerMergeBranchInputFactory mergeInputFactory = new TrackerMergeBranchInputFactory(issuesLoader, mergeBranchComponentsUuids, dbClient);
ClosedIssuesInputFactory closedIssuesInputFactory = new ClosedIssuesInputFactory(issuesLoader, dbClient, movedFilesRepository);
tracker = new TrackerExecution(baseInputFactory, rawInputFactory, closedIssuesInputFactory, new Tracker<>(), issuesLoader, analysisMetadataHolder);
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
index 2436989451c..12a3d68b91a 100644
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueLifecycleTest.java
@@ -395,6 +395,23 @@ public class IssueLifecycleTest {
}
@Test
+ public void mergeExistingOpenIssue_with_base_changed() {
+ DefaultIssue raw = new DefaultIssue()
+ .setNew(true)
+ .setKey("RAW_KEY")
+ .setRuleKey(XOO_X1);
+ DefaultIssue base = new DefaultIssue()
+ .setChanged(true)
+ .setKey("BASE_KEY")
+ .setResolution(RESOLUTION_FALSE_POSITIVE)
+ .setStatus(STATUS_RESOLVED);
+
+ underTest.mergeExistingOpenIssue(raw, base);
+
+ assertThat(raw.isChanged()).isTrue();
+ }
+
+ @Test
public void mergeExistingOpenIssue_with_attributes() {
DefaultIssue raw = new DefaultIssue()
.setNew(true)
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputTest.java
new file mode 100644
index 00000000000..9980407c94d
--- /dev/null
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ProjectTrackerBaseLazyInputTest.java
@@ -0,0 +1,164 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.ce.task.projectanalysis.issue;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.Date;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
+import org.sonar.ce.task.projectanalysis.component.Component;
+import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.ReportModulesPath;
+import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderRule;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueDto;
+import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.server.issue.IssueFieldsSetter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.utils.DateUtils.parseDate;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newModuleDto;
+
+public class ProjectTrackerBaseLazyInputTest {
+
+ private static final Date ANALYSIS_DATE = parseDate("2016-06-01");
+
+ @Rule
+ public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule().setAnalysisDate(ANALYSIS_DATE);
+ @Rule
+ public DbTester dbTester = DbTester.create(System2.INSTANCE);
+ @Rule
+ public ActiveRulesHolderRule activeRulesHolderRule = new ActiveRulesHolderRule();
+ @Rule
+ public RuleRepositoryRule ruleRepositoryRule = new RuleRepositoryRule();
+
+ private DbClient dbClient = dbTester.getDbClient();
+ private ProjectTrackerBaseLazyInput underTest;
+ private RuleDefinitionDto rule;
+ private ComponentDto rootProjectDto;
+ private ComponentIssuesLoader issuesLoader = new ComponentIssuesLoader(dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule, new MapSettings().asConfig(),
+ System2.INSTANCE);
+ private ReportModulesPath reportModulesPath;
+
+ @Before
+ public void prepare() {
+ rule = dbTester.rules().insert();
+ ruleRepositoryRule.add(rule.getKey());
+ rootProjectDto = dbTester.components().insertMainBranch();
+ ReportComponent rootProject = ReportComponent.builder(Component.Type.FILE, 1)
+ .setKey(rootProjectDto.getDbKey())
+ .setUuid(rootProjectDto.uuid()).build();
+ reportModulesPath = mock(ReportModulesPath.class);
+ underTest = new ProjectTrackerBaseLazyInput(analysisMetadataHolder, mock(ComponentsWithUnprocessedIssues.class), dbClient, new IssueFieldsSetter(), issuesLoader,
+ reportModulesPath, rootProject);
+ }
+
+ @Test
+ public void return_only_open_project_issues_if_no_modules_and_folders() {
+ ComponentDto file = dbTester.components().insertComponent(newFileDto(rootProjectDto));
+ IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null));
+ IssueDto closedIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("CLOSED").setResolution("FIXED"));
+ IssueDto openIssue1OnFile = dbTester.issues().insert(rule, rootProjectDto, file, i -> i.setStatus("OPEN").setResolution(null));
+
+ assertThat(underTest.loadIssues()).extracting(DefaultIssue::key).containsOnly(openIssueOnProject.getKey());
+ }
+
+ @Test
+ public void migrate_and_return_folder_issues_on_root_project() {
+ when(reportModulesPath.get()).thenReturn(Collections.emptyMap());
+ ComponentDto folder = dbTester.components().insertComponent(newDirectory(rootProjectDto, "src"));
+ ComponentDto file = dbTester.components().insertComponent(newFileDto(rootProjectDto));
+ IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null));
+ IssueDto openIssueOnDir = dbTester.issues().insert(rule, rootProjectDto, folder, i -> i.setStatus("OPEN").setMessage("Issue on dir").setResolution(null));
+ IssueDto openIssue1OnFile = dbTester.issues().insert(rule, rootProjectDto, file, i -> i.setStatus("OPEN").setResolution(null));
+
+ assertThat(underTest.loadIssues()).extracting(DefaultIssue::key, DefaultIssue::getMessage)
+ .containsExactlyInAnyOrder(
+ tuple(openIssueOnProject.getKey(), openIssueOnProject.getMessage()),
+ tuple(openIssueOnDir.getKey(), "[src] Issue on dir"));
+
+ }
+
+ @Test
+ public void migrate_and_return_module_and_folder_issues_on_module() {
+ ComponentDto module = dbTester.components().insertComponent(newModuleDto(rootProjectDto).setPath("moduleAInDb"));
+ when(reportModulesPath.get()).thenReturn(ImmutableMap.of(module.getDbKey(), "moduleAInReport"));
+ ComponentDto folder = dbTester.components().insertComponent(newDirectory(module, "src"));
+ ComponentDto file = dbTester.components().insertComponent(newFileDto(module));
+ IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null));
+ IssueDto openIssueOnModule = dbTester.issues().insert(rule, rootProjectDto, module, i -> i.setStatus("OPEN").setMessage("Issue on module").setResolution(null));
+ IssueDto openIssueOnDir = dbTester.issues().insert(rule, rootProjectDto, folder, i -> i.setStatus("OPEN").setMessage("Issue on dir").setResolution(null));
+ IssueDto openIssue1OnFile = dbTester.issues().insert(rule, rootProjectDto, file, i -> i.setStatus("OPEN").setResolution(null));
+
+ assertThat(underTest.loadIssues()).extracting(DefaultIssue::key, DefaultIssue::getMessage)
+ .containsExactlyInAnyOrder(
+ tuple(openIssueOnProject.getKey(), openIssueOnProject.getMessage()),
+ tuple(openIssueOnModule.getKey(), "[moduleAInReport] Issue on module"),
+ tuple(openIssueOnDir.getKey(), "[moduleAInReport/src] Issue on dir"));
+
+ }
+
+ @Test
+ public void use_db_path_if_module_missing_in_report() {
+ ComponentDto module = dbTester.components().insertComponent(newModuleDto(rootProjectDto).setPath("moduleAInDb"));
+ when(reportModulesPath.get()).thenReturn(Collections.emptyMap());
+ ComponentDto folder = dbTester.components().insertComponent(newDirectory(module, "src"));
+ IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null));
+ IssueDto openIssueOnModule = dbTester.issues().insert(rule, rootProjectDto, module, i -> i.setStatus("OPEN").setMessage("Issue on module").setResolution(null));
+ IssueDto openIssueOnDir = dbTester.issues().insert(rule, rootProjectDto, folder, i -> i.setStatus("OPEN").setMessage("Issue on dir").setResolution(null));
+
+ assertThat(underTest.loadIssues()).extracting(DefaultIssue::key, DefaultIssue::getMessage)
+ .containsExactlyInAnyOrder(
+ tuple(openIssueOnProject.getKey(), openIssueOnProject.getMessage()),
+ tuple(openIssueOnModule.getKey(), "[moduleAInDb] Issue on module"),
+ tuple(openIssueOnDir.getKey(), "[moduleAInDb/src] Issue on dir"));
+
+ }
+
+ @Test
+ public void empty_path_if_module_missing_in_report_and_db_and_for_slash_folder () {
+ ComponentDto module = dbTester.components().insertComponent(newModuleDto(rootProjectDto).setPath(null));
+ when(reportModulesPath.get()).thenReturn(Collections.emptyMap());
+ ComponentDto folder = dbTester.components().insertComponent(newDirectory(module, "/"));
+ IssueDto openIssueOnProject = dbTester.issues().insert(rule, rootProjectDto, rootProjectDto, i -> i.setStatus("OPEN").setResolution(null));
+ IssueDto openIssueOnModule = dbTester.issues().insert(rule, rootProjectDto, module, i -> i.setStatus("OPEN").setMessage("Issue on module").setResolution(null));
+ IssueDto openIssueOnDir = dbTester.issues().insert(rule, rootProjectDto, folder, i -> i.setStatus("OPEN").setMessage("Issue on dir").setResolution(null));
+
+ assertThat(underTest.loadIssues()).extracting(DefaultIssue::key, DefaultIssue::getMessage)
+ .containsExactlyInAnyOrder(
+ tuple(openIssueOnProject.getKey(), openIssueOnProject.getMessage()),
+ tuple(openIssueOnModule.getKey(), "Issue on module"),
+ tuple(openIssueOnDir.getKey(), "Issue on dir"));
+
+ }
+}
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactoryTest.java
index 556dc5088d0..f9562921b33 100644
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactoryTest.java
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerBaseInputFactoryTest.java
@@ -22,13 +22,19 @@ package org.sonar.ce.task.projectanalysis.issue;
import com.google.common.base.Optional;
import org.junit.Before;
import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.ReportComponent;
+import org.sonar.ce.task.projectanalysis.component.ReportModulesPath;
import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.tracking.Input;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.source.FileSourceDao;
+import org.sonar.server.issue.IssueFieldsSetter;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
@@ -37,7 +43,12 @@ import static org.mockito.Mockito.when;
public class TrackerBaseInputFactoryTest {
private static final String FILE_UUID = "uuid";
+ private static final String DIR_UUID = "dir";
private static final ReportComponent FILE = ReportComponent.builder(Component.Type.FILE, 1).setUuid(FILE_UUID).build();
+ private static final ReportComponent FOLDER = ReportComponent.builder(Component.Type.DIRECTORY, 2).setUuid(DIR_UUID).build();
+
+ @org.junit.Rule
+ public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule();
private ComponentIssuesLoader issuesLoader = mock(ComponentIssuesLoader.class);
private DbClient dbClient = mock(DbClient.class);
@@ -46,7 +57,8 @@ public class TrackerBaseInputFactoryTest {
private MovedFilesRepository movedFilesRepository = mock(MovedFilesRepository.class);
- private TrackerBaseInputFactory underTest = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository);
+ private TrackerBaseInputFactory underTest = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository, mock(ReportModulesPath.class), analysisMetadataHolder,
+ new IssueFieldsSetter(), mock(ComponentsWithUnprocessedIssues.class));
@Before
public void setUp() throws Exception {
@@ -64,6 +76,12 @@ public class TrackerBaseInputFactoryTest {
}
@Test
+ public void create_returns_empty_Input_for_folders() {
+ Input<DefaultIssue> input = underTest.create(FOLDER);
+ assertThat(input.getIssues()).isEmpty();
+ }
+
+ @Test
public void create_returns_Input_which_retrieves_lines_hashes_of_original_file_of_component_when_it_has_one() {
String originalUuid = "original uuid";
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
index fe2f3941ab3..aed5fc1138a 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
@@ -183,7 +183,7 @@ public class ComponentDao implements Dao {
return mapper(session).selectUuidsByKeyFromProjectKey(projectKey);
}
- public List<ComponentDto> selectModulesFromProjectKey(DbSession session, String projectKey, boolean excludeDisabled) {
+ public List<ComponentDto> selectProjectAndModulesFromProjectKey(DbSession session, String projectKey, boolean excludeDisabled) {
return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, Scopes.PROJECT, excludeDisabled);
}
@@ -192,7 +192,7 @@ public class ComponentDao implements Dao {
}
public List<ComponentDto> selectEnabledModulesFromProjectKey(DbSession session, String projectKey) {
- return selectModulesFromProjectKey(session, projectKey, true);
+ return selectProjectAndModulesFromProjectKey(session, projectKey, true);
}
public List<ComponentDto> selectByKeys(DbSession session, Collection<String> keys) {
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
index 4d2e46185e9..60da3909145 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
@@ -61,6 +61,10 @@ public class IssueDao implements Dao {
return mapper(session).selectComponentUuidsOfOpenIssuesForProjectUuid(projectUuid);
}
+ public Set<String> selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(DbSession session, String projectUuid) {
+ return mapper(session).selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(projectUuid);
+ }
+
public List<IssueDto> selectNonClosedByComponentUuidExcludingExternalsAndSecurityHotspots(DbSession dbSession, String componentUuid) {
return mapper(dbSession).selectNonClosedByComponentUuidExcludingExternals(componentUuid);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
index df441a1b778..e399267440d 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
@@ -32,6 +32,8 @@ public interface IssueMapper {
Set<String> selectComponentUuidsOfOpenIssuesForProjectUuid(String projectUuid);
+ Set<String> selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid(String projectUuid);
+
List<IssueDto> selectByKeys(List<String> keys);
List<IssueDto> selectByKeysIfNotUpdatedAt(@Param("keys") List<String> keys, @Param("updatedAt") long updatedAt);
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
index 9592c6831ce..cf9958a1e50 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
@@ -242,6 +242,13 @@
i.kee, ic.issue_change_creation_date desc
</select>
+ <select id="selectModuleAndDirComponentUuidsOfOpenIssuesForProjectUuid" parameterType="string" resultType="string">
+ select distinct(i.component_uuid)
+ from issues i
+ inner join projects p on p.uuid=i.component_uuid
+ where i.project_uuid=#{projectUuid,jdbcType=VARCHAR} and i.status &lt;&gt; 'CLOSED' and (p.qualifier = 'DIR' OR p.qualifier = 'BRC')
+ </select>
+
<select id="selectComponentUuidsOfOpenIssuesForProjectUuid" parameterType="string" resultType="string">
select distinct(i.component_uuid)
from issues i
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/SettingsLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/SettingsLoader.java
index c9ac75f8e75..adcd48e6003 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/SettingsLoader.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/repository/settings/SettingsLoader.java
@@ -20,8 +20,9 @@
package org.sonar.scanner.repository.settings;
import java.util.Map;
+import javax.annotation.Nullable;
@FunctionalInterface
public interface SettingsLoader {
- Map<String, String> load(String componentKey);
+ Map<String, String> load(@Nullable String componentKey);
}