]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22727 index software qualitiy measure to project measures
authorDejan Milisavljevic <130993898+dejan-milisavljevic-sonarsource@users.noreply.github.com>
Wed, 14 Aug 2024 10:03:22 +0000 (12:03 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 26 Aug 2024 20:03:06 +0000 (20:03 +0000)
Co-authored-by: Léo Geoffroy <leo.geoffroy@sonarsource.com>
14 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/measure/ProjectMeasuresIndexerIterator.java
server/sonar-server-common/src/main/java/org/sonar/server/es/IndexDefinitionHash.java
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/BuiltIndex.java
server/sonar-server-common/src/main/java/org/sonar/server/es/newindex/NewIndex.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-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializer.java [new file with mode: 0644]
server/sonar-server-common/src/test/java/org/sonar/server/es/IndexDefinitionHashTest.java
server/sonar-server-common/src/test/java/org/sonar/server/es/newindex/NewIndexTest.java
server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializerTest.java [new file with mode: 0644]
server/sonar-webserver-es/build.gradle
server/sonar-webserver-es/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndex.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

index 95c0eb350dc877e4d19b739e944314939e66eeeb..f7d898745b019538b84c012a2544f5e39dfd9a23 100644 (file)
@@ -40,6 +40,7 @@ import javax.annotation.Nullable;
 import org.apache.commons.lang3.StringUtils;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.resources.Qualifiers;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
 import org.sonar.core.util.CloseableIterator;
 import org.sonar.db.DatabaseUtils;
 import org.sonar.db.DbSession;
@@ -70,7 +71,18 @@ public class ProjectMeasuresIndexerIterator extends CloseableIterator<ProjectMea
     CoreMetrics.NEW_COVERAGE_KEY,
     CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY,
     CoreMetrics.NEW_LINES_KEY,
-    CoreMetrics.NEW_RELIABILITY_RATING_KEY);
+    CoreMetrics.NEW_RELIABILITY_RATING_KEY,
+
+    //Ratings based on software quality
+    SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY,
+    SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY,
+    SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY,
+    SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY,
+    SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY,
+    SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY,
+    SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY,
+    SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY
+    );
 
   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 " +
index e36f8a1acb1e2b2ef9e0457a284429c098b112c6..42783e5404feb15e03f888275bf44d65f090954e 100644 (file)
@@ -45,7 +45,8 @@ class IndexDefinitionHash {
       index.getSettings().toString(),
       Map.of(mainType.getIndex(), mainType),
       index.getRelationTypes().stream().collect(Collectors.toMap(IndexType.IndexRelationType::getName, Function.identity())),
-      index.getAttributes());
+      index.getAttributes(),
+      index.getCustomHashMetadata());
   }
 
   private static String of(String str, Map<?, ?>... maps) {
index 1e2c0b74208e3fa2457d704d420c0a1dd1f8c686..62763890bd27c72feeb510b9203b9e766c4bb912 100644 (file)
@@ -44,12 +44,14 @@ public final class BuiltIndex<T extends NewIndex<T>> {
   private final Set<IndexRelationType> relationTypes;
   private final Settings settings;
   private final Map<String, Object> attributes;
+  private final Map<String, String> customHashMetadata;
 
   BuiltIndex(T newIndex) {
     this.mainType = newIndex.getMainType();
     this.settings = newIndex.getSettings().build();
     this.relationTypes = newIndex.getRelationsStream().collect(Collectors.toSet());
     this.attributes = buildAttributes(newIndex);
+    this.customHashMetadata = newIndex.getCustomHashMetadata();
   }
 
   private static Map<String, Object> buildAttributes(NewIndex<?> newIndex) {
@@ -121,4 +123,8 @@ public final class BuiltIndex<T extends NewIndex<T>> {
   public Map<String, Object> getAttributes() {
     return attributes;
   }
+
+  public Map<String, String> getCustomHashMetadata() {
+    return customHashMetadata;
+  }
 }
index dc719bd4d64ad1bf531ad1bca97d9c5213fbe186..1a5c6fff264e60969840f9e8982d1ef3ad923b78 100644 (file)
@@ -47,6 +47,7 @@ public abstract class NewIndex<T extends NewIndex<T>> {
   private final Settings.Builder settings = DefaultIndexSettings.defaults();
   private final Map<String, Object> attributes = new TreeMap<>();
   private final Map<String, Object> properties = new TreeMap<>();
+  private final Map<String, String> customHashMetadata = new TreeMap<>();
 
   public NewIndex(Index index, SettingsConfiguration settingsConfiguration) {
     this.index = index;
@@ -158,4 +159,16 @@ public abstract class NewIndex<T extends NewIndex<T>> {
 
   public abstract BuiltIndex<T> build();
 
+  /**
+   * Set additional information to be used to compute the hash of the index.
+   * If hash metadata changes, the hash of index will also change.
+   */
+  public T addCustomHashMetadata(String key, String value) {
+    this.customHashMetadata.put(key, value);
+    return castThis();
+  }
+
+  public Map<String, String> getCustomHashMetadata() {
+    return customHashMetadata;
+  }
 }
index 3172c6f3bd07bda2f2691af54aaea57d212c4190..5766293f35231b7af209c42c2cfd4f98a4dbdbbb 100644 (file)
@@ -22,6 +22,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.db.measure.ProjectMeasuresIndexerIterator;
 import org.sonar.server.es.Index;
 import org.sonar.server.es.IndexDefinition;
 import org.sonar.server.es.IndexType;
@@ -65,6 +66,7 @@ public class ProjectMeasuresIndexDefinition implements IndexDefinition {
   public static final String FIELD_NCLOC_DISTRIBUTION_LANGUAGE = FIELD_NCLOC_DISTRIBUTION + "." + SUB_FIELD_DISTRIB_LANGUAGE;
   public static final String FIELD_NCLOC_DISTRIBUTION_NCLOC = FIELD_NCLOC_DISTRIBUTION + "." + SUB_FIELD_DISTRIB_NCLOC;
 
+  private static final String METRICS_CUSTOM_METADATA_KEY = "metrics";
   private final Configuration config;
   private final boolean enableSource;
 
@@ -94,7 +96,8 @@ public class ProjectMeasuresIndexDefinition implements IndexDefinition {
         .setRefreshInterval(MANUAL_REFRESH_INTERVAL)
         .setDefaultNbOfShards(5)
         .build())
-      .setEnableSource(enableSource);
+      .setEnableSource(enableSource)
+      .addCustomHashMetadata(METRICS_CUSTOM_METADATA_KEY, String.join(",", ProjectMeasuresIndexerIterator.METRIC_KEYS));
 
     TypeMapping mapping = index.createTypeMapping(TYPE_PROJECT_MEASURES);
     mapping.keywordFieldBuilder(FIELD_UUID).disableNorms().build();
index d698e08a6192f1c747ea252eb229f0a767f59ab7..6f5898c73c6cd57ec8810a8060a68348d912fc9c 100644 (file)
@@ -211,7 +211,7 @@ public class ProjectMeasuresIndexer implements EventIndexer, AnalysisIndexer, Ne
       .setTags(project.getTags())
       .setAnalysedAt(analysisDate == null ? null : new Date(analysisDate))
       .setCreatedAt(new Date(project.getCreationDate()))
-      .setMeasuresFromMap(projectMeasures.getMeasures().getNumericMeasures())
+      .setMeasuresFromMap(ProjectMeasuresSoftwareQualityRatingsInitializer.initializeSoftwareQualityRatings(projectMeasures.getMeasures().getNumericMeasures()))
       .setLanguages(new ArrayList<>(projectMeasures.getMeasures().getNclocByLanguages().keySet()))
       .setNclocLanguageDistributionFromMap(projectMeasures.getMeasures().getNclocByLanguages());
   }
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializer.java b/server/sonar-server-common/src/main/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializer.java
new file mode 100644 (file)
index 0000000..fecce77
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.measure.index;
+
+import java.util.Map;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
+import org.sonar.server.measure.Rating;
+
+/**
+ * This class defines "default" measures values for the "Software Quality Rating Metrics" when they do not exist.
+ * The "default" value is the same as the equivalent "Rule Type Rating Metric", except for E Rating that is converted to D Rating
+  * If the "Software Quality Rating Metrics" exists, then no changes are made
+ */
+public class ProjectMeasuresSoftwareQualityRatingsInitializer {
+
+  private static final Map<String, String> RATING_KEY_TO_SOFTWARE_QUALITY_RATING_KEY = Map.of(
+    CoreMetrics.SQALE_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY,
+    CoreMetrics.RELIABILITY_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY,
+    CoreMetrics.SECURITY_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY,
+    CoreMetrics.SECURITY_REVIEW_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY,
+    CoreMetrics.NEW_SECURITY_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY,
+    CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY,
+    CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY,
+    CoreMetrics.NEW_RELIABILITY_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY
+  );
+
+  private ProjectMeasuresSoftwareQualityRatingsInitializer() {
+  }
+
+  public static Map<String, Double> initializeSoftwareQualityRatings(Map<String, Double> measures) {
+
+    RATING_KEY_TO_SOFTWARE_QUALITY_RATING_KEY.forEach((k, v) -> initializeSoftwareQualityRatingMeasure(measures, k, v));
+    return measures;
+  }
+
+  private static void initializeSoftwareQualityRatingMeasure(Map<String, Double> measures, String ruleTypeMetric,
+    String softwareQualityMetric) {
+    if (measures.containsKey(softwareQualityMetric)) {
+      return;
+    }
+
+    Double value = measures.get(ruleTypeMetric);
+    if (value != null) {
+      measures.put(softwareQualityMetric, value > Rating.D.getIndex() ? Double.valueOf(Rating.D.getIndex()) : value);
+    }
+  }
+}
index 9c8047bcf414c615654b2d3485fff455f84a1192..4403aec2ae1dcef34270728dcd7d3c4f967fc8b2 100644 (file)
@@ -414,6 +414,30 @@ public class IndexDefinitionHashTest {
       .isNotEqualTo(hashOf(new TestNewIndex(mainType, someRefreshInterval)));
   }
 
+  @Test
+  public void hash_changes_if_customHashMetadata_changes() {
+    Index index = Index.simple("foo");
+    Configuration emptySettings = new MapSettings().asConfig();
+    SettingsConfiguration emptyConfiguration = SettingsConfiguration.newBuilder(emptySettings)
+      .build();
+    IndexMainType mainType = IndexMainType.main(index, "bar");
+    assertThat(hashOf(new TestNewIndex(mainType, emptyConfiguration)))
+      .isNotEqualTo(hashOf(new TestNewIndex(mainType, emptyConfiguration)
+        .addCustomHashMetadata("foo", "bar")));
+
+    assertThat(hashOf(new TestNewIndex(mainType, emptyConfiguration).addCustomHashMetadata("foo", "bar")))
+      .isNotEqualTo(hashOf(new TestNewIndex(mainType, emptyConfiguration).addCustomHashMetadata("foo2", "bar")));
+
+    assertThat(hashOf(new TestNewIndex(mainType, emptyConfiguration).addCustomHashMetadata("foo", "bar")))
+      .isNotEqualTo(hashOf(new TestNewIndex(mainType, emptyConfiguration).addCustomHashMetadata("foo", "bar,bar2")));
+
+    assertThat(hashOf(new TestNewIndex(mainType, emptyConfiguration).addCustomHashMetadata("foo", "bar")))
+      .isEqualTo(hashOf(new TestNewIndex(mainType, emptyConfiguration).addCustomHashMetadata("foo", "bar")));
+
+    assertThat(hashOf(new TestNewIndex(mainType, emptyConfiguration).addCustomHashMetadata("foo", "bar").addCustomHashMetadata("foo2", "bar2")))
+      .isEqualTo(hashOf(new TestNewIndex(mainType, emptyConfiguration).addCustomHashMetadata("foo2", "bar2").addCustomHashMetadata("foo", "bar")));
+  }
+
   private static SettingsConfiguration settingsConfigurationOf(MapSettings settings) {
     return SettingsConfiguration.newBuilder(settings.asConfig()).build();
   }
index eab6b8f9d11774671133c60c7f2f853d83db8819..6824d572e02141e2def43f4c6c095a7bfff11a96 100644 (file)
@@ -340,6 +340,17 @@ public class NewIndexTest {
     assertThat(getAttributeAsMap(newIndex, "_source")).containsExactly(entry("enabled", false));
   }
 
+  @Test
+  @UseDataProvider("indexWithAndWithoutRelations")
+  public void index_withHashMetadata(Index index) {
+    NewIndex newIndex = new SimplestNewIndex(IndexType.main(index, "foo"), defaultSettingsConfiguration).addCustomHashMetadata("custom", "hash");
+
+    assertThat(newIndex.getCustomHashMetadata()).containsExactly(entry("custom", "hash"));
+
+    BuiltIndex build = newIndex.build();
+    assertThat(build.getCustomHashMetadata()).containsExactly(entry("custom", "hash"));
+  }
+
   @Test
   public void createTypeMapping_with_IndexRelationType_fails_with_ISE_if_index_does_not_allow_relations() {
     IndexType.IndexRelationType indexRelationType = IndexType.relation(IndexType.main(Index.withRelations(someIndexName), "bar"), "bar");
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/measure/index/ProjectMeasuresSoftwareQualityRatingsInitializerTest.java
new file mode 100644 (file)
index 0000000..ed91191
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 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.measure.index;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
+import org.sonar.server.measure.Rating;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class ProjectMeasuresSoftwareQualityRatingsInitializerTest {
+
+  @Test
+  void initializeSoftwareQualityRatings_whenNoRating_thenNoSoftwareQualityRatingMetric() {
+    Map<String, Double> initialMeasures = new HashMap<>();
+    initialMeasures.put(CoreMetrics.BRANCH_COVERAGE_KEY, null);
+    initialMeasures.put(CoreMetrics.ACCEPTED_ISSUES_KEY, 12.0);
+    initialMeasures.put(CoreMetrics.SQALE_RATING_KEY, null);
+
+    Map<String, Double> measures = new HashMap<>(initialMeasures);
+    ProjectMeasuresSoftwareQualityRatingsInitializer.initializeSoftwareQualityRatings(measures);
+
+    assertThat(measures).containsExactlyInAnyOrderEntriesOf(initialMeasures);
+  }
+
+  @Test
+  void initializeSoftwareQualityRatings_whenRatingAndNoSoftwareQualityRating_thenSoftwareQualityRatingMetricAreCreated() {
+    Map<String, Double> initialMeasures = new HashMap<>();
+    initialMeasures.put(CoreMetrics.SQALE_RATING_KEY, 1.0);
+    initialMeasures.put(CoreMetrics.RELIABILITY_RATING_KEY, 2.0);
+    initialMeasures.put(CoreMetrics.SECURITY_RATING_KEY, 3.0);
+    initialMeasures.put(CoreMetrics.SECURITY_REVIEW_RATING_KEY, 4.0);
+    initialMeasures.put(CoreMetrics.NEW_SECURITY_RATING_KEY, 4.0);
+    initialMeasures.put(CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY, 3.0);
+    initialMeasures.put(CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, 2.0);
+    initialMeasures.put(CoreMetrics.NEW_RELIABILITY_RATING_KEY, 1.0);
+
+    Map<String, Double> measures = new HashMap<>(initialMeasures);
+
+    ProjectMeasuresSoftwareQualityRatingsInitializer.initializeSoftwareQualityRatings(measures);
+
+    assertThat(measures).hasSize(initialMeasures.size() * 2)
+      .containsAllEntriesOf(initialMeasures)
+      .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, 1.0)
+      .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, 2.0)
+      .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY, 3.0)
+      .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, 4.0)
+      .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, 4.0)
+      .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, 3.0)
+      .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, 2.0)
+      .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, 1.0);
+  }
+
+  @Test
+  void initializeSoftwareQualityRatings_whenERating_thenSoftwareQualityRatingCreatedWithD() {
+    Map<String, Double> initialMeasures = new HashMap<>();
+    initialMeasures.put(CoreMetrics.SQALE_RATING_KEY, (double) Rating.E.getIndex());
+    initialMeasures.put(CoreMetrics.RELIABILITY_RATING_KEY, (double) Rating.E.getIndex());
+    initialMeasures.put(CoreMetrics.SECURITY_RATING_KEY, (double) Rating.E.getIndex());
+    initialMeasures.put(CoreMetrics.SECURITY_REVIEW_RATING_KEY, (double) Rating.E.getIndex());
+    initialMeasures.put(CoreMetrics.NEW_SECURITY_RATING_KEY, (double) Rating.E.getIndex());
+    initialMeasures.put(CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY, (double) Rating.E.getIndex());
+    initialMeasures.put(CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, (double) Rating.E.getIndex());
+    initialMeasures.put(CoreMetrics.NEW_RELIABILITY_RATING_KEY, (double) Rating.E.getIndex());
+
+    Map<String, Double> measures = new HashMap<>(initialMeasures);
+
+    ProjectMeasuresSoftwareQualityRatingsInitializer.initializeSoftwareQualityRatings(measures);
+
+    assertThat(measures).hasSize(initialMeasures.size() * 2)
+      .containsAllEntriesOf(initialMeasures)
+      .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, (double) Rating.D.getIndex())
+      .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, (double) Rating.D.getIndex())
+      .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY, (double) Rating.D.getIndex())
+      .containsEntry(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, (double) Rating.D.getIndex())
+      .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, (double) Rating.D.getIndex())
+      .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, (double) Rating.D.getIndex())
+      .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, (double) Rating.D.getIndex())
+      .containsEntry(SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, (double) Rating.D.getIndex());
+  }
+}
index d59d52be5206e6b354889ae31a72e4e29baff07b..d51c2b4f16bb1f95d434ea4692f4ee8a1d48ad92 100644 (file)
@@ -21,6 +21,7 @@ dependencies {
   testImplementation 'com.github.spotbugs:spotbugs-annotations'
   testImplementation 'com.tngtech.java:junit-dataprovider'
   testImplementation 'org.junit.jupiter:junit-jupiter-api'
+  testImplementation 'org.junit.jupiter:junit-jupiter-params'
   testImplementation 'org.mockito:mockito-core'
   testImplementation 'org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures'
   testImplementation testFixtures(project(':server:sonar-webserver-auth'))
index 5e33203b4bd7497c9577459340e5891bd8ce40d3..9d6344f96e21f076a5c4521f685d62f39d6be235 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.measure.index;
 
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -103,6 +104,14 @@ import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY;
 import static org.sonar.api.measures.CoreMetrics.SECURITY_RATING_KEY;
 import static org.sonar.api.measures.CoreMetrics.SECURITY_REVIEW_RATING_KEY;
 import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY;
 import static org.sonar.server.es.EsUtils.escapeSpecialRegexChars;
 import static org.sonar.server.es.EsUtils.termsToMap;
 import static org.sonar.server.es.IndexType.FIELD_INDEX_TYPE;
@@ -154,6 +163,8 @@ public class ProjectMeasuresIndex {
     NEW_DUPLICATED_LINES_DENSITY(new RangeWithNoDataMeasureFacet(NEW_DUPLICATED_LINES_DENSITY_KEY, DUPLICATIONS_THRESHOLDS)),
     COVERAGE(new RangeWithNoDataMeasureFacet(COVERAGE_KEY, COVERAGE_THRESHOLDS)),
     NEW_COVERAGE(new RangeWithNoDataMeasureFacet(NEW_COVERAGE_KEY, COVERAGE_THRESHOLDS)),
+
+    //RuleType ratings
     SQALE_RATING(new RatingMeasureFacet(SQALE_RATING_KEY)),
     NEW_MAINTAINABILITY_RATING(new RatingMeasureFacet(NEW_MAINTAINABILITY_RATING_KEY)),
     RELIABILITY_RATING(new RatingMeasureFacet(RELIABILITY_RATING_KEY)),
@@ -162,6 +173,17 @@ public class ProjectMeasuresIndex {
     NEW_SECURITY_RATING(new RatingMeasureFacet(NEW_SECURITY_RATING_KEY)),
     SECURITY_REVIEW_RATING(new RatingMeasureFacet(SECURITY_REVIEW_RATING_KEY)),
     NEW_SECURITY_REVIEW_RATING(new RatingMeasureFacet(NEW_SECURITY_REVIEW_RATING_KEY)),
+
+    //Software quality ratings
+    SOFTWARE_QUALITY_MAINTAINABILITY_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, 4)),
+    NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, 4)),
+    SOFTWARE_QUALITY_RELIABILITY_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, 4)),
+    NEW_SOFTWARE_QUALITY_RELIABILITY_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, 4)),
+    SOFTWARE_QUALITY_SECURITY_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_SECURITY_RATING_KEY, 4)),
+    NEW_SOFTWARE_QUALITY_SECURITY_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY, 4)),
+    SOFTWARE_QUALITY_SECURITY_REVIEW_RATING(new RatingMeasureFacet(SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, 4)),
+    NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING(new RatingMeasureFacet(NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, 4)),
+
     SECURITY_HOTSPOTS_REVIEWED(new RangeMeasureFacet(SECURITY_HOTSPOTS_REVIEWED_KEY, SECURITY_REVIEW_RATING_THRESHOLDS)),
     NEW_SECURITY_HOTSPOTS_REVIEWED(new RangeMeasureFacet(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, SECURITY_REVIEW_RATING_THRESHOLDS)),
     ALERT_STATUS(new MeasureFacet(ALERT_STATUS_KEY, ProjectMeasuresIndex::buildAlertStatusFacet)),
@@ -566,14 +588,20 @@ public class ProjectMeasuresIndex {
   private static class RatingMeasureFacet extends MeasureFacet {
 
     private RatingMeasureFacet(String metricKey) {
-      super(metricKey, new MetricRatingFacetBuilder(metricKey));
+      super(metricKey, new MetricRatingFacetBuilder(metricKey, 5));
+    }
+
+    private RatingMeasureFacet(String metricKey, int maxRating) {
+      super(metricKey, new MetricRatingFacetBuilder(metricKey, maxRating));
     }
 
     private static class MetricRatingFacetBuilder implements FacetBuilder {
       private final String metricKey;
+      private final int maxRating;
 
-      private MetricRatingFacetBuilder(String metricKey) {
+      private MetricRatingFacetBuilder(String metricKey, int maxRating) {
         this.metricKey = metricKey;
+        this.maxRating = maxRating;
       }
 
       @Override
@@ -581,19 +609,20 @@ public class ProjectMeasuresIndex {
         return topAggregationHelper.buildTopAggregation(
           facet.getName(), facet.getTopAggregationDef(),
           NO_EXTRA_FILTER,
-          t -> t.subAggregation(createMeasureRatingFacet(metricKey)));
+          t -> t.subAggregation(createMeasureRatingFacet(metricKey, maxRating)));
       }
 
-      private static AbstractAggregationBuilder<?> createMeasureRatingFacet(String metricKey) {
+      private static AbstractAggregationBuilder<?> createMeasureRatingFacet(String metricKey, int maxRating) {
+        List<KeyedFilter> filter = new ArrayList<>();
+        for (int i = 1; i <= maxRating; i++) {
+          filter.add(new KeyedFilter(String.valueOf(i), termQuery(FIELD_MEASURES_MEASURE_VALUE, Double.valueOf(i))));
+        }
+
         return AggregationBuilders.nested("nested_" + metricKey, FIELD_MEASURES)
           .subAggregation(
             AggregationBuilders.filter("filter_" + metricKey, termsQuery(FIELD_MEASURES_MEASURE_KEY, metricKey))
-              .subAggregation(filters(metricKey,
-                new KeyedFilter("1", termQuery(FIELD_MEASURES_MEASURE_VALUE, 1D)),
-                new KeyedFilter("2", termQuery(FIELD_MEASURES_MEASURE_VALUE, 2D)),
-                new KeyedFilter("3", termQuery(FIELD_MEASURES_MEASURE_VALUE, 3D)),
-                new KeyedFilter("4", termQuery(FIELD_MEASURES_MEASURE_VALUE, 4D)),
-                new KeyedFilter("5", termQuery(FIELD_MEASURES_MEASURE_VALUE, 5D)))));
+              .subAggregation(filters(metricKey, filter.toArray(new KeyedFilter[0])
+              )));
       }
     }
   }
index 6e2961e4c4d6a195b8e5ceae1feb6f0e4f964f9d..8b21640cbb5517bc475da488d560e0134c89dc67 100644 (file)
  */
 package org.sonar.server.measure.index;
 
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 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;
+import java.util.Set;
 import java.util.stream.IntStream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.sonar.api.utils.System2;
 import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
@@ -55,7 +50,6 @@ import static com.google.common.collect.Sets.newHashSet;
 import static java.util.Arrays.asList;
 import static java.util.Arrays.stream;
 import static java.util.Collections.singletonList;
-import static java.util.stream.Collectors.toList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.Assertions.entry;
@@ -73,8 +67,7 @@ import static org.sonar.server.measure.index.ProjectMeasuresIndexDefinition.TYPE
 import static org.sonar.server.measure.index.ProjectMeasuresQuery.Operator;
 import static org.sonarqube.ws.client.project.ProjectsWsParameters.FILTER_QUALIFIER;
 
-@RunWith(DataProviderRunner.class)
-public class ProjectMeasuresIndexTest {
+class ProjectMeasuresIndexTest {
 
   private static final String MAINTAINABILITY_RATING = "sqale_rating";
   private static final String NEW_MAINTAINABILITY_RATING_KEY = "new_maintainability_rating";
@@ -84,6 +77,17 @@ public class ProjectMeasuresIndexTest {
   private static final String NEW_SECURITY_RATING = "new_security_rating";
   private static final String SECURITY_REVIEW_RATING = "security_review_rating";
   private static final String NEW_SECURITY_REVIEW_RATING = "new_security_review_rating";
+
+  //Software quality ratings
+  private static final String SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY = "software_quality_maintainability_rating";
+  private static final String NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY = "new_software_quality_maintainability_rating";
+  private static final String SOFTWARE_QUALITY_RELIABILITY_RATING_KEY = "software_quality_reliability_rating";
+  private static final String NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY = "new_software_quality_reliability_rating";
+  private static final String SOFTWARE_QUALITY_SECURITY_RATING_KEY = "software_quality_security_rating";
+  private static final String NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY = "new_software_quality_security_rating";
+  private static final String SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY = "software_quality_security_review_rating";
+  private static final String NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY = "new_software_quality_security_review_rating";
+
   private static final String SECURITY_HOTSPOTS_REVIEWED = "security_hotspots_reviewed";
   private static final String NEW_SECURITY_HOTSPOTS_REVIEWED = "new_security_hotspots_reviewed";
   private static final String COVERAGE = "coverage";
@@ -105,28 +109,41 @@ public class ProjectMeasuresIndexTest {
   private static final GroupDto GROUP1 = newGroupDto();
   private static final GroupDto GROUP2 = newGroupDto();
 
-  @Rule
-  public EsTester es = EsTester.create();
-  @Rule
-  public UserSessionRule userSession = UserSessionRule.standalone();
+  @RegisterExtension
+  private final EsTester es = EsTester.create();
+  @RegisterExtension
+  private final UserSessionRule userSession = UserSessionRule.standalone();
+
+  private static String[] rating_metric_keys() {
+    return new String[]{
+      MAINTAINABILITY_RATING, NEW_MAINTAINABILITY_RATING_KEY,
+      RELIABILITY_RATING, NEW_RELIABILITY_RATING,
+      SECURITY_RATING, NEW_SECURITY_RATING,
+      SECURITY_REVIEW_RATING, NEW_SECURITY_REVIEW_RATING
+    };
+  }
 
-  @DataProvider
-  public static Object[][] rating_metric_keys() {
-    return new Object[][] {{MAINTAINABILITY_RATING}, {NEW_MAINTAINABILITY_RATING_KEY}, {RELIABILITY_RATING}, {NEW_RELIABILITY_RATING}, {SECURITY_RATING}, {NEW_SECURITY_RATING},
-      {SECURITY_REVIEW_RATING}, {NEW_SECURITY_REVIEW_RATING}};
+  private static String[] software_quality_rating_metric_keys() {
+    return new String[]{
+      SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY,
+      SOFTWARE_QUALITY_RELIABILITY_RATING_KEY, NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY,
+      SOFTWARE_QUALITY_SECURITY_RATING_KEY, NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY,
+      SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY, NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY
+    };
   }
 
-  private ProjectMeasuresIndexer projectMeasureIndexer = new ProjectMeasuresIndexer(null, es.client());
-  private PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, projectMeasureIndexer);
-  private ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession), System2.INSTANCE);
+  private final ProjectMeasuresIndexer projectMeasureIndexer = new ProjectMeasuresIndexer(null, es.client());
+  private final PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, projectMeasureIndexer);
+  private final ProjectMeasuresIndex underTest = new ProjectMeasuresIndex(es.client(), new WebAuthorizationTypeSupport(userSession),
+    System2.INSTANCE);
 
   @Test
-  public void return_empty_if_no_projects() {
+  void return_empty_if_no_projects() {
     assertNoResults(new ProjectMeasuresQuery());
   }
 
   @Test
-  public void default_sort_is_by_ascending_case_insensitive_name_then_by_key() {
+  void default_sort_is_by_ascending_case_insensitive_name_then_by_key() {
     ComponentDto windows = ComponentTesting.newPrivateProjectDto().setUuid("windows").setName("Windows").setKey("project1");
     ComponentDto apachee = ComponentTesting.newPrivateProjectDto().setUuid("apachee").setName("apachee").setKey("project2");
     ComponentDto apache1 = ComponentTesting.newPrivateProjectDto().setUuid("apache-1").setName("Apache").setKey("project3");
@@ -137,7 +154,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void sort_by_insensitive_name() {
+  void sort_by_insensitive_name() {
     ComponentDto windows = ComponentTesting.newPrivateProjectDto().setUuid("windows").setName("Windows");
     ComponentDto apachee = ComponentTesting.newPrivateProjectDto().setUuid("apachee").setName("apachee");
     ComponentDto apache = ComponentTesting.newPrivateProjectDto().setUuid("apache").setName("Apache");
@@ -148,7 +165,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void sort_by_ncloc() {
+  void sort_by_ncloc() {
     index(
       newDoc(PROJECT1, NCLOC, 15_000d),
       newDoc(PROJECT2, NCLOC, 30_000d),
@@ -159,7 +176,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void sort_by_a_metric_then_by_name_then_by_key() {
+  void sort_by_a_metric_then_by_name_then_by_key() {
     ComponentDto windows = ComponentTesting.newPrivateProjectDto().setUuid("windows").setName("Windows").setKey("project1");
     ComponentDto apachee = ComponentTesting.newPrivateProjectDto().setUuid("apachee").setName("apachee").setKey("project2");
     ComponentDto apache1 = ComponentTesting.newPrivateProjectDto().setUuid("apache-1").setName("Apache").setKey("project3");
@@ -175,7 +192,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void sort_by_quality_gate_status() {
+  void sort_by_quality_gate_status() {
     ComponentDto project4 = ComponentTesting.newPrivateProjectDto().setUuid("Project-4").setName("Project 4").setKey("key-4");
     index(
       newDoc(PROJECT1).setQualityGateStatus(OK.name()),
@@ -186,40 +203,8 @@ 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() {
+  void sort_by_quality_gate_status_then_by_name_then_by_key() {
     ComponentDto windows = ComponentTesting.newPrivateProjectDto().setUuid("windows").setName("Windows").setKey("project1");
     ComponentDto apachee = ComponentTesting.newPrivateProjectDto().setUuid("apachee").setName("apachee").setKey("project2");
     ComponentDto apache1 = ComponentTesting.newPrivateProjectDto().setUuid("apache-1").setName("Apache").setKey("project3");
@@ -235,7 +220,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void paginate_results() {
+  void paginate_results() {
     IntStream.rangeClosed(1, 9)
       .forEach(i -> index(newDoc(newPrivateProjectDto("P" + i))));
 
@@ -246,7 +231,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_with_lower_than() {
+  void filter_with_lower_than() {
     index(
       newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
       newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
@@ -259,7 +244,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_with_lower_than_or_equals() {
+  void filter_with_lower_than_or_equals() {
     index(
       newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
       newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
@@ -272,7 +257,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_with_greater_than() {
+  void filter_with_greater_than() {
     index(
       newDoc(PROJECT1, COVERAGE, 80d, NCLOC, 30_000d),
       newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 30_001d),
@@ -286,7 +271,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_with_greater_than_or_equals() {
+  void filter_with_greater_than_or_equals() {
     index(
       newDoc(PROJECT1, COVERAGE, 80d, NCLOC, 30_000d),
       newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 30_001d),
@@ -300,7 +285,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_with_equals() {
+  void filter_with_equals() {
     index(
       newDoc(PROJECT1, COVERAGE, 79d, NCLOC, 10_000d),
       newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_000d),
@@ -313,7 +298,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_no_data_with_several_projects() {
+  void filter_on_no_data_with_several_projects() {
     index(
       newDoc(PROJECT1, NCLOC, 1d),
       newDoc(PROJECT2, DUPLICATION, 80d));
@@ -325,7 +310,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_no_data_should_not_return_projects_with_data_and_other_measures() {
+  void filter_on_no_data_should_not_return_projects_with_data_and_other_measures() {
     ComponentDto project = ComponentTesting.newPrivateProjectDto();
     index(newDoc(project, DUPLICATION, 80d, NCLOC, 1d));
 
@@ -335,7 +320,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_no_data_should_not_return_projects_with_data() {
+  void filter_on_no_data_should_not_return_projects_with_data() {
     ComponentDto project = ComponentTesting.newPrivateProjectDto();
     index(newDoc(project, DUPLICATION, 80d));
 
@@ -345,7 +330,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_no_data_should_return_projects_with_no_data() {
+  void filter_on_no_data_should_return_projects_with_no_data() {
     ComponentDto project = ComponentTesting.newPrivateProjectDto();
     index(newDoc(project, NCLOC, 1d));
 
@@ -355,7 +340,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_several_metrics() {
+  void filter_on_several_metrics() {
     index(
       newDoc(PROJECT1, COVERAGE, 81d, NCLOC, 10_001d),
       newDoc(PROJECT2, COVERAGE, 80d, NCLOC, 10_001d),
@@ -369,7 +354,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_security_hotspots_reviewed() {
+  void facet_security_hotspots_reviewed() {
     index(
       // 2 docs with no measure
       newDocWithNoMeasure(),
@@ -406,7 +391,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_new_security_hotspots_reviewed() {
+  void facet_new_security_hotspots_reviewed() {
     index(
       // 2 docs with no measure
       newDocWithNoMeasure(),
@@ -443,7 +428,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_quality_gate_status() {
+  void filter_on_quality_gate_status() {
     index(
       newDoc(PROJECT1).setQualityGateStatus(OK.name()),
       newDoc(PROJECT2).setQualityGateStatus(OK.name()),
@@ -454,7 +439,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_languages() {
+  void filter_on_languages() {
     ComponentDto project4 = ComponentTesting.newPrivateProjectDto().setUuid("Project-4").setName("Project 4").setKey("key-4");
     index(
       newDoc(PROJECT1).setLanguages(singletonList("java")),
@@ -468,7 +453,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_query_text() {
+  void filter_on_query_text() {
     ComponentDto windows = ComponentTesting.newPrivateProjectDto().setUuid("windows").setName("Windows").setKey("project1");
     ComponentDto apachee = ComponentTesting.newPrivateProjectDto().setUuid("apachee").setName("apachee").setKey("project2");
     ComponentDto apache1 = ComponentTesting.newPrivateProjectDto().setUuid("apache-1").setName("Apache").setKey("project3");
@@ -481,7 +466,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_ids() {
+  void filter_on_ids() {
     index(
       newDoc(PROJECT1),
       newDoc(PROJECT2),
@@ -492,7 +477,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_tags() {
+  void filter_on_tags() {
     index(
       newDoc(PROJECT1).setTags(newArrayList("finance", "platform")),
       newDoc(PROJECT2).setTags(newArrayList("marketing", "platform")),
@@ -506,7 +491,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void filter_on_qualifier() {
+  void filter_on_qualifier() {
     index(newDoc(PROJECT1), newDoc(PROJECT2), newDoc(PROJECT3),
       newDoc(APP1), newDoc(APP2), newDoc(APP3));
 
@@ -524,7 +509,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void return_correct_number_of_total_if_exceeds_index_max_results() {
+  void return_correct_number_of_total_if_exceeds_index_max_results() {
     index(IntStream.range(0, 12_000)
       .mapToObj(operand -> newDoc(ComponentTesting.newPrivateProjectDto()))
       .toArray(ProjectMeasuresDoc[]::new));
@@ -535,7 +520,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void return_only_projects_and_applications_authorized_for_user() {
+  void return_only_projects_and_applications_authorized_for_user() {
     indexForUser(USER1, newDoc(PROJECT1), newDoc(PROJECT2),
       newDoc(APP1), newDoc(APP2));
     indexForUser(USER2, newDoc(PROJECT3), newDoc(APP3));
@@ -545,7 +530,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void return_only_projects_and_applications_authorized_for_user_groups() {
+  void return_only_projects_and_applications_authorized_for_user_groups() {
     indexForGroup(GROUP1, newDoc(PROJECT1), newDoc(PROJECT2),
       newDoc(APP1), newDoc(APP2));
     indexForGroup(GROUP2, newDoc(PROJECT3));
@@ -555,7 +540,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void return_only_projects_and_applications_authorized_for_user_and_groups() {
+  void return_only_projects_and_applications_authorized_for_user_and_groups() {
     indexForUser(USER1, newDoc(PROJECT1), newDoc(PROJECT2),
       newDoc(APP1), newDoc(APP2));
     indexForGroup(GROUP1, newDoc(PROJECT3));
@@ -565,7 +550,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void anonymous_user_can_only_access_projects_and_applications_authorized_for_anyone() {
+  void anonymous_user_can_only_access_projects_and_applications_authorized_for_anyone() {
     index(newDoc(PROJECT1), newDoc(APP1));
     indexForUser(USER1, newDoc(PROJECT2), newDoc(APP2));
 
@@ -574,7 +559,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void return_all_projects_and_applications_when_setIgnoreAuthorization_is_true() {
+  void return_all_projects_and_applications_when_setIgnoreAuthorization_is_true() {
     indexForUser(USER1, newDoc(PROJECT1), newDoc(PROJECT2), newDoc(APP1), newDoc(APP2));
     indexForUser(USER2, newDoc(PROJECT3), newDoc(APP3));
     userSession.logIn(USER1);
@@ -584,7 +569,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void does_not_return_facet_when_no_facets_in_options() {
+  void does_not_return_facet_when_no_facets_in_options() {
     index(
       newDoc(PROJECT1, NCLOC, 10d, COVERAGE_KEY, 30d, MAINTAINABILITY_RATING, 3d)
         .setQualityGateStatus(OK.name()));
@@ -595,7 +580,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_ncloc() {
+  void facet_ncloc() {
     index(
       // 3 docs with ncloc<1K
       newDoc(NCLOC, 0d),
@@ -630,7 +615,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_ncloc_is_sticky() {
+  void facet_ncloc_is_sticky() {
     index(
       // 1 docs with ncloc<1K
       newDoc(NCLOC, 999d, COVERAGE, 0d, DUPLICATION, 0d),
@@ -670,7 +655,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_ncloc_contains_only_projects_authorized_for_user() {
+  void facet_ncloc_contains_only_projects_authorized_for_user() {
     // User can see these projects
     indexForUser(USER1,
       // docs with ncloc<1K
@@ -702,7 +687,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_new_lines() {
+  void facet_new_lines() {
     index(
       // 3 docs with ncloc<1K
       newDoc(NEW_LINES, 0d),
@@ -737,7 +722,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_coverage() {
+  void facet_coverage() {
     index(
       // 1 doc with no coverage
       newDocWithNoMeasure(),
@@ -775,7 +760,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_coverage_is_sticky() {
+  void facet_coverage_is_sticky() {
     index(
       // docs with no coverage
       newDoc(NCLOC, 999d, DUPLICATION, 0d),
@@ -819,7 +804,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_coverage_contains_only_projects_authorized_for_user() {
+  void facet_coverage_contains_only_projects_authorized_for_user() {
     // User can see these projects
     indexForUser(USER1,
       // 1 doc with no coverage
@@ -857,7 +842,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_new_coverage() {
+  void facet_new_coverage() {
     index(
       // 1 doc with no coverage
       newDocWithNoMeasure(),
@@ -895,7 +880,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_duplicated_lines_density() {
+  void facet_duplicated_lines_density() {
     index(
       // 1 doc with no duplication
       newDocWithNoMeasure(),
@@ -933,7 +918,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_duplicated_lines_density_is_sticky() {
+  void facet_duplicated_lines_density_is_sticky() {
     index(
       // docs with no duplication
       newDoc(NCLOC, 50_001d, COVERAGE, 29d),
@@ -973,7 +958,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_duplicated_lines_density_contains_only_projects_authorized_for_user() {
+  void facet_duplicated_lines_density_contains_only_projects_authorized_for_user() {
     // User can see these projects
     indexForUser(USER1,
       // docs with no duplication
@@ -1011,7 +996,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_new_duplicated_lines_density() {
+  void facet_new_duplicated_lines_density() {
     index(
       // 2 docs with no measure
       newDocWithNoMeasure(),
@@ -1049,9 +1034,9 @@ public class ProjectMeasuresIndexTest {
       entry("20.0-*", 5L));
   }
 
-  @Test
-  @UseDataProvider("rating_metric_keys")
-  public void facet_on_rating(String metricKey) {
+  @ParameterizedTest
+  @MethodSource("rating_metric_keys")
+  void facet_on_rating(String metricKey) {
     index(
       // 3 docs with rating A
       newDoc(metricKey, 1d),
@@ -1085,9 +1070,38 @@ public class ProjectMeasuresIndexTest {
       entry("5", 5L));
   }
 
-  @Test
-  @UseDataProvider("rating_metric_keys")
-  public void facet_on_rating_is_sticky(String metricKey) {
+  @ParameterizedTest
+  @MethodSource("software_quality_rating_metric_keys")
+  void facet_on_software_quality_rating(String metricKey) {
+    index(
+      // 3 docs with rating A
+      newDoc(metricKey, 1d),
+      newDoc(metricKey, 1d),
+      newDoc(metricKey, 1d),
+      // 2 docs with rating B
+      newDoc(metricKey, 2d),
+      newDoc(metricKey, 2d),
+      // 4 docs with rating C
+      newDoc(metricKey, 3d),
+      newDoc(metricKey, 3d),
+      newDoc(metricKey, 3d),
+      newDoc(metricKey, 3d),
+      // 2 docs with rating D
+      newDoc(metricKey, 4d),
+      newDoc(metricKey, 4d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(metricKey)).getFacets();
+
+    assertThat(facets.get(metricKey)).containsExactly(
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 4L),
+      entry("4", 2L));
+  }
+
+  @ParameterizedTest
+  @MethodSource("rating_metric_keys")
+  void facet_on_rating_is_sticky(String metricKey) {
     index(
       // docs with rating A
       newDoc(metricKey, 1d, NCLOC, 100d, COVERAGE, 0d),
@@ -1129,9 +1143,48 @@ public class ProjectMeasuresIndexTest {
       entry("500000.0-*", 0L));
   }
 
-  @Test
-  @UseDataProvider("rating_metric_keys")
-  public void facet_on_rating_contains_only_projects_authorized_for_user(String metricKey) {
+  @ParameterizedTest
+  @MethodSource("software_quality_rating_metric_keys")
+  void facet_on_software_quality_rating_is_sticky(String metricKey) {
+    index(
+      // docs with rating A
+      newDoc(metricKey, 1d, NCLOC, 100d, COVERAGE, 0d),
+      newDoc(metricKey, 1d, NCLOC, 200d, COVERAGE, 0d),
+      newDoc(metricKey, 1d, NCLOC, 999d, COVERAGE, 0d),
+      // docs with rating B
+      newDoc(metricKey, 2d, NCLOC, 2000d, COVERAGE, 0d),
+      newDoc(metricKey, 2d, NCLOC, 5000d, COVERAGE, 0d),
+      // docs with rating C
+      newDoc(metricKey, 3d, NCLOC, 20000d, COVERAGE, 0d),
+      newDoc(metricKey, 3d, NCLOC, 30000d, COVERAGE, 0d),
+      newDoc(metricKey, 3d, NCLOC, 40000d, COVERAGE, 0d),
+      newDoc(metricKey, 3d, NCLOC, 50000d, COVERAGE, 0d),
+      // docs with rating D
+      newDoc(metricKey, 4d, NCLOC, 120000d, COVERAGE, 0d));
+
+    Facets facets = underTest.search(new ProjectMeasuresQuery()
+        .addMetricCriterion(MetricCriterion.create(metricKey, Operator.LT, 3d))
+        .addMetricCriterion(MetricCriterion.create(COVERAGE, Operator.LT, 30d)),
+      new SearchOptions().addFacets(metricKey, NCLOC)).getFacets();
+
+    // Sticky facet on maintainability rating does not take into account maintainability rating filter
+    assertThat(facets.get(metricKey)).containsExactly(
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 4L),
+      entry("4", 1L));
+    // But facet on ncloc does well take into into filters
+    assertThat(facets.get(NCLOC)).containsExactly(
+      entry("*-1000.0", 3L),
+      entry("1000.0-10000.0", 2L),
+      entry("10000.0-100000.0", 0L),
+      entry("100000.0-500000.0", 0L),
+      entry("500000.0-*", 0L));
+  }
+
+  @ParameterizedTest
+  @MethodSource("rating_metric_keys")
+  void facet_on_rating_contains_only_projects_authorized_for_user(String metricKey) {
     // User can see these projects
     indexForUser(USER1,
       // 3 docs with rating A
@@ -1162,8 +1215,38 @@ public class ProjectMeasuresIndexTest {
       entry("5", 0L));
   }
 
+  @ParameterizedTest
+  @MethodSource("software_quality_rating_metric_keys")
+  void facet_on_software_quality_rating_contains_only_projects_authorized_for_user(String metricKey) {
+    // User can see these projects
+    indexForUser(USER1,
+      // 3 docs with rating A
+      newDoc(metricKey, 1d),
+      newDoc(metricKey, 1d),
+      newDoc(metricKey, 1d),
+      // 2 docs with rating B
+      newDoc(metricKey, 2d),
+      newDoc(metricKey, 2d));
+
+    // User cannot see these projects
+    indexForUser(USER2,
+      // docs with rating C
+      newDoc(metricKey, 3d),
+      // docs with rating D
+      newDoc(metricKey, 4d));
+
+    userSession.logIn(USER1);
+    Facets facets = underTest.search(new ProjectMeasuresQuery(), new SearchOptions().addFacets(metricKey)).getFacets();
+
+    assertThat(facets.get(metricKey)).containsExactly(
+      entry("1", 3L),
+      entry("2", 2L),
+      entry("3", 0L),
+      entry("4", 0L));
+  }
+
   @Test
-  public void facet_quality_gate() {
+  void facet_quality_gate() {
     index(
       // 2 docs with QG OK
       newDoc().setQualityGateStatus(OK.name()),
@@ -1182,7 +1265,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_quality_gate_is_sticky() {
+  void facet_quality_gate_is_sticky() {
     index(
       // 2 docs with QG OK
       newDoc(NCLOC, 10d, COVERAGE, 0d).setQualityGateStatus(OK.name()),
@@ -1212,7 +1295,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_quality_gate_contains_only_projects_authorized_for_user() {
+  void facet_quality_gate_contains_only_projects_authorized_for_user() {
     // User can see these projects
     indexForUser(USER1,
       // 2 docs with QG OK
@@ -1236,7 +1319,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_languages() {
+  void facet_languages() {
     index(
       newDoc().setLanguages(singletonList("java")),
       newDoc().setLanguages(singletonList("java")),
@@ -1255,7 +1338,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_languages_is_limited_to_10_languages() {
+  void facet_languages_is_limited_to_10_languages() {
     index(
       newDoc().setLanguages(asList("<null>", "java", "xoo", "css", "cpp")),
       newDoc().setLanguages(asList("xml", "php", "python", "perl", "ruby")),
@@ -1267,7 +1350,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_languages_is_sticky() {
+  void facet_languages_is_sticky() {
     index(
       newDoc(NCLOC, 10d).setLanguages(singletonList("java")),
       newDoc(NCLOC, 10d).setLanguages(singletonList("java")),
@@ -1277,7 +1360,7 @@ public class ProjectMeasuresIndexTest {
       newDoc(NCLOC, 5000d).setLanguages(asList("<null>", "java", "xoo")));
 
     Facets facets = underTest.search(
-      new ProjectMeasuresQuery().setLanguages(ImmutableSet.of("java")),
+      new ProjectMeasuresQuery().setLanguages(Set.of("java")),
       new SearchOptions().addFacets(LANGUAGES, NCLOC)).getFacets();
 
     // Sticky facet on language does not take into account language filter
@@ -1296,13 +1379,14 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_languages_returns_more_than_10_languages_when_languages_filter_contains_value_not_in_top_10() {
+  void facet_languages_returns_more_than_10_languages_when_languages_filter_contains_value_not_in_top_10() {
     index(
       newDoc().setLanguages(asList("<null>", "java", "xoo", "css", "cpp")),
       newDoc().setLanguages(asList("xml", "php", "python", "perl", "ruby")),
       newDoc().setLanguages(asList("js", "scala")));
 
-    Facets facets = underTest.search(new ProjectMeasuresQuery().setLanguages(ImmutableSet.of("xoo", "xml")), new SearchOptions().addFacets(LANGUAGES)).getFacets();
+    Facets facets = underTest.search(new ProjectMeasuresQuery().setLanguages(Set.of("xoo", "xml")),
+      new SearchOptions().addFacets(LANGUAGES)).getFacets();
 
     assertThat(facets.get(LANGUAGES)).containsOnly(
       entry("<null>", 1L),
@@ -1320,7 +1404,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_languages_contains_only_projects_authorized_for_user() {
+  void facet_languages_contains_only_projects_authorized_for_user() {
     // User can see these projects
     indexForUser(USER1,
       newDoc().setLanguages(singletonList("java")),
@@ -1340,7 +1424,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_qualifier() {
+  void facet_qualifier() {
     index(
       // 2 docs with qualifier APP
       newDoc().setQualifier(APP),
@@ -1359,7 +1443,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_qualifier_is_sticky() {
+  void facet_qualifier_is_sticky() {
     index(
       // 2 docs with qualifier APP
       newDoc(NCLOC, 10d, COVERAGE, 0d).setQualifier(APP),
@@ -1389,7 +1473,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_qualifier_contains_only_app_and_projects_authorized_for_user() {
+  void facet_qualifier_contains_only_app_and_projects_authorized_for_user() {
     // User can see these projects
     indexForUser(USER1,
       // 3 docs with qualifier APP, PROJECT
@@ -1414,7 +1498,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_tags() {
+  void facet_tags() {
     index(
       newDoc().setTags(newArrayList("finance", "offshore", "java")),
       newDoc().setTags(newArrayList("finance", "javascript")),
@@ -1434,7 +1518,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_tags_is_sticky() {
+  void facet_tags_is_sticky() {
     index(
       newDoc().setTags(newArrayList("finance")).setQualityGateStatus(OK.name()),
       newDoc().setTags(newArrayList("finance")).setQualityGateStatus(ERROR.name()),
@@ -1454,7 +1538,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_tags_returns_10_elements_by_default() {
+  void facet_tags_returns_10_elements_by_default() {
     index(
       newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
       newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
@@ -1466,13 +1550,14 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void facet_tags_returns_more_than_10_tags_when_tags_filter_contains_value_not_in_top_10() {
+  void facet_tags_returns_more_than_10_tags_when_tags_filter_contains_value_not_in_top_10() {
     index(
       newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
       newDoc().setTags(newArrayList("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10")),
       newDoc().setTags(newArrayList("solo", "solo2")));
 
-    Map<String, Long> result = underTest.search(new ProjectMeasuresQuery().setTags(ImmutableSet.of("solo", "solo2")), new SearchOptions().addFacets(FIELD_TAGS)).getFacets()
+    Map<String, Long> result = underTest.search(new ProjectMeasuresQuery().setTags(Set.of("solo", "solo2")),
+        new SearchOptions().addFacets(FIELD_TAGS)).getFacets()
       .get(FIELD_TAGS);
 
     assertThat(result).hasSize(12).containsOnlyKeys("finance1", "finance2", "finance3", "finance4", "finance5", "finance6", "finance7", "finance8", "finance9", "finance10", "solo",
@@ -1480,7 +1565,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_tags() {
+  void search_tags() {
     index(
       newDoc().setTags(newArrayList("finance", "offshore", "java")),
       newDoc().setTags(newArrayList("official", "javascript")),
@@ -1495,7 +1580,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_tags_return_all_tags() {
+  void search_tags_return_all_tags() {
     index(
       newDoc().setTags(newArrayList("finance", "offshore", "java")),
       newDoc().setTags(newArrayList("official", "javascript")),
@@ -1510,7 +1595,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_tags_in_lexical_order() {
+  void search_tags_in_lexical_order() {
     index(
       newDoc().setTags(newArrayList("finance", "offshore", "java")),
       newDoc().setTags(newArrayList("official", "javascript")),
@@ -1525,7 +1610,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_tags_follows_paging() {
+  void search_tags_follows_paging() {
     index(
       newDoc().setTags(newArrayList("finance", "offshore", "java")),
       newDoc().setTags(newArrayList("official", "javascript")),
@@ -1548,7 +1633,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_tags_returns_nothing_if_page_too_large() {
+  void search_tags_returns_nothing_if_page_too_large() {
     index(
       newDoc().setTags(newArrayList("finance", "offshore", "java")),
       newDoc().setTags(newArrayList("official", "javascript")),
@@ -1563,7 +1648,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_tags_only_of_authorized_projects() {
+  void search_tags_only_of_authorized_projects() {
     indexForUser(USER1,
       newDoc(PROJECT1).setTags(singletonList("finance")),
       newDoc(PROJECT2).setTags(singletonList("marketing")));
@@ -1578,14 +1663,14 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_tags_with_no_tags() {
+  void search_tags_with_no_tags() {
     List<String> result = underTest.searchTags("whatever", 1, 10);
 
     assertThat(result).isEmpty();
   }
 
   @Test
-  public void search_tags_with_page_size_at_0() {
+  void search_tags_with_page_size_at_0() {
     index(newDoc().setTags(newArrayList("offshore")));
 
     List<String> result = underTest.searchTags(null, 1, 0);
@@ -1594,14 +1679,14 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_statistics() {
+  void search_statistics() {
     es.putDocuments(TYPE_PROJECT_MEASURES,
       newDoc("lines", 10, "coverage", 80)
         .setLanguages(Arrays.asList("java", "cs", "js"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 200, "cs", 250, "js", 50)),
+        .setNclocLanguageDistributionFromMap(Map.of("java", 200, "cs", 250, "js", 50)),
       newDoc("lines", 20, "coverage", 80)
         .setLanguages(Arrays.asList("java", "python", "kotlin"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 300, "python", 100, "kotlin", 404)));
+        .setNclocLanguageDistributionFromMap(Map.of("java", 300, "python", 100, "kotlin", 404)));
 
     ProjectMeasuresStatistics result = underTest.searchSupportStatistics();
 
@@ -1613,7 +1698,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_statistics_for_large_instances() {
+  void search_statistics_for_large_instances() {
     int nbProjects = 25000;
     int javaLocByProjects = 100;
     int jsLocByProjects = 900;
@@ -1621,7 +1706,7 @@ public class ProjectMeasuresIndexTest {
 
     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);
+      .setNclocLanguageDistributionFromMap(Map.of("java", javaLocByProjects, "cs", csLocByProjects, "js", jsLocByProjects))).toArray(ProjectMeasuresDoc[]::new);
 
     es.putDocuments(TYPE_PROJECT_MEASURES, documents);
 
@@ -1642,23 +1727,23 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_statistics_should_ignore_applications() {
+  void search_statistics_should_ignore_applications() {
     es.putDocuments(TYPE_PROJECT_MEASURES,
       // insert projects
       newDoc(ComponentTesting.newPrivateProjectDto(), "lines", 10, "coverage", 80)
         .setLanguages(Arrays.asList("java", "cs", "js"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 200, "cs", 250, "js", 50)),
+        .setNclocLanguageDistributionFromMap(Map.of("java", 200, "cs", 250, "js", 50)),
       newDoc(ComponentTesting.newPrivateProjectDto(), "lines", 20, "coverage", 80)
         .setLanguages(Arrays.asList("java", "python", "kotlin"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 300, "python", 100, "kotlin", 404)),
+        .setNclocLanguageDistributionFromMap(Map.of("java", 300, "python", 100, "kotlin", 404)),
 
       // insert applications
       newDoc(ComponentTesting.newApplication(), "lines", 1000, "coverage", 70)
         .setLanguages(Arrays.asList("java", "python", "kotlin"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 300, "python", 100, "kotlin", 404)),
+        .setNclocLanguageDistributionFromMap(Map.of("java", 300, "python", 100, "kotlin", 404)),
       newDoc(ComponentTesting.newApplication(), "lines", 20, "coverage", 80)
         .setLanguages(Arrays.asList("java", "python", "kotlin"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 300, "python", 100, "kotlin", 404)));
+        .setNclocLanguageDistributionFromMap(Map.of("java", 300, "python", 100, "kotlin", 404)));
 
     ProjectMeasuresStatistics result = underTest.searchSupportStatistics();
 
@@ -1670,15 +1755,15 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void search_statistics_should_count_0_if_no_projects() {
+  void search_statistics_should_count_0_if_no_projects() {
     es.putDocuments(TYPE_PROJECT_MEASURES,
       // insert applications
       newDoc(ComponentTesting.newApplication(), "lines", 1000, "coverage", 70)
-        .setLanguages(Arrays.asList("java", "python", "kotlin"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 300, "python", 100, "kotlin", 404)),
+        .setLanguages(Arrays.asList("java", "Map", "kotlin"))
+        .setNclocLanguageDistributionFromMap(Map.of("java", 300, "python", 100, "kotlin", 404)),
       newDoc(ComponentTesting.newApplication(), "lines", 20, "coverage", 80)
         .setLanguages(Arrays.asList("java", "python", "kotlin"))
-        .setNclocLanguageDistributionFromMap(ImmutableMap.of("java", 300, "python", 100, "kotlin", 404)));
+        .setNclocLanguageDistributionFromMap(Map.of("java", 300, "python", 100, "kotlin", 404)));
 
     ProjectMeasuresStatistics result = underTest.searchSupportStatistics();
 
@@ -1687,14 +1772,14 @@ public class ProjectMeasuresIndexTest {
   }
 
   @Test
-  public void fail_if_page_size_greater_than_100() {
+  void fail_if_page_size_greater_than_100() {
     assertThatThrownBy(() -> underTest.searchTags("whatever", 1, 101))
       .isInstanceOf(IllegalArgumentException.class)
       .hasMessageContaining("Page size must be lower than or equals to 100");
   }
 
   @Test
-  public void fail_if_page_greater_than_20() {
+  void fail_if_page_greater_than_20() {
     assertThatThrownBy(() -> underTest.searchTags("whatever", 21, 100))
       .isInstanceOf(IllegalArgumentException.class)
       .hasMessageContaining("Page must be between 0 and 20");
@@ -1702,17 +1787,17 @@ public class ProjectMeasuresIndexTest {
 
   private void index(ProjectMeasuresDoc... docs) {
     es.putDocuments(TYPE_PROJECT_MEASURES, docs);
-    authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).allowAnyone()).collect(toList()));
+    authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).allowAnyone()).toList());
   }
 
   private void indexForUser(UserDto user, ProjectMeasuresDoc... docs) {
     es.putDocuments(TYPE_PROJECT_MEASURES, docs);
-    authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).addUserUuid(user.getUuid())).collect(toList()));
+    authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).addUserUuid(user.getUuid())).toList());
   }
 
   private void indexForGroup(GroupDto group, ProjectMeasuresDoc... docs) {
     es.putDocuments(TYPE_PROJECT_MEASURES, docs);
-    authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).addGroupUuid(group.getUuid())).collect(toList()));
+    authorizationIndexer.allow(stream(docs).map(doc -> new IndexPermissions(doc.getId(), PROJECT).addGroupUuid(group.getUuid())).toList());
   }
 
   private static ProjectMeasuresDoc newDoc(ComponentDto project) {
@@ -1740,7 +1825,7 @@ public class ProjectMeasuresIndexTest {
   }
 
   private static Map<String, Object> newMeasure(String key, Object value) {
-    return ImmutableMap.of("key", key, "value", value);
+    return Map.of("key", key, "value", value);
   }
 
   private static ProjectMeasuresDoc newDocWithNoMeasure() {
index eda28adb2fe880ec5238437df282bcea77be1296..3269fa61cce0c8046a05e91a0cb1fbc16a189c88 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.base.Joiner;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
@@ -95,6 +96,14 @@ import static org.sonar.api.server.ws.WebService.Param.PAGE;
 import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
 import static org.sonar.api.server.ws.WebService.Param.SORT;
 import static org.sonar.api.utils.DateUtils.formatDateTime;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_002;
 import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_003;
@@ -126,11 +135,39 @@ public class SearchProjectsActionIT {
     return new Object[][]{{SQALE_RATING_KEY}, {RELIABILITY_RATING_KEY}, {SECURITY_RATING_KEY}};
   }
 
+  @DataProvider
+  public static Object[][] software_quality_rating_metric_keys() {
+    return new Object[][]{{SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY}, {SOFTWARE_QUALITY_RELIABILITY_RATING_KEY},
+      {SOFTWARE_QUALITY_SECURITY_RATING_KEY}, {SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY}};
+  }
+
+  @DataProvider
+  public static Object[][] all_rating_metric_keys() {
+    List<Object[]> result = new ArrayList<>();
+    result.addAll(Arrays.asList(rating_metric_keys()));
+    result.addAll(Arrays.asList(software_quality_rating_metric_keys()));
+    return result.toArray(new Object[result.size()][]);
+  }
+
   @DataProvider
   public static Object[][] new_rating_metric_keys() {
     return new Object[][]{{NEW_MAINTAINABILITY_RATING_KEY}, {NEW_RELIABILITY_RATING_KEY}, {NEW_SECURITY_RATING_KEY}};
   }
 
+  @DataProvider
+  public static Object[][] new_software_quality_rating_metric_keys() {
+    return new Object[][]{{NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY}, {NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY},
+      {NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY}, {NEW_SOFTWARE_QUALITY_SECURITY_REVIEW_RATING_KEY}};
+  }
+
+  @DataProvider
+  public static Object[][] all_new_rating_metric_keys() {
+    List<Object[]> result = new ArrayList<>();
+    result.addAll(Arrays.asList(new_rating_metric_keys()));
+    result.addAll(Arrays.asList(new_software_quality_rating_metric_keys()));
+    return result.toArray(new Object[result.size()][]);
+  }
+
   @DataProvider
   public static Object[][] component_qualifiers_for_valid_editions() {
     return new Object[][]{
@@ -207,7 +244,15 @@ public class SearchProjectsActionIT {
       "new_maintainability_rating",
       "name",
       "analysisDate",
-      "creationDate");
+      "creationDate",
+      "new_software_quality_maintainability_rating",
+      "new_software_quality_reliability_rating",
+      "new_software_quality_security_rating",
+      "new_software_quality_security_review_rating",
+      "software_quality_maintainability_rating",
+      "software_quality_reliability_rating",
+      "software_quality_security_rating",
+      "software_quality_security_review_rating");
 
     Param asc = def.param("asc");
     assertThat(asc.defaultValue()).isEqualTo("true");
@@ -223,7 +268,15 @@ public class SearchProjectsActionIT {
       , "security_rating", "alert_status",
       "languages", "tags", "qualifier", "new_reliability_rating", "new_security_rating", "new_maintainability_rating", "new_coverage",
       "new_duplicated_lines_density", "new_lines",
-      "security_review_rating", "security_hotspots_reviewed", "new_security_hotspots_reviewed", "new_security_review_rating");
+      "security_review_rating", "security_hotspots_reviewed", "new_security_hotspots_reviewed", "new_security_review_rating",
+      "new_software_quality_maintainability_rating",
+      "new_software_quality_reliability_rating",
+      "new_software_quality_security_rating",
+      "new_software_quality_security_review_rating",
+      "software_quality_maintainability_rating",
+      "software_quality_reliability_rating",
+      "software_quality_security_rating",
+      "software_quality_security_review_rating");
   }
 
   @Test
@@ -366,7 +419,7 @@ public class SearchProjectsActionIT {
   }
 
   @Test
-  @UseDataProvider("rating_metric_keys")
+  @UseDataProvider("all_rating_metric_keys")
   public void filter_projects_by_rating(String metricKey) {
     userSession.logIn();
     MetricDto ratingMetric = db.measures().insertMetric(c -> c.setKey(metricKey).setValueType(INT.name()));
@@ -381,7 +434,7 @@ public class SearchProjectsActionIT {
   }
 
   @Test
-  @UseDataProvider("new_rating_metric_keys")
+  @UseDataProvider("all_new_rating_metric_keys")
   public void filter_projects_by_new_rating(String newMetricKey) {
     userSession.logIn();
     MetricDto ratingMetric = db.measures().insertMetric(c -> c.setKey(newMetricKey).setValueType(INT.name()));
@@ -944,6 +997,30 @@ public class SearchProjectsActionIT {
         tuple("5", 1L));
   }
 
+  @Test
+  @UseDataProvider("software_quality_rating_metric_keys")
+  public void return_software_quality_rating_facet(String ratingMetricKey) {
+    userSession.logIn();
+    MetricDto ratingMetric = db.measures().insertMetric(c -> c.setKey(ratingMetricKey).setValueType("RATING"));
+    insertProject(new Measure(ratingMetric, c -> c.setValue(1d)));
+    insertProject(new Measure(ratingMetric, c -> c.setValue(1d)));
+    insertProject(new Measure(ratingMetric, c -> c.setValue(3d)));
+    index();
+
+    SearchProjectsWsResponse result = call(request.setFacets(singletonList(ratingMetricKey)));
+
+    Common.Facet facet = result.getFacets().getFacetsList().stream()
+      .filter(oneFacet -> ratingMetricKey.equals(oneFacet.getProperty()))
+      .findFirst().orElseThrow(IllegalStateException::new);
+    assertThat(facet.getValuesList())
+      .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
+      .containsExactly(
+        tuple("1", 2L),
+        tuple("2", 0L),
+        tuple("3", 1L),
+        tuple("4", 0L));
+  }
+
   @Test
   @UseDataProvider("new_rating_metric_keys")
   public void return_new_rating_facet(String newRatingMetricKey) {
@@ -970,6 +1047,30 @@ public class SearchProjectsActionIT {
         tuple("5", 1L));
   }
 
+  @Test
+  @UseDataProvider("new_software_quality_rating_metric_keys")
+  public void return_new_software_quality_rating_facet(String newRatingMetricKey) {
+    userSession.logIn();
+    MetricDto newRatingMetric = db.measures().insertMetric(c -> c.setKey(newRatingMetricKey).setValueType("RATING"));
+    insertProject(new Measure(newRatingMetric, c -> c.setValue(1d)));
+    insertProject(new Measure(newRatingMetric, c -> c.setValue(1d)));
+    insertProject(new Measure(newRatingMetric, c -> c.setValue(3d)));
+    index();
+
+    SearchProjectsWsResponse result = call(request.setFacets(singletonList(newRatingMetricKey)));
+
+    Common.Facet facet = result.getFacets().getFacetsList().stream()
+      .filter(oneFacet -> newRatingMetricKey.equals(oneFacet.getProperty()))
+      .findFirst().orElseThrow(IllegalStateException::new);
+    assertThat(facet.getValuesList())
+      .extracting(Common.FacetValue::getVal, Common.FacetValue::getCount)
+      .containsExactly(
+        tuple("1", 2L),
+        tuple("2", 0L),
+        tuple("3", 1L),
+        tuple("4", 0L));
+  }
+
   @Test
   public void return_coverage_facet() {
     userSession.logIn();