From 900f99653d2e5cc0679fd6cb246e9f912195823d Mon Sep 17 00:00:00 2001 From: =?utf8?q?L=C3=A9o=20Geoffroy?= Date: Fri, 25 Oct 2024 09:46:34 +0200 Subject: [PATCH] Add new fields hasStandardConditions and hasMQRConditions in qualitgates/list endpoint and checks --- .../legacy/TelemetryDataLoaderImplIT.java | 17 +- .../QualityGateConditionsUpdaterIT.java | 154 ++++++++++++------ .../qualitygate/RegisterQualityGatesIT.java | 10 +- .../server/qualitygate/ws/CreateActionIT.java | 2 +- .../ws/CreateConditionActionIT.java | 17 +- .../server/qualitygate/ws/ListActionIT.java | 69 ++++++-- .../server/qualitygate/ws/ShowActionIT.java | 12 +- .../ws/UpdateConditionActionIT.java | 26 +-- .../qualitygate/QualityGateCaycChecker.java | 33 ++-- .../QualityGateConditionsUpdater.java | 40 ++++- .../qualitygate/QualityGateModeChecker.java | 41 +++++ .../server/qualitygate/QualityGateModule.java | 1 + .../qualitygate/ValidRatingMetrics.java | 10 ++ .../server/qualitygate/ws/ListAction.java | 41 ++++- .../qualitygate/ws/StandardToMQRMetrics.java | 130 +++++++++++++++ .../server/qualitygate/ws/list-example.json | 8 +- .../QualityGateModeCheckerTest.java | 60 +++++++ .../qualitygate/QualityGateModuleTest.java | 2 +- .../ws/StandardToMQRMetricsTest.java | 45 +++++ .../src/main/protobuf/ws-qualitygates.proto | 12 +- 20 files changed, 585 insertions(+), 145 deletions(-) create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModeChecker.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/StandardToMQRMetrics.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModeCheckerTest.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/StandardToMQRMetricsTest.java diff --git a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java index e4f21c21a6d..21da1debbaf 100644 --- a/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java +++ b/server/sonar-telemetry/src/it/java/org/sonar/telemetry/legacy/TelemetryDataLoaderImplIT.java @@ -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 values() { return Stream.of( Arguments.of(false), - Arguments.of(true) - ); + Arguments.of(true)); } } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/QualityGateConditionsUpdaterIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/QualityGateConditionsUpdaterIT.java index 2f50667688c..b5f2d096c51 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/QualityGateConditionsUpdaterIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/QualityGateConditionsUpdaterIT.java @@ -19,13 +19,14 @@ */ 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}, diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/RegisterQualityGatesIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/RegisterQualityGatesIT.java index 4314aa40691..9c421b23dde 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/RegisterQualityGatesIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/RegisterQualityGatesIT.java @@ -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(); } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/CreateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/CreateActionIT.java index 7f3e80da5ee..227157807ba 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/CreateActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/CreateActionIT.java @@ -138,7 +138,7 @@ public class CreateActionIT { @DataProvider public static Object[][] nullOrEmpty() { - return new Object[][]{ + return new Object[][] { {null}, {""}, {" "} diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/CreateConditionActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/CreateConditionActionIT.java index acbc60c89b5..40e05a65476 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/CreateConditionActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/CreateConditionActionIT.java @@ -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 diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/ListActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/ListActionIT.java index 7ba48f10308..c832973660b 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/ListActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/ListActionIT.java @@ -19,18 +19,23 @@ */ 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> hasCondition(QualityGateConditionDto condition) { + return collection -> collection.stream().anyMatch(e -> e.getUuid().equals(condition.getUuid())); + } + + private ArgumentMatcher> 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(); diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/ShowActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/ShowActionIT.java index c51cc403fbd..058964faa85 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/ShowActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/ShowActionIT.java @@ -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()) diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/UpdateConditionActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/UpdateConditionActionIT.java index 465d6bd7c32..9544a8e2a8e 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/UpdateConditionActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualitygate/ws/UpdateConditionActionIT.java @@ -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}, }; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateCaycChecker.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateCaycChecker.java index 601f2c1feb1..96e192e4b45 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateCaycChecker.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateCaycChecker.java @@ -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 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 EXISTENCY_REQUIREMENTS = Set.of( NEW_DUPLICATED_LINES_DENSITY_KEY, - NEW_COVERAGE_KEY - ); + NEW_COVERAGE_KEY); public static final Set> 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 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> 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 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 conditions, List 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); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java index f55d0cba08c..41490914c5b 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java @@ -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 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 INVALID_METRIC_KEYS = Stream.of(ALERT_STATUS_KEY, SECURITY_HOTSPOTS_KEY, NEW_SECURITY_HOTSPOTS_KEY) .collect(Collectors.toUnmodifiableSet()); private static final Map> 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 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 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 conditions, MetricDto metric) { + Optional 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 index 00000000000..e664db04910 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModeChecker.java @@ -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 metrics) { + Set 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) { + } + +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java index 4c8656724bd..53b6a93cc38 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java @@ -27,6 +27,7 @@ public class QualityGateModule extends Module { add( QualityGateUpdater.class, QualityGateCaycChecker.class, + QualityGateModeChecker.class, QualityGateConditionsUpdater.class, QualityGateFinder.class, QualityGateEvaluatorImpl.class); diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ValidRatingMetrics.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ValidRatingMetrics.java index 3570ee020c3..89f044dfab1 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ValidRatingMetrics.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ValidRatingMetrics.java @@ -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 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); + } } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ListAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ListAction.java index 26c971e581b..e9cebcf1ef3 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ListAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/ListAction.java @@ -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 conditions = dbClient.gateConditionDao().selectForQualityGate(dbSession, qualityGate.getUuid()); + List 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 getMetricsFromConditions(DbSession dbSession, Collection 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 index 00000000000..e50a91cf9cb --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualitygate/ws/StandardToMQRMetrics.java @@ -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 STANDARD_TO_MQR_MODE_METRICS; + + private static final BiMap 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 getEquivalentMetric(String metricKey) { + return Optional.ofNullable(STANDARD_TO_MQR_MODE_METRICS.get(metricKey)) + .or(() -> Optional.ofNullable(MQR_TO_STANDARD_MODE_METRICS.get(metricKey))); + } +} diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/list-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/list-example.json index 1c07d960016..c21ffa6327c 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/list-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualitygate/ws/list-example.json @@ -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 index 00000000000..5ff5c3e0a92 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModeCheckerTest.java @@ -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); + } + +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java index aca3cb2bf04..1c785a45dd1 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java @@ -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 index 00000000000..c779311c589 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualitygate/ws/StandardToMQRMetricsTest.java @@ -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); + } + +} diff --git a/sonar-ws/src/main/protobuf/ws-qualitygates.proto b/sonar-ws/src/main/protobuf/ws-qualitygates.proto index 69af5d6a15d..8a6e1b43ce1 100644 --- a/sonar-ws/src/main/protobuf/ws-qualitygates.proto +++ b/sonar-ws/src/main/protobuf/ws-qualitygates.proto @@ -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 { -- 2.39.5