]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10812 Application with branches
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 8 Jun 2018 08:57:23 +0000 (10:57 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 29 Jun 2018 07:10:15 +0000 (09:10 +0200)
25 files changed:
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.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/resources/org/sonar/db/component/ComponentMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentTesting.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v73/DbVersion73.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v73/PopulateMainApplicationBranches.java [new file with mode: 0644]
server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v73/package-info.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/MigrationConfigurationModuleTest.java
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v73/DbVersion73Test.java [new file with mode: 0644]
server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v73/PopulateMainApplicationBranchesTest.java [new file with mode: 0644]
server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v73/PopulateMainApplicationBranchesTest/schema.sql [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/branch/ws/ListAction.java
server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java
server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndex.java
server/sonar-server/src/test/java/org/sonar/server/branch/ws/DeleteActionTest.java
server/sonar-server/src/test/java/org/sonar/server/branch/ws/ListActionTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ComponentUpdaterTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java
server/sonar-server/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java
server/sonar-web/src/main/js/app/components/ComponentContainer.tsx
server/sonar-web/src/main/js/app/utils/exposeLibraries.ts

index 49cdaf24a2b18320c91a285b4a182120f51e2ce8..34dc809356e339b8b93976839bc182a78f215d8c 100644 (file)
@@ -202,7 +202,7 @@ public class BuildComponentTreeStepTest {
   }
 
   @Test
-  public void generate_keys_when_using_branch() {
+  public void generate_keys_when_using_new_branch() {
     Branch branch = mock(Branch.class);
     when(branch.getName()).thenReturn("origin/feature");
     when(branch.isMain()).thenReturn(false);
@@ -226,6 +226,27 @@ public class BuildComponentTreeStepTest {
     verifyComponent(FILE_1_REF, "generated", REPORT_MODULE_KEY + ":" + REPORT_FILE_KEY_1, null);
   }
 
+  @Test
+  public void generate_keys_when_using_existing_branch() {
+    ComponentDto projectDto = dbTester.components().insertMainBranch();
+    ComponentDto branchDto = dbTester.components().insertProjectBranch(projectDto);
+    Branch branch = mock(Branch.class);
+    when(branch.getName()).thenReturn(branchDto.getBranch());
+    when(branch.isMain()).thenReturn(false);
+    when(branch.isLegacyFeature()).thenReturn(false);
+    when(branch.generateKey(any(), any())).thenReturn(branchDto.getDbKey());
+    analysisMetadataHolder.setRootComponentRef(ROOT_REF)
+      .setAnalysisDate(ANALYSIS_DATE)
+      .setProject(Project.from(projectDto))
+      .setBranch(branch);
+    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder);
+    reportReader.putComponent(componentWithKey(ROOT_REF, PROJECT, branchDto.getKey()));
+
+    underTest.execute();
+
+    verifyComponent(ROOT_REF, branchDto.getDbKey(), branchDto.getKey(), branchDto.uuid());
+  }
+
   @Test
   public void generate_keys_when_using_main_branch() {
     Branch branch = new DefaultBranchImpl();
index 0899d7f4c2b6903e8e54fc92b8f99a09fcb8a5a0..f0149992fe512d0b90dae2044f5512930a31ad9a 100644 (file)
@@ -113,8 +113,8 @@ public class ComputeEngineContainerImplTest {
       );
       assertThat(picoContainer.getParent().getParent().getComponentAdapters()).hasSize(
         CONTAINER_ITSELF
-          + 14 // MigrationConfigurationModule
-          + 19 // level 2
+          + 17 // MigrationConfigurationModule
+          + 17 // level 2
       );
       assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
         COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
index e572a95ba15b3c168e49d5122f7602fccef128cf..37009c74415580581e5ad69f22a27c3e8a8d0149 100644 (file)
@@ -27,6 +27,7 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 import java.util.stream.Stream;
 import javax.annotation.CheckForNull;
@@ -195,6 +196,15 @@ public class ComponentDao implements Dao {
     return executeLargeInputs(allKeys, subKeys -> mapper(session).selectByKeysAndBranch(subKeys, branch));
   }
 
+  /**
+   * Return list of components that will will mix main and branch components.
+   * Please note that a project can only appear once in the list, it's not possible to ask for many branches on same project with this method.
+   */
+  public List<ComponentDto> selectByKeysAndBranches(DbSession session, Map<String, String> branchesByKey) {
+    List<String> dbKeys = branchesByKey.entrySet().stream().map(entry -> generateBranchKey(entry.getKey(), entry.getValue())).collect(toList());
+    return executeLargeInputs(dbKeys, subKeys -> mapper(session).selectByDbKeys(subKeys));
+  }
+
   public List<ComponentDto> selectByKeysAndPullRequest(DbSession session, Collection<String> keys, String pullRequestId) {
     List<String> dbKeys = keys.stream().map(k -> generatePullRequestKey(k, pullRequestId)).collect(toList());
     List<String> allKeys = Stream.of(keys, dbKeys).flatMap(Collection::stream).collect(toList());
index eb1023f17a0c7e95dc5fd2fb90ea8a1bbba01e5a..10e3b48241c0319a5391391c4f1285743643687f 100644 (file)
@@ -49,6 +49,8 @@ public interface ComponentMapper {
 
   List<ComponentDto> selectByKeys(@Param("keys") Collection<String> keys);
 
+  List<ComponentDto> selectByDbKeys(@Param("dbKeys") Collection<String> dbKeys);
+
   List<ComponentDto> selectByKeysAndBranch(@Param("keys") Collection<String> keys, @Param("branch") String branch);
 
   List<ComponentDto> selectByIds(@Param("ids") Collection<Long> ids);
index e59d48eefa3102cb8e25c39ff7978a22489d7feb..d6fd5aa89798fd464ddb99edee08d63101196e07 100644 (file)
       </foreach>
   </select>
 
+  <select id="selectByDbKeys" parameterType="String" resultType="Component">
+    select
+    <include refid="componentColumns"/>
+    from projects p
+    where
+    p.enabled=${_true}
+    and p.kee in
+    <foreach collection="dbKeys" open="(" close=")" item="key" separator=",">
+      #{key,jdbcType=VARCHAR}
+    </foreach>
+  </select>
+
   <select id="selectByKeysAndBranch" parameterType="String" resultType="Component">
     SELECT
     <include refid="componentColumns"/>
index 191e803eb7b23ee16f91e89dd4fc575f94c715dd..138ee1940175394aec673e78fed41688b4f7fc9f 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.db.component;
 
 import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
@@ -65,6 +66,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
 import static org.assertj.core.api.Assertions.tuple;
 import static org.assertj.guava.api.Assertions.assertThat;
+import static org.sonar.api.resources.Qualifiers.APP;
 import static org.sonar.db.component.ComponentTesting.newDirectory;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.component.ComponentTesting.newModuleDto;
@@ -286,6 +288,28 @@ public class ComponentDaoTest {
     assertThat(underTest.selectByKeysAndBranch(dbSession, singletonList(branch.getKey()), "master")).extracting(ComponentDto::uuid).containsExactlyInAnyOrder(project.uuid());
   }
 
+  @Test
+  public void select_by_keys_and_branches() {
+    ComponentDto project = db.components().insertMainBranch();
+    ComponentDto projectBranch = db.components().insertProjectBranch(project, b -> b.setKey("my_branch"));
+    ComponentDto application = db.components().insertMainBranch(a -> a.setQualifier(APP));
+    ComponentDto applicationBranch = db.components().insertProjectBranch(application, b -> b.setKey("my_branch"));
+
+    assertThat(underTest.selectByKeysAndBranches(db.getSession(), ImmutableMap.of(
+      projectBranch.getKey(), projectBranch.getBranch(),
+      applicationBranch.getKey(), applicationBranch.getBranch())))
+        .extracting(ComponentDto::getKey, ComponentDto::getBranch)
+        .containsExactlyInAnyOrder(
+          tuple(projectBranch.getKey(), "my_branch"),
+          tuple(applicationBranch.getKey(), "my_branch"));
+    assertThat(underTest.selectByKeysAndBranches(db.getSession(), ImmutableMap.of(
+      projectBranch.getKey(), "unknown",
+      "unknown", projectBranch.getBranch())))
+        .extracting(ComponentDto::getDbKey)
+        .isEmpty();
+    assertThat(underTest.selectByKeysAndBranches(db.getSession(), Collections.emptyMap())).isEmpty();
+  }
+
   @Test
   public void get_by_ids() {
     ComponentDto project1 = db.components().insertPrivateProject();
index b0408ddb77344843726f8723b30567f23fd4fc01..afc076e73094734057910e5c461b9bcae567e428 100644 (file)
@@ -30,7 +30,13 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.sonar.db.component.BranchType.PULL_REQUEST;
+import static org.sonar.db.component.ComponentDto.BRANCH_KEY_SEPARATOR;
+import static org.sonar.db.component.ComponentDto.PULL_REQUEST_SEPARATOR;
+import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT;
 import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR;
+import static org.sonar.db.component.ComponentDto.formatUuidPathFromParent;
+import static org.sonar.db.component.ComponentDto.generateBranchKey;
+import static org.sonar.db.component.ComponentDto.generatePullRequestKey;
 
 public class ComponentTesting {
 
@@ -101,11 +107,11 @@ public class ComponentTesting {
   private static String generateKey(String key, ComponentDto parentModuleOrProject) {
     String branch = parentModuleOrProject.getBranch();
     if (branch != null) {
-      return ComponentDto.generateBranchKey(key, branch);
+      return generateBranchKey(key, branch);
     }
     String pullRequest = parentModuleOrProject.getPullRequest();
     if (pullRequest != null) {
-      return ComponentDto.generatePullRequestKey(key, pullRequest);
+      return generatePullRequestKey(key, pullRequest);
     }
 
     return key;
@@ -135,7 +141,7 @@ public class ComponentTesting {
     return new ComponentDto()
       .setOrganizationUuid(organizationUuid)
       .setUuid(uuid)
-      .setUuidPath(ComponentDto.UUID_PATH_OF_ROOT)
+      .setUuidPath(UUID_PATH_OF_ROOT)
       .setProjectUuid(uuid)
       .setModuleUuidPath(UUID_PATH_SEPARATOR + uuid + UUID_PATH_SEPARATOR)
       .setRootUuid(uuid)
@@ -199,7 +205,7 @@ public class ComponentTesting {
     return new ComponentDto()
       .setOrganizationUuid(parent.getOrganizationUuid())
       .setUuid(uuid)
-      .setUuidPath(ComponentDto.formatUuidPathFromParent(parent))
+      .setUuidPath(formatUuidPathFromParent(parent))
       .setProjectUuid(moduleOrProject.projectUuid())
       .setRootUuid(moduleOrProject.uuid())
       .setModuleUuid(moduleOrProject.uuid())
@@ -237,15 +243,15 @@ public class ComponentTesting {
   }
 
   public static ComponentDto newProjectBranch(ComponentDto project, BranchDto branchDto) {
-    checkArgument(project.qualifier().equals(Qualifiers.PROJECT));
+    checkArgument(project.qualifier().equals(Qualifiers.PROJECT) || project.qualifier().equals(Qualifiers.APP));
     checkArgument(project.getMainBranchProjectUuid() == null);
     String branchName = branchDto.getKey();
-    String branchSeparator = branchDto.getBranchType() == PULL_REQUEST ? ":PULL_REQUEST:" : ":BRANCH:";
+    String branchSeparator = branchDto.getBranchType() == PULL_REQUEST ? PULL_REQUEST_SEPARATOR : BRANCH_KEY_SEPARATOR;
     String uuid = branchDto.getUuid();
     return new ComponentDto()
       .setUuid(uuid)
       .setOrganizationUuid(project.getOrganizationUuid())
-      .setUuidPath(ComponentDto.UUID_PATH_OF_ROOT)
+      .setUuidPath(UUID_PATH_OF_ROOT)
       .setProjectUuid(uuid)
       .setModuleUuidPath(UUID_PATH_SEPARATOR + uuid + UUID_PATH_SEPARATOR)
       .setRootUuid(uuid)
index 7cba424fbe46a98c575a9361ce1b3a7e5fca8695..0e4b55a68e0fd9f1d213d72f87073cfec1669ea0 100644 (file)
@@ -36,6 +36,7 @@ import org.sonar.server.platform.db.migration.version.v67.DbVersion67;
 import org.sonar.server.platform.db.migration.version.v70.DbVersion70;
 import org.sonar.server.platform.db.migration.version.v71.DbVersion71;
 import org.sonar.server.platform.db.migration.version.v72.DbVersion72;
+import org.sonar.server.platform.db.migration.version.v73.DbVersion73;
 
 public class MigrationConfigurationModule extends Module {
   @Override
@@ -55,6 +56,7 @@ public class MigrationConfigurationModule extends Module {
       DbVersion70.class,
       DbVersion71.class,
       DbVersion72.class,
+      DbVersion73.class,
 
       // migration steps
       MigrationStepRegistryImpl.class,
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v73/DbVersion73.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v73/DbVersion73.java
new file mode 100644 (file)
index 0000000..88bdf21
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * 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.server.platform.db.migration.version.v73;
+
+import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
+import org.sonar.server.platform.db.migration.version.DbVersion;
+
+public class DbVersion73 implements DbVersion {
+
+  @Override
+  public void addSteps(MigrationStepRegistry registry) {
+    registry
+      .add(2200, "Populate PROJECT_BRANCHES with existing main application branches", PopulateMainApplicationBranches.class)
+    ;
+  }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v73/PopulateMainApplicationBranches.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v73/PopulateMainApplicationBranches.java
new file mode 100644 (file)
index 0000000..6d795d5
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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.server.platform.db.migration.version.v73;
+
+import java.sql.SQLException;
+import org.sonar.api.utils.System2;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+import org.sonar.server.platform.db.migration.step.MassUpdate;
+
+public class PopulateMainApplicationBranches extends DataChange {
+
+  private static final String MAIN_BRANCH_NAME = "master";
+
+  private final System2 system2;
+
+  public PopulateMainApplicationBranches(Database db, System2 system2) {
+    super(db);
+    this.system2 = system2;
+  }
+
+  @Override
+  protected void execute(Context context) throws SQLException {
+    long now = system2.now();
+    MassUpdate massUpdate = context.prepareMassUpdate();
+    massUpdate.select("SELECT uuid FROM projects p "
+      + "WHERE p.scope='PRJ' AND p.qualifier='APP' AND p.main_branch_project_uuid IS NULL "
+      + "AND NOT EXISTS (SELECT uuid FROM project_branches b WHERE b.uuid = p.uuid)");
+    massUpdate.update("INSERT INTO project_branches (uuid, project_uuid, kee, branch_type, key_type, "
+      + "merge_branch_uuid, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
+    massUpdate.rowPluralName("applications");
+    massUpdate.execute((row, update) -> {
+      String uuid = row.getString(1);
+      update.setString(1, uuid);
+      update.setString(2, uuid);
+      update.setString(3, MAIN_BRANCH_NAME);
+      update.setString(4, "LONG");
+      update.setString(5, "BRANCH");
+      update.setString(6, null);
+      update.setLong(7, now);
+      update.setLong(8, now);
+      return true;
+    });
+  }
+
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v73/package-info.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v73/package-info.java
new file mode 100644 (file)
index 0000000..8282943
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.platform.db.migration.version.v73;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index 03db8bf6e465924ee68df63922ff57672ef00c5b..5ff4a49cd981b9741029535f274c09f17d5a5a50 100644 (file)
@@ -37,7 +37,7 @@ public class MigrationConfigurationModuleTest {
     assertThat(container.getPicoContainer().getComponentAdapters())
       .hasSize(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER
         // DbVersion classes
-        + 13
+        + 14
         // Others
         + 3);
   }
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v73/DbVersion73Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v73/DbVersion73Test.java
new file mode 100644 (file)
index 0000000..39e960a
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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.server.platform.db.migration.version.v73;
+
+import org.junit.Test;
+
+import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber;
+
+public class DbVersion73Test {
+
+  private DbVersion73 underTest = new DbVersion73();
+
+  @Test
+  public void migrationNumber_starts_at_2200() {
+    verifyMinimumMigrationNumber(underTest, 2200);
+  }
+
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v73/PopulateMainApplicationBranchesTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v73/PopulateMainApplicationBranchesTest.java
new file mode 100644 (file)
index 0000000..531869d
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+ * 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.server.platform.db.migration.version.v73;
+
+import java.sql.SQLException;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.assertj.core.groups.Tuple;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.utils.System2;
+import org.sonar.api.utils.internal.TestSystem2;
+import org.sonar.core.util.Uuids;
+import org.sonar.db.CoreDbTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+
+public class PopulateMainApplicationBranchesTest {
+
+  private final static long PAST = 10_000_000_000L;
+  private final static long NOW = 50_000_000_000L;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Rule
+  public CoreDbTester db = CoreDbTester.createForSchema(PopulateMainApplicationBranchesTest.class, "schema.sql");
+
+  private System2 system2 = new TestSystem2().setNow(NOW);
+
+  private PopulateMainApplicationBranches underTest = new PopulateMainApplicationBranches(db.database(), system2);
+
+  @Test
+  public void migrate() throws SQLException {
+    String project = insertApplication();
+
+    underTest.execute();
+
+    assertProjectBranches(tuple("master", project, project, "LONG", NOW, NOW));
+  }
+
+  @Test
+  public void does_nothing_on_non_applications() throws SQLException {
+    insertComponent(null, "BRC");
+    insertComponent(null, "VW");
+
+    underTest.execute();
+
+    assertThat(db.countRowsOfTable("project_branches")).isZero();
+  }
+
+  @Test
+  public void does_nothing_on_empty_table() throws SQLException {
+    underTest.execute();
+
+    assertThat(db.countRowsOfTable("project_branches")).isZero();
+  }
+
+  @Test
+  public void does_nothing_if_already_migrated() throws SQLException {
+    String application = insertApplication();
+    insertMainBranch(application);
+
+    underTest.execute();
+
+    assertProjectBranches(tuple("master", application, application, "LONG", PAST, PAST));
+  }
+
+  private void assertProjectBranches(Tuple... expectedTuples) {
+    assertThat(db.select("SELECT KEE, UUID, PROJECT_UUID, BRANCH_TYPE, CREATED_AT, UPDATED_AT FROM PROJECT_BRANCHES")
+      .stream()
+      .map(row -> new Tuple(row.get("KEE"), row.get("UUID"), row.get("PROJECT_UUID"), row.get("BRANCH_TYPE"), row.get("CREATED_AT"), row.get("UPDATED_AT")))
+      .collect(Collectors.toList()))
+        .containsExactlyInAnyOrder(expectedTuples);
+  }
+
+  private String insertApplication() {
+    return insertComponent(null, "APP");
+  }
+
+  private String insertComponent(@Nullable String mainBranchUuid, String qualifier) {
+    String uuid = Uuids.createFast();
+    db.executeInsert("PROJECTS",
+      "ORGANIZATION_UUID", "default-org",
+      "KEE", uuid + "-key",
+      "UUID", uuid,
+      "PROJECT_UUID", uuid,
+      "MAIN_BRANCH_PROJECT_UUID", mainBranchUuid,
+      "UUID_PATH", ".",
+      "ROOT_UUID", uuid,
+      "PRIVATE", "true",
+      "SCOPE", "PRJ",
+      "QUALIFIER", qualifier);
+    return uuid;
+  }
+
+  private void insertMainBranch(String uuid) {
+    db.executeInsert("PROJECT_BRANCHES",
+      "UUID", uuid,
+      "PROJECT_UUID", uuid,
+      "KEE", "master",
+      "KEY_TYPE", "BRANCH",
+      "BRANCH_TYPE", "LONG",
+      "CREATED_AT", PAST,
+      "UPDATED_AT", PAST);
+  }
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v73/PopulateMainApplicationBranchesTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v73/PopulateMainApplicationBranchesTest/schema.sql
new file mode 100644 (file)
index 0000000..9961146
--- /dev/null
@@ -0,0 +1,61 @@
+CREATE TABLE "PROJECTS" (
+  "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1),
+  "ORGANIZATION_UUID" VARCHAR(40) NOT NULL,
+  "KEE" VARCHAR(400),
+  "UUID" VARCHAR(50) NOT NULL,
+  "UUID_PATH" VARCHAR(1500) NOT NULL,
+  "ROOT_UUID" VARCHAR(50) NOT NULL,
+  "PROJECT_UUID" VARCHAR(50) NOT NULL,
+  "MODULE_UUID" VARCHAR(50),
+  "MODULE_UUID_PATH" VARCHAR(1500),
+  "MAIN_BRANCH_PROJECT_UUID" VARCHAR(50),
+  "NAME" VARCHAR(2000),
+  "DESCRIPTION" VARCHAR(2000),
+  "PRIVATE" BOOLEAN NOT NULL,
+  "TAGS" VARCHAR(500),
+  "ENABLED" BOOLEAN NOT NULL DEFAULT TRUE,
+  "SCOPE" VARCHAR(3),
+  "QUALIFIER" VARCHAR(10),
+  "DEPRECATED_KEE" VARCHAR(400),
+  "PATH" VARCHAR(2000),
+  "LANGUAGE" VARCHAR(20),
+  "COPY_COMPONENT_UUID" VARCHAR(50),
+  "LONG_NAME" VARCHAR(2000),
+  "DEVELOPER_UUID" VARCHAR(50),
+  "CREATED_AT" TIMESTAMP,
+  "AUTHORIZATION_UPDATED_AT" BIGINT,
+  "B_CHANGED" BOOLEAN,
+  "B_COPY_COMPONENT_UUID" VARCHAR(50),
+  "B_DESCRIPTION" VARCHAR(2000),
+  "B_ENABLED" BOOLEAN,
+  "B_UUID_PATH" VARCHAR(1500),
+  "B_LANGUAGE" VARCHAR(20),
+  "B_LONG_NAME" VARCHAR(500),
+  "B_MODULE_UUID" VARCHAR(50),
+  "B_MODULE_UUID_PATH" VARCHAR(1500),
+  "B_NAME" VARCHAR(500),
+  "B_PATH" VARCHAR(2000),
+  "B_QUALIFIER" VARCHAR(10)
+);
+CREATE INDEX "PROJECTS_ORGANIZATION" ON "PROJECTS" ("ORGANIZATION_UUID");
+CREATE UNIQUE INDEX "PROJECTS_KEE" ON "PROJECTS" ("KEE");
+CREATE INDEX "PROJECTS_ROOT_UUID" ON "PROJECTS" ("ROOT_UUID");
+CREATE UNIQUE INDEX "PROJECTS_UUID" ON "PROJECTS" ("UUID");
+CREATE INDEX "PROJECTS_PROJECT_UUID" ON "PROJECTS" ("PROJECT_UUID");
+CREATE INDEX "PROJECTS_MODULE_UUID" ON "PROJECTS" ("MODULE_UUID");
+CREATE INDEX "PROJECTS_QUALIFIER" ON "PROJECTS" ("QUALIFIER");
+
+CREATE TABLE "PROJECT_BRANCHES" (
+  "UUID" VARCHAR(50) NOT NULL,
+  "PROJECT_UUID" VARCHAR(50) NOT NULL,
+  "KEE" VARCHAR(255) NOT NULL,
+  "KEY_TYPE" VARCHAR(12) NOT NULL,
+  "BRANCH_TYPE" VARCHAR(12),
+  "MERGE_BRANCH_UUID" VARCHAR(50),
+  "PULL_REQUEST_BINARY" BLOB,
+  "CREATED_AT" BIGINT NOT NULL,
+  "UPDATED_AT" BIGINT NOT NULL,
+
+  CONSTRAINT "PK_PROJECT_BRANCHES" PRIMARY KEY ("UUID")
+);
+CREATE UNIQUE INDEX "PROJECT_BRANCHES_KEE_KEY_TYPE" ON "PROJECT_BRANCHES" ("PROJECT_UUID", "KEE", "KEY_TYPE");
\ No newline at end of file
index 74e2bc0f828a9ab71b1753cbe8c18979a1ec32f0..640e389971237bf80f5400f2c0d21e35ce71a993 100644 (file)
  */
 package org.sonar.server.branch.ws;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.io.Resources;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Function;
 import javax.annotation.Nullable;
+import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.component.BranchDto;
@@ -49,6 +51,7 @@ import org.sonarqube.ws.ProjectBranches;
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Collections.singletonList;
 import static org.sonar.api.measures.CoreMetrics.ALERT_STATUS_KEY;
+import static org.sonar.api.resources.Qualifiers.APP;
 import static org.sonar.api.resources.Qualifiers.PROJECT;
 import static org.sonar.api.utils.DateUtils.formatDateTime;
 import static org.sonar.api.web.UserRole.USER;
@@ -66,6 +69,8 @@ import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesEx
 
 public class ListAction implements BranchWsAction {
 
+  private static final Set<String> ALLOWED_QUALIFIERS = ImmutableSet.of(PROJECT, APP);
+
   private final DbClient dbClient;
   private final UserSession userSession;
   private final ComponentFinder componentFinder;
@@ -85,6 +90,7 @@ public class ListAction implements BranchWsAction {
       .setDescription("List the branches of a project.<br/>" +
         "Requires 'Browse' or 'Execute analysis' rights on the specified project.")
       .setResponseExample(Resources.getResource(getClass(), "list-example.json"))
+      .setChangelog(new Change("7.2", "Application can be used on this web service"))
       .setHandler(this);
 
     addProjectParam(action);
@@ -97,11 +103,11 @@ public class ListAction implements BranchWsAction {
     try (DbSession dbSession = dbClient.openSession(false)) {
       ComponentDto project = componentFinder.getByKey(dbSession, projectKey);
       checkPermission(project);
-      checkArgument(project.isEnabled() && PROJECT.equals(project.qualifier()), "Invalid project key");
+      checkArgument(ALLOWED_QUALIFIERS.contains(project.qualifier()), "Invalid project");
 
       Collection<BranchDto> branches = dbClient.branchDao().selectByComponent(dbSession, project).stream()
         .filter(b -> b.getBranchType() == SHORT || b.getBranchType() == LONG)
-        .collect(MoreCollectors.toList());
+        .collect(toList());
       List<String> branchUuids = branches.stream().map(BranchDto::getUuid).collect(toList());
 
       Map<String, BranchDto> mergeBranchesByUuid = dbClient.branchDao()
index 66a4b5fe0621e8193c6e111093f3551f02a006a3..14153e3bc6ff833f28a7a6f8cad9c85ecaaa374f 100644 (file)
  */
 package org.sonar.server.component;
 
+import com.google.common.collect.ImmutableSet;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import javax.annotation.Nullable;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.resources.Qualifiers;
@@ -46,6 +48,8 @@ import static org.sonar.server.ws.WsUtils.checkRequest;
 
 public class ComponentUpdater {
 
+  private static final Set<String> MAIN_BRANCH_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.APP);
+
   private final DbClient dbClient;
   private final I18n i18n;
   private final System2 system2;
@@ -74,7 +78,7 @@ public class ComponentUpdater {
     checkKeyFormat(newComponent.qualifier(), newComponent.key());
     ComponentDto componentDto = createRootComponent(dbSession, newComponent);
     if (isRootProject(componentDto)) {
-      createBranch(dbSession, componentDto.uuid());
+      createMainBranch(dbSession, componentDto.uuid());
     }
     removeDuplicatedProjects(dbSession, componentDto.getDbKey());
     handlePermissionTemplate(dbSession, componentDto, newComponent.getOrganizationUuid(), userId);
@@ -111,10 +115,11 @@ public class ComponentUpdater {
   }
 
   private static boolean isRootProject(ComponentDto componentDto) {
-    return Scopes.PROJECT.equals(componentDto.scope()) && Qualifiers.PROJECT.equals(componentDto.qualifier());
+    return Scopes.PROJECT.equals(componentDto.scope())
+      && MAIN_BRANCH_QUALIFIERS.contains(componentDto.qualifier());
   }
 
-  private BranchDto createBranch(DbSession session, String componentUuid) {
+  private BranchDto createMainBranch(DbSession session, String componentUuid) {
     BranchDto branch = new BranchDto()
       .setBranchType(BranchType.LONG)
       .setUuid(componentUuid)
index b5052c0177ef35455524d1af63277738e967034d..29e881db26ba1ef08f90bb9834478f238c3770dc 100644 (file)
@@ -84,6 +84,7 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
 import static java.lang.String.format;
 import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
 import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
 import static org.elasticsearch.index.query.QueryBuilders.existsQuery;
 import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
@@ -183,6 +184,32 @@ public class IssueIndex {
     this.sorting.addDefault(IssueIndexDefinition.FIELD_ISSUE_KEY);
   }
 
+  public SearchResponse search(IssueQuery query, SearchOptions options) {
+    SearchRequestBuilder requestBuilder = client.prepareSearch(INDEX_TYPE_ISSUE);
+
+    configureSorting(query, requestBuilder);
+    configurePagination(options, requestBuilder);
+    configureRouting(query, options, requestBuilder);
+
+    QueryBuilder esQuery = matchAllQuery();
+    BoolQueryBuilder esFilter = boolQuery();
+    Map<String, QueryBuilder> filters = createFilters(query);
+    for (QueryBuilder filter : filters.values()) {
+      if (filter != null) {
+        esFilter.must(filter);
+      }
+    }
+    if (esFilter.hasClauses()) {
+      requestBuilder.setQuery(boolQuery().must(esQuery).filter(esFilter));
+    } else {
+      requestBuilder.setQuery(esQuery);
+    }
+
+    configureStickyFacets(query, options, filters, esQuery, requestBuilder);
+    requestBuilder.setFetchSource(false);
+    return requestBuilder.get();
+  }
+
   /**
    * Optimization - do not send ES request to all shards when scope is restricted
    * to a set of projects. Because project UUID is used for routing, the request
@@ -201,31 +228,95 @@ public class IssueIndex {
     esSearch.setFrom(options.getOffset()).setSize(options.getLimit());
   }
 
+  private Map<String, QueryBuilder> createFilters(IssueQuery query) {
+    Map<String, QueryBuilder> filters = new HashMap<>();
+    filters.put("__authorization", createAuthorizationFilter(query.checkAuthorization()));
+
+    // Issue is assigned Filter
+    if (BooleanUtils.isTrue(query.assigned())) {
+      filters.put(IS_ASSIGNED_FILTER, existsQuery(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID));
+    } else if (BooleanUtils.isFalse(query.assigned())) {
+      filters.put(IS_ASSIGNED_FILTER, boolQuery().mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID)));
+    }
+
+    // Issue is Resolved Filter
+    String isResolved = "__isResolved";
+    if (BooleanUtils.isTrue(query.resolved())) {
+      filters.put(isResolved, existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION));
+    } else if (BooleanUtils.isFalse(query.resolved())) {
+      filters.put(isResolved, boolQuery().mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)));
+    }
+
+    // Field Filters
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_KEY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_KEY, query.issueKeys()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, query.assignees()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, query.languages()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_TAGS, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_TAGS, query.tags()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_TYPE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_TYPE, query.types()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, query.resolutions()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query.authors()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_RULE_ID, createTermsFilter(
+      IssueIndexDefinition.FIELD_ISSUE_RULE_ID,
+      query.rules().stream().map(RuleDefinitionDto::getId).collect(Collectors.toList())));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, query.severities()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_STATUS, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_STATUS, query.statuses()));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID, createTermFilter(IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID, query.organizationUuid()));
+
+    addComponentRelatedFilters(query, filters);
+
+    addDatesFilter(filters, query);
+    addCreatedAfterByProjectsFilter(filters, query);
+    return filters;
+  }
+
   private static void addComponentRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
-    QueryBuilder viewFilter = createViewFilter(query.viewUuids());
+    addCommonComponentRelatedFilters(query, filters);
+    if (query.viewUuids().isEmpty()) {
+      addBranchComponentRelatedFilters(query, filters);
+    } else {
+      addViewRelatedFilters(query, filters);
+    }
+  }
+
+  private static void addCommonComponentRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
     QueryBuilder componentFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.componentUuids());
     QueryBuilder projectFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, query.projectUuids());
     QueryBuilder moduleRootFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_PATH, query.moduleRootUuids());
     QueryBuilder moduleFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, query.moduleUuids());
     QueryBuilder directoryFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, query.directories());
     QueryBuilder fileFilter = createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, query.fileUuids());
-    QueryBuilder branchFilter = createTermFilter(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, query.branchUuid());
-    filters.put("__is_main_branch", createTermFilter(IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(query.isMainBranch())));
 
     if (BooleanUtils.isTrue(query.onComponentOnly())) {
       filters.put(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, componentFilter);
     } else {
-      filters.put("__view", viewFilter);
       filters.put(IssueIndexDefinition.FIELD_ISSUE_PROJECT_UUID, projectFilter);
-      filters.put(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, branchFilter);
       filters.put("__module", moduleRootFilter);
       filters.put(IssueIndexDefinition.FIELD_ISSUE_MODULE_UUID, moduleFilter);
       filters.put(IssueIndexDefinition.FIELD_ISSUE_DIRECTORY_PATH, directoryFilter);
-      if (fileFilter != null) {
-        filters.put(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, fileFilter);
-      } else {
-        filters.put(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, componentFilter);
-      }
+      filters.put(IssueIndexDefinition.FIELD_ISSUE_COMPONENT_UUID, fileFilter != null ? fileFilter : componentFilter);
+    }
+  }
+
+  private static void addBranchComponentRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
+    if (BooleanUtils.isTrue(query.onComponentOnly())) {
+      return;
+    }
+    QueryBuilder branchFilter = createTermFilter(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, query.branchUuid());
+    filters.put("__is_main_branch", createTermFilter(IssueIndexDefinition.FIELD_ISSUE_IS_MAIN_BRANCH, Boolean.toString(query.isMainBranch())));
+    filters.put(IssueIndexDefinition.FIELD_ISSUE_BRANCH_UUID, branchFilter);
+  }
+
+  private static void addViewRelatedFilters(IssueQuery query, Map<String, QueryBuilder> filters) {
+    if (BooleanUtils.isTrue(query.onComponentOnly())) {
+      return;
+    }
+    Collection<String> viewUuids = query.viewUuids();
+    String branchUuid = query.branchUuid();
+    boolean onApplicationBranch = branchUuid != null && !viewUuids.isEmpty();
+    if (onApplicationBranch) {
+      filters.put("__view", createViewFilter(singletonList(query.branchUuid())));
+    } else {
+      filters.put("__view", createViewFilter(viewUuids));
     }
   }
 
@@ -341,32 +432,6 @@ public class IssueIndex {
     return value == null ? null : termQuery(field, value);
   }
 
-  public SearchResponse search(IssueQuery query, SearchOptions options) {
-    SearchRequestBuilder requestBuilder = client.prepareSearch(INDEX_TYPE_ISSUE);
-
-    configureSorting(query, requestBuilder);
-    configurePagination(options, requestBuilder);
-    configureRouting(query, options, requestBuilder);
-
-    QueryBuilder esQuery = matchAllQuery();
-    BoolQueryBuilder esFilter = boolQuery();
-    Map<String, QueryBuilder> filters = createFilters(query);
-    for (QueryBuilder filter : filters.values()) {
-      if (filter != null) {
-        esFilter.must(filter);
-      }
-    }
-    if (esFilter.hasClauses()) {
-      requestBuilder.setQuery(boolQuery().must(esQuery).filter(esFilter));
-    } else {
-      requestBuilder.setQuery(esQuery);
-    }
-
-    configureStickyFacets(query, options, filters, esQuery, requestBuilder);
-    requestBuilder.setFetchSource(false);
-    return requestBuilder.get();
-  }
-
   private void configureSorting(IssueQuery query, SearchRequestBuilder esRequest) {
     createSortBuilders(query).forEach(esRequest::addSort);
   }
@@ -380,48 +445,6 @@ public class IssueIndex {
     return sorting.fillDefault();
   }
 
-  private Map<String, QueryBuilder> createFilters(IssueQuery query) {
-    Map<String, QueryBuilder> filters = new HashMap<>();
-    filters.put("__authorization", createAuthorizationFilter(query.checkAuthorization()));
-
-    // Issue is assigned Filter
-    if (BooleanUtils.isTrue(query.assigned())) {
-      filters.put(IS_ASSIGNED_FILTER, existsQuery(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID));
-    } else if (BooleanUtils.isFalse(query.assigned())) {
-      filters.put(IS_ASSIGNED_FILTER, boolQuery().mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID)));
-    }
-
-    // Issue is Resolved Filter
-    String isResolved = "__isResolved";
-    if (BooleanUtils.isTrue(query.resolved())) {
-      filters.put(isResolved, existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION));
-    } else if (BooleanUtils.isFalse(query.resolved())) {
-      filters.put(isResolved, boolQuery().mustNot(existsQuery(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION)));
-    }
-
-    // Field Filters
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_KEY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_KEY, query.issueKeys()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_ASSIGNEE_UUID, query.assignees()));
-
-    addComponentRelatedFilters(query, filters);
-
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_LANGUAGE, query.languages()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_TAGS, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_TAGS, query.tags()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_TYPE, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_TYPE, query.types()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_RESOLUTION, query.resolutions()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_AUTHOR_LOGIN, query.authors()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_RULE_ID, createTermsFilter(
-      IssueIndexDefinition.FIELD_ISSUE_RULE_ID,
-      query.rules().stream().map(RuleDefinitionDto::getId).collect(Collectors.toList())));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_SEVERITY, query.severities()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_STATUS, createTermsFilter(IssueIndexDefinition.FIELD_ISSUE_STATUS, query.statuses()));
-    filters.put(IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID, createTermFilter(IssueIndexDefinition.FIELD_ISSUE_ORGANIZATION_UUID, query.organizationUuid()));
-
-    addDatesFilter(filters, query);
-    addCreatedAfterByProjectsFilter(filters, query);
-    return filters;
-  }
-
   private QueryBuilder createAuthorizationFilter(boolean checkAuthorization) {
     if (checkAuthorization) {
       return authorizationTypeSupport.createQueryFilter();
index 384aa6274a3a55498b73fce3f8756a0cb97ba64e..691087c4690b5d31f269e87ff6528eff03d5e361 100644 (file)
@@ -29,7 +29,6 @@ import org.sonar.api.web.UserRole;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
 import org.sonar.server.component.ComponentCleanerService;
 import org.sonar.server.component.ComponentFinder;
 import org.sonar.server.component.TestComponentFinder;
@@ -96,16 +95,16 @@ public class DeleteActionTest {
     tester.newRequest().execute();
   }
 
-  public void fail_branch_does_not_exist() {
+  @Test
+  public void fail_if_branch_does_not_exist() {
     ComponentDto project = db.components().insertPrivateProject();
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
     userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
 
     expectedException.expect(NotFoundException.class);
     expectedException.expectMessage("Branch 'branch1' not found");
 
     tester.newRequest()
-      .setParam("project", file.getDbKey())
+      .setParam("project", project.getDbKey())
       .setParam("branch", "branch1")
       .execute();
   }
@@ -141,10 +140,8 @@ public class DeleteActionTest {
 
   @Test
   public void delete_branch() {
-
     ComponentDto project = db.components().insertMainBranch();
     ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("branch1"));
-
     userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
 
     tester.newRequest()
index 46c701c140eaf5dc780f9561faf0337e1687b845..a865ac049cc8fdb2474556ef7c31c2207ae8288e 100644 (file)
@@ -65,7 +65,6 @@ import static org.sonar.api.utils.DateUtils.dateToLong;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
 import static org.sonar.api.web.UserRole.USER;
 import static org.sonar.core.permission.GlobalPermissions.SCAN_EXECUTION;
-import static org.sonar.db.component.BranchType.LONG;
 import static org.sonar.db.component.BranchType.SHORT;
 import static org.sonar.db.component.SnapshotTesting.newAnalysis;
 import static org.sonar.test.JsonAssert.assertJson;
@@ -111,7 +110,7 @@ public class ListActionTest {
     ComponentDto project = db.components().insertPrivateProject(p -> p.setDbKey("sonarqube"));
 
     ComponentDto longLivingBranch = db.components()
-      .insertProjectBranch(project, b -> b.setKey("feature/bar").setBranchType(LONG));
+      .insertProjectBranch(project, b -> b.setKey("feature/bar").setBranchType(org.sonar.db.component.BranchType.LONG));
     db.getDbClient().snapshotDao().insert(db.getSession(),
       newAnalysis(longLivingBranch).setLast(true).setCreatedAt(parseDateTime("2017-04-01T01:15:42+0100").getTime()));
     db.measures().insertLiveMeasure(longLivingBranch, qualityGateStatus, m -> m.setData("OK"));
@@ -143,7 +142,7 @@ public class ListActionTest {
     ComponentDto project = db.components().insertPrivateProject(p -> p.setDbKey("sonarqube"));
 
     ComponentDto longLivingBranch = db.components()
-      .insertProjectBranch(project, b -> b.setKey("feature/bar").setBranchType(LONG));
+      .insertProjectBranch(project, b -> b.setKey("feature/bar").setBranchType(org.sonar.db.component.BranchType.LONG));
     db.getDbClient().snapshotDao().insert(db.getSession(),
       newAnalysis(longLivingBranch).setLast(true).setCreatedAt(parseDateTime("2017-04-01T01:15:42+0100").getTime()));
     db.measures().insertLiveMeasure(longLivingBranch, qualityGateStatus, m -> m.setData("OK"));
@@ -234,7 +233,7 @@ public class ListActionTest {
     ComponentDto project = db.components().insertMainBranch();
     userSession.logIn().addProjectPermission(USER, project);
     ComponentDto longLivingBranch = db.components().insertProjectBranch(project,
-      b -> b.setKey("long").setBranchType(LONG));
+      b -> b.setKey("long").setBranchType(org.sonar.db.component.BranchType.LONG));
     ComponentDto shortLivingBranch = db.components().insertProjectBranch(project,
       b -> b.setKey("short").setBranchType(SHORT).setMergeBranchUuid(longLivingBranch.uuid()));
     ComponentDto shortLivingBranchOnMaster = db.components().insertProjectBranch(project,
@@ -291,7 +290,7 @@ public class ListActionTest {
   public void status_on_long_living_branch() {
     ComponentDto project = db.components().insertMainBranch();
     userSession.logIn().addProjectPermission(USER, project);
-    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(LONG));
+    ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(org.sonar.db.component.BranchType.LONG));
     db.measures().insertLiveMeasure(branch, qualityGateStatus, m -> m.setData("OK"));
 
     ListWsResponse response = ws.newRequest()
@@ -307,7 +306,7 @@ public class ListActionTest {
   public void status_on_short_living_branches() {
     ComponentDto project = db.components().insertMainBranch();
     userSession.logIn().addProjectPermission(USER, project);
-    ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(LONG));
+    ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(org.sonar.db.component.BranchType.LONG));
     ComponentDto shortLivingBranch = db.components().insertProjectBranch(project,
       b -> b.setKey("short").setBranchType(SHORT).setMergeBranchUuid(longLivingBranch.uuid()));
     db.measures().insertLiveMeasure(shortLivingBranch, qualityGateStatus, m -> m.setData("OK"));
@@ -345,7 +344,7 @@ public class ListActionTest {
   public void status_on_short_living_branch_with_no_issue() {
     ComponentDto project = db.components().insertMainBranch();
     userSession.logIn().addProjectPermission(USER, project);
-    ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(LONG));
+    ComponentDto longLivingBranch = db.components().insertProjectBranch(project, b -> b.setBranchType(org.sonar.db.component.BranchType.LONG));
     db.components().insertProjectBranch(project, b -> b.setBranchType(SHORT).setMergeBranchUuid(longLivingBranch.uuid()));
     issueIndexer.indexOnStartup(emptySet());
     permissionIndexerTester.allowOnlyAnyone(project);
@@ -368,7 +367,7 @@ public class ListActionTest {
     ComponentDto project = db.components().insertMainBranch();
     userSession.logIn().addProjectPermission(USER, project);
     ComponentDto shortLivingBranch1 = db.components().insertProjectBranch(project, b -> b.setBranchType(SHORT).setMergeBranchUuid(project.uuid()));
-    ComponentDto longLivingBranch2 = db.components().insertProjectBranch(project, b -> b.setBranchType(LONG));
+    ComponentDto longLivingBranch2 = db.components().insertProjectBranch(project, b -> b.setBranchType(org.sonar.db.component.BranchType.LONG));
     ComponentDto shortLivingBranch2 = db.components().insertProjectBranch(project, b -> b.setBranchType(SHORT).setMergeBranchUuid(longLivingBranch2.uuid()));
     db.getDbClient().snapshotDao().insert(db.getSession(),
       newAnalysis(longLivingBranch2).setCreatedAt(lastAnalysisLongLivingBranch));
@@ -394,6 +393,24 @@ public class ListActionTest {
         tuple(BranchType.SHORT, true, lastAnalysisShortLivingBranch));
   }
 
+  @Test
+  public void application_branches() {
+    ComponentDto application = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    db.components().insertProjectBranch(application, b -> b.setKey("feature/bar"));
+    db.components().insertProjectBranch(application, b -> b.setKey("feature/foo"));
+    userSession.logIn().addProjectPermission(USER, application);
+
+    ListWsResponse response = ws.newRequest()
+      .setParam("project", application.getDbKey())
+      .executeProtobuf(ListWsResponse.class);
+
+    assertThat(response.getBranchesList())
+      .extracting(Branch::getName, Branch::getType)
+      .containsExactlyInAnyOrder(
+        tuple("feature/foo", BranchType.LONG),
+        tuple("feature/bar", BranchType.LONG));
+  }
+
   @Test
   public void fail_when_using_branch_db_key() throws Exception {
     OrganizationDto organization = db.organizations().insert();
@@ -424,7 +441,7 @@ public class ListActionTest {
     userSession.logIn().addProjectPermission(USER, project);
 
     expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Invalid project key");
+    expectedException.expectMessage("Invalid project");
 
     ws.newRequest()
       .setParam("project", file.getDbKey())
index a1378970de8e0472fdef5efdfd513ebf14d8800c..83b3e8986b2c342d2b941e736d9b789d14c9ffc1 100644 (file)
@@ -136,7 +136,7 @@ public class ComponentUpdaterTest {
   }
 
   @Test
-  public void create_project_with_branch() {
+  public void create_project_with_deprecated_branch() {
     ComponentDto project = underTest.create(db.getSession(),
       NewComponent.newComponentBuilder()
         .setKey(DEFAULT_PROJECT_KEY)
@@ -150,7 +150,7 @@ public class ComponentUpdaterTest {
   }
 
   @Test
-  public void persist_and_index_when_creating_view() {
+  public void create_view() {
     NewComponent view = NewComponent.newComponentBuilder()
       .setKey("view-key")
       .setName("view-name")
@@ -170,15 +170,15 @@ public class ComponentUpdaterTest {
   }
 
   @Test
-  public void persist_and_index_when_creating_application() {
-    NewComponent view = NewComponent.newComponentBuilder()
+  public void create_application() {
+    NewComponent application = NewComponent.newComponentBuilder()
       .setKey("app-key")
       .setName("app-name")
       .setQualifier(APP)
       .setOrganizationUuid(db.getDefaultOrganization().getUuid())
       .build();
 
-    ComponentDto returned = underTest.create(db.getSession(), view, null);
+    ComponentDto returned = underTest.create(db.getSession(), application, null);
 
     ComponentDto loaded = db.getDbClient().componentDao().selectOrFailByUuid(db.getSession(), returned.uuid());
     assertThat(loaded.getDbKey()).isEqualTo("app-key");
@@ -186,25 +186,12 @@ public class ComponentUpdaterTest {
     assertThat(loaded.qualifier()).isEqualTo("APP");
     assertThat(projectIndexers.hasBeenCalled(loaded.uuid(), ProjectIndexer.Cause.PROJECT_CREATION)).isTrue();
     Optional<BranchDto> branch = db.getDbClient().branchDao().selectByUuid(db.getSession(), returned.uuid());
-    assertThat(branch).isNotPresent();
-  }
-
-  @Test
-  public void create_application() {
-    NewComponent view = NewComponent.newComponentBuilder()
-      .setKey("app-key")
-      .setName("app-name")
-      .setQualifier(APP)
-      .setOrganizationUuid(db.getDefaultOrganization().getUuid())
-      .build();
-
-    ComponentDto returned = underTest.create(db.getSession(), view, null);
-
-    ComponentDto loaded = db.getDbClient().componentDao().selectByKey(db.getSession(), returned.getDbKey()).get();
-    assertThat(loaded.getDbKey()).isEqualTo("app-key");
-    assertThat(loaded.name()).isEqualTo("app-name");
-    assertThat(loaded.qualifier()).isEqualTo("APP");
-    assertThat(projectIndexers.hasBeenCalled(loaded.uuid(), ProjectIndexer.Cause.PROJECT_CREATION)).isTrue();
+    assertThat(branch).isPresent();
+    assertThat(branch.get().getKey()).isEqualTo(BranchDto.DEFAULT_MAIN_BRANCH_NAME);
+    assertThat(branch.get().getMergeBranchUuid()).isNull();
+    assertThat(branch.get().getBranchType()).isEqualTo(BranchType.LONG);
+    assertThat(branch.get().getUuid()).isEqualTo(returned.uuid());
+    assertThat(branch.get().getProjectUuid()).isEqualTo(returned.uuid());
   }
 
   @Test
index 0c2060e8a05837655391f59c4125e09c282d34ef..b02ac50d17ded7434556ecd5d490e30f3c9b83de 100644 (file)
@@ -68,6 +68,7 @@ import static org.assertj.core.api.Assertions.entry;
 import static org.assertj.core.api.Assertions.tuple;
 import static org.junit.rules.ExpectedException.none;
 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
+import static org.sonar.api.resources.Qualifiers.APP;
 import static org.sonar.api.rules.RuleType.BUG;
 import static org.sonar.api.rules.RuleType.CODE_SMELL;
 import static org.sonar.api.rules.RuleType.VULNERABILITY;
@@ -269,34 +270,32 @@ public class IssueIndexTest {
   }
 
   @Test
-  public void filter_by_views() {
-    OrganizationDto organizationDto = newOrganizationDto();
-    ComponentDto project1 = newPrivateProjectDto(organizationDto);
-    ComponentDto file1 = newFileDto(project1, null);
-    ComponentDto project2 = newPrivateProjectDto(organizationDto);
-    indexIssues(
-      // Project1 has 2 issues (one on a file and one on the project itself)
-      newDoc("I1", project1),
-      newDoc("I2", file1),
-      // Project2 has 1 issue
-      newDoc("I3", project2));
+  public void filter_by_portfolios() {
+    ComponentDto portfolio1 = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto portfolio2 = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project1));
+    ComponentDto project2 = db.components().insertPrivateProject();
 
-    // The view1 is containing 2 issues from project1
-    String view1 = "ABCD";
-    indexView(view1, asList(project1.uuid()));
+    IssueDoc issueOnProject1 = newDoc(project1);
+    IssueDoc issueOnFile = newDoc(file);
+    IssueDoc issueOnProject2 = newDoc(project2);
 
-    // The view2 is containing 1 issue from project2
-    String view2 = "CDEF";
-    indexView(view2, asList(project2.uuid()));
+    indexIssues(issueOnProject1, issueOnFile, issueOnProject2);
+    indexView(portfolio1.uuid(), singletonList(project1.uuid()));
+    indexView(portfolio2.uuid(), singletonList(project2.uuid()));
 
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(view1)), "I1", "I2");
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(view2)), "I3");
-    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(asList(view1, view2)), "I1", "I2", "I3");
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio1.uuid())), issueOnProject1.key(), issueOnFile.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio2.uuid())), issueOnProject2.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(asList(portfolio1.uuid(), portfolio2.uuid())), issueOnProject1.key(), issueOnFile.key(), issueOnProject2.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio1.uuid())).projectUuids(singletonList(project1.uuid())), issueOnProject1.key(),
+      issueOnFile.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(portfolio1.uuid())).fileUuids(singletonList(file.uuid())), issueOnFile.key());
     assertThatSearchReturnsEmpty(IssueQuery.builder().viewUuids(singletonList("unknown")));
   }
 
   @Test
-  public void filter_by_views_not_having_projects() {
+  public void filter_by_portfolios_not_having_projects() {
     OrganizationDto organizationDto = newOrganizationDto();
     ComponentDto project1 = newPrivateProjectDto(organizationDto);
     ComponentDto file1 = newFileDto(project1, null);
@@ -307,43 +306,6 @@ public class IssueIndexTest {
     assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(view1)));
   }
 
-  @Test
-  public void filter_by_created_after_by_projects() {
-    Date now = new Date();
-    OrganizationDto organizationDto = newOrganizationDto();
-    ComponentDto project1 = newPrivateProjectDto(organizationDto);
-    IssueDoc project1Issue1 = newDoc(project1).setFuncCreationDate(addDays(now, -10));
-    IssueDoc project1Issue2 = newDoc(project1).setFuncCreationDate(addDays(now, -20));
-    ComponentDto project2 = newPrivateProjectDto(organizationDto);
-    IssueDoc project2Issue1 = newDoc(project2).setFuncCreationDate(addDays(now, -15));
-    IssueDoc project2Issue2 = newDoc(project2).setFuncCreationDate(addDays(now, -30));
-    indexIssues(project1Issue1, project1Issue2, project2Issue1, project2Issue2);
-
-    // Search for issues of project 1 having less than 15 days
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfterByProjectUuids(ImmutableMap.of(project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -15), true))),
-      project1Issue1.key());
-
-    // Search for issues of project 1 having less than 14 days and project 2 having less then 25 days
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfterByProjectUuids(ImmutableMap.of(
-        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -14), true),
-        project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -25), true))),
-      project1Issue1.key(), project2Issue1.key());
-
-    // Search for issues of project 1 having less than 30 days
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfterByProjectUuids(ImmutableMap.of(
-        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -30), true))),
-      project1Issue1.key(), project1Issue2.key());
-
-    // Search for issues of project 1 and project 2 having less than 5 days
-    assertThatSearchReturnsOnly(IssueQuery.builder()
-      .createdAfterByProjectUuids(ImmutableMap.of(
-        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true),
-        project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true))));
-  }
-
   @Test
   public void filter_one_issue_by_project_and_branch() {
     ComponentDto project = db.components().insertPrivateProject();
@@ -416,6 +378,95 @@ public class IssueIndexTest {
     assertThatSearchReturnsOnly(IssueQuery.builder(), projectIssue.key());
   }
 
+  @Test
+  public void filter_by_application() {
+    ComponentDto application1 = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto application2 = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project1));
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    IssueDoc issueOnProject1 = newDoc(project1);
+    IssueDoc issueOnFile = newDoc(file);
+    IssueDoc issueOnProject2 = newDoc(project2);
+
+    indexIssues(issueOnProject1, issueOnFile, issueOnProject2);
+    indexView(application1.uuid(), singletonList(project1.uuid()));
+    indexView(application2.uuid(), singletonList(project2.uuid()));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application1.uuid())), issueOnProject1.key(), issueOnFile.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application2.uuid())), issueOnProject2.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(asList(application1.uuid(), application2.uuid())), issueOnProject1.key(), issueOnFile.key(), issueOnProject2.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application1.uuid())).projectUuids(singletonList(project1.uuid())), issueOnProject1.key(),
+      issueOnFile.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(application1.uuid())).fileUuids(singletonList(file.uuid())), issueOnFile.key());
+    assertThatSearchReturnsEmpty(IssueQuery.builder().viewUuids(singletonList("unknown")));
+  }
+
+  @Test
+  public void filter_by_application_and_branch() {
+    ComponentDto application = db.components().insertMainBranch(c -> c.setQualifier(APP));
+    ComponentDto branch1 = db.components().insertProjectBranch(application);
+    ComponentDto branch2 = db.components().insertProjectBranch(application);
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto file = db.components().insertComponent(newFileDto(project1));
+    ComponentDto project2 = db.components().insertPrivateProject();
+
+    IssueDoc issueOnProject1 = newDoc(project1);
+    IssueDoc issueOnFile = newDoc(file);
+    IssueDoc issueOnProject2 = newDoc(project2);
+    indexIssues(issueOnProject1, issueOnFile, issueOnProject2);
+
+    indexView(branch1.uuid(), singletonList(project1.uuid()));
+    indexView(branch2.uuid(), singletonList(project2.uuid()));
+
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(branch1.uuid())).branchUuid(branch1.uuid()).mainBranch(false), issueOnProject1.key(),
+      issueOnFile.key());
+    assertThatSearchReturnsOnly(
+      IssueQuery.builder().viewUuids(singletonList(branch1.uuid())).projectUuids(singletonList(project1.uuid())).branchUuid(branch1.uuid()).mainBranch(false),
+      issueOnProject1.key(), issueOnFile.key());
+    assertThatSearchReturnsOnly(IssueQuery.builder().viewUuids(singletonList(branch1.uuid())).fileUuids(singletonList(file.uuid())).branchUuid(branch1.uuid()).mainBranch(false),
+      issueOnFile.key());
+    assertThatSearchReturnsEmpty(IssueQuery.builder().branchUuid("unknown"));
+  }
+
+  @Test
+  public void filter_by_created_after_by_projects() {
+    Date now = new Date();
+    OrganizationDto organizationDto = newOrganizationDto();
+    ComponentDto project1 = newPrivateProjectDto(organizationDto);
+    IssueDoc project1Issue1 = newDoc(project1).setFuncCreationDate(addDays(now, -10));
+    IssueDoc project1Issue2 = newDoc(project1).setFuncCreationDate(addDays(now, -20));
+    ComponentDto project2 = newPrivateProjectDto(organizationDto);
+    IssueDoc project2Issue1 = newDoc(project2).setFuncCreationDate(addDays(now, -15));
+    IssueDoc project2Issue2 = newDoc(project2).setFuncCreationDate(addDays(now, -30));
+    indexIssues(project1Issue1, project1Issue2, project2Issue1, project2Issue2);
+
+    // Search for issues of project 1 having less than 15 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfterByProjectUuids(ImmutableMap.of(project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -15), true))),
+      project1Issue1.key());
+
+    // Search for issues of project 1 having less than 14 days and project 2 having less then 25 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfterByProjectUuids(ImmutableMap.of(
+        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -14), true),
+        project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -25), true))),
+      project1Issue1.key(), project2Issue1.key());
+
+    // Search for issues of project 1 having less than 30 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfterByProjectUuids(ImmutableMap.of(
+        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -30), true))),
+      project1Issue1.key(), project1Issue2.key());
+
+    // Search for issues of project 1 and project 2 having less than 5 days
+    assertThatSearchReturnsOnly(IssueQuery.builder()
+      .createdAfterByProjectUuids(ImmutableMap.of(
+        project1.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true),
+        project2.uuid(), new IssueQuery.PeriodStart(addDays(now, -5), true))));
+  }
+
   @Test
   public void filter_by_severities() {
     ComponentDto project = newPrivateProjectDto(newOrganizationDto());
index 62e2c28b31f10ecac81f3147239684ad09680eda..d5298d243693c89dd32677fc25790566891c81a0 100644 (file)
@@ -31,7 +31,6 @@ import org.sonar.api.rule.RuleKey;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.Durations;
 import org.sonar.api.utils.System2;
-import org.sonar.api.web.UserRole;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
 import org.sonar.db.component.ComponentDto;
@@ -61,8 +60,10 @@ import org.sonarqube.ws.client.issue.IssuesWsParameters;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
+import static org.sonar.api.resources.Qualifiers.APP;
 import static org.sonar.api.utils.DateUtils.addDays;
 import static org.sonar.api.utils.DateUtils.parseDateTime;
+import static org.sonar.api.web.UserRole.USER;
 import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01;
 import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02;
 import static org.sonar.db.component.BranchType.PULL_REQUEST;
@@ -665,15 +666,16 @@ public class SearchActionComponentsTest {
 
   @Test
   public void search_by_application_key() {
-    ComponentDto project1 = db.components().insertPublicProject();
-    ComponentDto project2 = db.components().insertPublicProject();
-    ComponentDto application = db.components().insertApplication(db.getDefaultOrganization());
-    db.components().insertComponents(newProjectCopy("PC1", project1, application));
-    db.components().insertComponents(newProjectCopy("PC2", project2, application));
+    ComponentDto application = db.components().insertPrivateApplication(db.getDefaultOrganization());
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+    db.components().insertComponents(newProjectCopy(project1, application));
+    db.components().insertComponents(newProjectCopy(project2, application));
     RuleDefinitionDto rule = db.rules().insert();
     IssueDto issue1 = db.issues().insert(rule, project1, project1);
     IssueDto issue2 = db.issues().insert(rule, project2, project2);
     allowAnyoneOnProjects(project1, project2, application);
+    userSession.addProjectPermission(USER, application);
     indexIssuesAndViews();
 
     SearchWsResponse result = ws.newRequest()
@@ -684,6 +686,30 @@ public class SearchActionComponentsTest {
       .containsExactlyInAnyOrder(issue1.getKey(), issue2.getKey());
   }
 
+  @Test
+  public void search_by_application_key_and_branch() {
+    ComponentDto application = db.components().insertMainBranch(c -> c.setQualifier(APP));
+    ComponentDto applicationBranch = db.components().insertProjectBranch(application);
+    ComponentDto project1 = db.components().insertPrivateProject();
+    ComponentDto project2 = db.components().insertPrivateProject();
+    db.components().insertComponents(newProjectCopy(project1, applicationBranch));
+    db.components().insertComponents(newProjectCopy(project2, applicationBranch));
+    RuleDefinitionDto rule = db.rules().insert();
+    IssueDto issue1 = db.issues().insert(rule, project1, project1);
+    IssueDto issue2 = db.issues().insert(rule, project2, project2);
+    allowAnyoneOnProjects(project1, project2, application);
+    userSession.addProjectPermission(USER, application);
+    indexIssuesAndViews();
+
+    SearchWsResponse result = ws.newRequest()
+      .setParam(PARAM_COMPONENT_KEYS, applicationBranch.getKey())
+      .setParam(PARAM_BRANCH, applicationBranch.getBranch())
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(result.getIssuesList()).extracting(Issue::getKey)
+      .containsExactlyInAnyOrder(issue1.getKey(), issue2.getKey());
+  }
+
   @Test
   public void ignore_application_without_browse_permission() {
     ComponentDto project = db.components().insertPublicProject();
@@ -838,7 +864,7 @@ public class SearchActionComponentsTest {
   public void search_by_branch() {
     RuleDefinitionDto rule = db.rules().insert();
     ComponentDto project = db.components().insertPrivateProject();
-    userSession.addProjectPermission(UserRole.USER, project);
+    userSession.addProjectPermission(USER, project);
     ComponentDto projectFile = db.components().insertComponent(newFileDto(project));
     IssueDto projectIssue = db.issues().insertIssue(newIssue(rule, project, projectFile));
     ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setBranchType(SHORT));
@@ -866,7 +892,7 @@ public class SearchActionComponentsTest {
   public void search_by_pull_request() {
     RuleDefinitionDto rule = db.rules().insert();
     ComponentDto project = db.components().insertPrivateProject();
-    userSession.addProjectPermission(UserRole.USER, project);
+    userSession.addProjectPermission(USER, project);
     ComponentDto projectFile = db.components().insertComponent(newFileDto(project));
     IssueDto projectIssue = db.issues().insertIssue(newIssue(rule, project, projectFile));
     ComponentDto pullRequest = db.components().insertProjectBranch(project, b -> b.setBranchType(PULL_REQUEST));
@@ -894,7 +920,7 @@ public class SearchActionComponentsTest {
   public void search_using_main_branch_name() {
     RuleDefinitionDto rule = db.rules().insert();
     ComponentDto project = db.components().insertMainBranch();
-    userSession.addProjectPermission(UserRole.USER, project);
+    userSession.addProjectPermission(USER, project);
     ComponentDto projectFile = db.components().insertComponent(newFileDto(project));
     IssueDto projectIssue = db.issues().insertIssue(newIssue(rule, project, projectFile));
     allowAnyoneOnProjects(project);
@@ -938,7 +964,7 @@ public class SearchActionComponentsTest {
   public void does_not_return_branch_issues_when_using_db_key() {
     RuleDefinitionDto rule = db.rules().insert();
     ComponentDto project = db.components().insertPrivateProject();
-    userSession.addProjectPermission(UserRole.USER, project);
+    userSession.addProjectPermission(USER, project);
     ComponentDto projectFile = db.components().insertComponent(newFileDto(project));
     IssueDto projectIssue = db.issues().insertIssue(newIssue(rule, project, projectFile));
     ComponentDto branch = db.components().insertProjectBranch(project);
index 20bd441df410941c46834acce73d9293d0814fcc..82d4b81197ee1fb7058a4512e770c542f6721d64 100644 (file)
@@ -132,6 +132,15 @@ export class ComponentContainer extends React.PureComponent<Props, State> {
   fetchBranches = (
     component: Component
   ): Promise<{ branchLike?: BranchLike; branchLikes: BranchLike[] }> => {
+    const application = component.breadcrumbs.find(({ qualifier }) => qualifier === 'APP');
+    if (application) {
+      return getBranches(application.key).then(branchLikes => {
+        return {
+          branchLike: this.getCurrentBranchLike(branchLikes),
+          branchLikes
+        };
+      });
+    }
     const project = component.breadcrumbs.find(({ qualifier }) => qualifier === 'TRK');
     return project
       ? Promise.all([getBranches(project.key), getPullRequests(project.key)]).then(
index fc7e79a9be31e61d387cd8de1ec032bd62c307d7..03bb25eac5b5d595bfa91b93536b9773ad462da8 100644 (file)
@@ -54,6 +54,10 @@ import HelpIcon from '../../components/icons-components/HelpIcon';
 import LockIcon from '../../components/icons-components/LockIcon';
 import QualifierIcon from '../../components/icons-components/QualifierIcon';
 import Rating from '../../components/ui/Rating';
+import BranchIcon from '../../components/icons-components/BranchIcon';
+import LongLivingBranchIcon from '../../components/icons-components/LongLivingBranchIcon';
+import PullRequestIcon from '../../components/icons-components/PullRequestIcon';
+import ActionsDropdown, { ActionsDropdownItem } from '../../components/controls/ActionsDropdown';
 
 const exposeLibraries = () => {
   const global = window as any;
@@ -63,9 +67,12 @@ const exposeLibraries = () => {
   global.SonarMeasures = measures;
   global.SonarRequest = { ...request, throwGlobalError, addGlobalSuccessMessage };
   global.SonarComponents = {
+    ActionsDropdown,
+    ActionsDropdownItem,
     AlertErrorIcon,
     AlertSuccessIcon,
     AlertWarnIcon,
+    BranchIcon,
     Button,
     Checkbox,
     CheckIcon,
@@ -86,7 +93,9 @@ const exposeLibraries = () => {
     Level,
     ListFooter,
     LockIcon,
+    LongLivingBranchIcon,
     Modal,
+    PullRequestIcon,
     QualifierIcon,
     Rating,
     ReloadButton,