]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-8640 WS api/projects/search query on key does partial match and is case insensitive
authorTeryk Bellahsene <teryk.bellahsene@sonarsource.com>
Thu, 31 Aug 2017 14:44:59 +0000 (16:44 +0200)
committerStas Vilchik <stas.vilchik@sonarsource.com>
Mon, 11 Sep 2017 09:28:29 +0000 (11:28 +0200)
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentQuery.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/ComponentQueryTest.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java
sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java
tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectSearchTest.java

index 58858246033bd5986990acf35975a0b3b064a624..2568f8ba22b3a1361baea9225ca0ceef368b57c1 100644 (file)
@@ -30,30 +30,16 @@ import static org.sonar.db.DaoDatabaseUtils.buildLikeValue;
 
 public class ComponentQuery {
   private final String nameOrKeyQuery;
+  private final boolean partialMatchOnKey;
   private final String[] qualifiers;
   private final String language;
   private final Boolean isPrivate;
   private final Set<Long> componentIds;
   private final Long analyzedBefore;
 
-  /**
-   * Used by Dev Cockpit 1.9.
-   * Could be removed when Developer Cockpit doesn't use it anymore.
-   *
-   * @deprecated since 5.4, use {@link Builder} instead
-   */
-  @Deprecated
-  public ComponentQuery(@Nullable String nameOrKeyQuery, String... qualifiers) {
-    this.nameOrKeyQuery = nameOrKeyQuery;
-    this.qualifiers = Builder.validateQualifiers(qualifiers);
-    this.language = null;
-    this.componentIds = null;
-    this.isPrivate = null;
-    this.analyzedBefore = null;
-  }
-
   private ComponentQuery(Builder builder) {
     this.nameOrKeyQuery = builder.nameOrKeyQuery;
+    this.partialMatchOnKey = builder.partialMatchOnKey == null ? false : builder.partialMatchOnKey;
     this.qualifiers = builder.qualifiers;
     this.language = builder.language;
     this.componentIds = builder.componentIds;
@@ -78,6 +64,13 @@ public class ComponentQuery {
     return buildLikeValue(nameOrKeyQuery, WildcardPosition.BEFORE_AND_AFTER).toUpperCase(Locale.ENGLISH);
   }
 
+  /**
+   * Used by MyBatis mapper
+   */
+  public boolean isPartialMatchOnKey() {
+    return partialMatchOnKey;
+  }
+
   @CheckForNull
   public String getLanguage() {
     return language;
@@ -104,6 +97,7 @@ public class ComponentQuery {
 
   public static class Builder {
     private String nameOrKeyQuery;
+    private Boolean partialMatchOnKey;
     private String[] qualifiers;
     private String language;
     private Boolean isPrivate;
@@ -115,6 +109,14 @@ public class ComponentQuery {
       return this;
     }
 
+    /**
+     * Beware, can be resource intensive! Should be used with precautions.
+     */
+    public Builder setPartialMatchOnKey(@Nullable Boolean partialMatchOnKey) {
+      this.partialMatchOnKey = partialMatchOnKey;
+      return this;
+    }
+
     public Builder setQualifiers(String... qualifiers) {
       this.qualifiers = qualifiers;
       return this;
@@ -140,13 +142,10 @@ public class ComponentQuery {
       return this;
     }
 
-    protected static String[] validateQualifiers(@Nullable String... qualifiers) {
+    public ComponentQuery build() {
       checkArgument(qualifiers != null && qualifiers.length > 0, "At least one qualifier must be provided");
-      return qualifiers;
-    }
+      checkArgument(nameOrKeyQuery != null || partialMatchOnKey == null, "A query must be provided if a partial match on key is specified.");
 
-    public ComponentQuery build() {
-      validateQualifiers(this.qualifiers);
       return new ComponentQuery(this);
     }
   }
index 905d1ef8717e5e07707ecd3a049108df85fb8a93..d9e90aa811053645e21e6006ebb444ea8db0a970 100644 (file)
       </if>
       <if test="query.nameOrKeyQuery!=null">
         and (
-          p.kee = #{query.nameOrKeyQuery,jdbcType=VARCHAR}
-          or
           upper(p.name) like #{query.nameOrKeyUpperLikeQuery,jdbcType=VARCHAR} escape '/'
+          or
+          <choose>
+            <when test="query.isPartialMatchOnKey()">
+              upper(p.kee) like #{query.nameOrKeyUpperLikeQuery,jdbcType=VARCHAR} escape '/'
+            </when>
+            <otherwise>
+              p.kee = #{query.nameOrKeyQuery,jdbcType=VARCHAR}
+            </otherwise>
+          </choose>
         )
       </if>
       <if test="query.private!=null">
index aae487bf389448b4bf7857ac63ec2191d527fb37..484612e7e28253ac5846631afcd9b8a63f051e37 100644 (file)
@@ -608,8 +608,8 @@ public class ComponentDaoTest {
     ComponentDto provisionedProject = db.components().insertPrivateProject();
     ComponentDto provisionedView = db.components().insertView(organization, (dto) -> {
     });
-    String projectUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organization)).getComponentUuid();
-    String disabledProjectUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organization).setEnabled(false)).getComponentUuid();
+    String projectUuid = db.components().insertProjectAndSnapshot(newPrivateProjectDto(organization)).getComponentUuid();
+    String disabledProjectUuid = db.components().insertProjectAndSnapshot(newPrivateProjectDto(organization).setEnabled(false)).getComponentUuid();
     String viewUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newView(organization)).getComponentUuid();
 
     assertThat(underTest.selectProjects(dbSession))
@@ -621,10 +621,10 @@ public class ComponentDaoTest {
   public void select_provisioned() {
     OrganizationDto organization = db.organizations().insert();
     ComponentDto provisionedProject = db.components()
-      .insertComponent(ComponentTesting.newPrivateProjectDto(organization).setDbKey("provisioned.project").setName("Provisioned Project"));
+      .insertComponent(newPrivateProjectDto(organization).setDbKey("provisioned.project").setName("Provisioned Project"));
     ComponentDto provisionedView = db.components().insertView(organization);
-    String projectUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organization)).getComponentUuid();
-    String disabledProjectUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organization).setEnabled(false)).getComponentUuid();
+    String projectUuid = db.components().insertProjectAndSnapshot(newPrivateProjectDto(organization)).getComponentUuid();
+    String disabledProjectUuid = db.components().insertProjectAndSnapshot(newPrivateProjectDto(organization).setEnabled(false)).getComponentUuid();
     String viewUuid = db.components().insertProjectAndSnapshot(ComponentTesting.newView(organization)).getComponentUuid();
 
     Set<String> projectQualifiers = newHashSet(Qualifiers.PROJECT);
@@ -668,7 +668,7 @@ public class ComponentDaoTest {
   public void count_provisioned() {
     OrganizationDto organization = db.organizations().insert();
     db.components().insertPrivateProject(organization);
-    db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organization));
+    db.components().insertProjectAndSnapshot(newPrivateProjectDto(organization));
     db.components().insertProjectAndSnapshot(ComponentTesting.newView(organization));
 
     assertThat(underTest.countProvisioned(dbSession, organization.getUuid(), null, newHashSet(Qualifiers.PROJECT))).isEqualTo(1);
@@ -898,10 +898,10 @@ public class ComponentDaoTest {
   @Test
   public void selectByQuery_with_paging_query_and_qualifiers() {
     OrganizationDto organizationDto = db.organizations().insert();
-    db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organizationDto).setName("aaaa-name"));
+    db.components().insertProjectAndSnapshot(newPrivateProjectDto(organizationDto).setName("aaaa-name"));
     db.components().insertProjectAndSnapshot(newView(organizationDto));
     for (int i = 9; i >= 1; i--) {
-      db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(organizationDto).setName("project-" + i));
+      db.components().insertProjectAndSnapshot(newPrivateProjectDto(organizationDto).setName("project-" + i));
     }
 
     ComponentQuery query = ComponentQuery.builder().setNameOrKeyQuery("oJect").setQualifiers(Qualifiers.PROJECT).build();
@@ -953,7 +953,7 @@ public class ComponentDaoTest {
 
   @Test
   public void selectByQuery_name_with_special_characters() {
-    db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setName("project-\\_%/-name"));
+    db.components().insertProjectAndSnapshot(newPrivateProjectDto(db.getDefaultOrganization()).setName("project-\\_%/-name"));
 
     ComponentQuery query = ComponentQuery.builder().setNameOrKeyQuery("-\\_%/-").setQualifiers(Qualifiers.PROJECT).build();
     List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
@@ -964,7 +964,8 @@ public class ComponentDaoTest {
 
   @Test
   public void selectByQuery_key_with_special_characters() {
-    db.components().insertProjectAndSnapshot(ComponentTesting.newPrivateProjectDto(db.organizations().insert()).setDbKey("project-_%-key"));
+    db.components().insertProjectAndSnapshot(newPrivateProjectDto(db.organizations().insert()).setDbKey("project-_%-key"));
+    db.components().insertProjectAndSnapshot(newPrivateProjectDto(db.organizations().insert()).setDbKey("project-key-that-does-not-match"));
 
     ComponentQuery query = ComponentQuery.builder().setNameOrKeyQuery("project-_%-key").setQualifiers(Qualifiers.PROJECT).build();
     List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
@@ -973,10 +974,24 @@ public class ComponentDaoTest {
     assertThat(result.get(0).getDbKey()).isEqualTo("project-_%-key");
   }
 
+  @Test
+  public void selectByQuery_on_key_partial_match_case_insensitive() {
+    db.components().insertProjectAndSnapshot(newPrivateProjectDto(db.organizations().insert()).setDbKey("project-key"));
+
+    ComponentQuery query = ComponentQuery.builder()
+      .setNameOrKeyQuery("JECT-K")
+      .setPartialMatchOnKey(true)
+      .setQualifiers(Qualifiers.PROJECT).build();
+    List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
+
+    assertThat(result).hasSize(1);
+    assertThat(result.get(0).getDbKey()).isEqualTo("project-key");
+  }
+
   @Test
   public void selectByQuery_filter_on_language() {
-    db.components().insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("java-project-key").setLanguage("java"));
-    db.components().insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("cpp-project-key").setLanguage("cpp"));
+    db.components().insertComponent(newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("java-project-key").setLanguage("java"));
+    db.components().insertComponent(newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("cpp-project-key").setLanguage("cpp"));
 
     ComponentQuery query = ComponentQuery.builder().setLanguage("java").setQualifiers(Qualifiers.PROJECT).build();
     List<ComponentDto> result = underTest.selectByQuery(dbSession, query, 0, 10);
@@ -1006,7 +1021,7 @@ public class ComponentDaoTest {
 
   @Test
   public void selectByQuery_filter_on_visibility() {
-    db.components().insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("private-key"));
+    db.components().insertComponent(newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("private-key"));
     db.components().insertComponent(ComponentTesting.newPublicProjectDto(db.getDefaultOrganization()).setDbKey("public-key"));
 
     ComponentQuery privateProjectsQuery = ComponentQuery.builder().setPrivate(true).setQualifiers(Qualifiers.PROJECT).build();
@@ -1031,9 +1046,9 @@ public class ComponentDaoTest {
   @Test
   public void selectByQuery_on_component_ids() {
     OrganizationDto organizationDto = db.organizations().insert();
-    ComponentDto sonarqube = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto));
-    ComponentDto jdk8 = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto));
-    ComponentDto cLang = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto));
+    ComponentDto sonarqube = db.components().insertComponent(newPrivateProjectDto(organizationDto));
+    ComponentDto jdk8 = db.components().insertComponent(newPrivateProjectDto(organizationDto));
+    ComponentDto cLang = db.components().insertComponent(newPrivateProjectDto(organizationDto));
 
     ComponentQuery query = ComponentQuery.builder().setQualifiers(Qualifiers.PROJECT)
       .setComponentIds(newHashSet(sonarqube.getId(), jdk8.getId())).build();
@@ -1181,12 +1196,12 @@ public class ComponentDaoTest {
   @Test
   public void select_projects_by_name_query() {
     OrganizationDto organizationDto = db.organizations().insert();
-    ComponentDto project1 = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setName("project1"));
+    ComponentDto project1 = db.components().insertComponent(newPrivateProjectDto(organizationDto).setName("project1"));
     ComponentDto module1 = db.components().insertComponent(newModuleDto(project1).setName("module1"));
     ComponentDto subModule1 = db.components().insertComponent(newModuleDto(module1).setName("subModule1"));
     db.components().insertComponent(newFileDto(subModule1).setName("file"));
-    ComponentDto project2 = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setName("project2"));
-    ComponentDto project3 = db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setName("project3"));
+    ComponentDto project2 = db.components().insertComponent(newPrivateProjectDto(organizationDto).setName("project2"));
+    ComponentDto project3 = db.components().insertComponent(newPrivateProjectDto(organizationDto).setName("project3"));
 
     assertThat(underTest.selectProjectsByNameQuery(dbSession, null, false)).extracting(ComponentDto::uuid)
       .containsOnly(project1.uuid(), project2.uuid(), project3.uuid());
@@ -1207,11 +1222,11 @@ public class ComponentDaoTest {
 
     OrganizationDto organizationDto = db.organizations().insert();
     String[] uuids = {
-      db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setProjectUuid(uuid1).setPrivate(true)).uuid(),
-      db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setProjectUuid(uuid1).setPrivate(false)).uuid(),
-      db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setProjectUuid(uuid2).setPrivate(true)).uuid(),
-      db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setProjectUuid(uuid2).setPrivate(false)).uuid(),
-      db.components().insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setRootUuid(uuid1).setProjectUuid("foo").setPrivate(false)).uuid(),
+      db.components().insertComponent(newPrivateProjectDto(organizationDto).setProjectUuid(uuid1).setPrivate(true)).uuid(),
+      db.components().insertComponent(newPrivateProjectDto(organizationDto).setProjectUuid(uuid1).setPrivate(false)).uuid(),
+      db.components().insertComponent(newPrivateProjectDto(organizationDto).setProjectUuid(uuid2).setPrivate(true)).uuid(),
+      db.components().insertComponent(newPrivateProjectDto(organizationDto).setProjectUuid(uuid2).setPrivate(false)).uuid(),
+      db.components().insertComponent(newPrivateProjectDto(organizationDto).setRootUuid(uuid1).setProjectUuid("foo").setPrivate(false)).uuid(),
     };
 
     underTest.setPrivateForRootComponentUuid(db.getSession(), uuid1, true);
index 1b6971c6fc75045a9b4d976877557f6b5530284f..d69f98c277ff3d0d25bb6f5f355abd8a408b721b 100644 (file)
@@ -57,6 +57,16 @@ public class ComponentQueryTest {
     assertThat(underTest.getQualifiers()).containsOnly(PROJECT);
   }
 
+  @Test
+  public void test_getNameOrKeyUpperLikeQuery() throws Exception {
+    ComponentQuery underTest = ComponentQuery.builder()
+      .setNameOrKeyQuery("NAME/key")
+      .setQualifiers(PROJECT)
+      .build();
+
+    assertThat(underTest.getNameOrKeyUpperLikeQuery()).isEqualTo("%NAME//KEY%");
+  }
+
   @Test
   public void fail_if_no_qualifier_provided() {
     expectedException.expect(IllegalArgumentException.class);
@@ -66,12 +76,10 @@ public class ComponentQueryTest {
   }
 
   @Test
-  public void test_getNameOrKeyUpperLikeQuery() throws Exception {
-    ComponentQuery underTest = ComponentQuery.builder()
-      .setNameOrKeyQuery("NAME/key")
-      .setQualifiers(PROJECT)
-      .build();
+  public void fail_if_partial_match_on_key_without_a_query() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("A query must be provided if a partial match on key is specified.");
 
-    assertThat(underTest.getNameOrKeyUpperLikeQuery()).isEqualTo("%NAME//KEY%");
+    ComponentQuery.builder().setQualifiers(PROJECT).setPartialMatchOnKey(false).build();
   }
 }
index 015bf3740b7f32f630a10663b4c49e035e00379a..562a136c070bb3c12d89edaefe1c8b558e33b1fd 100644 (file)
@@ -92,7 +92,7 @@ public class SearchAction implements ProjectsWsAction {
     action.createParam(Param.TEXT_QUERY)
       .setDescription("Limit search to: <ul>" +
         "<li>component names that contain the supplied string</li>" +
-        "<li>component keys that are exactly the same as the supplied string</li>" +
+        "<li>component keys that contain the supplied string</li>" +
         "</ul>")
       .setExampleValue("sonar");
 
@@ -153,9 +153,13 @@ public class SearchAction implements ProjectsWsAction {
   private static ComponentQuery buildQuery(SearchWsRequest request) {
     List<String> qualifiers = request.getQualifiers();
     ComponentQuery.Builder query = ComponentQuery.builder()
-      .setNameOrKeyQuery(request.getQuery())
       .setQualifiers(qualifiers.toArray(new String[qualifiers.size()]));
 
+    setNullable(request.getQuery(), q -> {
+      query.setNameOrKeyQuery(q);
+      query.setPartialMatchOnKey(true);
+      return query;
+    });
     setNullable(request.getVisibility(), v -> query.setPrivate(Visibility.isPrivate(v)));
     setNullable(request.getAnalyzedBefore(), d -> query.setAnalyzedBefore(parseDateOrDateTime(d).getTime()));
 
index 439d4d10f2f23e39f0bd946def228e6545817a6d..c4fbc6a0bf6b92b3fd5ce7547e2d92e4d1d363e6 100644 (file)
@@ -92,15 +92,16 @@ public class SearchActionTest {
     new SearchAction(db.getDbClient(), userSession, defaultOrganizationProvider, new ProjectsWsSupport(db.getDbClient(), mock(BillingValidationsProxy.class))));
 
   @Test
-  public void search_by_key_query() throws IOException {
+  public void search_by_key_query_with_partial_match_case_insensitive() throws IOException {
     userSession.addPermission(ADMINISTER, db.getDefaultOrganization());
     db.components().insertComponents(
       ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("project-_%-key"),
+      ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("PROJECT-_%-KEY"),
       ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setDbKey("project-key-without-escaped-characters"));
 
-    SearchWsResponse response = call(SearchWsRequest.builder().setQuery("project-_%-key").build());
+    SearchWsResponse response = call(SearchWsRequest.builder().setQuery("JeCt-_%-k").build());
 
-    assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("project-_%-key");
+    assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("project-_%-key", "PROJECT-_%-KEY");
   }
 
   @Test
@@ -288,7 +289,7 @@ public class SearchActionTest {
     assertThat(qParam.description()).isEqualTo("Limit search to: " +
       "<ul>" +
       "<li>component names that contain the supplied string</li>" +
-      "<li>component keys that are exactly the same as the supplied string</li>" +
+      "<li>component keys that contain the supplied string</li>" +
       "</ul>");
 
     WebService.Param qualifierParam = action.param("qualifiers");
index aaa7b86b9103db5a5ed933cc515b0628dfd82d59..f200cfe8cf28cab54df5d04ab79be1fc5a6ec3ea 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.api.server.ws;
 
-import com.google.common.base.Joiner;
 import java.io.IOException;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
@@ -428,7 +427,7 @@ public interface WebService extends Definable<WebService.Context> {
      * </p>
      */
     public NewParam createSearchQuery(String exampleValue, String... pluralFields) {
-      String actionDescription = format("Limit search to %s that contain the supplied string.", Joiner.on(" or ").join(pluralFields));
+      String actionDescription = format("Limit search to %s that contain the supplied string.", String.join(" or ", pluralFields));
 
       return createParam(Param.TEXT_QUERY)
         .setDescription(actionDescription)
index 69178286b3b9d3fb430fea3a2dddd447fc33b22e..c8c80fb374c361933b1a8f7f01baaba97baaf788 100644 (file)
@@ -65,6 +65,28 @@ public class ProjectSearchTest {
     assertThat(result.getComponentsList()).extracting(SearchWsResponse.Component::getKey).containsExactlyInAnyOrder(oldProject.getKey());
   }
 
+  @Test
+  public void search_on_key_partial_match_case_insensitive() {
+    Organizations.Organization organization = tester.organizations().generate();
+    CreateWsResponse.Project lowerCaseProject = tester.projects().generate(organization, p -> p.setKey("project-key"));
+    CreateWsResponse.Project upperCaseProject = tester.projects().generate(organization, p -> p.setKey("PROJECT-KEY"));
+    CreateWsResponse.Project anotherProject = tester.projects().generate(organization, p -> p.setKey("another-project"));
+
+    analyzeProject(lowerCaseProject.getKey(), organization.getKey());
+    analyzeProject(upperCaseProject.getKey(), organization.getKey());
+    analyzeProject(anotherProject.getKey(), organization.getKey());
+
+    SearchWsResponse result = tester.wsClient().projects().search(SearchWsRequest.builder()
+      .setOrganization(organization.getKey())
+      .setQualifiers(singletonList("TRK"))
+      .setQuery("JeCt-K")
+      .build());
+
+    assertThat(result.getComponentsList()).extracting(SearchWsResponse.Component::getKey)
+      .containsExactlyInAnyOrder(lowerCaseProject.getKey(), upperCaseProject.getKey())
+      .doesNotContain(anotherProject.getKey());
+  }
+
   private void analyzeProject(String projectKey, Date analysisDate, String organizationKey) {
     runProjectAnalysis(orchestrator, "shared/xoo-sample",
       "sonar.organization", organizationKey,
@@ -73,4 +95,8 @@ public class ProjectSearchTest {
       "sonar.login", "admin",
       "sonar.password", "admin");
   }
+
+  private void analyzeProject(String projectKey, String organizationKey) {
+    analyzeProject(projectKey, new Date(), organizationKey);
+  }
 }