]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20708 Add sorting project by creation date in search_projects endpoint
authorWojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com>
Fri, 13 Oct 2023 09:06:34 +0000 (11:06 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 18 Oct 2023 20:03:05 +0000 (20:03 +0000)
13 files changed:
server/sonar-db-dao/src/it/java/org/sonar/db/measure/ProjectMeasuresIndexerIteratorIT.java
server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java
server/sonar-server-common/src/it/java/org/sonar/server/measure/index/ProjectMeasuresIndexerIT.java
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresDoc.java
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexDefinition.java
server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java
server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.java
server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresQuery.java
server/sonar-webserver-es/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/component/ws/SearchProjectsActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidator.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidatorTest.java

index 93e4608d98173350be78385d865932496469cb40..a59c0e51c06d5e3d6df09b21f14dfcf845f93430 100644 (file)
@@ -248,6 +248,7 @@ public class ProjectMeasuresIndexerIteratorIT {
     assertThat(docsById).hasSize(1);
     ProjectMeasures doc = docsById.get(projectData.projectUuid());
     assertThat(doc.getProject().getAnalysisDate()).isNull();
+    assertThat(doc.getProject().getCreationDate()).isEqualTo(projectData.getProjectDto().getCreatedAt());
   }
 
   @Test
index 8997ce77028e09531bb9aea7c208c46ed61b02f4..1960486f9ac31e00be8198849049bb8a7f8e3813 100644 (file)
@@ -72,7 +72,7 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
     CoreMetrics.NEW_LINES_KEY,
     CoreMetrics.NEW_RELIABILITY_RATING_KEY);
 
-  private static final String SQL_PROJECTS = "SELECT p.uuid, p.kee, p.name, s.created_at, p.tags, p.qualifier " +
+  private static final String SQL_PROJECTS = "SELECT p.uuid, p.kee, p.name, p.created_at, s.created_at, p.tags, p.qualifier " +
     "FROM projects p " +
     "INNER JOIN project_branches pb ON pb.project_uuid = p.uuid AND pb.is_main = ? " +
     "LEFT OUTER JOIN snapshots s ON s.root_component_uuid=pb.uuid AND s.islast=? " +
@@ -141,10 +141,11 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
         String uuid = rs.getString(1);
         String key = rs.getString(2);
         String name = rs.getString(3);
-        Long analysisDate = DatabaseUtils.getLong(rs, 4);
-        List<String> tags = readDbTags(DatabaseUtils.getString(rs, 5));
-        String qualifier = rs.getString(6);
-        Project project = new Project(uuid, key, name, qualifier, tags, analysisDate);
+        Long creationDate = rs.getLong(4);
+        Long analysisDate = DatabaseUtils.getLong(rs, 5);
+        List<String> tags = readDbTags(DatabaseUtils.getString(rs, 6));
+        String qualifier = rs.getString(7);
+        Project project = new Project(uuid, key, name, qualifier, tags, creationDate, analysisDate);
         projects.add(project);
       }
       return projects;
@@ -331,16 +332,18 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
     private final String key;
     private final String name;
     private final String qualifier;
+    private final Long creationDate;
     private final Long analysisDate;
     private final List<String> tags;
 
-    public Project(String uuid, String key, String name, String qualifier, List<String> tags, @Nullable Long analysisDate) {
+    public Project(String uuid, String key, String name, String qualifier, List<String> tags, Long creationDate, @Nullable Long analysisDate) {
       this.uuid = uuid;
       this.key = key;
       this.name = name;
       this.qualifier = qualifier;
       this.tags = tags;
       this.analysisDate = analysisDate;
+      this.creationDate = creationDate;
     }
 
     public String getUuid() {
@@ -367,6 +370,10 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
     public Long getAnalysisDate() {
       return analysisDate;
     }
+
+    public Long getCreationDate() {
+      return creationDate;
+    }
   }
 
   public static class Measures {
index 24ce56bd724374d76d54c6a8a03978f876a1a0d4..479897a88bc85a1c7090914ee33db2439d1b1f56 100644 (file)
@@ -22,13 +22,16 @@ package org.sonar.server.measure.index;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import org.apache.lucene.search.join.ScoreMode;
+import org.assertj.core.groups.Tuple;
 import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.joda.time.DateTime;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.measures.CoreMetrics;
@@ -53,6 +56,7 @@ import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
 import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
 import static org.elasticsearch.index.query.QueryBuilders.nestedQuery;
 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
@@ -120,14 +124,21 @@ public class ProjectMeasuresIndexerIT {
 
   @Test
   public void indexAll_indexes_all_projects() {
-    SnapshotDto snapshot1 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
-    SnapshotDto snapshot2 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
-    SnapshotDto snapshot3 = db.components().insertProjectAndSnapshot(newPrivateProjectDto());
+
+    ProjectData project1 = db.components().insertPrivateProject();
+    SnapshotDto snapshot1 = db.components().insertSnapshot(project1.getMainBranchComponent());
+
+    ProjectData project2 = db.components().insertPrivateProject();
+    SnapshotDto snapshot2 = db.components().insertSnapshot(project2.getMainBranchComponent());
+
+    ProjectData project3 = db.components().insertPrivateProject();
+    SnapshotDto snapshot3 = db.components().insertSnapshot(project3.getMainBranchComponent());
 
     underTest.indexAll();
 
     assertThatIndexContainsOnly(snapshot1, snapshot2, snapshot3);
     assertThatQualifierIs("TRK", snapshot1, snapshot2, snapshot3);
+    assertThatIndexContainsCreationDate(project1, project2, project3);
   }
 
   /**
@@ -198,7 +209,7 @@ public class ProjectMeasuresIndexerIT {
     BranchDto branchDto = db.components().insertProjectBranch(project1.getProjectDto());
     underTest.indexOnAnalysis(branchDto.getUuid());
 
-    assertThatIndexContainsOnly(new ProjectDto[]{});
+    assertThatIndexContainsOnly(new ProjectDto[] {});
   }
 
   @Test
@@ -363,8 +374,7 @@ public class ProjectMeasuresIndexerIT {
           boolQuery()
             .filter(termQuery(FIELD_MEASURES_MEASURE_KEY, metric))
             .filter(termQuery(FIELD_MEASURES_MEASURE_VALUE, value)),
-          ScoreMode.Avg
-        )));
+          ScoreMode.Avg)));
 
     assertThat(es.client().search(request).getHits().getHits())
       .extracting(SearchHit::getId)
@@ -380,14 +390,32 @@ public class ProjectMeasuresIndexerIT {
       Arrays.stream(expectedSnapshots).map(this::getProjectUuidFromSnapshot).toArray(String[]::new));
   }
 
+  private void assertThatIndexContainsCreationDate(ProjectData... projectDatas) {
+    List<Map<String, Object>> documents = es.getDocuments(TYPE_PROJECT_MEASURES).stream().map(SearchHit::getSourceAsMap).toList();
+
+    List<Tuple> expected = Arrays.stream(projectDatas).map(
+      projectData -> tuple(
+        projectData.getProjectDto().getKey(),
+        projectData.getProjectDto().getCreatedAt()))
+      .toList();
+    assertThat(documents)
+      .extracting(hit -> hit.get("key"), hit -> stringDateToMilliseconds((String) hit.get("createdAt")))
+      .containsExactlyInAnyOrderElementsOf(expected);
+
+  }
+
+  private static long stringDateToMilliseconds(String date) {
+    return DateTime.parse(date).getMillis();
+  }
+
   private String getProjectUuidFromSnapshot(SnapshotDto s) {
     ProjectDto projectDto = db.getDbClient().projectDao().selectByBranchUuid(db.getSession(), s.getRootComponentUuid()).orElseThrow();
     return projectDto.getUuid();
   }
 
   private void assertThatIndexContainsOnly(ProjectDto... expectedProjects) {
-    assertThat(es.getIds(TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrder(
-      Arrays.stream(expectedProjects).map(ProjectDto::getUuid).toArray(String[]::new));
+    assertThat(es.getIds(TYPE_PROJECT_MEASURES)).containsExactlyInAnyOrderElementsOf(
+      Arrays.stream(expectedProjects).map(ProjectDto::getUuid).toList());
   }
 
   private void assertThatQualifierIs(String qualifier, ProjectDto... expectedProjects) {
index 92874fc6ced9932c7d8ed09f498d6c83fe902fe6..9e81bad32f5705e2b3307ff86b5c627a6f4db562 100644 (file)
@@ -32,6 +32,7 @@ import org.sonar.server.permission.index.AuthorizationDoc;
 import static org.sonar.api.measures.Metric.Level.ERROR;
 import static org.sonar.api.measures.Metric.Level.OK;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_CREATED_AT;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_LANGUAGES;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
@@ -103,6 +104,15 @@ public class ProjectMeasuresDoc extends BaseDoc {
     return this;
   }
 
+  public ProjectMeasuresDoc setCreatedAt(Date d) {
+    setField(FIELD_CREATED_AT, d);
+    return this;
+  }
+
+  public Date getCreatedAt() {
+    return getField(FIELD_CREATED_AT);
+  }
+
   public Collection<Map<String, Object>> getMeasures() {
     return getField(FIELD_MEASURES);
   }
index 8254969d4a37bc87cb051d5fc4fea570a8428016..0008c67ad45c5275122562c68b18a9906040684d 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.measure.index;
 
+import javax.inject.Inject;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.server.es.Index;
@@ -34,8 +35,6 @@ import static org.sonar.server.es.newindex.DefaultIndexSettingsElement.SORTABLE_
 import static org.sonar.server.es.newindex.SettingsConfiguration.MANUAL_REFRESH_INTERVAL;
 import static org.sonar.server.es.newindex.SettingsConfiguration.newBuilder;
 
-import javax.inject.Inject;
-
 public class ProjectMeasuresIndexDefinition implements IndexDefinition {
 
   public static final Index DESCRIPTOR = Index.withRelations("projectmeasures");
@@ -51,6 +50,7 @@ public class ProjectMeasuresIndexDefinition implements IndexDefinition {
   public static final String FIELD_NAME = "name";
   public static final String FIELD_QUALIFIER = "qualifier";
   public static final String FIELD_ANALYSED_AT = "analysedAt";
+  public static final String FIELD_CREATED_AT = "createdAt";
   public static final String FIELD_QUALITY_GATE_STATUS = "qualityGateStatus";
   public static final String FIELD_TAGS = "tags";
   public static final String FIELD_MEASURES = "measures";
@@ -113,5 +113,6 @@ public class ProjectMeasuresIndexDefinition implements IndexDefinition {
       .addIntegerField(SUB_FIELD_DISTRIB_NCLOC)
       .build();
     mapping.createDateTimeField(FIELD_ANALYSED_AT);
+    mapping.createDateTimeField(FIELD_CREATED_AT);
   }
 }
index e515469bf1dbce545d3afe914a20e89f4c3be569..007ab15c982631c21cb1a1f24dddaa9a159a1476 100644 (file)
@@ -215,6 +215,7 @@ public class ProjectMeasuresIndexer implements EventIndexer, AnalysisIndexer, Ne
       .setQualityGateStatus(projectMeasures.getMeasures().getQualityGateStatus())
       .setTags(project.getTags())
       .setAnalysedAt(analysisDate == null ? null : new Date(analysisDate))
+      .setCreatedAt(new Date(project.getCreationDate()))
       .setMeasuresFromMap(projectMeasures.getMeasures().getNumericMeasures())
       .setLanguages(new ArrayList<>(projectMeasures.getMeasures().getNclocByLanguages().keySet()))
       .setNclocLanguageDistributionFromMap(projectMeasures.getMeasures().getNclocByLanguages());
index 11dd647e80056072387dc19dcad32f18c36e03ce..8426cadfbe913d05b7616d5d84d9f685ddc2cbb1 100644 (file)
@@ -114,6 +114,7 @@ import static org.sonar.server.measure.index.ProjectMeasuresIndex.Facet.ALERT_ST
 import static org.sonar.server.measure.index.ProjectMeasuresIndex.Facet.LANGUAGES;
 import static org.sonar.server.measure.index.ProjectMeasuresIndex.Facet.TAGS;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_ANALYSED_AT;
+import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_CREATED_AT;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_KEY;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_LANGUAGES;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_MEASURES;
@@ -128,6 +129,7 @@ import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIEL
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.FIELD_TAGS;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.SUB_FIELD_MEASURES_KEY;
 import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE_PROJECT_MEASURES;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_CREATION_DATE;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
 import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_LANGUAGES;
@@ -302,6 +304,8 @@ public class ProjectMeasuresIndex {
       requestBuilder.sort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), query.isAsc() ? ASC : DESC);
     } else if (SORT_BY_LAST_ANALYSIS_DATE.equals(sort)) {
       requestBuilder.sort(FIELD_ANALYSED_AT, query.isAsc() ? ASC : DESC);
+    } else if (SORT_BY_CREATION_DATE.equals(sort)) {
+      requestBuilder.sort(FIELD_CREATED_AT, query.isAsc() ? ASC : DESC);
     } else if (ALERT_STATUS_KEY.equals(sort)) {
       requestBuilder.sort(FIELD_QUALITY_GATE_STATUS, query.isAsc() ? ASC : DESC);
       requestBuilder.sort(DefaultIndexSettingsElement.SORTABLE_ANALYZER.subField(FIELD_NAME), ASC);
index 61ad76dffc4a3e2c8ab6c78fc4ac520d0bf1b3c2..d0350a24e4bce4a808b01e74b4ec21600f91ae3b 100644 (file)
@@ -35,6 +35,7 @@ public class ProjectMeasuresQuery {
 
   public static final String SORT_BY_NAME = "name";
   public static final String SORT_BY_LAST_ANALYSIS_DATE = "analysisDate";
+  public static final String SORT_BY_CREATION_DATE = "creationDate";
 
   private List<MetricCriterion> metricCriteria = new ArrayList<>();
   private Metric.Level qualityGateStatus = null;
index 92a6654a190ab146c9c08fcc82b6b43d84422455..383e38db38e04ec2fac3d8d21d6fb70c48d6f6d5 100644 (file)
@@ -25,7 +25,9 @@ import com.google.common.collect.Sets;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.time.Instant;
 import java.util.Arrays;
+import java.util.Date;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -184,6 +186,38 @@ public class ProjectMeasuresIndexTest {
     assertResults(new ProjectMeasuresQuery().setSort("alert_status").setAsc(false), PROJECT2, PROJECT1, project4);
   }
 
+  public void sort_by_creation_date() {
+    Instant now = Instant.ofEpochMilli(1000L);
+    Date nowMinus10 = Date.from(now.minusSeconds(10));
+    Date nowMinus20 = Date.from(now.minusSeconds(20));
+    Date nowMinus30 = Date.from(now.minusSeconds(30));
+
+    ComponentDto project4 = ComponentTesting.newPrivateProjectDto().setUuid("Project-4").setName("Project 4").setKey("key-4");
+    index(
+      newDoc(PROJECT1).setCreatedAt(nowMinus10),
+      newDoc(PROJECT2).setCreatedAt(nowMinus30),
+      newDoc(project4).setCreatedAt(nowMinus20));
+
+    assertResults(new ProjectMeasuresQuery().setSort("creation_date").setAsc(true), PROJECT1, project4, PROJECT2);
+    assertResults(new ProjectMeasuresQuery().setSort("creation_date").setAsc(false), PROJECT2, PROJECT1, project4);
+  }
+
+  public void sort_by_analysis_date() {
+    Instant now = Instant.ofEpochMilli(1000L);
+    Date nowMinus10 = Date.from(now.minusSeconds(10));
+    Date nowMinus20 = Date.from(now.minusSeconds(20));
+    Date nowMinus30 = Date.from(now.minusSeconds(30));
+
+    ComponentDto project4 = ComponentTesting.newPrivateProjectDto().setUuid("Project-4").setName("Project 4").setKey("key-4");
+    index(
+      newDoc(PROJECT1).setAnalysedAt(nowMinus10),
+      newDoc(PROJECT2).setAnalysedAt(nowMinus30),
+      newDoc(project4).setAnalysedAt(nowMinus20));
+
+    assertResults(new ProjectMeasuresQuery().setSort("analysis_date").setAsc(true), PROJECT1, project4, PROJECT2);
+    assertResults(new ProjectMeasuresQuery().setSort("analysis_date").setAsc(false), PROJECT2, PROJECT1, project4);
+  }
+
   @Test
   public void sort_by_quality_gate_status_then_by_name_then_by_key() {
     ComponentDto windows = ComponentTesting.newPrivateProjectDto().setUuid("windows").setName("Windows").setKey("project1");
@@ -1455,7 +1489,7 @@ public class ProjectMeasuresIndexTest {
       newDoc().setTags(newArrayList("finance", "offshore")),
       newDoc().setTags(newArrayList("offshore")));
 
-    List<String> result = underTest.searchTags("off", 1,10);
+    List<String> result = underTest.searchTags("off", 1, 10);
 
     assertThat(result).containsOnly("offshore", "official", "Madhoff");
   }
@@ -1485,7 +1519,7 @@ public class ProjectMeasuresIndexTest {
       newDoc().setTags(newArrayList("finance", "offshore")),
       newDoc().setTags(newArrayList("offshore")));
 
-    List<String> result = underTest.searchTags(null, 1,10);
+    List<String> result = underTest.searchTags(null, 1, 10);
 
     assertThat(result).containsExactly("Madhoff", "finance", "java", "javascript", "marketing", "official", "offshore");
   }
@@ -1500,16 +1534,16 @@ public class ProjectMeasuresIndexTest {
       newDoc().setTags(newArrayList("finance", "offshore")),
       newDoc().setTags(newArrayList("offshore")));
 
-    List<String> result = underTest.searchTags(null, 1,3);
+    List<String> result = underTest.searchTags(null, 1, 3);
     assertThat(result).containsExactly("Madhoff", "finance", "java");
 
-    result = underTest.searchTags(null, 2,3);
+    result = underTest.searchTags(null, 2, 3);
     assertThat(result).containsExactly("javascript", "marketing", "official");
 
-    result = underTest.searchTags(null, 3,3);
+    result = underTest.searchTags(null, 3, 3);
     assertThat(result).containsExactly("offshore");
 
-    result = underTest.searchTags(null, 3,4);
+    result = underTest.searchTags(null, 3, 4);
     assertThat(result).isEmpty();
   }
 
@@ -1523,7 +1557,7 @@ public class ProjectMeasuresIndexTest {
       newDoc().setTags(newArrayList("finance", "offshore")),
       newDoc().setTags(newArrayList("offshore")));
 
-    List<String> result = underTest.searchTags(null, 10,2);
+    List<String> result = underTest.searchTags(null, 10, 2);
 
     assertThat(result).isEmpty();
   }
@@ -1538,14 +1572,14 @@ public class ProjectMeasuresIndexTest {
 
     userSession.logIn(USER1);
 
-    List<String> result = underTest.searchTags(null, 1,10);
+    List<String> result = underTest.searchTags(null, 1, 10);
 
     assertThat(result).containsOnly("finance", "marketing");
   }
 
   @Test
   public void search_tags_with_no_tags() {
-    List<String> result = underTest.searchTags("whatever", 1,10);
+    List<String> result = underTest.searchTags("whatever", 1, 10);
 
     assertThat(result).isEmpty();
   }
@@ -1554,7 +1588,7 @@ public class ProjectMeasuresIndexTest {
   public void search_tags_with_page_size_at_0() {
     index(newDoc().setTags(newArrayList("offshore")));
 
-    List<String> result = underTest.searchTags(null, 1,0);
+    List<String> result = underTest.searchTags(null, 1, 0);
 
     assertThat(result).isEmpty();
   }
@@ -1585,10 +1619,9 @@ public class ProjectMeasuresIndexTest {
     int jsLocByProjects = 900;
     int csLocByProjects = 2;
 
-    ProjectMeasuresDoc[] documents = IntStream.range(0, nbProjects).mapToObj(i ->
-      newDoc("lines", 10, "coverage", 80)
-        .setLanguages(asList("java", "cs", "js"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", javaLocByProjects, "cs", csLocByProjects, "js", jsLocByProjects))).toArray(ProjectMeasuresDoc[]::new);
+    ProjectMeasuresDoc[] documents = IntStream.range(0, nbProjects).mapToObj(i -> newDoc("lines", 10, "coverage", 80)
+      .setLanguages(asList("java", "cs", "js"))
+      .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", javaLocByProjects, "cs", csLocByProjects, "js", jsLocByProjects))).toArray(ProjectMeasuresDoc[]::new);
 
     es.putDocuments(TYPE_PROJECT_MEASURES, documents);
 
@@ -1603,9 +1636,9 @@ public class ProjectMeasuresIndexTest {
 
     assertThat(result.getNclocByLanguage())
       .hasSize(3)
-      .containsEntry("java",(long) nbProjects * javaLocByProjects)
-      .containsEntry("cs",(long) nbProjects * csLocByProjects)
-      .containsEntry("js",(long) nbProjects * jsLocByProjects);
+      .containsEntry("java", (long) nbProjects * javaLocByProjects)
+      .containsEntry("cs", (long) nbProjects * csLocByProjects)
+      .containsEntry("js", (long) nbProjects * jsLocByProjects);
   }
 
   @Test
index d3a7959ca6777c73bb5fa627fba3b3e99047781c..7742b7f2c22b7d88adbac2bf0b506a73a0c9108b 100644 (file)
@@ -32,7 +32,6 @@ import java.util.stream.IntStream;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
 import org.apache.ibatis.session.ResultHandler;
-import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -180,7 +179,7 @@ public class SearchProjectsActionIT {
     assertThat(def.isPost()).isFalse();
     assertThat(def.responseExampleAsString()).isNotEmpty();
     assertThat(def.params().stream().map(Param::key).toList()).containsOnly("filter", "facets", "s", "asc", "ps", "p", "f");
-    assertThat(def.changelog()).hasSize(3);
+    assertThat(def.changelog()).hasSize(4);
 
     Param sort = def.param("s");
     assertThat(sort.defaultValue()).isEqualTo("name");
@@ -205,7 +204,8 @@ public class SearchProjectsActionIT {
       "ncloc",
       "new_maintainability_rating",
       "name",
-      "analysisDate");
+      "analysisDate",
+      "creationDate");
 
     Param asc = def.param("asc");
     assertThat(asc.defaultValue()).isEqualTo("true");
@@ -1330,7 +1330,7 @@ public class SearchProjectsActionIT {
 
   private void addFavourite(@Nullable String entityUuid, @Nullable String entityKey, @Nullable String entityName, @Nullable String qualifier) {
     dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey("favourite")
-        .setEntityUuid(entityUuid).setUserUuid(userSession.getUuid()), userSession.getLogin(), entityKey, entityName, qualifier);
+      .setEntityUuid(entityUuid).setUserUuid(userSession.getUuid()), userSession.getLogin(), entityKey, entityName, qualifier);
     dbSession.commit();
   }
 
index 3143aedb168b4c24088c613e23e8fb49c5d7b238..391b1d0e58559139f48c56445df56feaf201290a 100644 (file)
@@ -30,12 +30,13 @@ import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Arrays.asList;
 import static org.sonar.db.measure.ProjectMeasuresIndexerIterator.METRIC_KEYS;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterion;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_CREATION_DATE;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
 
 public class ProjectMeasuresQueryValidator {
 
-  static final Set<String> NON_METRIC_SORT_KEYS = new HashSet<>(asList(SORT_BY_NAME, SORT_BY_LAST_ANALYSIS_DATE));
+  static final Set<String> NON_METRIC_SORT_KEYS = new HashSet<>(asList(SORT_BY_NAME, SORT_BY_LAST_ANALYSIS_DATE, SORT_BY_CREATION_DATE));
 
   private ProjectMeasuresQueryValidator() {
   }
index f031289552d87a8f938b4e2cef32b74fbd6e7ddf..9542c4adc34d6daebea1cc395fba9026e8dfe9e3 100644 (file)
@@ -83,6 +83,7 @@ import static org.sonar.db.measure.ProjectMeasuresIndexerIterator.METRIC_KEYS;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.IS_FAVORITE_CRITERION;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryFactory.newProjectMeasuresQuery;
 import static org.sonar.server.component.ws.ProjectMeasuresQueryValidator.NON_METRIC_SORT_KEYS;
+import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_CREATION_DATE;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_LAST_ANALYSIS_DATE;
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.SORT_BY_NAME;
 import static org.sonar.server.ws.WsUtils.writeProtobuf;
@@ -124,6 +125,7 @@ public class SearchProjectsAction implements ComponentsWsAction {
       .addPagingParams(DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE)
       .setInternal(true)
       .setChangelog(
+        new Change("10.3", "Add 'creationDate' sort parameter."),
         new Change("10.2", "Field 'needIssueSync' removed from response"),
         new Change("8.3", "Add 'qualifier' filter and facet"),
         new Change("8.0", "Field 'id' removed from response"))
@@ -195,8 +197,8 @@ public class SearchProjectsAction implements ComponentsWsAction {
         " <li>APP - for applications</li>" +
         HTML_UL_END_TAG);
     action.createParam(Param.SORT)
-      .setDescription("Sort projects by numeric metric key, quality gate status (using '%s'), last analysis date (using '%s'), or by project name.",
-        ALERT_STATUS_KEY, SORT_BY_LAST_ANALYSIS_DATE, PARAM_FILTER)
+      .setDescription("Sort projects by numeric metric key, quality gate status (using '%s'), last analysis date (using '%s'), project name or creationDate (using '%s').",
+        ALERT_STATUS_KEY, SORT_BY_LAST_ANALYSIS_DATE, PARAM_FILTER, SORT_BY_CREATION_DATE)
       .setDefaultValue(SORT_BY_NAME)
       .setPossibleValues(
         Stream.concat(METRIC_KEYS.stream(), NON_METRIC_SORT_KEYS.stream()).sorted().toList())
index 30e4d94122f0d1289764da67dad4bf1be6d6a746..8783ebb74a43d9a85431a36b3301ca32303e25bc 100644 (file)
@@ -28,7 +28,6 @@ import static org.sonar.server.measure.index.ProjectMeasuresQuery.MetricCriterio
 
 public class ProjectMeasuresQueryValidatorTest {
 
-
   @Test
   public void query_with_empty_metrics_is_valid() {
     ProjectMeasuresQueryValidator.validate(new ProjectMeasuresQuery());
@@ -199,6 +198,16 @@ public class ProjectMeasuresQueryValidatorTest {
     assertValidSortKey("new_reliability_rating");
   }
 
+  @Test
+  public void sort_by_creation_date_is_valid() {
+    assertValidSortKey("creationDate");
+  }
+
+  @Test
+  public void sort_by_analysis_date_is_valid() {
+    assertValidSortKey("analysisDate");
+  }
+
   @Test
   public void sort_by_bla_is_invalid() {
     assertInvalidSortKey("bla");