return mapper(dbSession).selectDescendants(query, componentOpt.get().uuid(), query.getUuidPath(component));
}
- public List<ComponentDto> selectChildren(DbSession dbSession, Collection<ComponentDto> components) {
+ public List<ComponentDto> selectChildren(DbSession dbSession, String branchUuid, Collection<ComponentDto> components) {
Set<String> uuidPaths = components.stream().map(c -> c.getUuidPath() + c.uuid() + ".").collect(Collectors.toSet());
- return mapper(dbSession).selectChildren(uuidPaths);
+ return mapper(dbSession).selectChildren(branchUuid, uuidPaths);
}
public ComponentDto selectOrFailByKey(DbSession session, String key) {
List<ComponentDto> selectDescendants(@Param("query") ComponentTreeQuery query, @Param("baseUuid") String baseUuid, @Param("baseUuidPath") String baseUuidPath);
- List<ComponentDto> selectChildren(@Param("uuidPaths") Set<String> uuidPaths);
+ List<ComponentDto> selectChildren(@Param("branchUuid") String branchUuid, @Param("uuidPaths") Set<String> uuidPaths);
/**
* Returns all enabled projects (Scope {@link org.sonar.api.resources.Scopes#PROJECT} and qualifier
select
<include refid="componentColumns"/>
from components p
- where p.uuid_path in
+ where
+ p.project_uuid = #{branchUuid,jdbcType=VARCHAR}
+ and p.uuid_path in
<foreach collection="uuidPaths" item="uuidPath" open="(" close=")" separator=",">
#{uuidPath,jdbcType=VARCHAR}
</foreach>
db.commit();
// test children of root
- assertThat(underTest.selectChildren(dbSession, List.of(project))).extracting("uuid").containsOnly(FILE_1_UUID, MODULE_UUID);
+ assertThat(underTest.selectChildren(dbSession, project.uuid(), List.of(project))).extracting("uuid").containsOnly(FILE_1_UUID, MODULE_UUID);
// test children of intermediate component (module here)
- assertThat(underTest.selectChildren(dbSession, List.of(module))).extracting("uuid").containsOnly(FILE_2_UUID, FILE_3_UUID);
+ assertThat(underTest.selectChildren(dbSession, project.uuid(), List.of(module))).extracting("uuid").containsOnly(FILE_2_UUID, FILE_3_UUID);
// test children of leaf component (file here)
- assertThat(underTest.selectChildren(dbSession, List.of(fileInProject))).isEmpty();
+ assertThat(underTest.selectChildren(dbSession, project.uuid(), List.of(fileInProject))).isEmpty();
// test children of 2 components
- assertThat(underTest.selectChildren(dbSession, List.of(project, module))).extracting("uuid").containsOnly(FILE_1_UUID, MODULE_UUID, FILE_2_UUID, FILE_3_UUID);
+ assertThat(underTest.selectChildren(dbSession, project.uuid(), List.of(project, module))).extracting("uuid").containsOnly(FILE_1_UUID, MODULE_UUID, FILE_2_UUID, FILE_3_UUID);
}
@Test
if (total == 0) {
return Optional.empty();
}
- return Optional.of(hotspotsReviewed * 100.0 / total);
+ return Optional.of(hotspotsReviewed * 100.0D / total);
}
public static Rating computeRating(@Nullable Double percent) {
- if (percent == null || percent >= 80.0) {
+ if (percent == null || percent >= 80.0D) {
return A;
- } else if (percent >= 70.0) {
+ } else if (percent >= 70.0D) {
return B;
- } else if (percent >= 50.0) {
+ } else if (percent >= 50.0D) {
return C;
- } else if (percent >= 30.0) {
+ } else if (percent >= 30.0D) {
return D;
}
return E;
sortedComponentsToRoot = loadTreeOfComponents(dbSession, touchedComponents);
branchComponent = findBranchComponent(sortedComponentsToRoot);
children = new HashMap<>();
- List<ComponentDto> childComponents = loadChildren(dbSession, sortedComponentsToRoot);
+ List<ComponentDto> childComponents = loadChildren(dbSession, branchComponent.uuid(), sortedComponentsToRoot);
for (ComponentDto c : childComponents) {
List<String> uuidPathAsList = c.getUuidPathAsList();
String parentUuid = uuidPathAsList.get(uuidPathAsList.size() - 1);
.orElseThrow(() -> new IllegalStateException("No project found in " + components));
}
- private List<ComponentDto> loadChildren(DbSession dbSession, Collection<ComponentDto> components) {
- return dbClient.componentDao().selectChildren(dbSession, components);
+ private List<ComponentDto> loadChildren(DbSession dbSession, String branchUuid, Collection<ComponentDto> components) {
+ return dbClient.componentDao().selectChildren(dbSession, branchUuid, components);
}
private List<ComponentDto> loadTreeOfComponents(DbSession dbSession, List<ComponentDto> touchedComponents) {
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.measure.live;
-
-import java.util.Optional;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.ComponentDto;
-
-import static org.sonar.server.security.SecurityReviewRating.computePercent;
-import static org.sonar.server.security.SecurityReviewRating.computeRating;
-
-public class HotspotMeasureUpdater {
- private final DbClient dbClient;
-
- public HotspotMeasureUpdater(DbClient dbClient) {
- this.dbClient = dbClient;
- }
- public void apply(DbSession dbSession, MeasureMatrix matrix, ComponentIndex components, boolean useLeakFormulas, long beginningOfLeak) {
- HotspotsCounter hotspotsCounter = new HotspotsCounter(dbClient.issueDao().selectBranchHotspotsCount(dbSession, components.getBranch().uuid(), beginningOfLeak));
- ComponentDto branch = components.getBranch();
-
- long reviewed = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_REVIEWED, false);
- long toReview = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false);
- matrix.setValue(branch, CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, reviewed);
- matrix.setValue(branch, CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY, toReview);
- Optional<Double> percent = computePercent(toReview, reviewed);
- percent.ifPresent(p -> matrix.setValue(branch, CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY, p));
- matrix.setValue(branch, CoreMetrics.SECURITY_REVIEW_RATING_KEY, computeRating(percent.orElse(null)));
-
- if (useLeakFormulas) {
- reviewed = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_REVIEWED, true);
- toReview = hotspotsCounter.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true);
- matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, reviewed);
- matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY, toReview);
- percent = computePercent(toReview, reviewed);
- percent.ifPresent(p -> matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, p));
- matrix.setLeakValue(branch, CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY, computeRating(percent.orElse(null)));
- }
- }
-}
ComponentIndexFactory.class,
LiveMeasureTreeUpdaterImpl.class,
LiveMeasureComputerImpl.class,
- HotspotMeasureUpdater.class,
LiveQualityGateComputerImpl.class);
}
}
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.function.Function;
import java.util.stream.Collectors;
import org.sonar.api.config.Configuration;
import org.sonar.api.measures.Metric;
import org.sonar.server.measure.Rating;
import static com.google.common.base.Preconditions.checkState;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
import static org.sonar.db.newcodeperiod.NewCodePeriodType.REFERENCE_BRANCH;
public class LiveMeasureTreeUpdaterImpl implements LiveMeasureTreeUpdater {
private final DbClient dbClient;
private final MeasureUpdateFormulaFactory formulaFactory;
- private final HotspotMeasureUpdater hotspotMeasureUpdater;
- public LiveMeasureTreeUpdaterImpl(DbClient dbClient, MeasureUpdateFormulaFactory formulaFactory, HotspotMeasureUpdater hotspotMeasureUpdater) {
+ public LiveMeasureTreeUpdaterImpl(DbClient dbClient, MeasureUpdateFormulaFactory formulaFactory) {
this.dbClient = dbClient;
this.formulaFactory = formulaFactory;
- this.hotspotMeasureUpdater = hotspotMeasureUpdater;
}
@Override
// 2. aggregate new measures up the component tree
updateMatrixWithHierarchy(measures, components, config, shouldUseLeakFormulas);
-
- // 3. Count hotspots at root level
- // this is only necessary because the count of reviewed and to_review hotspots is only saved for the root (not for all components).
- // For that reason, we can't incrementally generate the new counts up the tree. To have the correct numbers for the root component, we
- // run this extra step that set the hotspots measures to the root based on the total count of hotspots.
- hotspotMeasureUpdater.apply(dbSession, measures, components, shouldUseLeakFormulas, beginningOfLeak);
}
private void updateMatrixWithHierarchy(MeasureMatrix matrix, ComponentIndex components, Configuration config, boolean useLeakFormulas) {
this.debtRatingGrid = debtRatingGrid;
}
- private void change(ComponentDto component, MeasureUpdateFormula formula) {
+ void change(ComponentDto component, MeasureUpdateFormula formula) {
this.currentComponent = component;
this.currentFormula = formula;
}
.collect(Collectors.toList());
}
+ /**
+ * Some child components may not have the measures 'SECURITY_HOTSPOTS_TO_REVIEW_STATUS' and 'SECURITY_HOTSPOTS_REVIEWED_STATUS' saved for them,
+ * so we may need to calculate them based on 'SECURITY_HOTSPOTS_REVIEWED' and 'SECURITY_HOTSPOTS'.
+ */
+ @Override
+ public long getChildrenHotspotsReviewed() {
+ return getChildrenHotspotsReviewed(LiveMeasureDto::getValue, SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, SECURITY_HOTSPOTS_REVIEWED_KEY, SECURITY_HOTSPOTS_KEY);
+ }
+
+ /**
+ * Some child components may not have the measure 'SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY'. We assume that 'SECURITY_HOTSPOTS_KEY' has the same value.
+ */
+ @Override
+ public long getChildrenHotspotsToReview() {
+ return componentIndex.getChildren(currentComponent)
+ .stream()
+ .map(c -> matrix.getMeasure(c, SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).or(() -> matrix.getMeasure(c, SECURITY_HOTSPOTS_KEY)))
+ .mapToLong(lmOpt -> lmOpt.flatMap(lm -> Optional.ofNullable(lm.getValue())).orElse(0D).longValue())
+ .sum();
+ }
+
+ @Override
+ public long getChildrenNewHotspotsReviewed() {
+ return getChildrenHotspotsReviewed(LiveMeasureDto::getVariation, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, NEW_SECURITY_HOTSPOTS_KEY);
+ }
+
+ /**
+ * Some child components may not have the measure 'NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY'. We assume that 'NEW_SECURITY_HOTSPOTS_KEY' has the same value.
+ */
+ @Override
+ public long getChildrenNewHotspotsToReview() {
+ return componentIndex.getChildren(currentComponent)
+ .stream()
+ .map(c -> matrix.getMeasure(c, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).or(() -> matrix.getMeasure(c, NEW_SECURITY_HOTSPOTS_KEY)))
+ .mapToLong(lmOpt -> lmOpt.flatMap(lm -> Optional.ofNullable(lm.getVariation())).orElse(0D).longValue())
+ .sum();
+ }
+
+ private long getChildrenHotspotsReviewed(Function<LiveMeasureDto, Double> valueFunc, String metricKey, String percMetricKey, String hotspotsMetricKey) {
+ return componentIndex.getChildren(currentComponent)
+ .stream()
+ .mapToLong(c -> getHotspotsReviewed(c, valueFunc, metricKey, percMetricKey, hotspotsMetricKey))
+ .sum();
+ }
+
+ private long getHotspotsReviewed(ComponentDto c, Function<LiveMeasureDto, Double> valueFunc, String metricKey, String percMetricKey, String hotspotsMetricKey) {
+ Optional<LiveMeasureDto> measure = matrix.getMeasure(c, metricKey);
+ return measure.map(lm -> Optional.ofNullable(valueFunc.apply(lm)).orElse(0D).longValue())
+ .orElseGet(() -> matrix.getMeasure(c, percMetricKey)
+ .flatMap(percentage -> matrix.getMeasure(c, hotspotsMetricKey)
+ .map(hotspots -> {
+ double perc = Optional.ofNullable(valueFunc.apply(percentage)).orElse(0D) / 100D;
+ double toReview = Optional.ofNullable(valueFunc.apply(hotspots)).orElse(0D);
+ double reviewed = (toReview * perc) / (1D - perc);
+ return Math.round(reviewed);
+ }))
+ .orElse(0L));
+ }
+
public List<Double> getChildrenLeakValues() {
List<ComponentDto> children = componentIndex.getChildren(currentComponent);
return children.stream()
import java.util.List;
import java.util.Optional;
import java.util.function.BiConsumer;
+import java.util.stream.DoubleStream;
import org.sonar.api.measures.Metric;
import org.sonar.db.component.ComponentDto;
import org.sonar.server.measure.DebtRatingGrid;
interface Context {
List<Double> getChildrenValues();
+ long getChildrenHotspotsReviewed();
+
+ long getChildrenHotspotsToReview();
+
+ long getChildrenNewHotspotsReviewed();
+
+ long getChildrenNewHotspotsToReview();
+
List<Double> getChildrenLeakValues();
ComponentDto getComponent();
new MeasureUpdateFormula(CoreMetrics.SECURITY_RATING, false, new MaxRatingChildren(),
(context, issues) -> context.setValue(RATING_BY_SEVERITY.get(issues.getHighestSeverityOfUnresolved(RuleType.VULNERABILITY, false).orElse(Severity.INFO)))),
- new MeasureUpdateFormula(SECURITY_HOTSPOTS_REVIEWED_STATUS, false, new AddChildren(),
+ new MeasureUpdateFormula(SECURITY_HOTSPOTS_REVIEWED_STATUS, false,
+ (context, formula) -> context.setValue(context.getValue(SECURITY_HOTSPOTS_REVIEWED_STATUS).orElse(0D) + context.getChildrenHotspotsReviewed()),
(context, issues) -> context.setValue(issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, false))),
- new MeasureUpdateFormula(SECURITY_HOTSPOTS_TO_REVIEW_STATUS, false, new AddChildren(),
+ new MeasureUpdateFormula(SECURITY_HOTSPOTS_TO_REVIEW_STATUS, false,
+ (context, formula) -> context.setValue(context.getValue(SECURITY_HOTSPOTS_TO_REVIEW_STATUS).orElse(0D) + context.getChildrenHotspotsToReview()),
(context, issues) -> context.setValue(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, false))),
new MeasureUpdateFormula(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED, false,
context.setLeakValue(RATING_BY_SEVERITY.get(highestSeverity));
}),
- new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, true, new AddChildren(),
+ new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, true,
+ (context, formula) -> context.setLeakValue(context.getLeakValue(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS).orElse(0D) + context.getChildrenNewHotspotsReviewed()),
(context, issues) -> context.setLeakValue(issues.countHotspotsByStatus(Issue.STATUS_REVIEWED, true))),
- new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, true, new AddChildren(),
+ new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, true,
+ (context, formula) -> context.setLeakValue(context.getLeakValue(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS).orElse(0D) + context.getChildrenNewHotspotsToReview()),
(context, issues) -> context.setLeakValue(issues.countHotspotsByStatus(Issue.STATUS_TO_REVIEW, true))),
new MeasureUpdateFormula(NEW_SECURITY_HOTSPOTS_REVIEWED, true,
private static double newDebtDensity(MeasureUpdateFormula.Context context) {
double debt = Math.max(context.getLeakValue(CoreMetrics.NEW_TECHNICAL_DEBT).orElse(0.0D), 0.0D);
- Optional<Double> devCost = context.getText(CoreMetrics.NEW_DEVELOPMENT_COST).map(Double::parseDouble);
+ Optional<Double> devCost = context.getLeakValue(CoreMetrics.NEW_DEVELOPMENT_COST);
if (devCost.isPresent() && Double.doubleToRawLongBits(devCost.get()) > 0L) {
return debt / devCost.get();
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.measure.live;
-
-import java.util.Date;
-import java.util.List;
-import org.assertj.core.data.Offset;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.metric.MetricDto;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
-import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_REVIEW_RATING_KEY;
-import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY;
-import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
-import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
-import static org.sonar.api.measures.CoreMetrics.SECURITY_REVIEW_RATING_KEY;
-
-public class HotspotMeasureUpdaterTest {
- @Rule
- public DbTester db = DbTester.create();
-
- private final HotspotMeasureUpdater hotspotMeasureUpdater = new HotspotMeasureUpdater(db.getDbClient());
- private ComponentIndexImpl componentIndex;
- private MeasureMatrix matrix;
- private ComponentDto project;
- private ComponentDto dir;
- private ComponentDto file1;
- private ComponentDto file2;
-
- @Before
- public void setUp() {
- // insert project and file structure
- project = db.components().insertPrivateProject();
- dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java"));
- file1 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
- file2 = db.components().insertComponent(ComponentTesting.newFileDto(project, dir));
-
- // other needed data
- matrix = new MeasureMatrix(List.of(project, dir, file1, file2), insertHotspotMetrics(), List.of());
- componentIndex = new ComponentIndexImpl(db.getDbClient());
- }
-
- private List<MetricDto> insertHotspotMetrics() {
- return List.of(
- db.measures().insertMetric(m -> m.setKey(SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)),
- db.measures().insertMetric(m -> m.setKey(SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)),
- db.measures().insertMetric(m -> m.setKey(SECURITY_HOTSPOTS_REVIEWED_KEY)),
- db.measures().insertMetric(m -> m.setKey(SECURITY_REVIEW_RATING_KEY)),
-
- db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)),
- db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)),
- db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)),
- db.measures().insertMetric(m -> m.setKey(NEW_SECURITY_REVIEW_RATING_KEY))
- );
- }
-
- @Test
- public void should_count_hotspots_for_root() {
- db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(999)).setStatus("TO_REVIEW"));
- db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(999)).setStatus("REVIEWED"));
-
- db.issues().insertHotspot(project, dir, i -> i.setIssueCreationDate(new Date(1001)).setStatus("TO_REVIEW"));
- db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1002)).setStatus("REVIEWED"));
- db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1003)).setStatus("REVIEWED"));
-
- componentIndex.load(db.getSession(), List.of(file1));
- hotspotMeasureUpdater.apply(db.getSession(), matrix, componentIndex, true, 1000L);
-
- assertThat(matrix.getMeasure(project, SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY).get().getValue()).isEqualTo(3d);
- assertThat(matrix.getMeasure(project, SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).get().getValue()).isEqualTo(2d);
- assertThat(matrix.getMeasure(project, SECURITY_REVIEW_RATING_KEY).get().getDataAsString()).isEqualTo("C");
- assertThat(matrix.getMeasure(project, SECURITY_HOTSPOTS_REVIEWED_KEY).get().getValue()).isEqualTo(60d);
-
- assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY).get().getVariation()).isEqualTo(2d);
- assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY).get().getVariation()).isEqualTo(1d);
- assertThat(matrix.getMeasure(project, NEW_SECURITY_REVIEW_RATING_KEY).get().getVariation()).isEqualTo(3);
- assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY).get().getVariation()).isCloseTo(66d, Offset.offset(1d));
- }
-
- @Test
- public void dont_create_leak_measures_if_no_leak_period() {
- db.issues().insertHotspot(project, dir, i -> i.setIssueCreationDate(new Date(1001)).setStatus("TO_REVIEW"));
- db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1002)).setStatus("REVIEWED"));
- db.issues().insertHotspot(project, file1, i -> i.setIssueCreationDate(new Date(1003)).setStatus("REVIEWED"));
-
- componentIndex.load(db.getSession(), List.of(file1));
- hotspotMeasureUpdater.apply(db.getSession(), matrix, componentIndex, false, 1000L);
-
- assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY)).isEmpty();
- assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY)).isEmpty();
- assertThat(matrix.getMeasure(project, NEW_SECURITY_REVIEW_RATING_KEY)).isEmpty();
- assertThat(matrix.getMeasure(project, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY)).isEmpty();
- }
-}
import org.sonar.api.config.Configuration;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.measures.Metric;
+import org.sonar.api.rules.RuleType;
import org.sonar.db.DbTester;
import org.sonar.db.component.BranchDto;
import org.sonar.db.component.BranchType;
import org.sonar.db.newcodeperiod.NewCodePeriodType;
import org.sonar.db.rule.RuleDto;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.sonar.api.CoreProperties.RATING_GRID;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY;
+import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY;
public class LiveMeasureTreeUpdaterImplTest {
@Rule
public DbTester db = DbTester.create();
private final Configuration config = new MapSettings().setProperty(RATING_GRID, "0.05,0.1,0.2,0.5").asConfig();
- private final HotspotMeasureUpdater hotspotMeasureUpdater = mock(HotspotMeasureUpdater.class);
private LiveMeasureTreeUpdaterImpl treeUpdater;
private ComponentIndexImpl componentIndex;
private MeasureMatrix matrix;
@Test
public void should_aggregate_values_up_the_hierarchy() {
snapshot = db.components().insertSnapshot(project);
- treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new AggregateValuesFormula(), hotspotMeasureUpdater);
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new AggregateValuesFormula());
componentIndex.load(db.getSession(), List.of(file1));
List<LiveMeasureDto> initialValues = List.of(
@Test
public void should_set_values_up_the_hierarchy() {
snapshot = db.components().insertSnapshot(project);
- treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new SetValuesFormula(), hotspotMeasureUpdater);
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new SetValuesFormula());
componentIndex.load(db.getSession(), List.of(file1));
treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
@Test
public void dont_use_leak_formulas_if_no_period() {
snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(null));
- treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak());
componentIndex.load(db.getSession(), List.of(file1));
treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
public void use_leak_formulas_if_pr() {
snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(null));
branch.setBranchType(BranchType.PULL_REQUEST);
- treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak());
componentIndex.load(db.getSession(), List.of(file1));
treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
@Test
public void calculate_new_metrics_if_using_new_code_branch_reference() {
snapshot = db.components().insertSnapshot(project, s -> s.setPeriodMode(NewCodePeriodType.REFERENCE_BRANCH.name()));
- treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak());
componentIndex.load(db.getSession(), List.of(file1));
treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
@Test
public void issue_counter_based_on_new_code_branch_reference() {
snapshot = db.components().insertSnapshot(project, s -> s.setPeriodMode(NewCodePeriodType.REFERENCE_BRANCH.name()));
- treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak());
- RuleDto rule = db.rules().insert();
+ RuleDto rule = db.rules().insert(r -> r.setType(RuleType.BUG));
IssueDto issue1 = db.issues().insertIssue(rule, project, file1);
IssueDto issue2 = db.issues().insertIssue(rule, project, file1);
db.issues().insertNewCodeReferenceIssue(issue1);
@Test
public void issue_counter_uses_begin_of_leak() {
snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(1000L));
- treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
+ treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak());
db.issues().insertIssue(i -> i.setIssueCreationDate(new Date(999)).setComponentUuid(file1.uuid()));
db.issues().insertIssue(i -> i.setIssueCreationDate(new Date(1001)).setComponentUuid(file1.uuid()));
}
@Test
- public void calls_hotspot_updater() {
- snapshot = db.components().insertSnapshot(project, s -> s.setPeriodDate(1000L));
+ public void context_calculates_hotspot_counts_from_percentage() {
+ List<MetricDto> metrics = List.of(new MetricDto().setKey(SECURITY_HOTSPOTS_KEY), new MetricDto().setKey(SECURITY_HOTSPOTS_REVIEWED_KEY),
+ new MetricDto().setKey(SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY), new MetricDto().setKey(SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY));
+ componentIndex.load(db.getSession(), List.of(file1));
+ matrix = new MeasureMatrix(List.of(project, dir, file1, file2), metrics, List.of());
+
+ LiveMeasureTreeUpdaterImpl.FormulaContextImpl context = new LiveMeasureTreeUpdaterImpl.FormulaContextImpl(matrix, componentIndex, null);
+ matrix.setValue(file1, SECURITY_HOTSPOTS_KEY, 4d);
+ matrix.setValue(file1, SECURITY_HOTSPOTS_REVIEWED_KEY, 33d);
+
+ matrix.setValue(file2, SECURITY_HOTSPOTS_KEY, 2d);
+ matrix.setValue(file2, SECURITY_HOTSPOTS_REVIEWED_KEY, 50d);
+
+ context.change(dir, null);
+ assertThat(context.getChildrenHotspotsToReview()).isEqualTo(6);
+ assertThat(context.getChildrenHotspotsReviewed()).isEqualTo(4);
+ }
+ @Test
+ public void context_calculates_new_hotspot_counts_from_percentage() {
+ List<MetricDto> metrics = List.of(new MetricDto().setKey(NEW_SECURITY_HOTSPOTS_KEY), new MetricDto().setKey(NEW_SECURITY_HOTSPOTS_REVIEWED_KEY),
+ new MetricDto().setKey(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY), new MetricDto().setKey(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY));
componentIndex.load(db.getSession(), List.of(file1));
- treeUpdater = new LiveMeasureTreeUpdaterImpl(db.getDbClient(), new CountUnresolvedInLeak(), hotspotMeasureUpdater);
- treeUpdater.update(db.getSession(), snapshot, config, componentIndex, branch, matrix);
- verify(hotspotMeasureUpdater).apply(eq(db.getSession()), any(), eq(componentIndex), eq(true), eq(1000L));
+ matrix = new MeasureMatrix(List.of(project, dir, file1, file2), metrics, List.of());
+
+ LiveMeasureTreeUpdaterImpl.FormulaContextImpl context = new LiveMeasureTreeUpdaterImpl.FormulaContextImpl(matrix, componentIndex, null);
+ matrix.setLeakValue(file1, NEW_SECURITY_HOTSPOTS_KEY, 4d);
+ matrix.setLeakValue(file1, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, 33d);
+
+ matrix.setLeakValue(file2, NEW_SECURITY_HOTSPOTS_KEY, 2d);
+ matrix.setLeakValue(file2, NEW_SECURITY_HOTSPOTS_REVIEWED_KEY, 50d);
+
+ context.change(dir, null);
+ assertThat(context.getChildrenNewHotspotsToReview()).isEqualTo(6);
+ assertThat(context.getChildrenNewHotspotsReviewed()).isEqualTo(4);
+ }
+
+ @Test
+ public void context_returns_hotspots_counts_from_measures() {
+ List<MetricDto> metrics = List.of(new MetricDto().setKey(SECURITY_HOTSPOTS_KEY), new MetricDto().setKey(SECURITY_HOTSPOTS_REVIEWED_KEY),
+ new MetricDto().setKey(SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY), new MetricDto().setKey(SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY));
+ componentIndex.load(db.getSession(), List.of(file1));
+ matrix = new MeasureMatrix(List.of(project, dir, file1, file2), metrics, List.of());
+
+ LiveMeasureTreeUpdaterImpl.FormulaContextImpl context = new LiveMeasureTreeUpdaterImpl.FormulaContextImpl(matrix, componentIndex, null);
+ matrix.setValue(file1, SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, 5D);
+ matrix.setValue(file1, SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY, 5D);
+ matrix.setValue(file1, SECURITY_HOTSPOTS_KEY, 6d);
+ matrix.setValue(file1, SECURITY_HOTSPOTS_REVIEWED_KEY, 33d);
+
+ matrix.setValue(file2, SECURITY_HOTSPOTS_REVIEWED_STATUS_KEY, 5D);
+ matrix.setValue(file2, SECURITY_HOTSPOTS_TO_REVIEW_STATUS_KEY, 5D);
+ matrix.setValue(file2, SECURITY_HOTSPOTS_KEY, 4d);
+ matrix.setValue(file2, SECURITY_HOTSPOTS_REVIEWED_KEY, 50d);
+
+ context.change(dir, null);
+ assertThat(context.getChildrenHotspotsToReview()).isEqualTo(10);
+ assertThat(context.getChildrenHotspotsReviewed()).isEqualTo(10);
}
private class AggregateValuesFormula implements MeasureUpdateFormulaFactory {
}
}
+ private class NoOpFormula implements MeasureUpdateFormulaFactory {
+
+ @Override
+ public List<MeasureUpdateFormula> getFormulas() {
+ return emptyList();
+ }
+
+ @Override public Set<Metric> getFormulaMetrics() {
+ return emptySet();
+ }
+ }
+
private class SetValuesFormula implements MeasureUpdateFormulaFactory {
@Override
public List<MeasureUpdateFormula> getFormulas() {
@Test
public void hierarchy_combining_other_metrics() {
+ new HierarchyTester(SECURITY_HOTSPOTS_TO_REVIEW_STATUS)
+ .withValue(SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1d)
+ .withChildrenHotspotsCounts(10, 10, 2, 10)
+ .expectedResult(3d);
+
+ new HierarchyTester(SECURITY_HOTSPOTS_REVIEWED_STATUS)
+ .withValue(SECURITY_HOTSPOTS_REVIEWED_STATUS, 1d)
+ .withChildrenHotspotsCounts(2, 10, 10, 10)
+ .expectedResult(3d);
+
+ new HierarchyTester(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS)
+ .withValue(NEW_SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1d)
+ .withChildrenHotspotsCounts(10, 10, 10, 2)
+ .expectedResult(3d);
+
+ new HierarchyTester(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS)
+ .withValue(NEW_SECURITY_HOTSPOTS_REVIEWED_STATUS, 1d)
+ .withChildrenHotspotsCounts(10, 2, 10, 10)
+ .expectedResult(3d);
+
new HierarchyTester(CoreMetrics.SECURITY_HOTSPOTS_REVIEWED)
.withValue(SECURITY_HOTSPOTS_TO_REVIEW_STATUS, 1d)
.withValue(SECURITY_HOTSPOTS_REVIEWED_STATUS, 1d)
.assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
- .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "160")
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 160.0)
.assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 12.5)
.assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.C);
withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
- .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "10")
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 10.0D)
.assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 200.0)
.assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.E);
// A is 5% --> min debt is exactly 200*0.05=10
- with(CoreMetrics.NEW_DEVELOPMENT_COST, "200")
+ withLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 200.0)
.andLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 10.0)
.assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 5.0)
.assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0)
- .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "0")
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0)
.assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
.assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0)
- .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "80")
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 80.0)
.assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0);
withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, -20.0)
- .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "0")
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0.0)
.assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
.assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
// bug, debt can't be negative
withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, -20.0)
- .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "80")
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 80.0)
.assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
.assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
// bug, cost can't be negative
withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.0)
- .andText(CoreMetrics.NEW_DEVELOPMENT_COST, "-80")
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, -80.0)
.assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
.assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
}
this.initialValues = initialValues;
}
- @Override public List<Double> getChildrenValues() {
+ @Override
+ public List<Double> getChildrenValues() {
return initialValues.childrenValues;
}
- @Override public List<Double> getChildrenLeakValues() {
+ @Override
+ public long getChildrenHotspotsReviewed() {
+ return initialValues.childrenHotspotsReviewed;
+ }
+
+ @Override
+ public long getChildrenHotspotsToReview() {
+ return initialValues.childrenHotspotsToReview;
+ }
+
+ @Override
+ public long getChildrenNewHotspotsReviewed() {
+ return initialValues.childrenNewHotspotsReviewed;
+ }
+
+ @Override
+ public long getChildrenNewHotspotsToReview() {
+ return initialValues.childrenNewHotspotsToReview;
+ }
+
+ @Override
+ public List<Double> getChildrenLeakValues() {
return initialValues.childrenLeakValues;
}
private final List<Double> childrenValues = new ArrayList<>();
private final List<Double> childrenLeakValues = new ArrayList<>();
private final Map<Metric, String> text = new HashMap<>();
+ private long childrenHotspotsReviewed = 0;
+ private long childrenNewHotspotsReviewed = 0;
+ private long childrenHotspotsToReview = 0;
+ private long childrenNewHotspotsToReview = 0;
+
}
private class HierarchyTester {
return this;
}
+ public HierarchyTester withChildrenHotspotsCounts(long childrenHotspotsReviewed, long childrenNewHotspotsReviewed, long childrenHotspotsToReview,
+ long childrenNewHotspotsToReview) {
+ this.initialValues.childrenHotspotsReviewed = childrenHotspotsReviewed;
+ this.initialValues.childrenNewHotspotsReviewed = childrenNewHotspotsReviewed;
+ this.initialValues.childrenHotspotsToReview = childrenHotspotsToReview;
+ this.initialValues.childrenNewHotspotsToReview = childrenNewHotspotsToReview;
+ return this;
+ }
+
public HierarchyTester withValue(Double value) {
return withValue(metric, value);
}