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;
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));
@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);
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));
}
}
*/
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;
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();
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();
}
@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();
}
@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);
}
@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"))
.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();
.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();
}
@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();
}
@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();
}
@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();
.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();
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();
}
@Test
- public void update_condition() {
+ void update_condition() {
MetricDto metric = insertMetric(PERCENT);
QualityGateDto qualityGate = db.qualityGates().insertQualityGate();
QualityGateConditionDto condition = db.qualityGates().addCondition(qualityGate, metric,
}
@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,
}
@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,
}
@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,
}
@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,
}
@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,
.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,
.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,
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,
.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},
};
}
- @DataProvider
public static Object[][] valid_values() {
return new Object[][] {
{INT, "10"},
};
}
- @DataProvider
public static Object[][] invalid_values() {
return new Object[][] {
{INT, "ABCD"},
};
}
- @DataProvider
public static Object[][] invalid_operators_and_direction() {
return new Object[][] {
{"EQ", 0},
};
}
- @DataProvider
public static Object[][] update_invalid_operators_and_direction() {
return new Object[][] {
{"LT", "EQ", 0},
};
}
- @DataProvider
public static Object[][] valid_operators_and_direction() {
return new Object[][] {
{"LT", 0},
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();
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);
}
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();
}
@DataProvider
public static Object[][] nullOrEmpty() {
- return new Object[][]{
+ return new Object[][] {
{null},
{""},
{" "}
@RunWith(DataProviderRunner.class)
public class CreateConditionActionIT {
-
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
.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
.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
.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
.setParam(PARAM_OPERATOR, "LT")
.setParam(PARAM_ERROR, "90")
.execute())
- .isInstanceOf(ForbiddenException.class)
- .hasMessageContaining("Insufficient privileges");
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessageContaining("Insufficient privileges");
}
@Test
*/
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;
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;
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
@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);
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);
}
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();
@Before
public void setUp() {
- when(qualityGateCaycChecker.checkCaycCompliant(any(), any())).thenReturn(COMPLIANT);
+ when(qualityGateCaycChecker.checkCaycCompliant(any(), any(String.class))).thenReturn(COMPLIANT);
}
@Test
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
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
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())
.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
.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
.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
.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
.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
.setParam(PARAM_OPERATOR, "LT")
.setParam(PARAM_ERROR, "90")
.execute())
- .isInstanceOf(ForbiddenException.class)
- .hasMessageContaining("Insufficient privileges");
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessageContaining("Insufficient privileges");
}
@Test
@DataProvider
public static Object[][] update_invalid_operators_and_direction() {
- return new Object[][]{
+ return new Object[][] {
{"GT", "LT", -1},
{"LT", "GT", 1},
};
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;
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;
}
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()));
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);
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;
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;
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(
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())
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())
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));
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)) {
--- /dev/null
+/*
+ * 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) {
+ }
+
+}
add(
QualityGateUpdater.class,
QualityGateCaycChecker.class,
+ QualityGateModeChecker.class,
QualityGateConditionsUpdater.class,
QualityGateFinder.class,
QualityGateEvaluatorImpl.class);
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;
.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
}
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);
+ }
}
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;
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
.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"),
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();
+ }
+
}
--- /dev/null
+/*
+ * 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)));
+ }
+}
"manageConditions": false,
"delegate": false
},
- "caycStatus": "compliant"
+ "caycStatus": "compliant",
+ "hasStandardConditions": false,
+ "hasMQRConditions": false
},
{
"name": "Sonar way - Without Coverage",
"manageConditions": true,
"delegate": true
},
- "caycStatus": "non-compliant"
+ "caycStatus": "non-compliant",
+ "hasStandardConditions": false,
+ "hasMQRConditions": false
}
],
"actions": {
--- /dev/null
+/*
+ * 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);
+ }
+
+}
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);
}
}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
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;
optional bool isBuiltIn = 4;
optional Actions actions = 5;
optional string caycStatus = 6;
+ optional bool hasStandardConditions = 7;
+ optional bool hasMQRConditions = 8;
}
message RootActions {