*/
package org.sonar.server.platform.db.migration.version.v108;
+import com.google.gson.Gson;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.util.HashMap;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.slf4j.event.Level;
+import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.testfixtures.log.LogTesterJUnit5;
import org.sonar.api.utils.System2;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
import org.sonar.core.util.SequenceUuidFactory;
import org.sonar.db.MigrationDbTester;
import org.sonar.server.platform.db.migration.step.DataChange;
.isEmpty();
}
+ @Test
+ void should_include_new_measures_based_on_previous_available_measures() throws SQLException {
+ Set<String> metricsToMigrate = MeasureMigration.MIGRATION_MAP.keySet().stream().map(e -> insertMetric(e, "DATA"))
+ .collect(Collectors.toSet());
+
+ String branch = "branch_4";
+ insertNotMigratedBranch(branch);
+ String component1 = uuidFactory.create();
+ metricsToMigrate.forEach(metricUuid -> insertMeasure(branch, component1, metricUuid,
+ Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":7}")));
+
+ underTest.execute();
+
+ assertBranchMigrated(branch);
+ assertThat(db.countRowsOfTable("measures")).isEqualTo(1);
+
+ List<Map<String, Object>> measuresFromDB = db.select(format(SELECT_MEASURE, component1));
+ assertThat(measuresFromDB).hasSize(1);
+
+ Gson gson = new Gson();
+ Map<String, Object> jsonValue = gson.fromJson((String) measuresFromDB.get(0).get("json_value"), Map.class);
+
+ Map<String, Object> expectedExistingMetrics = MeasureMigration.MIGRATION_MAP.keySet().stream().collect(
+ Collectors.toMap(s -> s, s -> "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":7}"));
+
+ Map<String, Object> expectedNewMetrics = MeasureMigration.MIGRATION_MAP.values().stream().collect(
+ Collectors.toMap(s -> s, s -> 7.0));
+
+ assertThat(jsonValue).containsAllEntriesOf(expectedExistingMetrics).containsAllEntriesOf(expectedNewMetrics);
+ }
+
+ @Test
+ void should_migrate_other_measures_when_there_is_an_error_converting_previous_measures() throws SQLException {
+ String nclocMetricUuid = insertMetric("ncloc", "INT");
+ String maintainabilityMetricUuid = insertMetric(CoreMetrics.MAINTAINABILITY_ISSUES_KEY, "DATA");
+ String reliabilityMetricUuid = insertMetric(CoreMetrics.RELIABILITY_ISSUES_KEY, "DATA");
+ String securityMetricUuid = insertMetric(CoreMetrics.SECURITY_ISSUES_KEY, "DATA");
+
+ String branch = "branch_4";
+ insertNotMigratedBranch(branch);
+ String component1 = uuidFactory.create();
+ insertMeasure(branch, component1, nclocMetricUuid, Map.of("value", 120));
+ // total is not a number
+ insertMeasure(branch, component1, maintainabilityMetricUuid, Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4," +
+ "\"total\":\"ABC\"}"));
+ // total cannot fit in a long
+ insertMeasure(branch, component1, reliabilityMetricUuid, Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4," +
+ "\"total\":98723987498723987429874928748748}"));
+ insertMeasure(branch, component1, securityMetricUuid, Map.of("measure_data", "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":37}"));
+
+ logTester.setLevel(Level.DEBUG);
+ underTest.execute();
+
+ assertBranchMigrated(branch);
+ assertThat(db.countRowsOfTable("measures")).isEqualTo(1);
+
+ List<Map<String, Object>> measuresFromDB = db.select(format(SELECT_MEASURE, component1));
+ assertThat(measuresFromDB).hasSize(1);
+
+ Gson gson = new Gson();
+ Map<String, Object> jsonValue = gson.fromJson((String) measuresFromDB.get(0).get("json_value"), Map.class);
+
+ Map<String, Object> expectedExistingMetrics = Map.of(
+ "ncloc", 120.0,
+ CoreMetrics.MAINTAINABILITY_ISSUES_KEY, "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":\"ABC\"}",
+ CoreMetrics.RELIABILITY_ISSUES_KEY, "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":98723987498723987429874928748748}",
+ CoreMetrics.SECURITY_ISSUES_KEY, "{\"LOW\":3,\"MEDIUM\":0,\"HIGH\":4,\"total\":37}"
+ );
+
+ Map<String, Object> expectedNewMetrics = Map.of(SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES_KEY, 37.0);
+
+ assertThat(jsonValue).hasSize(5).containsAllEntriesOf(expectedExistingMetrics).containsAllEntriesOf(expectedNewMetrics);
+ assertThat(logTester.logs(Level.DEBUG)).contains("Failed to migrate metric reliability_issues with value {\"LOW\":3,\"MEDIUM\":0," +
+ "\"HIGH\":4,\"total\":98723987498723987429874928748748}");
+ }
+
private void assertBranchMigrated(String branch) {
List<Map<String, Object>> result = db.select(format("select %s as \"MIGRATED\" from project_branches where uuid = '%s'", MEASURES_MIGRATED_COLUMN, branch));
assertThat(result)
Object metricValue = getMetricValue(data, textValue, valueType, numericValue);
if (metricValue != null
- && !measuresPlannedForDeletion(metricName)) {
+ && !MeasureMigration.isMetricPlannedForDeletion(metricName)) {
measureValues.put(metricName, metricValue);
+ migrateMeasureIfNeeded(measureValues, metricName, metricValue);
}
}
- private static boolean measuresPlannedForDeletion(String metricName) {
- return DeleteSoftwareQualityRatingFromProjectMeasures.SOFTWARE_QUALITY_METRICS_TO_DELETE.contains(metricName);
+ private static void migrateMeasureIfNeeded(Map<String, Object> measureValues, String metricName, Object metricValue) {
+ String migratedMetricKey = MeasureMigration.getMigrationMetricKey(metricName);
+ if (migratedMetricKey != null) {
+ try {
+ Object migratedValue = MeasureMigration.migrate(metricValue);
+ if (migratedValue != null) {
+ measureValues.put(migratedMetricKey, migratedValue);
+ }
+ } catch (Exception e) {
+ LOGGER.debug("Failed to migrate metric {} with value {}", metricName, metricValue);
+ }
+ }
}
private static Object getMetricValue(@Nullable byte[] data, @Nullable String textValue, String valueType, Double numericValue) {
--- /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.platform.db.migration.version.v108;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.annotation.CheckForNull;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.core.metric.SoftwareQualitiesMetrics;
+
+public class MeasureMigration {
+
+ static final Pattern VALUE_EXTRACTION_PATTERN = Pattern.compile("\"total\":(\\d+)");
+
+ static final Map<String, String> MIGRATION_MAP = Map.of(
+ CoreMetrics.MAINTAINABILITY_ISSUES_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY,
+ CoreMetrics.NEW_MAINTAINABILITY_ISSUES_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_MAINTAINABILITY_ISSUES_KEY,
+ CoreMetrics.RELIABILITY_ISSUES_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY,
+ CoreMetrics.NEW_RELIABILITY_ISSUES_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_RELIABILITY_ISSUES_KEY,
+ CoreMetrics.SECURITY_ISSUES_KEY, SoftwareQualitiesMetrics.SOFTWARE_QUALITY_SECURITY_ISSUES_KEY,
+ CoreMetrics.NEW_SECURITY_ISSUES_KEY, SoftwareQualitiesMetrics.NEW_SOFTWARE_QUALITY_SECURITY_ISSUES_KEY);
+
+ private MeasureMigration() {
+ //Only static methods
+ }
+
+ @CheckForNull
+ public static String getMigrationMetricKey(String metricKey) {
+ return MIGRATION_MAP.get(metricKey);
+ }
+
+ @CheckForNull
+ public static Object migrate(Object value) {
+ Matcher matcher = VALUE_EXTRACTION_PATTERN.matcher(value.toString());
+ if (matcher.find()) {
+ return Long.valueOf(matcher.group(1));
+ }
+ return null;
+ }
+
+ public static boolean isMetricPlannedForDeletion(String metricKey) {
+ return DeleteSoftwareQualityRatingFromProjectMeasures.SOFTWARE_QUALITY_METRICS_TO_DELETE.contains(metricKey);
+ }
+}