]> source.dussan.org Git - sonarqube.git/commitdiff
Add new fields hasStandardConditions and hasMQRConditions in qualitgates/list endpoin...
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>
Fri, 25 Oct 2024 07:46:34 +0000 (09:46 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 5 Nov 2024 20:03:01 +0000 (20:03 +0000)
20 files changed:
server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/QualityGateConditionsUpdaterIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/RegisterQualityGatesIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/CreateActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/CreateConditionActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/ListActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/ShowActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/UpdateConditionActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateCaycChecker.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModeChecker.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ValidRatingMetrics.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ListAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/StandardToMQRMetrics.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/list-example.json
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModeCheckerTest.java [new file with mode: 0644]
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/StandardToMQRMetricsTest.java [new file with mode: 0644]
sonar-ws/src/main/protobuf/ws-qualitygates.proto

index e4f21c21a6d2c283933df5efdaafc54404a7ee4e..21da1debbafaaa8f94ca8232b57a6f1c2aec9c9d 100644 (file)
@@ -130,10 +130,12 @@ class TelemetryDataLoaderImplIT {
   private final AiCodeAssuranceVerifier aiCodeAssuranceVerifier = mock(AiCodeAssuranceVerifier.class);
 
   private final TelemetryDataLoader communityUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
-    internalProperties, configuration, containerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService, cloudUsageDataProvider, qualityProfileDataProvider, aiCodeAssuranceVerifier,
+    internalProperties, configuration, containerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService, cloudUsageDataProvider, qualityProfileDataProvider,
+    aiCodeAssuranceVerifier,
     projectLocDistributionDataProvider);
   private final TelemetryDataLoader commercialUnderTest = new TelemetryDataLoaderImpl(server, db.getDbClient(), pluginRepository, editionProvider,
-    internalProperties, configuration, containerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService, cloudUsageDataProvider, qualityProfileDataProvider, aiCodeAssuranceVerifier,
+    internalProperties, configuration, containerSupport, qualityGateCaycChecker, qualityGateFinder, managedInstanceService, cloudUsageDataProvider, qualityProfileDataProvider,
+    aiCodeAssuranceVerifier,
     projectLocDistributionDataProvider);
 
   private QualityGateDto builtInDefaultQualityGate;
@@ -147,7 +149,7 @@ class TelemetryDataLoaderImplIT {
   void setUpBuiltInQualityGate() {
     String builtInQgName = "Sonar way";
     builtInDefaultQualityGate = db.qualityGates().insertQualityGate(qg -> qg.setName(builtInQgName).setBuiltIn(true));
-    when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(NON_COMPLIANT);
+    when(qualityGateCaycChecker.checkCaycCompliant(any(), any(String.class))).thenReturn(NON_COMPLIANT);
     db.qualityGates().setDefaultQualityGate(builtInDefaultQualityGate);
 
     bugsDto = db.measures().insertMetric(m -> m.setKey(BUGS_KEY));
@@ -538,7 +540,8 @@ class TelemetryDataLoaderImplIT {
   @ParameterizedTest
   @MethodSource("values")
   void load_shouldContainCorrectAiCodeAssuranceField(boolean expected) {
-    ProjectDto project1 = db.components().insertPublicProject(componentDto -> {},
+    ProjectDto project1 = db.components().insertPublicProject(componentDto -> {
+    },
       projectDto -> projectDto.setAiCodeAssurance(expected)).getProjectDto();
 
     when(aiCodeAssuranceVerifier.isAiCodeAssured(project1.getAiCodeAssurance())).thenReturn(expected);
@@ -752,14 +755,12 @@ class TelemetryDataLoaderImplIT {
       Arguments.of(true, "scim"),
       Arguments.of(true, "github"),
       Arguments.of(true, "gitlab"),
-      Arguments.of(false, null)
-    );
+      Arguments.of(false, null));
   }
 
   private static Stream<Arguments> values() {
     return Stream.of(
       Arguments.of(false),
-      Arguments.of(true)
-    );
+      Arguments.of(true));
   }
 }
index 2f50667688ce5cd3c25a5777a7dfb6e0806ba317..b5f2d096c51db759186c3dcb32cb8aa2189a2ea4 100644 (file)
  */
 package org.sonar.server.qualitygate;
 
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-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.measures.CoreMetrics;
 import org.sonar.api.measures.Metric;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
+import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.metric.MetricDto;
 import org.sonar.db.qualitygate.QualityGateConditionDto;
@@ -51,16 +52,15 @@ import static org.sonar.api.measures.Metric.ValueType.RATING;
 import static org.sonar.api.measures.Metric.ValueType.STRING;
 import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
 
-@RunWith(DataProviderRunner.class)
-public class QualityGateConditionsUpdaterIT {
+class QualityGateConditionsUpdaterIT {
 
-  @Rule
+  @RegisterExtension
   public DbTester db = DbTester.create();
 
   private final QualityGateConditionsUpdater underTest = new QualityGateConditionsUpdater(db.getDbClient());
 
   @Test
-  public void create_error_condition() {
+  void create_error_condition() {
     MetricDto metric = insertMetric(INT, "new_coverage");
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -69,9 +69,9 @@ public class QualityGateConditionsUpdaterIT {
     verifyCondition(result, qualityGate, metric, "LT", "80");
   }
 
-  @Test
-  @UseDataProvider("valid_operators_and_direction")
-  public void create_condition_with_valid_operators_and_direction(String operator, int direction) {
+  @ParameterizedTest
+  @MethodSource("valid_operators_and_direction")
+  void create_condition_with_valid_operators_and_direction(String operator, int direction) {
     MetricDto metric = db.measures().insertMetric(m -> m.setKey("key").setValueType(INT.name()).setHidden(false).setDirection(direction));
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -81,7 +81,7 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void create_condition_throws_NPE_if_errorThreshold_is_null() {
+  void create_condition_throws_NPE_if_errorThreshold_is_null() {
     MetricDto metric = insertMetric(RATING, SQALE_RATING_KEY);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -91,7 +91,7 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void fail_to_create_condition_when_condition_on_same_metric_already_exist() {
+  void fail_to_create_condition_when_condition_on_same_metric_already_exist() {
     MetricDto metric = insertMetric(PERCENT);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     db.qualityGates().addCondition(qualityGate, metric);
@@ -102,7 +102,7 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void fail_to_create_condition_on_missing_metric() {
+  void fail_to_create_condition_on_missing_metric() {
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
     assertThatThrownBy(() -> underTest.createCondition(db.getSession(), qualityGate, "new_coverage", "LT", "80"))
@@ -110,9 +110,9 @@ public class QualityGateConditionsUpdaterIT {
       .hasMessageContaining("There is no metric with key=new_coverage");
   }
 
-  @Test
-  @UseDataProvider("invalid_metrics")
-  public void fail_to_create_condition_on_invalid_metric(String metricKey, Metric.ValueType valueType, boolean hidden) {
+  @ParameterizedTest
+  @MethodSource("invalid_metrics")
+  void fail_to_create_condition_on_invalid_metric(String metricKey, Metric.ValueType valueType, boolean hidden) {
     MetricDto metric = db.measures().insertMetric(m -> m.setKey(metricKey).setValueType(valueType.name()).setHidden(hidden).setDirection(0));
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -121,9 +121,9 @@ public class QualityGateConditionsUpdaterIT {
       .hasMessageContaining(format("Metric '%s' cannot be used to define a condition", metric.getKey()));
   }
 
-  @Test
-  @UseDataProvider("invalid_operators_and_direction")
-  public void fail_to_create_condition_on_not_allowed_operator_for_metric_direction(String operator, int direction) {
+  @ParameterizedTest
+  @MethodSource("invalid_operators_and_direction")
+  void fail_to_create_condition_on_not_allowed_operator_for_metric_direction(String operator, int direction) {
     MetricDto metric = db.measures().insertMetric(m -> m.setKey("key").setValueType(INT.name()).setHidden(false).setDirection(direction));
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -133,7 +133,7 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void create_condition_on_rating_metric() {
+  void create_condition_on_rating_metric() {
     MetricDto metric = insertMetric(RATING, SQALE_RATING_KEY);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -143,7 +143,31 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void fail_to_create_error_condition_on_invalid_rating_metric() {
+  void create_whenMetricIsBasedOnSoftwareQuality_shouldWork() {
+    MetricDto metric = insertMetric(RATING, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY);
+    QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
+
+    QualityGateConditionDto result = underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "GT", "3");
+
+    verifyCondition(result, qualityGate, metric, "GT", "3");
+  }
+
+  @Test
+  void create_whenEquivalentConditionAlreadyExists_shouldFail() {
+    MetricDto equivalentMetric = insertMetric(RATING, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY);
+    MetricDto newMetric = insertMetric(RATING, SQALE_RATING_KEY);
+    QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
+    DbSession session = db.getSession();
+    underTest.createCondition(session, qualityGate, equivalentMetric.getKey(), "GT", "3");
+
+    String newMetricKey = newMetric.getKey();
+    assertThatThrownBy(() -> underTest.createCondition(session, qualityGate, newMetricKey, "GT", "3"))
+      .isInstanceOf(BadRequestException.class)
+      .hasMessageContaining(format("Condition for metric '%s' already exists on equivalent metric '%s''.", newMetricKey, equivalentMetric.getKey()));
+  }
+
+  @Test
+  void fail_to_create_error_condition_on_invalid_rating_metric() {
     MetricDto metric = insertMetric(RATING, SQALE_RATING_KEY);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -153,7 +177,7 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void fail_to_create_condition_on_rating_greater_than_E() {
+  void fail_to_create_condition_on_rating_greater_than_E() {
     MetricDto metric = insertMetric(RATING, SQALE_RATING_KEY);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -162,9 +186,9 @@ public class QualityGateConditionsUpdaterIT {
       .hasMessageContaining("There's no worse rating than E (5)");
   }
 
-  @Test
-  @UseDataProvider("valid_values")
-  public void create_error_condition(Metric.ValueType valueType, String value) {
+  @ParameterizedTest
+  @MethodSource("valid_values")
+  void create_error_condition(Metric.ValueType valueType, String value) {
     MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false).setDirection(0));
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -173,9 +197,9 @@ public class QualityGateConditionsUpdaterIT {
     verifyCondition(result, qualityGate, metric, "LT", value);
   }
 
-  @Test
-  @UseDataProvider("invalid_values")
-  public void fail_to_create_error_INT_condition_when_value_is_not_an_integer(Metric.ValueType valueType, String value) {
+  @ParameterizedTest
+  @MethodSource("invalid_values")
+  void fail_to_create_error_INT_condition_when_value_is_not_an_integer(Metric.ValueType valueType, String value) {
     MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false).setDirection(0));
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
@@ -185,7 +209,7 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void update_condition() {
+  void update_condition() {
     MetricDto metric = insertMetric(PERCENT);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
@@ -197,7 +221,7 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void update_condition_throws_NPE_if_errorThreshold_is_null() {
+  void update_condition_throws_NPE_if_errorThreshold_is_null() {
     MetricDto metric = insertMetric(PERCENT);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
@@ -209,7 +233,7 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void update_condition_on_rating_metric() {
+  void update_condition_on_rating_metric() {
     MetricDto metric = insertMetric(RATING, SQALE_RATING_KEY);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
@@ -221,8 +245,38 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  @UseDataProvider("update_invalid_operators_and_direction")
-  public void fail_to_update_condition_on_not_allowed_operator_for_metric_direction(String validOperator, String updatedOperator, int direction) {
+  void update_whenReplaceWithEquivalentCondition_shouldChangeCondition() {
+    MetricDto metric = insertMetric(RATING, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY);
+    MetricDto metric2 = insertMetric(RATING, SQALE_RATING_KEY);
+    QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
+    QualityGateConditionDto gt = underTest.createCondition(db.getSession(), qualityGate, metric.getKey(), "GT", "3");
+
+    QualityGateConditionDto result = underTest.updateCondition(db.getSession(), gt, metric2.getKey(), "GT", "4");
+
+    verifyCondition(result, qualityGate, metric2, "GT", "4");
+  }
+
+  @Test
+  void update_whenEquivalentMetricExistsInAnotherCondition_shouldFail() {
+    MetricDto baseMetric = insertMetric(RATING, CoreMetrics.RELIABILITY_RATING_KEY);
+    MetricDto equivalentMetric = insertMetric(RATING, SQALE_RATING_KEY);
+    MetricDto updatedMetric = insertMetric(RATING, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY);
+
+    QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
+    DbSession session = db.getSession();
+    QualityGateConditionDto condition1 = underTest.createCondition(session, qualityGate, baseMetric.getKey(), "GT", "3");
+    underTest.createCondition(session, qualityGate, equivalentMetric.getKey(), "GT", "3");
+
+    String updatedMetricKey = updatedMetric.getKey();
+    assertThatThrownBy(() -> underTest.updateCondition(session, condition1, updatedMetricKey, "GT", "4"))
+      .isInstanceOf(BadRequestException.class)
+      .hasMessageContaining(format("Condition for metric '%s' already exists on equivalent metric '%s''.", updatedMetricKey, equivalentMetric.getKey()));
+
+  }
+
+  @ParameterizedTest
+  @MethodSource("update_invalid_operators_and_direction")
+  void fail_to_update_condition_on_not_allowed_operator_for_metric_direction(String validOperator, String updatedOperator, int direction) {
     MetricDto metric = db.measures().insertMetric(m -> m.setValueType(PERCENT.name()).setHidden(false).setDirection(direction));
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
@@ -234,7 +288,7 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void fail_to_update_condition_on_rating_metric_on_new_code_period() {
+  void fail_to_update_condition_on_rating_metric_on_new_code_period() {
     MetricDto metric = insertMetric(RATING, SQALE_RATING_KEY);
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
@@ -246,7 +300,7 @@ public class QualityGateConditionsUpdaterIT {
   }
 
   @Test
-  public void fail_to_update_condition_on_rating_metric_on_not_core_rating_metric() {
+  void fail_to_update_condition_on_rating_metric_on_not_core_rating_metric() {
     MetricDto metric = insertMetric(RATING, "not_core_rating_metric");
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
@@ -257,9 +311,9 @@ public class QualityGateConditionsUpdaterIT {
       .hasMessageContaining(format("The metric '%s' cannot be used", metric.getShortName()));
   }
 
-  @Test
-  @UseDataProvider("invalid_metrics")
-  public void fail_to_update_condition_on_invalid_metric(String metricKey, Metric.ValueType valueType, boolean hidden) {
+  @ParameterizedTest
+  @MethodSource("invalid_metrics")
+  void fail_to_update_condition_on_invalid_metric(String metricKey, Metric.ValueType valueType, boolean hidden) {
     MetricDto metric = db.measures().insertMetric(m -> m.setKey(metricKey).setValueType(valueType.name()).setHidden(hidden));
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
@@ -270,9 +324,9 @@ public class QualityGateConditionsUpdaterIT {
       .hasMessageContaining(format("Metric '%s' cannot be used to define a condition", metric.getKey()));
   }
 
-  @Test
-  @UseDataProvider("valid_values")
-  public void update_error_condition(Metric.ValueType valueType, String value) {
+  @ParameterizedTest
+  @MethodSource("valid_values")
+  void update_error_condition(Metric.ValueType valueType, String value) {
     MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false).setDirection(0));
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
@@ -283,9 +337,9 @@ public class QualityGateConditionsUpdaterIT {
     verifyCondition(result, qualityGate, metric, "LT", value);
   }
 
-  @Test
-  @UseDataProvider("invalid_values")
-  public void fail_to_update_error_INT_condition_when_value_is_not_an_integer(Metric.ValueType valueType, String value) {
+  @ParameterizedTest
+  @MethodSource("invalid_values")
+  void fail_to_update_error_INT_condition_when_value_is_not_an_integer(Metric.ValueType valueType, String value) {
     MetricDto metric = db.measures().insertMetric(m -> m.setValueType(valueType.name()).setHidden(false).setDirection(0));
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
     QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
@@ -296,7 +350,6 @@ public class QualityGateConditionsUpdaterIT {
       .hasMessageContaining(format("Invalid value '%s' for metric '%s'", value, metric.getShortName()));
   }
 
-  @DataProvider
   public static Object[][] invalid_metrics() {
     return new Object[][] {
       {ALERT_STATUS_KEY, INT, false},
@@ -310,7 +363,6 @@ public class QualityGateConditionsUpdaterIT {
     };
   }
 
-  @DataProvider
   public static Object[][] valid_values() {
     return new Object[][] {
       {INT, "10"},
@@ -321,7 +373,6 @@ public class QualityGateConditionsUpdaterIT {
     };
   }
 
-  @DataProvider
   public static Object[][] invalid_values() {
     return new Object[][] {
       {INT, "ABCD"},
@@ -332,7 +383,6 @@ public class QualityGateConditionsUpdaterIT {
     };
   }
 
-  @DataProvider
   public static Object[][] invalid_operators_and_direction() {
     return new Object[][] {
       {"EQ", 0},
@@ -342,7 +392,6 @@ public class QualityGateConditionsUpdaterIT {
     };
   }
 
-  @DataProvider
   public static Object[][] update_invalid_operators_and_direction() {
     return new Object[][] {
       {"LT", "EQ", 0},
@@ -352,7 +401,6 @@ public class QualityGateConditionsUpdaterIT {
     };
   }
 
-  @DataProvider
   public static Object[][] valid_operators_and_direction() {
     return new Object[][] {
       {"LT", 0},
index 4314aa406910c6503b3b7356c043b174f38b646a..9c421b23dde70a563f0eaee482424d8d0a31e023 100644 (file)
@@ -128,7 +128,7 @@ public class RegisterQualityGatesIT {
     insertMetrics();
     QualityGateDto builtInQualityGate = db.qualityGates().insertBuiltInQualityGate();
     createBuiltInConditions(builtInQualityGate);
-    //Conditions added twice as found in some DB instances
+    // Conditions added twice as found in some DB instances
     createBuiltInConditionsWithoutCheckingDuplicates(builtInQualityGate);
     dbSession.commit();
 
@@ -260,10 +260,10 @@ public class RegisterQualityGatesIT {
     insertMetrics();
     QualityGateDto builtin = new QualityGateDto().setName(BUILTIN_QUALITY_GATE_NAME).setBuiltIn(true).setUuid(Uuids.createFast());
     qualityGateDao.insert(dbSession, builtin);
-    if(!isNewInstance) {
+    if (!isNewInstance) {
       createBuiltInConditions(builtin);
     }
-    if(hasSonarWayLegacyQG) {
+    if (hasSonarWayLegacyQG) {
       QualityGateDto sonarWayLegacy = new QualityGateDto().setName(SONAR_WAY_LEGACY_QUALITY_GATE_NAME).setBuiltIn(true).setUuid(Uuids.createFast());
       qualityGateDao.insert(dbSession, sonarWayLegacy);
     }
@@ -272,8 +272,8 @@ public class RegisterQualityGatesIT {
     underTest.start();
 
     var qualityGateDto = qualityGateDao.selectByName(dbSession, SONAR_WAY_LEGACY_QUALITY_GATE_NAME);
-    if(hasSonarWayLegacyQG) {
-     assertThat(qualityGateDto).isNotNull();
+    if (hasSonarWayLegacyQG) {
+      assertThat(qualityGateDto).isNotNull();
     } else {
       assertThat(qualityGateDto).isNull();
     }
index 7f3e80da5ee35a4adb423edcbac1b0680e94de2c..227157807bae2a8b2f8ebec59102a4c08d9f45c6 100644 (file)
@@ -138,7 +138,7 @@ public class CreateActionIT {
 
   @DataProvider
   public static Object[][] nullOrEmpty() {
-    return new Object[][]{
+    return new Object[][] {
       {null},
       {""},
       {"  "}
index acbc60c89b50ea0bffae26af3998ada1b0f06ab4..40e05a6547690f48adde115659b733c07f51262d 100644 (file)
@@ -58,7 +58,6 @@ import static org.sonar.server.qualitygate.ws.QualityGatesWsParameters.PARAM_OPE
 @RunWith(DataProviderRunner.class)
 public class CreateConditionActionIT {
 
-
   @Rule
   public UserSessionRule userSession = UserSessionRule.standalone();
 
@@ -116,8 +115,8 @@ public class CreateConditionActionIT {
       .setParam(PARAM_OPERATOR, "LT")
       .setParam(PARAM_ERROR, "90")
       .execute())
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessageContaining(format("Operation forbidden for built-in Quality Gate '%s'", qualityGate.getName()));
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining(format("Operation forbidden for built-in Quality Gate '%s'", qualityGate.getName()));
   }
 
   @Test
@@ -132,8 +131,8 @@ public class CreateConditionActionIT {
       .setParam(PARAM_OPERATOR, "ABC")
       .setParam(PARAM_ERROR, "90")
       .execute())
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessageContaining("Value of parameter 'op' (ABC) must be one of: [LT, GT]");
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("Value of parameter 'op' (ABC) must be one of: [LT, GT]");
   }
 
   @Test
@@ -149,8 +148,8 @@ public class CreateConditionActionIT {
       .setParam(PARAM_OPERATOR, operator)
       .setParam(PARAM_ERROR, "90")
       .execute())
-      .isInstanceOf(BadRequestException.class)
-      .hasMessageContaining(format("Operator %s is not allowed for this metric.", operator));
+        .isInstanceOf(BadRequestException.class)
+        .hasMessageContaining(format("Operator %s is not allowed for this metric.", operator));
   }
 
   @Test
@@ -222,8 +221,8 @@ public class CreateConditionActionIT {
       .setParam(PARAM_OPERATOR, "LT")
       .setParam(PARAM_ERROR, "90")
       .execute())
-      .isInstanceOf(ForbiddenException.class)
-      .hasMessageContaining("Insufficient privileges");
+        .isInstanceOf(ForbiddenException.class)
+        .hasMessageContaining("Insufficient privileges");
   }
 
   @Test
index 7ba48f103087f0bb3c5e0e5e9142e8d6a94e2fba..c832973660ba8efd15c10be3a9fd088844b974de 100644 (file)
  */
 package org.sonar.server.qualitygate.ws;
 
+import java.util.Collection;
+import java.util.List;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentMatcher;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.qualitygate.QualityGateConditionDto;
 import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.db.user.UserDto;
 import org.sonar.server.component.TestComponentFinder;
 import org.sonar.server.qualitygate.QualityGateCaycChecker;
 import org.sonar.server.qualitygate.QualityGateFinder;
+import org.sonar.server.qualitygate.QualityGateModeChecker;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsActionTester;
 import org.sonarqube.ws.Qualitygates.ListWsResponse.QualityGate;
@@ -39,7 +44,8 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.AssertionsForClassTypes.tuple;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_GATES;
@@ -61,13 +67,17 @@ public class ListActionIT {
   private final QualityGateFinder qualityGateFinder = new QualityGateFinder(dbClient);
 
   private final QualityGateCaycChecker qualityGateCaycChecker = mock(QualityGateCaycChecker.class);
+  private final QualityGateModeChecker qualityGateModeChecker = mock(QualityGateModeChecker.class);
 
   private final WsActionTester ws = new WsActionTester(new ListAction(db.getDbClient(),
-    new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), qualityGateFinder, qualityGateCaycChecker));
+    new QualityGatesWsSupport(dbClient, userSession, TestComponentFinder.from(db)), qualityGateFinder, qualityGateCaycChecker, qualityGateModeChecker));
 
   @Before
   public void setUp() {
-    when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(COMPLIANT);
+    when(qualityGateCaycChecker.checkCaycCompliant(any(Collection.class), any(List.class))).thenReturn(COMPLIANT);
+    doReturn(new QualityGateModeChecker.QualityModeResult(false, false))
+      .when(qualityGateModeChecker).getUsageOfModeMetrics(any(List.class));
+
   }
 
   @Test
@@ -105,11 +115,14 @@ public class ListActionIT {
   @Test
   public void test_caycStatus_flag() {
     QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate();
+    QualityGateConditionDto condition1 = db.qualityGates().addCondition(qualityGate1, db.measures().insertMetric());
     QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate();
+    QualityGateConditionDto condition2 = db.qualityGates().addCondition(qualityGate2, db.measures().insertMetric());
     QualityGateDto qualityGate3 = db.qualityGates().insertQualityGate();
-    when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate1.getUuid()))).thenReturn(COMPLIANT);
-    when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate2.getUuid()))).thenReturn(NON_COMPLIANT);
-    when(qualityGateCaycChecker.checkCaycCompliant(any(DbSession.class), eq(qualityGate3.getUuid()))).thenReturn(OVER_COMPLIANT);
+    QualityGateConditionDto condition3 = db.qualityGates().addCondition(qualityGate3, db.measures().insertMetric());
+    doReturn(COMPLIANT).when(qualityGateCaycChecker).checkCaycCompliant(argThat(hasCondition(condition1)), any(List.class));
+    doReturn(NON_COMPLIANT).when(qualityGateCaycChecker).checkCaycCompliant(argThat(hasCondition(condition2)), any(List.class));
+    doReturn(OVER_COMPLIANT).when(qualityGateCaycChecker).checkCaycCompliant(argThat(hasCondition(condition3)), any(List.class));
 
     db.qualityGates().setDefaultQualityGate(qualityGate1);
 
@@ -124,13 +137,47 @@ public class ListActionIT {
         tuple(qualityGate3.getName(), OVER_COMPLIANT.toString()));
   }
 
+  @Test
+  public void execute_shouldReturnExpectedModeFlags() {
+    QualityGateDto qualityGate1 = db.qualityGates().insertQualityGate();
+    MetricDto metric1 = db.measures().insertMetric();
+    db.qualityGates().addCondition(qualityGate1, metric1);
+    QualityGateDto qualityGate2 = db.qualityGates().insertQualityGate();
+    MetricDto metric2 = db.measures().insertMetric();
+    db.qualityGates().addCondition(qualityGate2, metric2);
+
+    doReturn(new QualityGateModeChecker.QualityModeResult(true, false))
+      .when(qualityGateModeChecker).getUsageOfModeMetrics(argThat(hasMetric(metric1)));
+    doReturn(new QualityGateModeChecker.QualityModeResult(false, true))
+      .when(qualityGateModeChecker).getUsageOfModeMetrics(argThat(hasMetric(metric2)));
+
+    db.qualityGates().setDefaultQualityGate(qualityGate1);
+
+    ListWsResponse response = ws.newRequest()
+      .executeProtobuf(ListWsResponse.class);
+
+    assertThat(response.getQualitygatesList())
+      .extracting(QualityGate::getName, QualityGate::getHasMQRConditions, QualityGate::getHasStandardConditions)
+      .containsExactlyInAnyOrder(
+        tuple(qualityGate1.getName(), true, false),
+        tuple(qualityGate2.getName(), false, true));
+  }
+
+  private ArgumentMatcher<Collection<QualityGateConditionDto>> hasCondition(QualityGateConditionDto condition) {
+    return collection -> collection.stream().anyMatch(e -> e.getUuid().equals(condition.getUuid()));
+  }
+
+  private ArgumentMatcher<List<MetricDto>> hasMetric(MetricDto metricDto) {
+    return collection -> collection.stream().anyMatch(e -> e.getUuid().equals(metricDto.getUuid()));
+  }
+
   @Test
   public void no_default_quality_gate() {
     QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
 
     assertThatThrownBy(() -> ws.newRequest()
       .executeProtobuf(ListWsResponse.class))
-      .isInstanceOf(IllegalStateException.class);
+        .isInstanceOf(IllegalStateException.class);
 
   }
 
@@ -209,10 +256,12 @@ public class ListActionIT {
   public void json_example() {
     userSession.logIn("admin").addPermission(ADMINISTER_QUALITY_GATES);
     QualityGateDto defaultQualityGate = db.qualityGates().insertQualityGate(qualityGate -> qualityGate.setName("Sonar way").setBuiltIn(true));
+    QualityGateConditionDto condition1 = db.qualityGates().addCondition(defaultQualityGate, db.measures().insertMetric());
     QualityGateDto otherQualityGate = db.qualityGates().insertQualityGate(qualityGate -> qualityGate.setName("Sonar way - Without Coverage").setBuiltIn(false));
+    QualityGateConditionDto condition2 = db.qualityGates().addCondition(otherQualityGate, db.measures().insertMetric());
     db.qualityGates().setDefaultQualityGate(defaultQualityGate);
-    when(qualityGateCaycChecker.checkCaycCompliant(any(), eq(defaultQualityGate.getUuid()))).thenReturn(COMPLIANT);
-    when(qualityGateCaycChecker.checkCaycCompliant(any(), eq(otherQualityGate.getUuid()))).thenReturn(NON_COMPLIANT);
+    doReturn(COMPLIANT).when(qualityGateCaycChecker).checkCaycCompliant(argThat(hasCondition(condition1)), any(List.class));
+    doReturn(NON_COMPLIANT).when(qualityGateCaycChecker).checkCaycCompliant(argThat(hasCondition(condition2)), any(List.class));
 
     String response = ws.newRequest().execute().getInput();
 
index c51cc403fbde396d3ea34ab9c928b7518f71272c..058964faa851355bb2505b45b49439bd0b4c0c37 100644 (file)
@@ -71,7 +71,7 @@ public class ShowActionIT {
 
   @Before
   public void setUp() {
-    when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(COMPLIANT);
+    when(qualityGateCaycChecker.checkCaycCompliant(any(), any(String.class))).thenReturn(COMPLIANT);
   }
 
   @Test
@@ -300,8 +300,8 @@ public class ShowActionIT {
     assertThatThrownBy(() -> ws.newRequest()
       .setParam("name", qualityGate.getName())
       .execute())
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessageContaining(format("Could not find metric with id %s", metric.getUuid()));
+        .isInstanceOf(IllegalStateException.class)
+        .hasMessageContaining(format("Could not find metric with id %s", metric.getUuid()));
   }
 
   @Test
@@ -311,8 +311,8 @@ public class ShowActionIT {
     assertThatThrownBy(() -> ws.newRequest()
       .setParam("name", "UNKNOWN")
       .execute())
-      .isInstanceOf(NotFoundException.class)
-      .hasMessageContaining("No quality gate has been found for name UNKNOWN");
+        .isInstanceOf(NotFoundException.class)
+        .hasMessageContaining("No quality gate has been found for name UNKNOWN");
   }
 
   @Test
@@ -325,7 +325,7 @@ public class ShowActionIT {
     MetricDto criticalViolationsMetric = db.measures().insertMetric(m -> m.setKey("tests"));
     db.qualityGates().addCondition(qualityGate, blockerViolationsMetric, c -> c.setOperator("GT").setErrorThreshold("0"));
     db.qualityGates().addCondition(qualityGate, criticalViolationsMetric, c -> c.setOperator("LT").setErrorThreshold("10"));
-    when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(NON_COMPLIANT);
+    when(qualityGateCaycChecker.checkCaycCompliant(any(), any(String.class))).thenReturn(NON_COMPLIANT);
 
     String response = ws.newRequest()
       .setParam("name", qualityGate.getName())
index 465d6bd7c32bc782b9e8538c6fc55cb37dad9773..9544a8e2a8e13f0adaa3953f6c2bd88e72411bf8 100644 (file)
@@ -162,8 +162,8 @@ public class UpdateConditionActionIT {
       .setParam(PARAM_OPERATOR, "LT")
       .setParam(PARAM_ERROR, "10")
       .execute())
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessageContaining(format("Operation forbidden for built-in Quality Gate '%s'", qualityGate.getName()));
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining(format("Operation forbidden for built-in Quality Gate '%s'", qualityGate.getName()));
   }
 
   @Test
@@ -179,8 +179,8 @@ public class UpdateConditionActionIT {
       .setParam(PARAM_OPERATOR, "LT")
       .setParam(PARAM_ERROR, "90")
       .execute())
-      .isInstanceOf(NotFoundException.class)
-      .hasMessageContaining("No quality gate condition with uuid '123'");
+        .isInstanceOf(NotFoundException.class)
+        .hasMessageContaining("No quality gate condition with uuid '123'");
   }
 
   @Test
@@ -199,8 +199,8 @@ public class UpdateConditionActionIT {
       .setParam(PARAM_OPERATOR, "LT")
       .setParam(PARAM_ERROR, "90")
       .execute())
-      .isInstanceOf(IllegalStateException.class)
-      .hasMessageContaining(format("Condition '%s' is linked to an unknown quality gate '%s'", condition.getUuid(), 123L));
+        .isInstanceOf(IllegalStateException.class)
+        .hasMessageContaining(format("Condition '%s' is linked to an unknown quality gate '%s'", condition.getUuid(), 123L));
   }
 
   @Test
@@ -217,8 +217,8 @@ public class UpdateConditionActionIT {
       .setParam(PARAM_OPERATOR, "ABC")
       .setParam(PARAM_ERROR, "90")
       .execute())
-      .isInstanceOf(IllegalArgumentException.class)
-      .hasMessageContaining("Value of parameter 'op' (ABC) must be one of: [LT, GT]");
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessageContaining("Value of parameter 'op' (ABC) must be one of: [LT, GT]");
   }
 
   @Test
@@ -236,8 +236,8 @@ public class UpdateConditionActionIT {
       .setParam(PARAM_OPERATOR, updateOperator)
       .setParam(PARAM_ERROR, "90")
       .execute())
-      .isInstanceOf(BadRequestException.class)
-      .hasMessageContaining(format("Operator %s is not allowed for this metric.", updateOperator));
+        .isInstanceOf(BadRequestException.class)
+        .hasMessageContaining(format("Operator %s is not allowed for this metric.", updateOperator));
   }
 
   @Test
@@ -253,8 +253,8 @@ public class UpdateConditionActionIT {
       .setParam(PARAM_OPERATOR, "LT")
       .setParam(PARAM_ERROR, "90")
       .execute())
-      .isInstanceOf(ForbiddenException.class)
-      .hasMessageContaining("Insufficient privileges");
+        .isInstanceOf(ForbiddenException.class)
+        .hasMessageContaining("Insufficient privileges");
   }
 
   @Test
@@ -275,7 +275,7 @@ public class UpdateConditionActionIT {
 
   @DataProvider
   public static Object[][] update_invalid_operators_and_direction() {
-    return new Object[][]{
+    return new Object[][] {
       {"GT", "LT", -1},
       {"LT", "GT", 1},
     };
index 601f2c1feb189065d20dcb54b5f4a851d354fc0e..96e192e4b4535e1a8908d53ef9762469c0eaadb3 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.qualitygate;
 
 import java.io.Serializable;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -51,35 +52,30 @@ public class QualityGateCaycChecker {
 
   static final Map<String, Double> BEST_VALUE_REQUIREMENTS = Stream.of(
     NEW_VIOLATIONS,
-    NEW_SECURITY_HOTSPOTS_REVIEWED
-  ).collect(toMap(Metric::getKey, Metric::getBestValue));
+    NEW_SECURITY_HOTSPOTS_REVIEWED).collect(toMap(Metric::getKey, Metric::getBestValue));
 
   static final Set<String> EXISTENCY_REQUIREMENTS = Set.of(
     NEW_DUPLICATED_LINES_DENSITY_KEY,
-    NEW_COVERAGE_KEY
-  );
+    NEW_COVERAGE_KEY);
   public static final Set<Metric<? extends Serializable>> CAYC_METRICS = Set.of(
     NEW_VIOLATIONS,
     NEW_SECURITY_HOTSPOTS_REVIEWED,
     NEW_DUPLICATED_LINES_DENSITY,
-    NEW_COVERAGE
-  );
+    NEW_COVERAGE);
 
   // To be removed after transition
   static final Map<String, Double> LEGACY_BEST_VALUE_REQUIREMENTS = Stream.of(
     NEW_MAINTAINABILITY_RATING,
     NEW_RELIABILITY_RATING,
     NEW_SECURITY_HOTSPOTS_REVIEWED,
-    NEW_SECURITY_RATING
-  ).collect(toMap(Metric::getKey, Metric::getBestValue));
+    NEW_SECURITY_RATING).collect(toMap(Metric::getKey, Metric::getBestValue));
   static final Set<Metric<? extends Serializable>> LEGACY_CAYC_METRICS = Set.of(
     NEW_MAINTAINABILITY_RATING,
     NEW_RELIABILITY_RATING,
     NEW_SECURITY_HOTSPOTS_REVIEWED,
     NEW_SECURITY_RATING,
     NEW_DUPLICATED_LINES_DENSITY,
-    NEW_COVERAGE
-  );
+    NEW_COVERAGE);
 
   private final DbClient dbClient;
 
@@ -88,7 +84,17 @@ public class QualityGateCaycChecker {
   }
 
   public QualityGateCaycStatus checkCaycCompliant(DbSession dbSession, String qualityGateUuid) {
-    var conditionsByMetricId = dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGateUuid)
+    Collection<QualityGateConditionDto> conditions = dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGateUuid);
+    var metrics = dbClient.metricDao().selectByUuids(dbSession, conditions.stream().map(QualityGateConditionDto::getMetricUuid).collect(Collectors.toSet()))
+      .stream()
+      .filter(MetricDto::isEnabled)
+      .toList();
+
+    return checkCaycCompliant(conditions, metrics);
+  }
+
+  public QualityGateCaycStatus checkCaycCompliant(Collection<QualityGateConditionDto> conditions, List<MetricDto> metrics) {
+    var conditionsByMetricId = conditions
       .stream()
       .collect(Collectors.toMap(QualityGateConditionDto::getMetricUuid, Function.identity()));
 
@@ -96,11 +102,6 @@ public class QualityGateCaycChecker {
       return NON_COMPLIANT;
     }
 
-    var metrics = dbClient.metricDao().selectByUuids(dbSession, conditionsByMetricId.keySet())
-      .stream()
-      .filter(MetricDto::isEnabled)
-      .toList();
-
     var caycStatus = checkCaycConditions(metrics, conditionsByMetricId, false);
     if (caycStatus == NON_COMPLIANT) {
       caycStatus = checkCaycConditions(metrics, conditionsByMetricId, true);
index f55d0cba08c581f3b5e686a7f474fcf7fb486cba..41490914c5b154ee97dcac7e9a1836a56ef5bf62 100644 (file)
@@ -25,13 +25,12 @@ import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import javax.annotation.Nullable;
-import org.sonar.api.measures.Metric;
 import org.sonar.api.measures.Metric.ValueType;
-import org.sonar.core.metric.SoftwareQualitiesMetrics;
 import org.sonar.core.util.Uuids;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
@@ -40,6 +39,7 @@ import org.sonar.db.qualitygate.QualityGateConditionDto;
 import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.measure.Rating;
+import org.sonar.server.qualitygate.ws.StandardToMQRMetrics;
 
 import static com.google.common.base.Strings.isNullOrEmpty;
 import static java.lang.Double.parseDouble;
@@ -56,14 +56,15 @@ import static org.sonar.api.measures.Metric.DIRECTION_NONE;
 import static org.sonar.api.measures.Metric.DIRECTION_WORST;
 import static org.sonar.api.measures.Metric.ValueType.RATING;
 import static org.sonar.server.exceptions.BadRequestException.checkRequest;
+import static org.sonar.server.exceptions.BadRequestException.throwBadRequestException;
 import static org.sonar.server.measure.Rating.E;
 import static org.sonar.server.qualitygate.Condition.Operator.GREATER_THAN;
 import static org.sonar.server.qualitygate.Condition.Operator.LESS_THAN;
 import static org.sonar.server.qualitygate.ValidRatingMetrics.isCoreRatingMetric;
+import static org.sonar.server.qualitygate.ValidRatingMetrics.isSoftwareQualityRatingMetric;
 
 public class QualityGateConditionsUpdater {
-  public static final Set<String> INVALID_METRIC_KEYS = Stream.concat(Stream.of(ALERT_STATUS_KEY, SECURITY_HOTSPOTS_KEY, NEW_SECURITY_HOTSPOTS_KEY),
-    new SoftwareQualitiesMetrics().getMetrics().stream().map(Metric::getKey))
+  public static final Set<String> INVALID_METRIC_KEYS = Stream.of(ALERT_STATUS_KEY, SECURITY_HOTSPOTS_KEY, NEW_SECURITY_HOTSPOTS_KEY)
     .collect(Collectors.toUnmodifiableSet());
 
   private static final Map<Integer, Set<Condition.Operator>> VALID_OPERATORS_BY_DIRECTION = Map.of(
@@ -86,14 +87,16 @@ public class QualityGateConditionsUpdater {
 
   public QualityGateConditionsUpdater(DbClient dbClient) {
     this.dbClient = dbClient;
+
   }
 
   public QualityGateConditionDto createCondition(DbSession dbSession, QualityGateDto qualityGate, String metricKey, String operator,
     String errorThreshold) {
     MetricDto metric = getNonNullMetric(dbSession, metricKey);
     validateCondition(metric, operator, errorThreshold);
-    checkConditionDoesNotExistOnSameMetric(getConditions(dbSession, qualityGate.getUuid()), metric);
-
+    Collection<QualityGateConditionDto> conditions = getConditions(dbSession, qualityGate.getUuid());
+    checkConditionDoesNotExistOnSameMetric(conditions, metric);
+    checkConditionDoesNotExistOnEquivalentMetric(dbSession, conditions, metric);
     QualityGateConditionDto newCondition = new QualityGateConditionDto().setQualityGateUuid(qualityGate.getUuid())
       .setUuid(Uuids.create())
       .setMetricUuid(metric.getUuid()).setMetricKey(metric.getKey())
@@ -107,7 +110,11 @@ public class QualityGateConditionsUpdater {
     String errorThreshold) {
     MetricDto metric = getNonNullMetric(dbSession, metricKey);
     validateCondition(metric, operator, errorThreshold);
-
+    Collection<QualityGateConditionDto> otherConditions = getConditions(dbSession, condition.getQualityGateUuid())
+      .stream()
+      .filter(c -> !c.getUuid().equals(condition.getUuid()))
+      .toList();
+    checkConditionDoesNotExistOnEquivalentMetric(dbSession, otherConditions, metric);
     condition
       .setMetricUuid(metric.getUuid())
       .setMetricKey(metric.getKey())
@@ -169,6 +176,23 @@ public class QualityGateConditionsUpdater {
     checkRequest(!conditionExists, format("Condition on metric '%s' already exists.", metric.getShortName()));
   }
 
+  private void checkConditionDoesNotExistOnEquivalentMetric(DbSession dbSession, Collection<QualityGateConditionDto> conditions, MetricDto metric) {
+    Optional<String> equivalentMetric = StandardToMQRMetrics.getEquivalentMetric(metric.getKey());
+
+    if (conditions.isEmpty() || equivalentMetric.isEmpty()) {
+      return;
+    }
+
+    MetricDto equivalentMetricDto = dbClient.metricDao().selectByKey(dbSession, equivalentMetric.get());
+    boolean conditionExists = conditions.stream()
+      .anyMatch(c -> equivalentMetricDto != null && c.getMetricUuid().equals(equivalentMetricDto.getUuid()));
+
+    if (conditionExists) {
+      throwBadRequestException(
+        format("Condition for metric '%s' already exists on equivalent metric '%s''.", metric.getKey(), equivalentMetricDto.getKey()));
+    }
+  }
+
   private static boolean isAllowedOperator(String operator, MetricDto metric) {
     if (VALID_OPERATORS_BY_DIRECTION.containsKey(metric.getDirection())) {
       return VALID_OPERATORS_BY_DIRECTION.get(metric.getDirection()).contains(Condition.Operator.fromDbValue(operator));
@@ -204,7 +228,7 @@ public class QualityGateConditionsUpdater {
     if (!metric.getValueType().equals(RATING.name())) {
       return;
     }
-    if (!isCoreRatingMetric(metric.getKey())) {
+    if (!isCoreRatingMetric(metric.getKey()) && !isSoftwareQualityRatingMetric(metric.getKey())) {
       errors.add(format("The metric '%s' cannot be used", metric.getShortName()));
     }
     if (!isValidRating(errorThreshold)) {
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModeChecker.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModeChecker.java
new file mode 100644 (file)
index 0000000..e664db0
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.qualitygate;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.server.qualitygate.ws.StandardToMQRMetrics;
+
+public class QualityGateModeChecker {
+
+  public QualityModeResult getUsageOfModeMetrics(List<MetricDto> metrics) {
+    Set<String> metricKeys = metrics.stream().map(MetricDto::getKey).collect(Collectors.toSet());
+
+    boolean hasStandardConditions = metricKeys.stream().anyMatch(StandardToMQRMetrics::isStandardMetric);
+    boolean hasMQRConditions = metricKeys.stream().anyMatch(StandardToMQRMetrics::isMQRMetric);
+    return new QualityModeResult(hasMQRConditions, hasStandardConditions);
+  }
+
+  public record QualityModeResult(boolean hasMQRConditions, boolean hasStandardConditions) {
+  }
+
+}
index 4c8656724bd18d32b89969743df7a00e8b028c2b..53b6a93cc3825afd8c024f4bf13d3c3911831f6f 100644 (file)
@@ -27,6 +27,7 @@ public class QualityGateModule extends Module {
     add(
       QualityGateUpdater.class,
       QualityGateCaycChecker.class,
+      QualityGateModeChecker.class,
       QualityGateConditionsUpdater.class,
       QualityGateFinder.class,
       QualityGateEvaluatorImpl.class);
index 3570ee020c328fb2348c475cc720f417fb384298..89f044dfab1556234bac1e42a5645940e63af34c 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.qualitygate;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.sonar.api.measures.CoreMetrics;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
 
 import static org.sonar.api.measures.Metric.ValueType.RATING;
 
@@ -32,6 +33,11 @@ public class ValidRatingMetrics {
     .map(org.sonar.api.measures.Metric::getKey)
     .collect(Collectors.toSet());
 
+  private static final Set<String> SOFTWARE_QUALITY_RATING_METRICS = new SoftwareQualitiesMetrics().getMetrics().stream()
+    .filter(metric -> metric.getType().equals(RATING))
+    .map(org.sonar.api.measures.Metric::getKey)
+    .collect(Collectors.toSet());
+
   private ValidRatingMetrics() {
     // only static methods
   }
@@ -39,4 +45,8 @@ public class ValidRatingMetrics {
   public static boolean isCoreRatingMetric(String metricKey) {
     return CORE_RATING_METRICS.contains(metricKey);
   }
+
+  public static boolean isSoftwareQualityRatingMetric(String metricKey) {
+    return SOFTWARE_QUALITY_RATING_METRICS.contains(metricKey);
+  }
 }
index 26c971e581b7c59f3c605af0f72e31bb904c7a93..e9cebcf1ef36468df220c4d1ad743bf3ad585624 100644 (file)
@@ -21,16 +21,22 @@ package org.sonar.server.qualitygate.ws;
 
 import com.google.common.io.Resources;
 import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
 import javax.annotation.Nullable;
+import org.jetbrains.annotations.NotNull;
 import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.qualitygate.QualityGateConditionDto;
 import org.sonar.db.qualitygate.QualityGateDto;
 import org.sonar.server.qualitygate.QualityGateCaycChecker;
 import org.sonar.server.qualitygate.QualityGateFinder;
+import org.sonar.server.qualitygate.QualityGateModeChecker;
 import org.sonarqube.ws.Qualitygates.ListWsResponse;
 import org.sonarqube.ws.Qualitygates.ListWsResponse.QualityGate;
 
@@ -42,12 +48,15 @@ public class ListAction implements QualityGatesWsAction {
   private final QualityGatesWsSupport wsSupport;
   private final QualityGateFinder finder;
   private final QualityGateCaycChecker qualityGateCaycChecker;
+  private final QualityGateModeChecker qualityGateModeChecker;
 
-  public ListAction(DbClient dbClient, QualityGatesWsSupport wsSupport, QualityGateFinder finder, QualityGateCaycChecker qualityGateCaycChecker) {
+  public ListAction(DbClient dbClient, QualityGatesWsSupport wsSupport, QualityGateFinder finder, QualityGateCaycChecker qualityGateCaycChecker,
+    QualityGateModeChecker qualityGateModeChecker) {
     this.dbClient = dbClient;
     this.wsSupport = wsSupport;
     this.finder = finder;
     this.qualityGateCaycChecker = qualityGateCaycChecker;
+    this.qualityGateModeChecker = qualityGateModeChecker;
   }
 
   @Override
@@ -57,6 +66,7 @@ public class ListAction implements QualityGatesWsAction {
       .setSince("4.3")
       .setResponseExample(Resources.getResource(this.getClass(), "list-example.json"))
       .setChangelog(
+        new Change("10.8", "'hasMQRConditions' and 'hasStandardConditions' fields are added on quality gate"),
         new Change("10.0", "Field 'default' in the response has been removed"),
         new Change("10.0", "Field 'id' in the response has been removed"),
         new Change("9.9", "'caycStatus' field is added on quality gate"),
@@ -83,15 +93,30 @@ public class ListAction implements QualityGatesWsAction {
     ListWsResponse.Builder builder = ListWsResponse.newBuilder()
       .setActions(ListWsResponse.RootActions.newBuilder().setCreate(wsSupport.isQualityGateAdmin()))
       .addAllQualitygates(qualityGates.stream()
-        .map(qualityGate -> QualityGate.newBuilder()
-          .setName(qualityGate.getName())
-          .setIsDefault(qualityGate.getUuid().equals(defaultUuid))
-          .setIsBuiltIn(qualityGate.isBuiltIn())
-          .setCaycStatus(qualityGateCaycChecker.checkCaycCompliant(dbSession, qualityGate.getUuid()).toString())
-          .setActions(wsSupport.getActions(dbSession, qualityGate, defaultQualityGate))
-          .build())
+        .map(qualityGate -> {
+          Collection<QualityGateConditionDto> conditions = dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGate.getUuid());
+          List<MetricDto> metrics = getMetricsFromConditions(dbSession, conditions);
+          QualityGateModeChecker.QualityModeResult qualityModeResult = qualityGateModeChecker.getUsageOfModeMetrics(metrics);
+          return QualityGate.newBuilder()
+            .setName(qualityGate.getName())
+            .setIsDefault(qualityGate.getUuid().equals(defaultUuid))
+            .setIsBuiltIn(qualityGate.isBuiltIn())
+            .setCaycStatus(qualityGateCaycChecker.checkCaycCompliant(conditions, metrics).toString())
+            .setHasMQRConditions(qualityModeResult.hasMQRConditions())
+            .setHasStandardConditions(qualityModeResult.hasStandardConditions())
+            .setActions(wsSupport.getActions(dbSession, qualityGate, defaultQualityGate))
+            .build();
+        })
         .toList());
     return builder.build();
   }
 
+  @NotNull
+  private List<MetricDto> getMetricsFromConditions(DbSession dbSession, Collection<QualityGateConditionDto> conditions) {
+    return dbClient.metricDao().selectByUuids(dbSession, conditions.stream().map(QualityGateConditionDto::getMetricUuid).collect(Collectors.toSet()))
+      .stream()
+      .filter(MetricDto::isEnabled)
+      .toList();
+  }
+
 }
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/StandardToMQRMetrics.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/StandardToMQRMetrics.java
new file mode 100644 (file)
index 0000000..e50a91c
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * 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.qualitygate.ws;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.util.Optional;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
+
+import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS_KEY;
+import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY;
+import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_CODE_SMELLS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS_KEY;
+import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_HIGH_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_INFO_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_LOW_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_HIGH_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_INFO_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_LOW_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY;
+import static org.sonar.core.metric.SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY;
+
+/**
+ * Defines the metrics mapping between the standard mode and the MQR mode.
+ * This list all the metrics that are specific for each mode, and the equivalent metric in the other mode.
+ */
+public class StandardToMQRMetrics {
+  private static final BiMap<String, String> STANDARD_TO_MQR_MODE_METRICS;
+
+  private static final BiMap<String, String> MQR_TO_STANDARD_MODE_METRICS;
+
+  static {
+    STANDARD_TO_MQR_MODE_METRICS = HashBiMap.create();
+    // Severity related metrics
+    STANDARD_TO_MQR_MODE_METRICS.put(BLOCKER_VIOLATIONS_KEY, SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(NEW_BLOCKER_VIOLATIONS_KEY, NEW_SOFTWARE_QUALITY_BLOCKER_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CRITICAL_VIOLATIONS_KEY, SOFTWARE_QUALITY_HIGH_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(NEW_CRITICAL_VIOLATIONS_KEY, NEW_SOFTWARE_QUALITY_HIGH_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(MAJOR_VIOLATIONS_KEY, SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(NEW_MAJOR_VIOLATIONS_KEY, NEW_SOFTWARE_QUALITY_MEDIUM_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(MINOR_VIOLATIONS_KEY, SOFTWARE_QUALITY_LOW_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(NEW_MINOR_VIOLATIONS_KEY, NEW_SOFTWARE_QUALITY_LOW_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(INFO_VIOLATIONS_KEY, SOFTWARE_QUALITY_INFO_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(NEW_INFO_VIOLATIONS_KEY, NEW_SOFTWARE_QUALITY_INFO_ISSUES_KEY);
+
+    // Maintainability related metrics
+    STANDARD_TO_MQR_MODE_METRICS.put(CODE_SMELLS_KEY, SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(NEW_CODE_SMELLS_KEY, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(SQALE_RATING_KEY, SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY, NEW_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.TECHNICAL_DEBT_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.NEW_TECHNICAL_DEBT_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_REMEDIATION_EFFORT_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.SQALE_DEBT_RATIO_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_DEBT_RATIO_KEY);
+
+    STANDARD_TO_MQR_MODE_METRICS.put(EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY, EFFORT_TO_REACH_SOFTWARE_QUALITY_MAINTAINABILITY_RATING_A_KEY);
+
+    // Security related metrics
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.SECURITY_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_RATING_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.NEW_SECURITY_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_RATING_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.SECURITY_REMEDIATION_EFFORT_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_REMEDIATION_EFFORT_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.VULNERABILITIES_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.NEW_VULNERABILITIES_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_ISSUES_KEY);
+
+    // Reliability related metrics
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.RELIABILITY_RATING_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.NEW_RELIABILITY_RATING_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_RATING_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_REMEDIATION_EFFORT_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.BUGS_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY);
+    STANDARD_TO_MQR_MODE_METRICS.put(CoreMetrics.NEW_BUGS_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY);
+
+    MQR_TO_STANDARD_MODE_METRICS = STANDARD_TO_MQR_MODE_METRICS.inverse();
+  }
+
+  private StandardToMQRMetrics() {
+  }
+
+  public static boolean isStandardMetric(String metricKey) {
+    return STANDARD_TO_MQR_MODE_METRICS.containsKey(metricKey);
+  }
+
+  public static boolean isMQRMetric(String metricKey) {
+    return MQR_TO_STANDARD_MODE_METRICS.containsKey(metricKey);
+  }
+
+  /**
+   * Retrieves equivalent metric in the other mode. Return empty if metric has no equivalence
+   */
+  public static Optional<String> getEquivalentMetric(String metricKey) {
+    return Optional.ofNullable(STANDARD_TO_MQR_MODE_METRICS.get(metricKey))
+      .or(() -> Optional.ofNullable(MQR_TO_STANDARD_MODE_METRICS.get(metricKey)));
+  }
+}
index 1c07d960016e465ea9f015f2c4db1911b6958309..c21ffa6327c248669c67f9314c866396819a1dad 100644 (file)
@@ -13,7 +13,9 @@
         "manageConditions": false,
         "delegate": false
       },
-      "caycStatus": "compliant"
+      "caycStatus": "compliant",
+      "hasStandardConditions": false,
+      "hasMQRConditions": false
     },
     {
       "name": "Sonar way - Without Coverage",
@@ -28,7 +30,9 @@
         "manageConditions": true,
         "delegate": true
       },
-      "caycStatus": "non-compliant"
+      "caycStatus": "non-compliant",
+      "hasStandardConditions": false,
+      "hasMQRConditions": false
     }
   ],
   "actions": {
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModeCheckerTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModeCheckerTest.java
new file mode 100644 (file)
index 0000000..5ff5c3e
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * 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.qualitygate;
+
+import java.util.List;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
+import org.sonar.db.metric.MetricDto;
+
+class QualityGateModeCheckerTest {
+
+  QualityGateModeChecker underTest = new QualityGateModeChecker();
+
+  @Test
+  void getUsageOfModeMetrics_shouldReturnNoResult_whenMetricsIsEmpty() {
+    QualityGateModeChecker.QualityModeResult qualityModeResult = underTest.getUsageOfModeMetrics(List.of());
+    Assertions.assertThat(qualityModeResult.hasMQRConditions()).isFalse();
+    Assertions.assertThat(qualityModeResult.hasStandardConditions()).isFalse();
+  }
+
+  @Test
+  void getUsageOfModeMetrics_shouldReturnExpectedResult_whenMetricsContainsStandardAndMQR() {
+    QualityGateModeChecker.QualityModeResult qualityModeResult = underTest.getUsageOfModeMetrics(List.of(newMetric(CoreMetrics.RELIABILITY_RATING_KEY)));
+    Assertions.assertThat(qualityModeResult.hasMQRConditions()).isFalse();
+    Assertions.assertThat(qualityModeResult.hasStandardConditions()).isTrue();
+
+    qualityModeResult = underTest.getUsageOfModeMetrics(List.of(newMetric(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_HIGH_ISSUES_KEY)));
+    Assertions.assertThat(qualityModeResult.hasMQRConditions()).isTrue();
+    Assertions.assertThat(qualityModeResult.hasStandardConditions()).isFalse();
+
+    qualityModeResult = underTest.getUsageOfModeMetrics(List.of(newMetric(CoreMetrics.RELIABILITY_RATING_KEY),
+      newMetric(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_HIGH_ISSUES_KEY)));
+    Assertions.assertThat(qualityModeResult.hasMQRConditions()).isTrue();
+    Assertions.assertThat(qualityModeResult.hasStandardConditions()).isTrue();
+  }
+
+  private static MetricDto newMetric(String metricKey) {
+    return new MetricDto().setKey(metricKey);
+  }
+
+}
index aca3cb2bf0426eee2818a37eacab754b51434abe..1c785a45dd1b3c619cb8f7fc7d3ac97f54aa95b3 100644 (file)
@@ -29,6 +29,6 @@ public class QualityGateModuleTest {
   public void verify_count_of_added_components() {
     ListContainer container = new ListContainer();
     new QualityGateModule().configure(container);
-    assertThat(container.getAddedObjects()).hasSize(5);
+    assertThat(container.getAddedObjects()).hasSize(6);
   }
 }
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/StandardToMQRMetricsTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/StandardToMQRMetricsTest.java
new file mode 100644 (file)
index 0000000..c779311
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.qualitygate.ws;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
+
+class StandardToMQRMetricsTest {
+
+  @Test
+  void isStandardMetric_shouldReturnExpectedResult() {
+    Assertions.assertThat(StandardToMQRMetrics.isStandardMetric(CoreMetrics.RELIABILITY_RATING_KEY)).isTrue();
+    Assertions.assertThat(StandardToMQRMetrics.isMQRMetric(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_HIGH_ISSUES_KEY)).isTrue();
+
+    Assertions.assertThat(StandardToMQRMetrics.isMQRMetric(CoreMetrics.SECURITY_RATING_KEY)).isFalse();
+    Assertions.assertThat(StandardToMQRMetrics.isStandardMetric(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY)).isFalse();
+  }
+
+  @Test
+  void getEquivalentMetric_shouldReturnExpectedResult() {
+    Assertions.assertThat(StandardToMQRMetrics.getEquivalentMetric(CoreMetrics.COMMENT_LINES_DENSITY_KEY)).isEmpty();
+    Assertions.assertThat(StandardToMQRMetrics.getEquivalentMetric(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY)).hasValue(CoreMetrics.RELIABILITY_RATING_KEY);
+    Assertions.assertThat(StandardToMQRMetrics.getEquivalentMetric(CoreMetrics.RELIABILITY_RATING_KEY)).hasValue(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_RATING_KEY);
+  }
+
+}
index 69af5d6a15da03bde3ba8c1c8fd43155b943e70b..8a6e1b43ce1a664955bc171687bdc2f84ab11c63 100644 (file)
@@ -49,11 +49,11 @@ message ProjectStatusResponse {
     optional string actualValue = 7;
   }
 
-    message NewCodePeriod {
-      optional string mode = 1;
-      optional string date = 2;
-      optional string parameter = 3;
-    }
+  message NewCodePeriod {
+    optional string mode = 1;
+    optional string date = 2;
+    optional string parameter = 3;
+  }
 
   enum Status {
     OK = 1;
@@ -163,6 +163,8 @@ message ListWsResponse {
     optional bool isBuiltIn = 4;
     optional Actions actions = 5;
     optional string caycStatus = 6;
+    optional bool hasStandardConditions = 7;
+    optional bool hasMQRConditions = 8;
   }
 
   message RootActions {