]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11463 Relocate issues from modules/dirs to root project
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Mon, 19 Nov 2018 22:27:46 +0000 (16:27 -0600)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Jan 2019 08:43:01 +0000 (09:43 +0100)
18 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilder.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/duplication/DuplicationMeasures.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRoot.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStep.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilderTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRootTest.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/ValidateProjectStepTest.java
server/sonar-db-dao/src/main/java/org/sonar/db/MyBatis.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDao.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentMapper.java
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentWithModuleUuidDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java

index 68080a5fa07e861d34f5e9e295c71d527bf064df..91479aee677349153e640e2f5c9ef61ad44a3b7b 100644 (file)
@@ -31,6 +31,7 @@ import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import org.sonar.api.internal.apachecommons.lang.StringUtils;
 import org.sonar.ce.task.projectanalysis.analysis.Branch;
+import org.sonar.ce.task.projectanalysis.issue.IssueRelocationToRoot;
 import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.component.SnapshotDto;
 import org.sonar.scanner.protocol.output.ScannerReport;
@@ -67,6 +68,7 @@ public class ComponentTreeBuilder {
   private final Branch branch;
   @Nullable
   private final SnapshotDto baseAnalysis;
+  private final IssueRelocationToRoot issueRelocationToRoot;
 
   private ScannerReport.Component rootComponent;
   private String scmBasePath;
@@ -77,7 +79,7 @@ public class ComponentTreeBuilder {
     Function<String, String> uuidSupplier,
     Function<Integer, ScannerReport.Component> scannerComponentSupplier,
     Project project,
-    Branch branch, @Nullable SnapshotDto baseAnalysis) {
+    Branch branch, @Nullable SnapshotDto baseAnalysis, IssueRelocationToRoot issueRelocationToRoot) {
 
     this.keyGenerator = keyGenerator;
     this.publicKeyGenerator = publicKeyGenerator;
@@ -86,17 +88,18 @@ public class ComponentTreeBuilder {
     this.project = project;
     this.branch = branch;
     this.baseAnalysis = baseAnalysis;
+    this.issueRelocationToRoot = issueRelocationToRoot;
   }
 
   public Component buildProject(ScannerReport.Component project, String scmBasePath) {
     this.rootComponent = project;
     this.scmBasePath = trimToNull(scmBasePath);
 
-    Node root = buildFolderHierarchy(project);
-    return buildNode(root, "");
+    Node root = createProjectHierarchy(project);
+    return buildComponent(root, "");
   }
 
-  private Node buildFolderHierarchy(ScannerReport.Component rootComponent) {
+  private Node createProjectHierarchy(ScannerReport.Component rootComponent) {
     Preconditions.checkArgument(rootComponent.getType() == ScannerReport.Component.ComponentType.PROJECT, "Expected root component of type 'PROJECT'");
 
     LinkedList<ScannerReport.Component> queue = new LinkedList<>();
@@ -112,16 +115,11 @@ public class ComponentTreeBuilder {
       ScannerReport.Component component = queue.removeFirst();
       switch (component.getType()) {
         case FILE:
-          addFileOrDirectory(root, component);
+          addFile(root, component);
           break;
         case MODULE:
-
-          component.getChildRefList().stream()
-            .map(scannerComponentSupplier)
-            .forEach(queue::addLast);
-          break;
         case DIRECTORY:
-          addFileOrDirectory(root, component);
+          issueRelocationToRoot.relocate(rootComponent, component);
           component.getChildRefList().stream()
             .map(scannerComponentSupplier)
             .forEach(queue::addLast);
@@ -133,9 +131,8 @@ public class ComponentTreeBuilder {
     return root;
   }
 
-  private static void addFileOrDirectory(Node root, ScannerReport.Component file) {
-    Preconditions.checkArgument(file.getType() != ScannerReport.Component.ComponentType.FILE || !StringUtils.isEmpty(file.getProjectRelativePath()),
-      "Files should have a relative path: " + file);
+  private static void addFile(Node root, ScannerReport.Component file) {
+    Preconditions.checkArgument(!StringUtils.isEmpty(file.getProjectRelativePath()), "Files should have a project relative path: " + file);
     String[] split = StringUtils.split(file.getProjectRelativePath(), '/');
     Node currentNode = root.children().computeIfAbsent("", k -> new Node());
 
@@ -145,7 +142,7 @@ public class ComponentTreeBuilder {
     currentNode.reportComponent = file;
   }
 
-  private Component buildNode(Node node, String currentPath) {
+  private Component buildComponent(Node node, String currentPath) {
     List<Component> childComponents = buildChildren(node, currentPath);
     ScannerReport.Component component = node.reportComponent();
 
@@ -157,7 +154,7 @@ public class ComponentTreeBuilder {
       }
     }
 
-    return buildDirectory(currentPath, component, childComponents);
+    return buildDirectory(currentPath, childComponents);
   }
 
   private List<Component> buildChildren(Node node, String currentPath) {
@@ -165,14 +162,15 @@ public class ComponentTreeBuilder {
 
     for (Map.Entry<String, Node> e : node.children().entrySet()) {
       String path = buildPath(currentPath, e.getKey());
-      Node n = e.getValue();
+      Node childNode = e.getValue();
 
-      while (n.children().size() == 1 && n.children().values().iterator().next().children().size() > 0) {
-        Map.Entry<String, Node> childEntry = n.children().entrySet().iterator().next();
+      // collapse folders that only contain one folder
+      while (childNode.children().size() == 1 && childNode.children().values().iterator().next().children().size() > 0) {
+        Map.Entry<String, Node> childEntry = childNode.children().entrySet().iterator().next();
         path = buildPath(path, childEntry.getKey());
-        n = childEntry.getValue();
+        childNode = childEntry.getValue();
       }
-      children.add(buildNode(n, path));
+      children.add(buildComponent(childNode, path));
     }
     return children;
   }
@@ -216,18 +214,17 @@ public class ComponentTreeBuilder {
       .build();
   }
 
-  private ComponentImpl buildDirectory(String path, @Nullable ScannerReport.Component scannerComponent, List<Component> children) {
+  private ComponentImpl buildDirectory(String path, List<Component> children) {
     String nonEmptyPath = path.isEmpty() ? "/" : path;
     String key = keyGenerator.generateKey(rootComponent, nonEmptyPath);
     String publicKey = publicKeyGenerator.generateKey(rootComponent, nonEmptyPath);
-    Integer ref = scannerComponent != null ? scannerComponent.getRef() : null;
     return ComponentImpl.builder(Component.Type.DIRECTORY)
       .setUuid(uuidSupplier.apply(key))
       .setDbKey(key)
       .setKey(publicKey)
       .setName(publicKey)
       .setStatus(convertStatus(FileStatus.UNAVAILABLE))
-      .setReportAttributes(createAttributesBuilder(ref, nonEmptyPath, scmBasePath, path).build())
+      .setReportAttributes(createAttributesBuilder(null, nonEmptyPath, scmBasePath, path).build())
       .addChildren(children)
       .build();
   }
@@ -359,7 +356,7 @@ public class ComponentTreeBuilder {
   @CheckForNull
   private static String computeScmPath(@Nullable String scmBasePath, String scmRelativePath) {
     if (scmRelativePath.isEmpty()) {
-      return null;
+      return scmBasePath;
     }
     if (scmBasePath == null) {
       return scmRelativePath;
index 58e5ac7041e6af6e10f01e9c764c60d7eeb3e3ca..b87e740492a8a93759e390b723d10bfb0a3d923c 100644 (file)
@@ -22,20 +22,69 @@ package org.sonar.ce.task.projectanalysis.component;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.core.component.ComponentKeys;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentWithModuleUuidDto;
 import org.sonar.db.component.KeyWithUuidDto;
 
 public class ComponentUuidFactory {
-
   private final Map<String, String> uuidsByKey = new HashMap<>();
 
   public ComponentUuidFactory(DbClient dbClient, DbSession dbSession, String rootKey) {
-    List<KeyWithUuidDto> keys = dbClient.componentDao().selectUuidsByKeyFromProjectKey(dbSession, rootKey);
-    for (KeyWithUuidDto dto : keys) {
-      uuidsByKey.put(dto.key(), dto.uuid());
+    Map<String, String> modulePathsByUuid = loadModulePathsByUuid(dbClient, dbSession, rootKey);
+
+    if (modulePathsByUuid.isEmpty()) {
+      // only contains root project
+      List<KeyWithUuidDto> keys = dbClient.componentDao().selectUuidsByKeyFromProjectKey(dbSession, rootKey);
+      keys.forEach(dto -> uuidsByKey.put(dto.key(), dto.uuid()));
+    } else {
+      List<ComponentWithModuleUuidDto> dtos = loadComponentsWithModuleUuid(dbClient, dbSession, rootKey);
+      for (ComponentWithModuleUuidDto dto : dtos) {
+        String pathFromRootProject = modulePathsByUuid.get(dto.moduleUuid());
+        String componentPath = StringUtils.isEmpty(pathFromRootProject) ? dto.path() : (pathFromRootProject + "/" + dto.path());
+        uuidsByKey.put(ComponentKeys.createEffectiveKey(rootKey, componentPath), dto.uuid());
+      }
+    }
+  }
+
+  private static List<ComponentWithModuleUuidDto> loadComponentsWithModuleUuid(DbClient dbClient, DbSession dbSession, String rootKey) {
+    return dbClient.componentDao().selectComponentsWithModuleUuidFromProjectKey(dbSession, rootKey);
+  }
+
+  private static Map<String, String> loadModulePathsByUuid(DbClient dbClient, DbSession dbSession, String rootKey) {
+    List<ComponentDto> moduleDtos = dbClient.componentDao()
+      .selectEnabledModulesFromProjectKey(dbSession, rootKey, false).stream()
+      .filter(c -> Qualifiers.MODULE.equals(c.qualifier()))
+      .collect(Collectors.toList());
+
+    Map<String, ComponentDto> dtoByUuid = moduleDtos.stream()
+      .collect(Collectors.toMap(ComponentDto::uuid, dto -> dto));
+
+    Map<String, String> modulePathByUuid = new HashMap<>();
+
+    for (ComponentDto dto : moduleDtos) {
+      String modulePath = null;
+      ComponentDto currentDto = dto;
+      while (currentDto != null && currentDto.moduleUuid() != null) {
+        String path = currentDto.path();
+        if (modulePath == null) {
+          modulePath = path;
+        } else {
+          modulePath = path + "/" + modulePath;
+        }
+        currentDto = dtoByUuid.get(currentDto.moduleUuid());
+      }
+
+      modulePathByUuid.put(dto.uuid(), modulePath);
     }
+
+    return modulePathByUuid;
   }
 
   /**
index 051b90d4c27d1551bed67d34d9ad70153eab1db4..7f661f1cde325f7ce2dcd8463accacbe16dcb565 100644 (file)
@@ -64,6 +64,7 @@ import org.sonar.ce.task.projectanalysis.issue.IssueCache;
 import org.sonar.ce.task.projectanalysis.issue.IssueCounter;
 import org.sonar.ce.task.projectanalysis.issue.IssueCreationDateCalculator;
 import org.sonar.ce.task.projectanalysis.issue.IssueLifecycle;
+import org.sonar.ce.task.projectanalysis.issue.IssueRelocationToRoot;
 import org.sonar.ce.task.projectanalysis.issue.IssueTrackingDelegator;
 import org.sonar.ce.task.projectanalysis.issue.IssueVisitors;
 import org.sonar.ce.task.projectanalysis.issue.IssuesRepositoryVisitor;
@@ -232,6 +233,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
       ComponentsWithUnprocessedIssues.class,
       ComponentIssuesRepositoryImpl.class,
       IssueFilter.class,
+      IssueRelocationToRoot.class,
 
       // common rules
       CommonRuleEngineImpl.class,
index 861c1afe725cb930e8ed0ac2d99cbfeaff52c7cf..f29477995f37a10c1c380f388f6c5f532626631f 100644 (file)
@@ -156,10 +156,7 @@ public class DuplicationMeasures {
 
     private static int getMeasure(CounterInitializationContext context, String metricKey) {
       Optional<Measure> files = context.getMeasure(metricKey);
-      if (files.isPresent()) {
-        return files.get().getIntValue();
-      }
-      return 0;
+      return files.map(Measure::getIntValue).orElse(0);
     }
   }
 
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRoot.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRoot.java
new file mode 100644 (file)
index 0000000..af6f87a
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
+import org.sonar.core.util.CloseableIterator;
+import org.sonar.scanner.protocol.output.ScannerReport;
+
+public class IssueRelocationToRoot {
+  private final BatchReportReader reader;
+  private final List<ScannerReport.Issue> movedIssues = new ArrayList<>();
+
+  public IssueRelocationToRoot(BatchReportReader reader) {
+    this.reader = reader;
+  }
+
+  public List<ScannerReport.Issue> getMovedIssues() {
+    return Collections.unmodifiableList(movedIssues);
+  }
+
+  public void relocate(ScannerReport.Component root, ScannerReport.Component component) {
+    CloseableIterator<ScannerReport.Issue> issueIt = reader.readComponentIssues(component.getRef());
+    while (issueIt.hasNext()) {
+      ScannerReport.Issue issue = issueIt.next();
+      movedIssues.add(moveIssueToRoot(root, component, issue));
+    }
+  }
+
+  private static ScannerReport.Issue moveIssueToRoot(ScannerReport.Component root, ScannerReport.Component component, ScannerReport.Issue issue) {
+    return ScannerReport.Issue.newBuilder(issue)
+      .clearFlow()
+      .clearTextRange()
+      .addAllFlow(issue.getFlowList().stream()
+        .map(flow -> moveFlow(root, component, flow))
+        .collect(Collectors.toList()))
+      .build();
+  }
+
+  private static ScannerReport.IssueLocation moveLocation(ScannerReport.Component root, ScannerReport.Component component, ScannerReport.IssueLocation location) {
+    String msg = "[" + component.getProjectRelativePath() + "] " + location.getMsg();
+    return ScannerReport.IssueLocation.newBuilder()
+      .setComponentRef(root.getRef())
+      .setMsg(msg)
+      .build();
+  }
+
+  private static ScannerReport.Flow moveFlow(ScannerReport.Component root, ScannerReport.Component component, ScannerReport.Flow flow) {
+    return ScannerReport.Flow.newBuilder()
+      .addAllLocation(flow.getLocationList().stream()
+        .map(location -> moveLocation(root, component, location))
+        .collect(Collectors.toList()))
+      .build();
+  }
+}
index 2cd65c16a0a5c344d0018955ba5333330fb84bab..eb44e5e43d8eb5e085b8a320317c6a9d2fb2a634 100644 (file)
@@ -61,10 +61,11 @@ public class TrackerRawInputFactory {
   private final SourceLinesHashRepository sourceLinesHash;
   private final RuleRepository ruleRepository;
   private final ActiveRulesHolder activeRulesHolder;
+  private final IssueRelocationToRoot issueRelocationToRoot;
 
   public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader,
     SourceLinesHashRepository sourceLinesHash, CommonRuleEngine commonRuleEngine, IssueFilter issueFilter, RuleRepository ruleRepository,
-    ActiveRulesHolder activeRulesHolder) {
+    ActiveRulesHolder activeRulesHolder, IssueRelocationToRoot issueRelocationToRoot) {
     this.treeRootHolder = treeRootHolder;
     this.reportReader = reportReader;
     this.sourceLinesHash = sourceLinesHash;
@@ -72,6 +73,7 @@ public class TrackerRawInputFactory {
     this.issueFilter = issueFilter;
     this.ruleRepository = ruleRepository;
     this.activeRulesHolder = activeRulesHolder;
+    this.issueRelocationToRoot = issueRelocationToRoot;
   }
 
   public Input<DefaultIssue> create(Component component) {
@@ -144,6 +146,13 @@ public class TrackerRawInputFactory {
         }
       }
 
+      if (component.getType() == Component.Type.PROJECT) {
+        // TODO apply filters?
+        issueRelocationToRoot.getMovedIssues().stream()
+          .map(i -> toIssue(getLineHashSequence(), i))
+          .forEach(result::add);
+      }
+
       return result;
     }
 
index f4b73f412d36062ce403549786c6e5d5a763c730..342e5cdbc8147b97f9dbbef83ad1b3d3f90119ce 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.ce.task.projectanalysis.component.ComponentTreeBuilder;
 import org.sonar.ce.task.projectanalysis.component.ComponentUuidFactory;
 import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
 import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolder;
+import org.sonar.ce.task.projectanalysis.issue.IssueRelocationToRoot;
 import org.sonar.ce.task.step.ComputationStep;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -47,13 +48,15 @@ public class BuildComponentTreeStep implements ComputationStep {
   private final BatchReportReader reportReader;
   private final MutableTreeRootHolder treeRootHolder;
   private final MutableAnalysisMetadataHolder analysisMetadataHolder;
+  private final IssueRelocationToRoot issueRelocationToRoot;
 
   public BuildComponentTreeStep(DbClient dbClient, BatchReportReader reportReader,
-    MutableTreeRootHolder treeRootHolder, MutableAnalysisMetadataHolder analysisMetadataHolder) {
+    MutableTreeRootHolder treeRootHolder, MutableAnalysisMetadataHolder analysisMetadataHolder, IssueRelocationToRoot issueRelocationToRoot) {
     this.dbClient = dbClient;
     this.reportReader = reportReader;
     this.treeRootHolder = treeRootHolder;
     this.analysisMetadataHolder = analysisMetadataHolder;
+    this.issueRelocationToRoot = issueRelocationToRoot;
   }
 
   @Override
@@ -82,7 +85,7 @@ public class BuildComponentTreeStep implements ComputationStep {
         reportReader::readComponent,
         analysisMetadataHolder.getProject(),
         analysisMetadataHolder.getBranch(),
-        baseAnalysis);
+        baseAnalysis, issueRelocationToRoot);
       String relativePathFromScmRoot = reportReader.readMetadata().getRelativePathFromScmRoot();
 
       Component reportTreeRoot = builder.buildProject(reportProject, relativePathFromScmRoot);
index f5fe6f8ab543017e976a908ce6f779e940e70a3e..9f4920dac21c24762571b44731cab707caa60430 100644 (file)
@@ -43,6 +43,8 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 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;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 import static org.sonar.ce.task.projectanalysis.component.ComponentVisitor.Order.PRE_ORDER;
 import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
@@ -64,6 +66,7 @@ public class ComponentTreeBuilderTest {
   private static final EnumSet<ScannerReport.Component.ComponentType> REPORT_TYPES = EnumSet.of(PROJECT, MODULE, DIRECTORY, FILE);
   private static final String NO_SCM_BASE_PATH = "";
 
+  private IssueRelocationToRoot issueRelocationToRoot = mock(IssueRelocationToRoot.class);
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
@@ -570,6 +573,39 @@ public class ComponentTreeBuilderTest {
     assertThat(file.getUuid()).isEqualTo("generated_c1:src/js/Foo.js_uuid");
   }
 
+  @Test
+  public void issues_are_relocated_from_directories_and_modules_to_root() {
+    ScannerReport.Component project = newBuilder()
+      .setType(PROJECT)
+      .setKey("c1")
+      .setRef(1)
+      .addChildRef(2)
+      .build();
+    ScannerReport.Component.Builder module = newBuilder()
+      .setRef(2)
+      .setType(MODULE)
+      .setKey("c2")
+      .addChildRef(3);
+    scannerComponentProvider.add(module);
+    ScannerReport.Component.Builder directory = newBuilder()
+      .setRef(3)
+      .setType(DIRECTORY)
+      .setProjectRelativePath("src/js")
+      .addChildRef(4);
+    scannerComponentProvider.add(directory);
+    ScannerReport.Component.Builder file = newBuilder()
+      .setRef(4)
+      .setType(FILE)
+      .setProjectRelativePath("src/js/Foo.js")
+      .setLines(1);
+    scannerComponentProvider.add(file);
+
+    call(project);
+    verify(issueRelocationToRoot).relocate(project, module.build());
+    verify(issueRelocationToRoot).relocate(project, directory.build());
+    verifyNoMoreInteractions(issueRelocationToRoot);
+  }
+
   @Test
   public void descriptions_of_module_directory_and_file_are_null_if_absent_from_report() {
     ScannerReport.Component project = newBuilder()
@@ -826,19 +862,7 @@ public class ComponentTreeBuilderTest {
     Branch branch = mock(Branch.class);
     when(branch.isMain()).thenReturn(mainBranch);
     return new ComponentTreeBuilder(KEY_GENERATOR, PUBLIC_KEY_GENERATOR, UUID_SUPPLIER, scannerComponentProvider, projectInDb, branch, baseAnalysis,
-      mock(IssueRelocationToRoot.class));
-  }
-
-  private static Map<Integer, Component> indexComponentByRef(Component root) {
-    Map<Integer, Component> componentsByRef = new HashMap<>();
-    new DepthTraversalTypeAwareCrawler(
-      new TypeAwareVisitorAdapter(CrawlerDepthLimit.FILE, PRE_ORDER) {
-        @Override
-        public void visitAny(Component any) {
-          componentsByRef.put(any.getReportAttributes().getRef(), any);
-        }
-      }).visit(root);
-    return componentsByRef;
+      issueRelocationToRoot);
   }
 
   private static Map<String, Component> indexComponentByKey(Component root) {
index 9b9f4c56196c29419756264e914c616047a5412d..90ad37a15cbc3fbdd24c1f3ddd5e023691e2919e 100644 (file)
@@ -36,11 +36,94 @@ public class ComponentUuidFactoryTest {
   @Test
   public void load_uuids_from_existing_components_in_db() {
     ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project));
+    ComponentDto module = db.components().insertComponent(ComponentTesting
+      .newModuleDto(project).setPath("module1"));
 
     ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey());
+
+    assertThat(underTest.getOrCreateForKey(project.getDbKey())).isEqualTo(project.uuid());
+    assertThat(underTest.getOrCreateForKey(module.getDbKey())).isNotEqualTo(module.uuid());
+  }
+
+  @Test
+  public void migrate_project_with_modules() {
+    ComponentDto project = db.components().insertPrivateProject(dto -> dto.setDbKey("project"));
+    ComponentDto module1 = db.components().insertComponent(ComponentTesting.newModuleDto(project)
+      .setDbKey("project:module1")
+      .setPath("module1_path"));
+    ComponentDto module2 = db.components().insertComponent(ComponentTesting.newModuleDto(module1)
+      .setDbKey("project:module1:module2")
+      .setPath("module2_path"));
+    ComponentDto file1 = db.components().insertComponent(ComponentTesting.newFileDto(project)
+      .setDbKey("project:file1")
+      .setPath("file1_path"));
+    ComponentDto file2 = db.components().insertComponent(ComponentTesting.newFileDto(module2)
+      .setDbKey("project:module1:module2:file2")
+      .setPath("file2_path"));
+
+    assertThat(file2.moduleUuidPath()).isEqualTo("." + project.uuid() + "." + module1.uuid() + "." + module2.uuid() + ".");
+
+    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey());
+
+    // migrated files
+    assertThat(underTest.getOrCreateForKey("project:file1_path")).isEqualTo(file1.uuid());
+    assertThat(underTest.getOrCreateForKey("project:module1_path/module2_path/file2_path")).isEqualTo(file2.uuid());
+
+    // project remains the same
     assertThat(underTest.getOrCreateForKey(project.getDbKey())).isEqualTo(project.uuid());
-    assertThat(underTest.getOrCreateForKey(module.getDbKey())).isEqualTo(module.uuid());
+
+    // old keys with modules don't exist
+    assertThat(underTest.getOrCreateForKey(module1.getDbKey())).isNotEqualTo(module1.uuid());
+    assertThat(underTest.getOrCreateForKey(module2.getDbKey())).isNotEqualTo(module2.uuid());
+    assertThat(underTest.getOrCreateForKey(file1.getDbKey())).isNotEqualTo(file1.uuid());
+    assertThat(underTest.getOrCreateForKey(file2.getDbKey())).isNotEqualTo(file2.uuid());
+  }
+
+  @Test
+  public void migrate_project_with_disabled_modules() {
+    ComponentDto project = db.components().insertPrivateProject(dto -> dto.setDbKey("project"));
+    ComponentDto module1 = db.components().insertComponent(ComponentTesting.newModuleDto(project)
+      .setDbKey("project:module1")
+      .setEnabled(false)
+      .setPath("module1_path"));
+    ComponentDto file1 = db.components().insertComponent(ComponentTesting.newFileDto(module1)
+      .setDbKey("project:file1")
+      .setEnabled(false)
+      .setPath("file1_path"));
+
+    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey());
+
+    // migrated files
+    assertThat(underTest.getOrCreateForKey("project:module1_path/file1_path")).isEqualTo(file1.uuid());
+  }
+
+  @Test
+  public void migrate_project_having_modules_without_paths() {
+    ComponentDto project = db.components().insertPrivateProject(dto -> dto.setDbKey("project"));
+    ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project)
+      .setDbKey("project:module")
+      .setPath(null));
+    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(module)
+      .setDbKey("project:module:file")
+      .setPath("file_path"));
+
+    assertThat(file.moduleUuidPath()).isEqualTo("." + project.uuid() + "." + module.uuid() + ".");
+
+    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey());
+
+    // file will have this key since the module has a null path
+    assertThat(underTest.getOrCreateForKey("project:file_path")).isEqualTo(file.uuid());
+
+    // migrated module
+    // TODO!!
+    //assertThat(underTest.getOrCreateForKey("project:module")).isEqualTo(module.uuid());
+
+    // project remains the same
+    //assertThat(underTest.getOrCreateForKey(project.getDbKey())).isEqualTo(project.uuid());
+
+    // old keys with modules don't exist
+    assertThat(underTest.getOrCreateForKey(module.getDbKey())).isNotEqualTo(module.uuid());
+    assertThat(underTest.getOrCreateForKey(file.getDbKey())).isNotEqualTo(file.uuid());
   }
 
   @Test
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRootTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IssueRelocationToRootTest.java
new file mode 100644 (file)
index 0000000..b344e58
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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.Collections;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
+import org.sonar.scanner.protocol.output.ScannerReport;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssueRelocationToRootTest {
+  @Rule
+  public BatchReportReaderRule reader = new BatchReportReaderRule();
+  private IssueRelocationToRoot underTest = new IssueRelocationToRoot(reader);
+
+  private ScannerReport.Component root;
+  private ScannerReport.Component module;
+  private ScannerReport.Component directory;
+
+  @Before
+  public void createComponents() {
+    root = ScannerReport.Component.newBuilder()
+      .setType(ScannerReport.Component.ComponentType.PROJECT)
+      .setRef(1)
+      .setStatus(ScannerReport.Component.FileStatus.UNAVAILABLE)
+      .setKey("PROJECT")
+      .addChildRef(2)
+      .build();
+    module = ScannerReport.Component.newBuilder()
+      .setType(ScannerReport.Component.ComponentType.MODULE)
+      .setRef(2)
+      .setStatus(ScannerReport.Component.FileStatus.UNAVAILABLE)
+      .setKey("PROJECT:MODULE")
+      .addChildRef(3)
+      .build();
+    directory = ScannerReport.Component.newBuilder()
+      .setType(ScannerReport.Component.ComponentType.DIRECTORY)
+      .setRef(3)
+      .setStatus(ScannerReport.Component.FileStatus.UNAVAILABLE)
+      .setKey("PROJECT:MODULE:DIRECTORY")
+      .addChildRef(4)
+      .build();
+
+    reader.putComponent(root);
+    reader.putComponent(module);
+    reader.putComponent(directory);
+  }
+
+  private void createIssues() {
+    reader.putIssues(2, Collections.singletonList(ScannerReport.Issue.newBuilder().setRuleKey("module_issue").build()));
+    reader.putIssues(3, Collections.singletonList(ScannerReport.Issue.newBuilder().setRuleKey("directory_issue").build()));
+  }
+
+  @Test
+  public void move_module_and_directory_issues() {
+    createComponents();
+    createIssues();
+
+    underTest.relocate(root, module);
+    underTest.relocate(root, directory);
+
+    assertThat(underTest.getMovedIssues()).hasSize(2);
+    assertThat(underTest.getMovedIssues()).extracting(ScannerReport.Issue::getRuleKey).containsOnly("module_issue", "directory_issue");
+  }
+
+  @Test
+  public void do_nothing_if_no_issues() {
+    createComponents();
+    underTest.relocate(root, module);
+    underTest.relocate(root, directory);
+
+    assertThat(underTest.getMovedIssues()).hasSize(0);
+  }
+
+}
index 46afb3798517fbbd2a578491e4dd8a8f7d71ed85..ba95929db6a894734dc121c33b3b9d0df5ff2c65 100644 (file)
@@ -165,25 +165,49 @@ public class BuildComponentTreeStepTest {
 
   @Test
   public void return_existing_uuids() {
+    setAnalysisMetadataHolder();
+    OrganizationDto organizationDto = dbTester.organizations().insert();
+    ComponentDto project = insertComponent(newPrivateProjectDto(organizationDto, "ABCD").setDbKey(REPORT_PROJECT_KEY));
+    ComponentDto directory = newDirectory(project, "CDEF", REPORT_DIR_PATH_1);
+    insertComponent(directory.setDbKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1));
+    insertComponent(newFileDto(project, directory, "DEFG")
+      .setDbKey(REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1)
+      .setPath(REPORT_FILE_PATH_1));
+
+    // new structure, without modules
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, DIR_REF_1));
+    reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, REPORT_DIR_PATH_1, FILE_1_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
+
+    underTest.execute(new TestComputationStepContext());
+
+    verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, "ABCD");
+    verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, "CDEF");
+    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, "DEFG");
+  }
+
+  @Test
+  public void return_existing_uuids_with_modules() {
     setAnalysisMetadataHolder();
     OrganizationDto organizationDto = dbTester.organizations().insert();
     ComponentDto project = insertComponent(newPrivateProjectDto(organizationDto, "ABCD").setDbKey(REPORT_PROJECT_KEY));
     ComponentDto module = insertComponent(newModuleDto("BCDE", project).setDbKey(REPORT_MODULE_KEY));
     ComponentDto directory = newDirectory(module, "CDEF", REPORT_DIR_PATH_1);
     insertComponent(directory.setDbKey(REPORT_MODULE_KEY + ":" + REPORT_DIR_PATH_1));
-    insertComponent(newFileDto(module, directory, "DEFG").setDbKey(REPORT_MODULE_KEY + ":" + REPORT_FILE_PATH_1));
+    insertComponent(newFileDto(module, directory, "DEFG")
+      .setDbKey(REPORT_MODULE_KEY + ":" + REPORT_FILE_PATH_1)
+      .setPath(REPORT_FILE_PATH_1));
 
-    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, MODULE_REF));
-    reportReader.putComponent(component(MODULE_REF, MODULE, REPORT_MODULE_KEY, DIR_REF_1));
-    reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, REPORT_DIR_PATH_1, FILE_1_REF));
-    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1));
+    // new structure, without modules
+    reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, DIR_REF_1));
+    reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, "module/" + REPORT_DIR_PATH_1, FILE_1_REF));
+    reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, "module/" + REPORT_FILE_PATH_1));
 
     underTest.execute(new TestComputationStepContext());
 
     verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, "ABCD");
-    // TODO migrate modules
-    //    verifyComponentByRef(DIR_REF_1, REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, "CDEF");
-    //   verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, "DEFG");
+    verifyComponentByKey(REPORT_PROJECT_KEY + ":module/" + REPORT_DIR_PATH_1, REPORT_PROJECT_KEY + ":module/" + REPORT_DIR_PATH_1, "CDEF");
+    verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":module/" + REPORT_FILE_PATH_1, "DEFG");
   }
 
   @Test
index 6e472812af7183817933b97089271df47ab28419..5f26eadf1e25c16766583f835b68d23a759a0359 100644 (file)
@@ -28,7 +28,6 @@ import org.sonar.api.utils.MessageException;
 import org.sonar.api.utils.System2;
 import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule;
 import org.sonar.ce.task.projectanalysis.analysis.Branch;
-import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
 import org.sonar.ce.task.projectanalysis.component.Component;
 import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
 import org.sonar.ce.task.projectanalysis.component.ReportComponent;
@@ -39,8 +38,6 @@ import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
 import org.sonar.db.component.SnapshotTesting;
-import org.sonar.scanner.protocol.output.ScannerReport;
-import org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType;
 
 public class ValidateProjectStepTest {
 
@@ -54,9 +51,6 @@ public class ValidateProjectStepTest {
   @Rule
   public ExpectedException thrown = ExpectedException.none();
 
-  @Rule
-  public BatchReportReaderRule reportReader = new BatchReportReaderRule();
-
   @Rule
   public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
 
@@ -67,17 +61,10 @@ public class ValidateProjectStepTest {
 
   DbClient dbClient = dbTester.getDbClient();
 
-  ValidateProjectStep underTest = new ValidateProjectStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
+  ValidateProjectStep underTest = new ValidateProjectStep(dbClient, treeRootHolder, analysisMetadataHolder);
 
   @Test
   public void not_fail_if_analysis_date_is_after_last_analysis() {
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(1)
-      .setType(ComponentType.PROJECT)
-      .setKey(PROJECT_KEY)
-      .addChildRef(2)
-      .build());
-
     ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), "ABCD").setDbKey(PROJECT_KEY);
     dbClient.componentDao().insert(dbTester.getSession(), project);
     dbClient.snapshotDao().insert(dbTester.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(1420088400000L)); // 2015-01-01
@@ -92,13 +79,6 @@ public class ValidateProjectStepTest {
   public void fail_if_analysis_date_is_before_last_analysis() {
     analysisMetadataHolder.setAnalysisDate(DateUtils.parseDate("2015-01-01"));
 
-    reportReader.putComponent(ScannerReport.Component.newBuilder()
-      .setRef(1)
-      .setType(ComponentType.PROJECT)
-      .setKey(PROJECT_KEY)
-      .addChildRef(2)
-      .build());
-
     ComponentDto project = ComponentTesting.newPrivateProjectDto(dbTester.organizations().insert(), "ABCD").setDbKey(PROJECT_KEY);
     dbClient.componentDao().insert(dbTester.getSession(), project);
     dbClient.snapshotDao().insert(dbTester.getSession(), SnapshotTesting.newAnalysis(project).setCreatedAt(1433131200000L)); // 2015-06-01
index 18c7509ffeb1ce796e5b4fb8e1d3cd3cd9ef0d9c..5ce01ea804a493dd1db5133b019eb5f88c786293 100644 (file)
@@ -48,6 +48,7 @@ import org.sonar.db.ce.CeTaskMessageMapper;
 import org.sonar.db.component.AnalysisPropertiesMapper;
 import org.sonar.db.component.BranchMapper;
 import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentWithModuleUuidDto;
 import org.sonar.db.component.ComponentDtoWithSnapshotId;
 import org.sonar.db.component.ComponentKeyUpdaterMapper;
 import org.sonar.db.component.ComponentMapper;
@@ -167,6 +168,7 @@ public class MyBatis implements Startable {
     confBuilder.loadAlias("ActiveRuleParam", ActiveRuleParamDto.class);
     confBuilder.loadAlias("CeTaskCharacteristic", CeTaskCharacteristicDto.class);
     confBuilder.loadAlias("Component", ComponentDto.class);
+    confBuilder.loadAlias("ComponentWithModuleUuid", ComponentWithModuleUuidDto.class);
     confBuilder.loadAlias("ComponentWithSnapshot", ComponentDtoWithSnapshotId.class);
     confBuilder.loadAlias("CustomMeasure", CustomMeasureDto.class);
     confBuilder.loadAlias("DuplicationUnit", DuplicationUnitDto.class);
index f6f79f3de773bd59bd5f0bdaae87820cb48ff625..c0449b593e66ae39be5736a100a7c96313ed55f0 100644 (file)
@@ -183,8 +183,12 @@ public class ComponentDao implements Dao {
     return mapper(session).selectUuidsByKeyFromProjectKey(projectKey);
   }
 
+  public List<ComponentDto> selectEnabledModulesFromProjectKey(DbSession session, String projectKey, boolean excludeDisabled) {
+    return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, Scopes.PROJECT, excludeDisabled);
+  }
+
   public List<ComponentDto> selectEnabledModulesFromProjectKey(DbSession session, String projectKey) {
-    return mapper(session).selectComponentsFromProjectKeyAndScope(projectKey, Scopes.PROJECT, true);
+    return selectEnabledModulesFromProjectKey(session, projectKey, true);
   }
 
   public List<ComponentDto> selectByKeys(DbSession session, Collection<String> keys) {
@@ -347,6 +351,10 @@ public class ComponentDao implements Dao {
     return new HashSet<>(mapper(dbSession).selectComponentsByQualifiers(qualifiers));
   }
 
+  public List<ComponentWithModuleUuidDto> selectComponentsWithModuleUuidFromProjectKey(DbSession dbSession, String projectKey) {
+    return mapper(dbSession).selectComponentsWithModuleUuidFromProjectKey(projectKey);
+  }
+
   public List<ComponentDto> selectProjectsByNameQuery(DbSession dbSession, @Nullable String nameQuery, boolean includeModules) {
     String nameQueryForSql = nameQuery == null ? null : buildLikeValue(nameQuery, BEFORE_AND_AFTER).toUpperCase(Locale.ENGLISH);
     return mapper(dbSession).selectProjectsByNameQuery(nameQueryForSql, includeModules);
index 687ee46addeb2d46a3481bf5ba35f022ca53f13a..f096156ed3c4e78ca76759a6b0dc75a2cd6fea59 100644 (file)
@@ -164,4 +164,6 @@ public interface ComponentMapper {
   List<KeyWithUuidDto> selectComponentKeysHavingIssuesToMerge(@Param("mergeBranchUuid") String mergeBranchUuid);
 
   List<ProjectNclocDistributionDto> selectPrivateProjectsWithNcloc(@Param("organizationUuid") String organizationUuid);
+
+  List<ComponentWithModuleUuidDto> selectComponentsWithModuleUuidFromProjectKey(String projectKey);
 }
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentWithModuleUuidDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentWithModuleUuidDto.java
new file mode 100644 (file)
index 0000000..3c2045f
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.db.component;
+
+import javax.annotation.concurrent.Immutable;
+
+@Immutable
+public class ComponentWithModuleUuidDto {
+  private final String uuid;
+  private final String moduleUuid;
+  private final String path;
+
+  public ComponentWithModuleUuidDto(String uuid, String moduleUuid, String path) {
+    this.uuid = uuid;
+    this.moduleUuid = moduleUuid;
+    this.path = path;
+  }
+
+  public String path() {
+    return path;
+  }
+
+  public String moduleUuid() {
+    return moduleUuid;
+  }
+
+  public String uuid() {
+    return uuid;
+  }
+}
index 1f79ca8d1a303d5ad910c3d3e3cc324c46848c3e..77fd01b84ba22de908e60bdb7f00e98fc41bb887 100644 (file)
       </if>
     </where>
   </select>
+
+  <select id="selectComponentsWithModuleUuidFromProjectKey" resultType="ComponentWithModuleUuid">
+    SELECT
+      p.uuid as uuid, p.module_uuid as moduleUuid, p.path as path
+    FROM
+      projects p
+    INNER JOIN
+      projects root ON root.uuid=p.project_uuid AND root.kee=#{projectKey,jdbcType=VARCHAR}
+  </select>
   
   <select id="selectUuidsByKeyFromProjectKey" parameterType="string" resultType="KeyWithUuid">
     SELECT
index c1839b702a22292a52be17ad0e70a89c2bd7eee8..76ae9566b0c3e544afffcd703266c06dcf90148c 100644 (file)
@@ -527,6 +527,34 @@ public class ComponentDaoTest {
     assertThat(underTest.selectEnabledDescendantModules(dbSession, "unknown")).isEmpty();
   }
 
+  @Test
+  public void select_components_with_module_dto() {
+    ComponentDto project = db.components().insertPrivateProject();
+    ComponentDto module = db.components().insertComponent(newModuleDto(project));
+    ComponentDto removedModule = db.components().insertComponent(newModuleDto(project).setEnabled(false));
+    ComponentDto subModule = db.components().insertComponent(newModuleDto(module));
+    ComponentDto removedSubModule = db.components().insertComponent(newModuleDto(module).setEnabled(false));
+    ComponentDto directory = db.components().insertComponent(newDirectory(subModule, "src"));
+    ComponentDto removedDirectory = db.components().insertComponent(newDirectory(subModule, "src2").setEnabled(false));
+    ComponentDto file = db.components().insertComponent(newFileDto(subModule, directory));
+    ComponentDto removedFile = db.components().insertComponent(newFileDto(subModule, directory).setEnabled(false));
+
+    // From root project
+    assertThat(underTest.selectComponentsWithModuleUuidFromProjectKey(dbSession, project.getDbKey()))
+      .extracting(ComponentWithModuleUuidDto::uuid)
+      .containsExactlyInAnyOrder(
+        project.uuid(),
+        module.uuid(),
+        removedModule.uuid(),
+        subModule.uuid(),
+        removedSubModule.uuid(),
+        directory.uuid(),
+        removedDirectory.uuid(),
+        file.uuid(),
+        removedFile.uuid()
+      );
+  }
+
   @Test
   public void select_all_modules_tree() {
     ComponentDto project = db.components().insertPrivateProject();