Browse Source

SONAR-12131 Compute Security Review Rating measures on projects

* Compute Security Review Rating measures on projects
* Live update Security Review Rating measures
tags/7.8
Julien Lancelot 4 years ago
parent
commit
f6028da201

+ 3
- 1
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java View File

@@ -77,9 +77,9 @@ import org.sonar.ce.task.projectanalysis.issue.RuleRepositoryImpl;
import org.sonar.ce.task.projectanalysis.issue.RuleTagsCopier;
import org.sonar.ce.task.projectanalysis.issue.ScmAccountToUser;
import org.sonar.ce.task.projectanalysis.issue.ScmAccountToUserLoader;
import org.sonar.ce.task.projectanalysis.issue.ShortBranchOrPullRequestTrackerExecution;
import org.sonar.ce.task.projectanalysis.issue.SiblingsIssueMerger;
import org.sonar.ce.task.projectanalysis.issue.SiblingsIssuesLoader;
import org.sonar.ce.task.projectanalysis.issue.ShortBranchOrPullRequestTrackerExecution;
import org.sonar.ce.task.projectanalysis.issue.TrackerBaseInputFactory;
import org.sonar.ce.task.projectanalysis.issue.TrackerExecution;
import org.sonar.ce.task.projectanalysis.issue.TrackerMergeOrTargetBranchInputFactory;
@@ -111,6 +111,7 @@ import org.sonar.ce.task.projectanalysis.qualitymodel.NewMaintainabilityMeasures
import org.sonar.ce.task.projectanalysis.qualitymodel.NewReliabilityAndSecurityRatingMeasuresVisitor;
import org.sonar.ce.task.projectanalysis.qualitymodel.RatingSettings;
import org.sonar.ce.task.projectanalysis.qualitymodel.ReliabilityAndSecurityRatingMeasuresVisitor;
import org.sonar.ce.task.projectanalysis.qualitymodel.SecurityReviewRatingVisitor;
import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderImpl;
import org.sonar.ce.task.projectanalysis.qualityprofile.QProfileStatusRepositoryImpl;
import org.sonar.ce.task.projectanalysis.scm.ScmInfoDbLoader;
@@ -265,6 +266,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
NewMaintainabilityMeasuresVisitor.class,
ReliabilityAndSecurityRatingMeasuresVisitor.class,
NewReliabilityAndSecurityRatingMeasuresVisitor.class,
SecurityReviewRatingVisitor.class,
LastCommitVisitor.class,
MeasureComputersVisitor.class,


+ 4
- 14
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepository.java View File

@@ -32,8 +32,7 @@ public interface MeasureRepository {
* Retrieves the base measure (ie. the one currently existing in DB) for the specified {@link Component} for
* the specified {@link MetricImpl} if it exists.
* <p>
* This method searches for Measure which are specific to the Component and not associated to a rule or a
* characteristic.
* This method searches for Measure which are specific to the Component.
* </p>
*
* @throws NullPointerException if either argument is {@code null}
@@ -42,32 +41,23 @@ public interface MeasureRepository {

/**
* Retrieves the measure created during the current analysis for the specified {@link Component} for the specified
* {@link Metric} if it exists (ie. one created by the Compute Engine or the Batch) and which is <strong>not</strong>
* associated to a rule, a characteristic, or a developer.
* {@link Metric} if it exists (ie. one created by the Compute Engine or the Scanner).
*/
Optional<Measure> getRawMeasure(Component component, Metric metric);

/**
* Returns the {@link Measure}s for the specified {@link Component} and the specified {@link Metric}.
* <p>
* Their will be one measure not associated to rules, characteristics or developers, the other ones will be associated to rules or to characteristics
* (see {@link Measure#equals(Object)}.
* </p>
*/
Set<Measure> getRawMeasures(Component component, Metric metric);

/**
* Returns the {@link Measure}s for the specified {@link Component} mapped by their metric key.
* <p>
* Their can be multiple measures for the same Metric but only one which has no rule nor characteristic, one with a
* specific ruleId and one with specific characteristicId (see {@link Measure#equals(Object)}.
* </p>
*/
SetMultimap<String, Measure> getRawMeasures(Component component);

/**
* Adds the specified measure for the specified Component and Metric. There can be no more than one measure for a
* specific combination of Component, Metric and association to a specific rule or characteristic.
* specific combination of Component, Metric.
*
* @throws NullPointerException if any of the arguments is null
* @throws UnsupportedOperationException when trying to add a measure when one already exists for the specified Component/Metric paar
@@ -76,7 +66,7 @@ public interface MeasureRepository {

/**
* Updates the specified measure for the specified Component and Metric. There can be no more than one measure for a
* specific combination of Component, Metric and association to a specific rule or characteristic.
* specific combination of Component, Metric.
*
* @throws NullPointerException if any of the arguments is null
* @throws UnsupportedOperationException when trying to update a non existing measure

+ 67
- 0
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewRatingVisitor.java View File

@@ -0,0 +1,67 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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.ce.task.projectanalysis.qualitymodel;

import java.util.Optional;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.TypeAwareVisitorAdapter;
import org.sonar.ce.task.projectanalysis.measure.Measure;
import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
import org.sonar.ce.task.projectanalysis.metric.Metric;
import org.sonar.ce.task.projectanalysis.metric.MetricRepository;
import org.sonar.server.measure.Rating;
import org.sonar.server.security.SecurityReviewRating;

import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
import static org.sonar.api.measures.CoreMetrics.SECURITY_REVIEW_RATING_KEY;
import static org.sonar.ce.task.projectanalysis.component.CrawlerDepthLimit.PROJECT;
import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;

public class SecurityReviewRatingVisitor extends TypeAwareVisitorAdapter {

private final MeasureRepository measureRepository;
private final Metric nclocMetric;
private final Metric securityHostspotsMetric;
private final Metric securityReviewRatingMetric;

public SecurityReviewRatingVisitor(MeasureRepository measureRepository, MetricRepository metricRepository) {
super(PROJECT, Order.POST_ORDER);
this.measureRepository = measureRepository;
this.nclocMetric = metricRepository.getByKey(NCLOC_KEY);
this.securityHostspotsMetric = metricRepository.getByKey(SECURITY_HOTSPOTS_KEY);
this.securityReviewRatingMetric = metricRepository.getByKey(SECURITY_REVIEW_RATING_KEY);
}

@Override
public void visitProject(Component project) {
Optional<Measure> nclocMeasure = measureRepository.getRawMeasure(project, nclocMetric);
Optional<Measure> securityHostspotsMeasure = measureRepository.getRawMeasure(project, securityHostspotsMetric);
if (!nclocMeasure.isPresent() || !securityHostspotsMeasure.isPresent()) {
return;
}
int ncloc = nclocMeasure.get().getIntValue();
int securityHotspots = securityHostspotsMeasure.get().getIntValue();
Rating rating = SecurityReviewRating.compute(ncloc, securityHotspots);
measureRepository.add(project, securityReviewRatingMetric, newMeasureBuilder().create(rating.getIndex(), rating.name()));
}

}

+ 2
- 34
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/measure/MeasureRepositoryRule.java View File

@@ -23,7 +23,6 @@ import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.SetMultimap;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -61,15 +60,8 @@ public class MeasureRepositoryRule extends ExternalResource implements MeasureRe
private final Map<InternalKey, Measure> baseMeasures = new HashMap<>();
private final Map<InternalKey, Measure> rawMeasures = new HashMap<>();
private final Map<InternalKey, Measure> initialRawMeasures = new HashMap<>();
private Collection<Component> loadedAsRawComponents;
private Collection<Metric> loadedAsRawMetrics;
private final Predicate<Map.Entry<InternalKey, Measure>> isAddedMeasure = new Predicate<Map.Entry<InternalKey, Measure>>() {
@Override
public boolean apply(@Nonnull Map.Entry<InternalKey, Measure> input) {
return !initialRawMeasures.containsKey(input.getKey())
|| !MeasureRepoEntry.deepEquals(input.getValue(), initialRawMeasures.get(input.getKey()));
}
};
private final Predicate<Map.Entry<InternalKey, Measure>> isAddedMeasure = input -> !initialRawMeasures.containsKey(input.getKey())
|| !MeasureRepoEntry.deepEquals(input.getValue(), initialRawMeasures.get(input.getKey()));

private MeasureRepositoryRule(ComponentProvider componentProvider, @Nullable MetricRepositoryRule metricRepositoryRule) {
this.componentProvider = componentProvider;
@@ -95,18 +87,6 @@ public class MeasureRepositoryRule extends ExternalResource implements MeasureRe
return new MeasureRepositoryRule(new TreeComponentProvider(treeRoot), requireNonNull(metricRepositoryRule));
}

public MeasureRepositoryRule addBaseMeasure(Component component, Metric metric, Measure measure) {
checkAndInitProvidersState();

InternalKey internalKey = new InternalKey(component, metric);
checkState(!baseMeasures.containsKey(internalKey),
format("Can not add a BaseMeasure twice for a Component (ref=%s) and Metric (key=%s)", getRef(component), metric.getKey()));

baseMeasures.put(internalKey, measure);

return this;
}

public MeasureRepositoryRule addBaseMeasure(int componentRef, String metricKey, Measure measure) {
checkAndInitProvidersState();

@@ -188,23 +168,11 @@ public class MeasureRepositoryRule extends ExternalResource implements MeasureRe
return Optional.ofNullable(baseMeasures.get(new InternalKey(component, metric)));
}

public Collection<Component> getComponentsLoadedAsRaw() {
return loadedAsRawComponents;
}

public Collection<Metric> getMetricsLoadedAsRaw() {
return loadedAsRawMetrics;
}

@Override
public Optional<Measure> getRawMeasure(Component component, Metric metric) {
return Optional.ofNullable(rawMeasures.get(new InternalKey(component, metric)));
}

public Optional<Measure> getRawRuleMeasure(Component component, Metric metric, int ruleId) {
return Optional.ofNullable(rawMeasures.get(new InternalKey(component, metric)));
}

@Override
public Set<Measure> getRawMeasures(Component component, Metric metric) {
return from(filterKeys(rawMeasures, hasComponentRef(component)).entrySet()).filter(new MatchMetric(metric)).transform(ToMeasure.INSTANCE).toSet();

+ 95
- 0
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualitymodel/SecurityReviewRatingVisitorTest.java View File

@@ -0,0 +1,95 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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.ce.task.projectanalysis.qualitymodel;

import org.junit.Rule;
import org.junit.Test;
import org.sonar.ce.task.projectanalysis.component.Component;
import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
import org.sonar.ce.task.projectanalysis.component.VisitorsCrawler;
import org.sonar.ce.task.projectanalysis.measure.Measure;
import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
import org.sonar.server.measure.Rating;

import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.measures.CoreMetrics.NCLOC;
import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS;
import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
import static org.sonar.api.measures.CoreMetrics.SECURITY_REVIEW_RATING;
import static org.sonar.api.measures.CoreMetrics.SECURITY_REVIEW_RATING_KEY;
import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;

public class SecurityReviewRatingVisitorTest {

private static final int PROJECT_REF = 1;
private static final Component PROJECT = builder(Component.Type.PROJECT, PROJECT_REF).setKey("project").build();

@Rule
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();

@Rule
public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
.add(NCLOC)
.add(SECURITY_HOTSPOTS)
.add(SECURITY_REVIEW_RATING);

@Rule
public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);

private VisitorsCrawler underTest = new VisitorsCrawler(singletonList(new SecurityReviewRatingVisitor(measureRepository, metricRepository)));

@Test
public void compute_security_review_rating_on_project() {
treeRootHolder.setRoot(PROJECT);
measureRepository.addRawMeasure(PROJECT_REF, NCLOC_KEY, newMeasureBuilder().create(1000));
measureRepository.addRawMeasure(PROJECT_REF, SECURITY_HOTSPOTS_KEY, newMeasureBuilder().create(12));

underTest.visit(PROJECT);

Measure measure = measureRepository.getAddedRawMeasure(PROJECT_REF, SECURITY_REVIEW_RATING_KEY).get();
assertThat(measure.getIntValue()).isEqualTo(Rating.C.getIndex());
assertThat(measure.getData()).isEqualTo(Rating.C.name());
}

@Test
public void compute_nothing_when_no_ncloc() {
treeRootHolder.setRoot(PROJECT);
measureRepository.addRawMeasure(PROJECT_REF, SECURITY_HOTSPOTS_KEY, newMeasureBuilder().create(2));

underTest.visit(PROJECT);

assertThat(measureRepository.getAddedRawMeasure(PROJECT_REF, SECURITY_REVIEW_RATING_KEY)).isEmpty();
}

@Test
public void compute_nothing_when_no_security_hotspot() {
treeRootHolder.setRoot(PROJECT);
measureRepository.addRawMeasure(PROJECT_REF, NCLOC_KEY, newMeasureBuilder().create(1000));

underTest.visit(PROJECT);

assertThat(measureRepository.getAddedRawMeasure(PROJECT_REF, SECURITY_REVIEW_RATING_KEY)).isEmpty();
}
}

+ 48
- 0
server/sonar-server-common/src/main/java/org/sonar/server/security/SecurityReviewRating.java View File

@@ -0,0 +1,48 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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.security;

import org.sonar.server.measure.Rating;

public class SecurityReviewRating {

private SecurityReviewRating() {
// Only static method
}

public static Rating compute(int ncloc, int securityHotspots) {
if (ncloc == 0) {
return Rating.A;
}
double ratio = (double) securityHotspots * 1000d / (double) ncloc;
if (ratio <= 3d) {
return Rating.A;
} else if (ratio <= 10) {
return Rating.B;
} else if (ratio <= 15) {
return Rating.C;
} else if (ratio <= 25) {
return Rating.D;
} else {
return Rating.E;
}
}
}

+ 69
- 0
server/sonar-server-common/src/test/java/org/sonar/server/security/SecurityReviewRatingTest.java View File

@@ -0,0 +1,69 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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.security;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.sonar.server.measure.Rating;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.server.measure.Rating.A;
import static org.sonar.server.measure.Rating.B;
import static org.sonar.server.measure.Rating.C;
import static org.sonar.server.measure.Rating.D;
import static org.sonar.server.measure.Rating.E;

@RunWith(DataProviderRunner.class)
public class SecurityReviewRatingTest {

@DataProvider
public static Object[][] values() {
List<Object[]> res = new ArrayList<>();
res.add(new Object[] {1000, 0, A});
res.add(new Object[] {1000, 3, A});
res.add(new Object[] {1000, 4, B});
res.add(new Object[] {1000, 10, B});
res.add(new Object[] {1000, 11, C});
res.add(new Object[] {1000, 15, C});
res.add(new Object[] {1000, 16, D});
res.add(new Object[] {1000, 25, D});
res.add(new Object[] {1000, 26, E});
res.add(new Object[] {1000, 900, E});

res.add(new Object[] {0, 2, A});
res.add(new Object[] {1001, 3, A});
res.add(new Object[] {999, 3, B});
res.add(new Object[] {Integer.MAX_VALUE, Integer.MAX_VALUE, E});
return res.toArray(new Object[res.size()][3]);
}

@Test
@UseDataProvider("values")
public void compute_security_review_rating_on_project(int ncloc, int securityHotspots, Rating expectedRating) {
assertThat(SecurityReviewRating.compute(ncloc, securityHotspots)).isEqualTo(expectedRating);
}

}

+ 6
- 0
server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java View File

@@ -28,6 +28,7 @@ import org.sonar.api.measures.Metric;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.server.measure.Rating;
import org.sonar.server.security.SecurityReviewRating;

import static java.util.Arrays.asList;
import static org.sonar.server.measure.Rating.RATING_BY_SEVERITY;
@@ -107,6 +108,11 @@ public class IssueMetricFormulaFactoryImpl implements IssueMetricFormulaFactory
new IssueMetricFormula(CoreMetrics.SECURITY_RATING, false,
(context, issues) -> context.setValue(RATING_BY_SEVERITY.get(issues.getHighestSeverityOfUnresolved(RuleType.VULNERABILITY, false).orElse(Severity.INFO)))),

new IssueMetricFormula(CoreMetrics.SECURITY_REVIEW_RATING, false,
(context, issues) -> context.setValue(SecurityReviewRating.compute(context.getValue(CoreMetrics.NCLOC).orElse(0d).intValue(),
context.getValue(CoreMetrics.SECURITY_HOTSPOTS).orElse(0d).intValue())),
asList(CoreMetrics.NCLOC, CoreMetrics.SECURITY_HOTSPOTS)),

new IssueMetricFormula(CoreMetrics.NEW_CODE_SMELLS, true,
(context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.CODE_SMELL, true))),


+ 9
- 0
server/sonar-server/src/test/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImplTest.java View File

@@ -123,6 +123,15 @@ public class IssueMetricFormulaFactoryImplTest {
.assertThatValueIs(CoreMetrics.SECURITY_HOTSPOTS, 3 + 5);
}

@Test
public void test_security_review_rating() {
withNoIssues().assertThatValueIs(CoreMetrics.SECURITY_REVIEW_RATING, Rating.A);

with(CoreMetrics.SECURITY_HOTSPOTS, 12.0)
.and(CoreMetrics.NCLOC, 1000.0)
.assertThatValueIs(CoreMetrics.SECURITY_REVIEW_RATING, Rating.C);
}

@Test
public void count_unresolved_by_severity() {
withNoIssues()

+ 3
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -1988,6 +1988,9 @@ metric.security_rating.tooltip.E=Security rating is E when there is at least one
metric.security_remediation_effort.description=Security remediation effort
metric.security_remediation_effort.name=Security Remediation Effort
metric.security_remediation_effort.extra_short_name=Remediation Effort
metric.security_review_rating.description=Security Review Rating
metric.security_review_rating.name=Security Review Rating
metric.security_review_rating.extra_short_name=Review Rating
metric.skipped_tests.description=Number of skipped unit tests
metric.skipped_tests.name=Skipped Unit Tests
metric.skipped_tests.short_name=Skipped

+ 17
- 0
sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java View File

@@ -1506,6 +1506,23 @@ public final class CoreMetrics {
.setWorstValue(5.0)
.create();

/**
* @since 7.8
*/
public static final String SECURITY_REVIEW_RATING_KEY = "security_review_rating";

/**
* @since 7.8
*/
public static final Metric<Integer> SECURITY_REVIEW_RATING = new Metric.Builder(SECURITY_REVIEW_RATING_KEY, "Security Review Rating", Metric.ValueType.RATING)
.setDescription("Security Review Rating")
.setDomain(DOMAIN_SECURITY)
.setDirection(Metric.DIRECTION_WORST)
.setQualitative(true)
.setBestValue(1d)
.setWorstValue(5d)
.create();

// --------------------------------------------------------------------------------------------------------------------
//
// FILE DATA

Loading…
Cancel
Save