aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2017-12-05 18:04:06 +0100
committerSimon Brandhof <simon.brandhof@sonarsource.com>2018-01-10 06:48:47 +0100
commitbaccb26f1aecd27b7d7fc06a185e7fdeaf03ea83 (patch)
treef5e3cb8af2a4d355c415a0f8c7b2d459b4d0ccec
parent01c3e23df232c1006344f4957bf35af92b17a985 (diff)
downloadsonarqube-baccb26f1aecd27b7d7fc06a185e7fdeaf03ea83.tar.gz
sonarqube-baccb26f1aecd27b7d7fc06a185e7fdeaf03ea83.zip
SONAR-10117 SONAR-1018 Update measures after relevant issue changes and send webhooks
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneBugIssuePerLineSensor.java10
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java3
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java3
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java8
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueGroupDto.java98
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java6
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureDao.java5
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/metric/MetricDao.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDeliveryDto.java3
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml36
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java25
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java55
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/measure/LiveMeasureDaoTest.java148
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexer.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/formula/counter/RatingValue.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolder.java12
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolder.java10
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImpl.java43
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateService.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateServiceImpl.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/DebtRatingGrid.java (renamed from server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingGrid.java)52
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java7
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java11
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java12
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/Rating.java63
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingSettings.java67
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitor.java78
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java10
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexer.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/Action.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangePostProcessor.java39
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangePostProcessorImpl.java46
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/IssueUpdater.java40
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndexer.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java112
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java20
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java21
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java17
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueCounter.java153
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormula.java89
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactory.java40
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java200
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureComputer.java43
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureComputerImpl.java242
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureModule.java32
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputer.java40
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java151
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/MeasureMatrix.java203
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/live/package-info.java23
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java19
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java181
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java56
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/LiveQualityGateFactoryImpl.java134
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateConverter.java61
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluator.java60
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java133
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java12
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java1
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactory.java138
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryImpl.java156
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListeners.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java7
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/GetByProjectAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/settings/ProjectConfigurationLoader.java9
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/test/index/TestIndexer.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentAction.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookQGChangeEventListener.java11
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookWsSupport.java15
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/formula/counter/RatingValueTest.java10
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/measure/BestValueOptimizationTest.java4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolderRule.java16
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImplTest.java33
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderRule.java19
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/DebtRatingGridTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingGridTest.java)18
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java9
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java54
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java12
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingSettingsTest.java24
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitorTest.java12
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStepTest.java28
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ActionTest.java61
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/IssueUpdaterTest.java16
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/TestIssueChangePostProcessor.java47
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/AddCommentActionTest.java19
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java29
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/BulkChangeActionTest.java145
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/DoTransitionActionTest.java35
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetSeverityActionTest.java17
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTagsActionTest.java6
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java32
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImplTest.java832
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureComputerImplTest.java377
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureModuleTest.java37
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveQualityGateComputerImplTest.java198
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/live/MeasureMatrixTest.java209
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/measure/live/TestIssueMetricFormulaFactory.java43
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionEvaluatorTest.java436
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java41
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualitygate/LiveQualityGateFactoryImplTest.java186
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateConverterTest.java (renamed from server/sonar-server/src/main/java/org/sonar/server/qualitygate/LiveQualityGateFactory.java)11
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryImplTest.java594
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryTest.java88
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java39
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/settings/TestProjectConfigurationLoader.java45
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java7
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookDeliveriesActionTest.java14
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookDeliveryActionTest.java32
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java5
-rw-r--r--sonar-core/src/main/java/org/sonar/core/util/stream/MoreCollectors.java9
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties5
-rw-r--r--sonar-core/src/test/java/org/sonar/core/util/stream/MoreCollectorsTest.java21
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java22
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/resources/Qualifiers.java13
-rw-r--r--tests/projects/measure/LiveMeasuresTest/sonar-project.properties5
-rw-r--r--tests/projects/measure/LiveMeasuresTest/src/file_with_one_line.xoo1
-rw-r--r--tests/projects/measure/LiveMeasuresTest/src/file_with_three_lines.xoo3
-rw-r--r--tests/src/test/java/org/sonarqube/tests/issue/IssueMeasureTest.java6
-rw-r--r--tests/src/test/java/org/sonarqube/tests/issue/IssueSearchTest.java10
-rw-r--r--tests/src/test/java/org/sonarqube/tests/measure/LiveMeasuresTest.java198
-rw-r--r--tests/src/test/java/org/sonarqube/tests/qualityModel/ReliabilityMeasureTest.java10
-rw-r--r--tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java61
-rw-r--r--tests/src/test/resources/measure/LiveMeasuresTest/one-bug-per-line-profile.xml12
153 files changed, 5497 insertions, 2144 deletions
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneBugIssuePerLineSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneBugIssuePerLineSensor.java
index fffd48a4de3..59574256742 100644
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneBugIssuePerLineSensor.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneBugIssuePerLineSensor.java
@@ -23,6 +23,7 @@ import org.sonar.api.batch.fs.FilePredicates;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputFile.Type;
+import org.sonar.api.batch.fs.TextRange;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
@@ -56,15 +57,20 @@ public class OneBugIssuePerLineSensor implements Sensor {
}
}
- private void createIssues(InputFile file, SensorContext context, String repo) {
+ private static void createIssues(InputFile file, SensorContext context, String repo) {
RuleKey ruleKey = RuleKey.of(repo, RULE_KEY);
for (int line = 1; line <= file.lines(); line++) {
+ TextRange text = file.selectLine(line);
+ // do not count empty lines, which can be a pain with end-of-file return
+ if (text.end().lineOffset() == 0) {
+ continue;
+ }
NewIssue newIssue = context.newIssue();
newIssue
.forRule(ruleKey)
.at(newIssue.newLocation()
.on(file)
- .at(file.selectLine(line))
+ .at(text)
.message("This bug issue is generated on each line"))
.save();
}
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
index 7f4cd20fcb8..8c0b8822e7f 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
@@ -137,6 +137,7 @@ import org.sonar.server.plugins.ServerExtensionInstaller;
import org.sonar.server.plugins.privileged.PrivilegedPluginsBootstraper;
import org.sonar.server.plugins.privileged.PrivilegedPluginsStopper;
import org.sonar.server.property.InternalPropertiesImpl;
+import org.sonar.server.qualitygate.QualityGateModule;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.rule.CommonRuleDefinitionsImpl;
import org.sonar.server.rule.DefaultRuleFinder;
@@ -430,6 +431,8 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
// webhooks
WebhookModule.class,
+ QualityGateModule.class,
+
// cleaning
CeCleaningModule.class);
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
index 21c15533399..9c12b9f6101 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
@@ -91,7 +91,8 @@ public class ComputeEngineContainerImplTest {
assertThat(picoContainer.getComponentAdapters())
.hasSize(
CONTAINER_ITSELF
- + 77 // level 4
+ + 78 // level 4
+ + 21 // content of QualityGateModule
+ 6 // content of CeConfigurationModule
+ 4 // content of CeQueueModule
+ 4 // content of CeHttpModule
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java
index 25540e22faf..75d23fd49e5 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentDto.java
@@ -29,9 +29,11 @@ import javax.annotation.Nullable;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.sonar.api.resources.Scopes;
+import org.sonar.db.WildcardPosition;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.String.format;
+import static org.sonar.db.DaoDatabaseUtils.buildLikeValue;
import static org.sonar.db.component.ComponentValidator.checkComponentKey;
import static org.sonar.db.component.ComponentValidator.checkComponentName;
import static org.sonar.db.component.DbTagsReader.readDbTags;
@@ -157,6 +159,10 @@ public class ComponentDto {
return parent.getUuidPath() + parent.uuid() + UUID_PATH_SEPARATOR;
}
+ public String getUuidPathLikeIncludingSelf() {
+ return buildLikeValue(formatUuidPathFromParent(this), WildcardPosition.AFTER);
+ }
+
public Long getId() {
return id;
}
@@ -196,7 +202,7 @@ public class ComponentDto {
/**
* List of ancestor UUIDs, ordered by depth in tree.
*/
- List<String> getUuidPathAsList() {
+ public List<String> getUuidPathAsList() {
return UUID_PATH_SPLITTER.splitToList(uuidPath);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
index 2a20cc79dc8..5570e5c8494 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueDao.java
@@ -107,6 +107,10 @@ public class IssueDao implements Dao {
return executeLargeInputs(componentUuids, mapper(dbSession)::selectOpenByComponentUuids);
}
+ public Collection<IssueGroupDto> selectIssueGroupsByBaseComponent(DbSession dbSession, ComponentDto baseComponent, long leakPeriodBeginningDate) {
+ return mapper(dbSession).selectIssueGroupsByBaseComponent(baseComponent, leakPeriodBeginningDate);
+ }
+
public void insert(DbSession session, IssueDto dto) {
mapper(session).insert(dto);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueGroupDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueGroupDto.java
new file mode 100644
index 00000000000..0b8e259c271
--- /dev/null
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueGroupDto.java
@@ -0,0 +1,98 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.db.issue;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+public class IssueGroupDto {
+ private int ruleType;
+ private String severity;
+ @Nullable
+ private String resolution;
+ private String status;
+ private double effort;
+ private long count;
+ private boolean inLeak;
+
+ public int getRuleType() {
+ return ruleType;
+ }
+
+ public String getSeverity() {
+ return severity;
+ }
+
+ @CheckForNull
+ public String getResolution() {
+ return resolution;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public double getEffort() {
+ return effort;
+ }
+
+ public long getCount() {
+ return count;
+ }
+
+ public boolean isInLeak() {
+ return inLeak;
+ }
+
+ public IssueGroupDto setRuleType(int ruleType) {
+ this.ruleType = ruleType;
+ return this;
+ }
+
+ public IssueGroupDto setSeverity(String severity) {
+ this.severity = severity;
+ return this;
+ }
+
+ public IssueGroupDto setResolution(@Nullable String resolution) {
+ this.resolution = resolution;
+ return this;
+ }
+
+ public IssueGroupDto setStatus(String status) {
+ this.status = status;
+ return this;
+ }
+
+ public IssueGroupDto setEffort(double effort) {
+ this.effort = effort;
+ return this;
+ }
+
+ public IssueGroupDto setCount(long count) {
+ this.count = count;
+ return this;
+ }
+
+ public IssueGroupDto setInLeak(boolean inLeak) {
+ this.inLeak = inLeak;
+ return this;
+ }
+}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
index ed0de50d912..9e6956976dd 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java
@@ -19,10 +19,12 @@
*/
package org.sonar.db.issue;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.session.ResultHandler;
+import org.sonar.db.component.ComponentDto;
public interface IssueMapper {
@@ -46,4 +48,8 @@ public interface IssueMapper {
@Param("projectUuid") String projectUuid,
@Param("likeModuleUuidPath") String likeModuleUuidPath,
ResultHandler<IssueDto> handler);
+
+ Collection<IssueGroupDto> selectIssueGroupsByBaseComponent(
+ @Param("baseComponent") ComponentDto baseComponent,
+ @Param("leakPeriodBeginningDate") long leakPeriodBeginningDate);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureDao.java
index 17f3878cca9..14e8636561c 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/measure/LiveMeasureDao.java
@@ -42,7 +42,7 @@ public class LiveMeasureDao implements Dao {
this.system2 = system2;
}
- public List<LiveMeasureDto> selectByComponentUuids(DbSession dbSession, Collection<String> largeComponentUuids, Collection<Integer> metricIds) {
+ public List<LiveMeasureDto> selectByComponentUuidsAndMetricIds(DbSession dbSession, Collection<String> largeComponentUuids, Collection<Integer> metricIds) {
if (largeComponentUuids.isEmpty() || metricIds.isEmpty()) {
return Collections.emptyList();
}
@@ -90,6 +90,9 @@ public class LiveMeasureDao implements Dao {
}
}
+ /**
+ * Delete the rows that do NOT have the specified marker
+ */
public void deleteByProjectUuidExcludingMarker(DbSession dbSession, String projectUuid, String marker) {
mapper(dbSession).deleteByProjectUuidExcludingMarker(projectUuid, marker);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/metric/MetricDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/metric/MetricDao.java
index 160f43505a0..0f27ac2bae9 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/metric/MetricDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/metric/MetricDao.java
@@ -47,7 +47,7 @@ public class MetricDao implements Dao {
return mapper(session).selectByKey(key);
}
- public List<MetricDto> selectByKeys(final DbSession session, List<String> keys) {
+ public List<MetricDto> selectByKeys(final DbSession session, Collection<String> keys) {
return executeLargeInputs(keys, mapper(session)::selectByKeys);
}
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDeliveryDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDeliveryDto.java
index b414ecc7e18..91d076cb5dc 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDeliveryDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/webhook/WebhookDeliveryDto.java
@@ -39,12 +39,11 @@ public class WebhookDeliveryDto extends WebhookDeliveryLiteDto<WebhookDeliveryDt
return this;
}
- @CheckForNull
public String getPayload() {
return payload;
}
- public WebhookDeliveryDto setPayload(@Nullable String s) {
+ public WebhookDeliveryDto setPayload(String s) {
this.payload = s;
return this;
}
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
index 545963ea5e5..a077a1e17ca 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
@@ -246,5 +246,41 @@
p.module_uuid_path like #{likeModuleUuidPath, jdbcType=VARCHAR} escape '/' and
i.status &lt;&gt; 'CLOSED'
</select>
+
+ <select id="selectIssueGroupsByBaseComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map">
+ select i.issue_type as ruleType, i.severity as severity, i.resolution as resolution, i.status as status, sum(i.effort) as effort, count(i.issue_type) as "count", (i.issue_creation_date &gt;= #{leakPeriodBeginningDate,jdbcType=BIGINT}) as inLeak
+ from issues i
+ inner join projects p on p.uuid = i.component_uuid and p.project_uuid = i.project_uuid
+ where i.status !='CLOSED'
+ and i.project_uuid = #{baseComponent.projectUuid,jdbcType=VARCHAR}
+ and (p.uuid_path like #{baseComponent.uuidPathLikeIncludingSelf,jdbcType=VARCHAR} escape '/' or p.uuid = #{baseComponent.uuid,jdbcType=VARCHAR})
+ group by i.issue_type, i.severity, i.resolution, i.status, inLeak
+ </select>
+
+ <select id="selectIssueGroupsByBaseComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map" databaseId="oracle">
+ select i2.issue_type as ruleType, i2.severity as severity, i2.resolution as resolution, i2.status as status, sum(i2.effort) as effort, count(i2.issue_type) as "count", i2.inLeak as inLeak
+ from (
+ select i.issue_type, i.severity, i.resolution, i.status, i.effort, case when i.issue_creation_date &gt; #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak
+ from issues i
+ inner join projects p on p.uuid = i.component_uuid and p.project_uuid = i.project_uuid
+ where i.status !='CLOSED'
+ and i.project_uuid = #{baseComponent.projectUuid,jdbcType=VARCHAR}
+ and (p.uuid_path like #{baseComponent.uuidPathLikeIncludingSelf,jdbcType=VARCHAR} escape '/' or p.uuid = #{baseComponent.uuid,jdbcType=VARCHAR})
+ ) i2
+ group by i2.issue_type, i2.severity, i2.resolution, i2.status, i2.inLeak
+ </select>
+
+ <select id="selectIssueGroupsByBaseComponent" resultType="org.sonar.db.issue.IssueGroupDto" parameterType="map" databaseId="mssql">
+ select i2.issue_type as ruleType, i2.severity as severity, i2.resolution as resolution, i2.status as status, sum(i2.effort) as effort, count(i2.issue_type) as "count", i2.inLeak as inLeak
+ from (
+ select i.issue_type, i.severity, i.resolution, i.status, i.effort, case when i.issue_creation_date &gt; #{leakPeriodBeginningDate,jdbcType=BIGINT} then 1 else 0 end as inLeak
+ from issues i
+ inner join projects p on p.uuid = i.component_uuid and p.project_uuid = i.project_uuid
+ where i.status !='CLOSED'
+ and i.project_uuid = #{baseComponent.projectUuid,jdbcType=VARCHAR}
+ and (p.uuid_path like #{baseComponent.uuidPathLikeIncludingSelf,jdbcType=VARCHAR} escape '/' or p.uuid = #{baseComponent.uuid,jdbcType=VARCHAR})
+ ) i2
+ group by i2.issue_type, i2.severity, i2.resolution, i2.status, i2.inLeak
+ </select>
</mapper>
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java
index 92a9ef6a664..709308d9ce6 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentDtoTest.java
@@ -22,9 +22,10 @@ package org.sonar.db.component;
import org.junit.Test;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.resources.Scopes;
-import org.sonar.db.organization.OrganizationTesting;
+import org.sonar.db.organization.OrganizationDto;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
public class ComponentDtoTest {
@@ -89,13 +90,27 @@ public class ComponentDtoTest {
}
@Test
- public void test_formatUuidPathFromParent() {
- ComponentDto parent = ComponentTesting.newPrivateProjectDto(OrganizationTesting.newOrganizationDto(), "123").setUuidPath(ComponentDto.UUID_PATH_OF_ROOT);
+ public void formatUuidPathFromParent() {
+ ComponentDto parent = ComponentTesting.newPrivateProjectDto(newOrganizationDto(), "123").setUuidPath(ComponentDto.UUID_PATH_OF_ROOT);
assertThat(ComponentDto.formatUuidPathFromParent(parent)).isEqualTo(".123.");
}
@Test
- public void test_Name() {
+ public void getUuidPathLikeIncludingSelf() {
+ OrganizationDto organizationDto = newOrganizationDto();
+
+ ComponentDto project = ComponentTesting.newPrivateProjectDto(organizationDto).setUuidPath(ComponentDto.UUID_PATH_OF_ROOT);
+ assertThat(project.getUuidPathLikeIncludingSelf()).isEqualTo("." + project.uuid() + ".%");
+
+ ComponentDto module = ComponentTesting.newModuleDto(project);
+ assertThat(module.getUuidPathLikeIncludingSelf()).isEqualTo("." + project.uuid() + "." + module.uuid() + ".%");
+
+ ComponentDto file = ComponentTesting.newFileDto(module);
+ assertThat(file.getUuidPathLikeIncludingSelf()).isEqualTo("." + project.uuid() + "." + module.uuid() + "." + file.uuid() + ".%");
+ }
+
+ @Test
+ public void getUuidPathAsList() {
ComponentDto root = new ComponentDto().setUuidPath(ComponentDto.UUID_PATH_OF_ROOT);
assertThat(root.getUuidPathAsList()).isEmpty();
@@ -104,7 +119,7 @@ public class ComponentDtoTest {
}
@Test
- public void test_getKey_and_getBranch() {
+ public void getKey_and_getBranch() {
ComponentDto underTest = new ComponentDto().setDbKey("my_key:BRANCH:my_branch");
assertThat(underTest.getKey()).isEqualTo("my_key");
assertThat(underTest.getBranch()).isEqualTo("my_branch");
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java
index 1b948c72a65..c32c54e366a 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueDaoTest.java
@@ -21,6 +21,7 @@ package org.sonar.db.issue;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.ibatis.session.ResultContext;
@@ -30,6 +31,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.issue.Issue;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.db.RowNotFoundException;
@@ -234,6 +236,59 @@ public class IssueDaoTest {
assertThat(fp.getIssueCreationDate()).isNotNull();
}
+ @Test
+ public void test_selectGroupsOfComponentTreeOnLeak_on_component_without_issues() {
+ ComponentDto project = db.components().insertPublicProject();
+ ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+
+ Collection<IssueGroupDto> groups = underTest.selectIssueGroupsByBaseComponent(db.getSession(), file, 1_000L);
+
+ assertThat(groups).isEmpty();
+ }
+
+ @Test
+ public void selectGroupsOfComponentTreeOnLeak_on_file() {
+ ComponentDto project = db.components().insertPublicProject();
+ ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
+ RuleDefinitionDto rule = db.rules().insert();
+ IssueDto fpBug = db.issues().insert(rule, project, file,
+ i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE").setSeverity("MAJOR").setType(RuleType.BUG).setIssueCreationTime(1_500L));
+ IssueDto criticalBug1 = db.issues().insert(rule, project, file,
+ i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_600L));
+ IssueDto criticalBug2 = db.issues().insert(rule, project, file,
+ i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L));
+ // closed issues are ignored
+ IssueDto closed = db.issues().insert(rule, project, file,
+ i -> i.setStatus("CLOSED").setResolution("REMOVED").setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L));
+
+ Collection<IssueGroupDto> result = underTest.selectIssueGroupsByBaseComponent(db.getSession(), file, 1_000L);
+
+ assertThat(result.stream().mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
+
+ assertThat(result.stream().filter(g -> g.getRuleType()==RuleType.BUG.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
+ assertThat(result.stream().filter(g -> g.getRuleType()==RuleType.CODE_SMELL.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(0);
+ assertThat(result.stream().filter(g -> g.getRuleType()==RuleType.VULNERABILITY.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(0);
+
+ assertThat(result.stream().filter(g -> g.getSeverity().equals("CRITICAL")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2);
+ assertThat(result.stream().filter(g -> g.getSeverity().equals("MAJOR")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(1);
+ assertThat(result.stream().filter(g -> g.getSeverity().equals("MINOR")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(0);
+
+ assertThat(result.stream().filter(g -> g.getStatus().equals("OPEN")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2);
+ assertThat(result.stream().filter(g -> g.getStatus().equals("RESOLVED")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(1);
+ assertThat(result.stream().filter(g -> g.getStatus().equals("CLOSED")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(0);
+
+ assertThat(result.stream().filter(g -> "FALSE-POSITIVE" .equals(g.getResolution())).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(1);
+ assertThat(result.stream().filter(g -> g.getResolution()== null).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2);
+
+ assertThat(result.stream().filter(g -> g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
+ assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(0);
+
+ // test leak
+ result = underTest.selectIssueGroupsByBaseComponent(db.getSession(), file, 999_999_999L);
+ assertThat(result.stream().filter(g -> g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(0);
+ assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
+ }
+
private static IssueDto newIssueDto(String key) {
IssueDto dto = new IssueDto();
dto.setComponent(new ComponentDto().setDbKey("struts:Action").setId(123L).setUuid("component-uuid"));
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/measure/LiveMeasureDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/LiveMeasureDaoTest.java
index de232ef5a43..a37fc2ce3b3 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/measure/LiveMeasureDaoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/measure/LiveMeasureDaoTest.java
@@ -19,59 +19,115 @@
*/
package org.sonar.db.measure;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.List;
-import org.assertj.core.groups.Tuple;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
import org.sonar.db.metric.MetricDto;
import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
import static org.sonar.db.measure.MeasureTesting.newLiveMeasure;
public class LiveMeasureDaoTest {
- private static final int A_METRIC_ID = 42;
-
@Rule
public DbTester db = DbTester.create(System2.INSTANCE);
private LiveMeasureDao underTest = db.getDbClient().liveMeasureDao();
+ private MetricDto metric;
+
+ @Before
+ public void setUp() throws Exception {
+ metric = db.measures().insertMetric();
+ }
@Test
- public void test_selectByComponentUuids() {
- LiveMeasureDto measure1 = newLiveMeasure().setMetricId(A_METRIC_ID);
- LiveMeasureDto measure2 = newLiveMeasure().setMetricId(A_METRIC_ID);
+ public void test_selectByComponentUuidsAndMetricIds() {
+ LiveMeasureDto measure1 = newLiveMeasure().setMetricId(metric.getId());
+ LiveMeasureDto measure2 = newLiveMeasure().setMetricId(metric.getId());
underTest.insert(db.getSession(), measure1);
underTest.insert(db.getSession(), measure2);
- List<LiveMeasureDto> selected = underTest.selectByComponentUuids(db.getSession(), asList(measure1.getComponentUuid(), measure2.getComponentUuid()), singletonList(A_METRIC_ID));
+ List<LiveMeasureDto> selected = underTest.selectByComponentUuidsAndMetricIds(db.getSession(),
+ asList(measure1.getComponentUuid(), measure2.getComponentUuid()), singletonList(metric.getId()));
assertThat(selected)
.extracting(LiveMeasureDto::getComponentUuid, LiveMeasureDto::getProjectUuid, LiveMeasureDto::getMetricId, LiveMeasureDto::getValue, LiveMeasureDto::getDataAsString)
.containsExactlyInAnyOrder(
- Tuple.tuple(measure1.getComponentUuid(), measure1.getProjectUuid(), measure1.getMetricId(), measure1.getValue(), measure1.getDataAsString()),
- Tuple.tuple(measure2.getComponentUuid(), measure2.getProjectUuid(), measure2.getMetricId(), measure2.getValue(), measure2.getDataAsString()));
+ tuple(measure1.getComponentUuid(), measure1.getProjectUuid(), measure1.getMetricId(), measure1.getValue(), measure1.getDataAsString()),
+ tuple(measure2.getComponentUuid(), measure2.getProjectUuid(), measure2.getMetricId(), measure2.getValue(), measure2.getDataAsString()));
+
+ assertThat(underTest.selectByComponentUuidsAndMetricIds(db.getSession(), emptyList(), singletonList(metric.getId()))).isEmpty();
+ assertThat(underTest.selectByComponentUuidsAndMetricIds(db.getSession(), singletonList(measure1.getComponentUuid()), emptyList())).isEmpty();
}
@Test
- public void selectByComponentUuids_returns_empty_list_if_metric_does_not_match() {
- LiveMeasureDto measure = newLiveMeasure().setMetricId(10);
+ public void selectByComponentUuidsAndMetricIds_returns_empty_list_if_metric_does_not_match() {
+ LiveMeasureDto measure = newLiveMeasure().setMetricId(metric.getId());
underTest.insert(db.getSession(), measure);
- List<LiveMeasureDto> selected = underTest.selectByComponentUuids(db.getSession(), singletonList(measure.getComponentUuid()), singletonList(222));
+ int otherMetricId = metric.getId() + 100;
+ List<LiveMeasureDto> selected = underTest.selectByComponentUuidsAndMetricIds(db.getSession(), singletonList(measure.getComponentUuid()), singletonList(otherMetricId));
assertThat(selected).isEmpty();
}
@Test
- public void selectByComponentUuids_returns_empty_list_if_component_does_not_match() {
+ public void selectByComponentUuidsAndMetricIds_returns_empty_list_if_component_does_not_match() {
LiveMeasureDto measure = newLiveMeasure();
underTest.insert(db.getSession(), measure);
- List<LiveMeasureDto> selected = underTest.selectByComponentUuids(db.getSession(), singletonList("_missing_"), singletonList(measure.getMetricId()));
+ List<LiveMeasureDto> selected = underTest.selectByComponentUuidsAndMetricIds(db.getSession(), singletonList("_missing_"), singletonList(measure.getMetricId()));
+
+ assertThat(selected).isEmpty();
+ }
+
+ @Test
+ public void test_selectByComponentUuidsAndMetricKeys() {
+ LiveMeasureDto measure1 = newLiveMeasure().setMetricId(metric.getId());
+ LiveMeasureDto measure2 = newLiveMeasure().setMetricId(metric.getId());
+ underTest.insert(db.getSession(), measure1);
+ underTest.insert(db.getSession(), measure2);
+
+ List<LiveMeasureDto> selected = underTest.selectByComponentUuidsAndMetricKeys(db.getSession(), asList(measure1.getComponentUuid(), measure2.getComponentUuid()),
+ singletonList(metric.getKey()));
+ assertThat(selected)
+ .extracting(LiveMeasureDto::getComponentUuid, LiveMeasureDto::getProjectUuid, LiveMeasureDto::getMetricId, LiveMeasureDto::getValue, LiveMeasureDto::getDataAsString)
+ .containsExactlyInAnyOrder(
+ tuple(measure1.getComponentUuid(), measure1.getProjectUuid(), measure1.getMetricId(), measure1.getValue(), measure1.getDataAsString()),
+ tuple(measure2.getComponentUuid(), measure2.getProjectUuid(), measure2.getMetricId(), measure2.getValue(), measure2.getDataAsString()));
+
+ assertThat(underTest.selectByComponentUuidsAndMetricKeys(db.getSession(), emptyList(), singletonList(metric.getKey()))).isEmpty();
+ assertThat(underTest.selectByComponentUuidsAndMetricKeys(db.getSession(), singletonList(measure1.getComponentUuid()), emptyList())).isEmpty();
+ }
+
+ @Test
+ public void selectByComponentUuidsAndMetricKeys_returns_empty_list_if_metric_does_not_match() {
+ LiveMeasureDto measure = newLiveMeasure().setMetricId(metric.getId());
+ underTest.insert(db.getSession(), measure);
+
+ List<LiveMeasureDto> selected = underTest.selectByComponentUuidsAndMetricKeys(db.getSession(), singletonList(measure.getComponentUuid()), singletonList("_other_"));
+
+ assertThat(selected).isEmpty();
+ }
+
+ @Test
+ public void selectByComponentUuidsAndMetricKeys_returns_empty_list_if_component_does_not_match() {
+ LiveMeasureDto measure = newLiveMeasure().setMetricId(metric.getId());
+ underTest.insert(db.getSession(), measure);
+
+ List<LiveMeasureDto> selected = underTest.selectByComponentUuidsAndMetricKeys(db.getSession(), singletonList("_missing_"), singletonList(metric.getKey()));
assertThat(selected).isEmpty();
}
@@ -97,6 +153,68 @@ public class LiveMeasureDaoTest {
}
@Test
+ public void selectTreeByQuery() {
+ List<LiveMeasureDto> results = new ArrayList<>();
+ MetricDto metric = db.measures().insertMetric();
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto file = db.components().insertComponent(newFileDto(project));
+ underTest.insert(db.getSession(), newLiveMeasure(file, metric).setValue(3.14));
+
+ underTest.selectTreeByQuery(db.getSession(), project,
+ MeasureTreeQuery.builder()
+ .setMetricIds(singleton(metric.getId()))
+ .setStrategy(MeasureTreeQuery.Strategy.LEAVES).build(),
+ context -> results.add(context.getResultObject()));
+
+ assertThat(results).hasSize(1);
+ LiveMeasureDto result = results.get(0);
+ assertThat(result.getComponentUuid()).isEqualTo(file.uuid());
+ assertThat(result.getMetricId()).isEqualTo(metric.getId());
+ assertThat(result.getValue()).isEqualTo(3.14);
+ }
+
+ @Test
+ public void selectTreeByQuery_with_empty_results() {
+ List<LiveMeasureDto> results = new ArrayList<>();
+ underTest.selectTreeByQuery(db.getSession(), newPrivateProjectDto(db.getDefaultOrganization()),
+ MeasureTreeQuery.builder().setStrategy(MeasureTreeQuery.Strategy.LEAVES).build(),
+ context -> results.add(context.getResultObject()));
+
+ assertThat(results).isEmpty();
+ }
+
+ @Test
+ public void selectMeasure_map_fields() {
+ MetricDto metric = db.measures().insertMetric();
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto file = db.components().insertComponent(newFileDto(project));
+ underTest.insert(db.getSession(), newLiveMeasure(file, metric).setValue(3.14).setVariation(0.1).setData("text_value"));
+
+ LiveMeasureDto result = underTest.selectMeasure(db.getSession(), file.uuid(), metric.getKey()).orElseThrow(() -> new IllegalArgumentException("Measure not found"));
+
+ assertThat(result).as("Fail to map fields of %s", result.toString()).extracting(
+ LiveMeasureDto::getProjectUuid, LiveMeasureDto::getComponentUuid, LiveMeasureDto::getMetricId, LiveMeasureDto::getValue, LiveMeasureDto::getVariation,
+ LiveMeasureDto::getDataAsString, LiveMeasureDto::getTextValue)
+ .contains(project.uuid(), file.uuid(), metric.getId(), 3.14, 0.1, "text_value", "text_value");
+ }
+
+ @Test
+ public void insert_data() {
+ byte[] data = "text_value".getBytes(StandardCharsets.UTF_8);
+ MetricDto metric = db.measures().insertMetric();
+ ComponentDto project = db.components().insertPrivateProject();
+ ComponentDto file = db.components().insertComponent(newFileDto(project));
+ LiveMeasureDto measure = newLiveMeasure(file, metric).setData(data);
+
+ underTest.insert(db.getSession(), measure);
+
+
+ LiveMeasureDto result = underTest.selectMeasure(db.getSession(), file.uuid(), metric.getKey()).orElseThrow(() -> new IllegalArgumentException("Measure not found"));
+ assertThat(new String(result.getData(), StandardCharsets.UTF_8)).isEqualTo("text_value");
+ assertThat(result.getDataAsString()).isEqualTo("text_value");
+ }
+
+ @Test
public void test_insertOrUpdate() {
// insert
LiveMeasureDto dto = newLiveMeasure();
@@ -139,7 +257,7 @@ public class LiveMeasureDaoTest {
}
private void verifyPersisted(LiveMeasureDto dto) {
- List<LiveMeasureDto> selected = underTest.selectByComponentUuids(db.getSession(), singletonList(dto.getComponentUuid()), singletonList(dto.getMetricId()));
+ List<LiveMeasureDto> selected = underTest.selectByComponentUuidsAndMetricIds(db.getSession(), singletonList(dto.getComponentUuid()), singletonList(dto.getMetricId()));
assertThat(selected).hasSize(1);
assertThat(selected.get(0)).isEqualToComparingFieldByField(dto);
}
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java
index 49012759ba7..7a21a6b0850 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/purge/PurgeDaoTest.java
@@ -412,8 +412,8 @@ public class PurgeDaoTest {
underTest.deleteProject(dbSession, project1.uuid());
- assertThat(dbClient.liveMeasureDao().selectByComponentUuids(dbSession, asList(project1.uuid(), module1.uuid()), asList(metric.getId()))).isEmpty();
- assertThat(dbClient.liveMeasureDao().selectByComponentUuids(dbSession, asList(project2.uuid(), module2.uuid()), asList(metric.getId()))).hasSize(2);
+ assertThat(dbClient.liveMeasureDao().selectByComponentUuidsAndMetricIds(dbSession, asList(project1.uuid(), module1.uuid()), asList(metric.getId()))).isEmpty();
+ assertThat(dbClient.liveMeasureDao().selectByComponentUuidsAndMetricIds(dbSession, asList(project2.uuid(), module2.uuid()), asList(metric.getId()))).hasSize(2);
}
private void verifyNoEffect(ComponentDto firstRoot, ComponentDto... otherRoots) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexer.java
index 71f40c3618c..86d055ccbba 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/index/ComponentIndexer.java
@@ -84,9 +84,10 @@ public class ComponentIndexer implements ProjectIndexer, NeedAuthorizationIndexe
@Override
public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, Cause cause) {
switch (cause) {
+ case MEASURE_CHANGE:
case PROJECT_TAGS_UPDATE:
case PERMISSION_CHANGE:
- // tags and permissions are not part of type components/component
+ // measures, tags and permissions are not part of type components/component
return emptyList();
case PROJECT_CREATION:
diff --git a/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java b/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
index f4fa6bc5ab2..562ffd31f7a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/component/ws/AppAction.java
@@ -190,7 +190,7 @@ public class AppAction implements ComponentsWsAction {
List<MetricDto> metrics = dbClient.metricDao().selectByKeys(dbSession, METRIC_KEYS);
Map<Integer, MetricDto> metricsById = Maps.uniqueIndex(metrics, MetricDto::getId);
List<LiveMeasureDto> measures = dbClient.liveMeasureDao()
- .selectByComponentUuids(dbSession, Collections.singletonList(component.uuid()), metricsById.keySet());
+ .selectByComponentUuidsAndMetricIds(dbSession, Collections.singletonList(component.uuid()), metricsById.keySet());
return Maps.uniqueIndex(measures, m -> metricsById.get(m.getMetricId()).getKey());
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java
index 7e2c720470d..8af751c52bc 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/PostProjectAnalysisTasksExecutor.java
@@ -163,7 +163,7 @@ public class PostProjectAnalysisTasksExecutor implements ComputationStepExecutor
@CheckForNull
private QualityGateImpl createQualityGate() {
- com.google.common.base.Optional<org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGate> qualityGateOptional = this.qualityGateHolder.getQualityGate();
+ Optional<org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGate> qualityGateOptional = this.qualityGateHolder.getQualityGate();
if (qualityGateOptional.isPresent()) {
org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGate qualityGate = qualityGateOptional.get();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/formula/counter/RatingValue.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/formula/counter/RatingValue.java
index d8e175cea19..03077e12a64 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/formula/counter/RatingValue.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/formula/counter/RatingValue.java
@@ -20,7 +20,7 @@
package org.sonar.server.computation.task.projectanalysis.formula.counter;
import javax.annotation.Nullable;
-import org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
/**
* Convenience class wrapping a rating to compute the value and know it is has ever been set.
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolder.java
index 722fd38894c..06f366cbb04 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolder.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolder.java
@@ -19,14 +19,12 @@
*/
package org.sonar.server.computation.task.projectanalysis.qualitygate;
-import org.sonar.server.computation.task.projectanalysis.component.Component;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
public interface MutableQualityGateHolder extends QualityGateHolder {
/**
* Sets the quality gate.
- * Settings a quality gate more than once is not allowed and it can never be set to {@code null}.
- *
- * @param qualityGate a {@link Component}, can not be {@code null}
+ * Setting a quality gate more than once is not allowed and it can never be set to {@code null}.
*
* @throws NullPointerException if {@code qualityGate} is {@code null}
* @throws IllegalStateException if the holder has already been initialized
@@ -34,9 +32,11 @@ public interface MutableQualityGateHolder extends QualityGateHolder {
void setQualityGate(QualityGate qualityGate);
/**
- * Sets that there is no quality gate for the project of the currently processed {@link ReportQueue.Item}.
+ * Sets the evaluation of quality gate.
+ * Setting more than once is not allowed and it can never be set to {@code null}.
*
+ * @throws NullPointerException if {@code qualityGate} is {@code null}
* @throws IllegalStateException if the holder has already been initialized
*/
- void setNoQualityGate();
+ void setEvaluation(EvaluatedQualityGate evaluation);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolder.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolder.java
index 86f695c865f..ab4087034d4 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolder.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolder.java
@@ -19,7 +19,8 @@
*/
package org.sonar.server.computation.task.projectanalysis.qualitygate;
-import com.google.common.base.Optional;
+import java.util.Optional;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
public interface QualityGateHolder {
/**
@@ -28,4 +29,11 @@ public interface QualityGateHolder {
* @throws IllegalStateException if the holder has not been initialized (ie. we don't know yet what is the QualityGate)
*/
Optional<QualityGate> getQualityGate();
+
+ /**
+ * Evaluation of quality gate, including status and condition details.
+ *
+ * @throws IllegalStateException if the holder has not been initialized (ie. gate has not been evaluated yet)
+ */
+ Optional<EvaluatedQualityGate> getEvaluation();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImpl.java
index cbba9334d4b..ca83ac5eff5 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImpl.java
@@ -19,44 +19,43 @@
*/
package org.sonar.server.computation.task.projectanalysis.qualitygate;
-import com.google.common.base.Optional;
-import javax.annotation.CheckForNull;
+import java.util.Optional;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
-import static com.google.common.base.Optional.absent;
-import static com.google.common.base.Optional.of;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
public class QualityGateHolderImpl implements MutableQualityGateHolder {
- private boolean initialized = false;
- @CheckForNull
- private Optional<QualityGate> qualityGate;
+ private QualityGate qualityGate;
+ private EvaluatedQualityGate evaluation;
@Override
- public void setQualityGate(QualityGate qualityGate) {
+ public void setQualityGate(QualityGate g) {
// fail fast
- requireNonNull(qualityGate);
- checkNotInitialized();
+ requireNonNull(g);
+ checkState(qualityGate == null, "QualityGateHolder can be initialized only once");
- this.initialized = true;
- this.qualityGate = of(qualityGate);
+ this.qualityGate = g;
}
@Override
- public void setNoQualityGate() {
- checkNotInitialized();
-
- this.initialized = true;
- this.qualityGate = absent();
+ public Optional<QualityGate> getQualityGate() {
+ checkState(qualityGate != null, "QualityGate has not been set yet");
+ return Optional.of(qualityGate);
}
- private void checkNotInitialized() {
- checkState(!initialized, "QualityGateHolder can be initialized only once");
+ @Override
+ public void setEvaluation(EvaluatedQualityGate g) {
+ // fail fast
+ requireNonNull(g);
+ checkState(evaluation == null, "QualityGateHolder evaluation can be initialized only once");
+
+ this.evaluation = g;
}
@Override
- public Optional<QualityGate> getQualityGate() {
- checkState(initialized, "QualityGate has not been set yet");
- return qualityGate;
+ public Optional<EvaluatedQualityGate> getEvaluation() {
+ checkState(evaluation != null, "Evaluation of QualityGate has not been set yet");
+ return Optional.of(evaluation);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateService.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateService.java
index c8fb00d37b0..580c4d58c27 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateService.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateService.java
@@ -30,8 +30,9 @@ public interface QualityGateService {
Optional<QualityGate> findById(long id);
/**
- * Retrieve the {@link QualityGate} from the database with the specified uuid, if it exists.
+ * Retrieve the {@link QualityGate} from the database with the specified uuid.
+ * @throws IllegalStateException if database is corrupted and default gate can't be found.
*/
- Optional<QualityGate> findDefaultQualityGate(Organization organizationDto);
+ QualityGate findDefaultQualityGate(Organization organizationDto);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateServiceImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateServiceImpl.java
index bb34218455e..bb4a3e0a36a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateServiceImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateServiceImpl.java
@@ -57,13 +57,13 @@ public class QualityGateServiceImpl implements QualityGateService {
}
@Override
- public Optional<QualityGate> findDefaultQualityGate(Organization organization) {
+ public QualityGate findDefaultQualityGate(Organization organization) {
try (DbSession dbSession = dbClient.openSession(false)) {
QualityGateDto qualityGateDto = dbClient.qualityGateDao().selectByOrganizationAndUuid(dbSession, organization.toDto(), organization.getDefaultQualityGateUuid());
if (qualityGateDto == null) {
- return Optional.empty();
+ throw new IllegalStateException("The default Quality gate is missing on organization " + organization.getKey());
}
- return Optional.of(toQualityGate(dbSession, qualityGateDto));
+ return toQualityGate(dbSession, qualityGateDto);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingGrid.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/DebtRatingGrid.java
index 0b33207cd0e..9114db76556 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingGrid.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/DebtRatingGrid.java
@@ -21,20 +21,34 @@ package org.sonar.server.computation.task.projectanalysis.qualitymodel;
import com.google.common.annotations.VisibleForTesting;
import java.util.Arrays;
+import org.sonar.api.config.Configuration;
import org.sonar.api.utils.MessageException;
-import static java.lang.String.format;
-import static java.util.Arrays.stream;
+import static org.sonar.api.CoreProperties.RATING_GRID;
+import static org.sonar.api.CoreProperties.RATING_GRID_DEF_VALUES;
-public class RatingGrid {
+public class DebtRatingGrid {
private final double[] gridValues;
- RatingGrid(double[] gridValues) {
+ public DebtRatingGrid(double[] gridValues) {
this.gridValues = Arrays.copyOf(gridValues, gridValues.length);
}
- Rating getRatingForDensity(double density) {
+ public DebtRatingGrid(Configuration config) {
+ try {
+ String[] grades = config.getStringArray(RATING_GRID);
+ gridValues = new double[4];
+ for (int i = 0; i < 4; i++) {
+ gridValues[i] = Double.parseDouble(grades[i]);
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException("The rating grid is incorrect. Expected something similar to '"
+ + RATING_GRID_DEF_VALUES + "' and got '" + config.get(RATING_GRID).orElse("") + "'", e);
+ }
+ }
+
+ public Rating getRatingForDensity(double density) {
for (Rating rating : Rating.values()) {
double lowerBound = getGradeLowerBound(rating);
if (density >= lowerBound) {
@@ -44,7 +58,7 @@ public class RatingGrid {
throw MessageException.of("The rating density value should be between 0 and " + Double.MAX_VALUE + " and got " + density);
}
- double getGradeLowerBound(Rating rating) {
+ public double getGradeLowerBound(Rating rating) {
if (rating.getIndex() > 1) {
return gridValues[rating.getIndex() - 2];
}
@@ -56,30 +70,4 @@ public class RatingGrid {
return gridValues;
}
- public enum Rating {
- E(5),
- D(4),
- C(3),
- B(2),
- A(1);
-
- private final int index;
-
- Rating(int index) {
- this.index = index;
- }
-
- public int getIndex() {
- return index;
- }
-
- public static Rating valueOf(int index) {
- return stream(Rating.values()).filter(r -> r.getIndex() == index).findFirst().orElseThrow(() -> new IllegalArgumentException(format("Unknown value '%s'", index)));
- }
-
- public static boolean isValidRating(String value) {
- return stream(Rating.values()).anyMatch(r -> r.name().equals(value));
- }
- }
-
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java
index 8670962407c..e707df6d9fc 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitor.java
@@ -29,7 +29,6 @@ import org.sonar.server.computation.task.projectanalysis.measure.Measure;
import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository;
import org.sonar.server.computation.task.projectanalysis.metric.Metric;
import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
-import org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST_KEY;
import static org.sonar.api.measures.CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A_KEY;
@@ -50,7 +49,6 @@ import static org.sonar.server.computation.task.projectanalysis.measure.Measure.
public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<MaintainabilityMeasuresVisitor.Counter> {
private final MeasureRepository measureRepository;
private final RatingSettings ratingSettings;
- private final RatingGrid ratingGrid;
private final Metric nclocMetric;
private final Metric developmentCostMetric;
@@ -64,7 +62,6 @@ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<Main
super(CrawlerDepthLimit.FILE, POST_ORDER, CounterFactory.INSTANCE);
this.measureRepository = measureRepository;
this.ratingSettings = ratingSettings;
- this.ratingGrid = ratingSettings.getRatingGrid();
// Input metrics
this.nclocMetric = metricRepository.getByKey(NCLOC_KEY);
@@ -134,7 +131,7 @@ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<Main
}
private void addMaintainabilityRatingMeasure(Component component, double density) {
- Rating rating = ratingGrid.getRatingForDensity(density);
+ Rating rating = ratingSettings.getDebtRatingGrid().getRatingForDensity(density);
measureRepository.add(component, maintainabilityRatingMetric, newMeasureBuilder().create(rating.getIndex(), rating.name()));
}
@@ -142,7 +139,7 @@ public class MaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<Main
long developmentCostValue = path.current().devCosts;
Optional<Measure> effortMeasure = measureRepository.getRawMeasure(component, maintainabilityRemediationEffortMetric);
long effort = effortMeasure.isPresent() ? effortMeasure.get().getLongValue() : 0L;
- long upperGradeCost = ((Double) (ratingGrid.getGradeLowerBound(Rating.B) * developmentCostValue)).longValue();
+ long upperGradeCost = ((Double) (ratingSettings.getDebtRatingGrid().getGradeLowerBound(Rating.B) * developmentCostValue)).longValue();
long effortToRatingA = upperGradeCost < effort ? (effort - upperGradeCost) : 0L;
measureRepository.add(component, effortToMaintainabilityRatingAMetric, Measure.newMeasureBuilder().create(effortToRatingA));
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java
index 69aa35bf076..ff993ee8932 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java
@@ -42,6 +42,7 @@ import org.sonar.server.computation.task.projectanalysis.scm.ScmInfo;
import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepository;
import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_DEVELOPMENT_COST_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_TECHNICAL_DEBT_KEY;
@@ -64,22 +65,21 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N
private final MeasureRepository measureRepository;
private final PeriodHolder periodHolder;
private final RatingSettings ratingSettings;
- private final RatingGrid ratingGrid;
private final Metric newDebtMetric;
private final Metric nclocDataMetric;
+ private final Metric newDevelopmentCostMetric;
private final Metric newDebtRatioMetric;
private final Metric newMaintainabilityRatingMetric;
public NewMaintainabilityMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, ScmInfoRepository scmInfoRepository,
- PeriodHolder periodHolder, RatingSettings ratingSettings) {
+ PeriodHolder periodHolder, RatingSettings ratingSettings) {
super(CrawlerDepthLimit.FILE, POST_ORDER, CounterFactory.INSTANCE);
this.measureRepository = measureRepository;
this.scmInfoRepository = scmInfoRepository;
this.periodHolder = periodHolder;
this.ratingSettings = ratingSettings;
- this.ratingGrid = ratingSettings.getRatingGrid();
// computed by NewDebtAggregator which is executed by IntegrateIssuesVisitor
this.newDebtMetric = metricRepository.getByKey(NEW_TECHNICAL_DEBT_KEY);
@@ -87,6 +87,7 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N
this.nclocDataMetric = metricRepository.getByKey(NCLOC_DATA_KEY);
// output metrics
+ this.newDevelopmentCostMetric = metricRepository.getByKey(NEW_DEVELOPMENT_COST_KEY);
this.newDebtRatioMetric = metricRepository.getByKey(NEW_SQALE_DEBT_RATIO_KEY);
this.newMaintainabilityRatingMetric = metricRepository.getByKey(NEW_MAINTAINABILITY_RATING_KEY);
}
@@ -121,7 +122,9 @@ public class NewMaintainabilityMeasuresVisitor extends PathAwareVisitorAdapter<N
}
double density = computeDensity(path.current());
double newDebtRatio = 100.0 * density;
- double newMaintainability = ratingGrid.getRatingForDensity(density).getIndex();
+ double newMaintainability = ratingSettings.getDebtRatingGrid().getRatingForDensity(density).getIndex();
+ long newDevelopmentCost = path.current().getDevCost().getValue();
+ measureRepository.add(component, this.newDevelopmentCostMetric, newMeasureBuilder().setVariation(newDevelopmentCost).createNoValue());
measureRepository.add(component, this.newDebtRatioMetric, newMeasureBuilder().setVariation(newDebtRatio).createNoValue());
measureRepository.add(component, this.newMaintainabilityRatingMetric, newMeasureBuilder().setVariation(newMaintainability).createNoValue());
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java
index 34d26986865..b683fab8ceb 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java
@@ -47,12 +47,12 @@ import static org.sonar.api.utils.DateUtils.truncateToSeconds;
import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
import static org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit.LEAVES;
import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.B;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.C;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.D;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.E;
+
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.A;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.B;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.C;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.D;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.E;
/**
* Compute following measures :
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/Rating.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/Rating.java
new file mode 100644
index 00000000000..82e37937c34
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/Rating.java
@@ -0,0 +1,63 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.computation.task.projectanalysis.qualitymodel;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+
+import static java.lang.String.format;
+import static java.util.Arrays.stream;
+import static org.sonar.api.rule.Severity.BLOCKER;
+import static org.sonar.api.rule.Severity.CRITICAL;
+import static org.sonar.api.rule.Severity.INFO;
+import static org.sonar.api.rule.Severity.MAJOR;
+import static org.sonar.api.rule.Severity.MINOR;
+
+public enum Rating {
+ E(5),
+ D(4),
+ C(3),
+ B(2),
+ A(1);
+
+ private final int index;
+
+ Rating(int index) {
+ this.index = index;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public static Rating valueOf(int index) {
+ return stream(Rating.values())
+ .filter(r -> r.getIndex() == index)
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException(format("Unknown value '%s'", index)));
+ }
+
+ public static final Map<String, Rating> RATING_BY_SEVERITY = ImmutableMap.of(
+ BLOCKER, E,
+ CRITICAL, D,
+ MAJOR, C,
+ MINOR, B,
+ INFO, A);
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingSettings.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingSettings.java
index fa8669eddc4..023875ff2b8 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingSettings.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingSettings.java
@@ -24,6 +24,7 @@ import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
+import org.sonar.api.ce.ComputeEngineSide;
import org.sonar.api.config.Configuration;
import org.sonar.api.utils.MessageException;
@@ -33,45 +34,22 @@ import static org.sonar.api.CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS;
import static org.sonar.api.CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY;
import static org.sonar.api.CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY;
import static org.sonar.api.CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_SIZE_METRIC_KEY;
-import static org.sonar.api.CoreProperties.RATING_GRID;
-import static org.sonar.api.CoreProperties.RATING_GRID_DEF_VALUES;
+@ComputeEngineSide
public class RatingSettings {
- private final Configuration config;
+ private final DebtRatingGrid ratingGrid;
+ private final long defaultDevCost;
private final Map<String, LanguageSpecificConfiguration> languageSpecificConfigurationByLanguageKey;
public RatingSettings(Configuration config) {
- this.config = config;
- this.languageSpecificConfigurationByLanguageKey = buildLanguageSpecificConfigurationByLanguageKey(config);
+ ratingGrid = new DebtRatingGrid(config);
+ defaultDevCost = initDefaultDevelopmentCost(config);
+ languageSpecificConfigurationByLanguageKey = initLanguageSpecificConfigurationByLanguageKey(config);
}
- private static Map<String, LanguageSpecificConfiguration> buildLanguageSpecificConfigurationByLanguageKey(Configuration config) {
- ImmutableMap.Builder<String, LanguageSpecificConfiguration> builder = ImmutableMap.builder();
- String[] languageConfigIndexes = config.getStringArray(LANGUAGE_SPECIFIC_PARAMETERS);
- for (String languageConfigIndex : languageConfigIndexes) {
- String languagePropertyKey = LANGUAGE_SPECIFIC_PARAMETERS + "." + languageConfigIndex + "." + LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY;
- String languageKey = config.get(languagePropertyKey)
- .orElseThrow(() -> MessageException.of("Technical debt configuration is corrupted. At least one language specific parameter has no Language key. " +
- "Contact your administrator to update this configuration in the global administration section of SonarQube."));
- builder.put(languageKey, LanguageSpecificConfiguration.create(config, languageConfigIndex));
- }
- return builder.build();
- }
-
- public RatingGrid getRatingGrid() {
- try {
- String[] ratingGrades = config.getStringArray(RATING_GRID);
- double[] grid = new double[4];
- for (int i = 0; i < 4; i++) {
- grid[i] = Double.parseDouble(ratingGrades[i]);
- }
- return new RatingGrid(grid);
- } catch (Exception e) {
- throw new IllegalArgumentException("The rating grid is incorrect. Expected something similar to '"
- + RATING_GRID_DEF_VALUES + "' and got '"
- + config.get(RATING_GRID).get() + "'", e);
- }
+ public DebtRatingGrid getDebtRatingGrid() {
+ return ratingGrid;
}
public long getDevCost(@Nullable String languageKey) {
@@ -86,10 +64,28 @@ public class RatingSettings {
}
}
- return getDefaultDevelopmentCost();
+ return defaultDevCost;
}
- private long getDefaultDevelopmentCost() {
+ @CheckForNull
+ private LanguageSpecificConfiguration getSpecificParametersForLanguage(String languageKey) {
+ return languageSpecificConfigurationByLanguageKey.get(languageKey);
+ }
+
+ private static Map<String, LanguageSpecificConfiguration> initLanguageSpecificConfigurationByLanguageKey(Configuration config) {
+ ImmutableMap.Builder<String, LanguageSpecificConfiguration> builder = ImmutableMap.builder();
+ String[] languageConfigIndexes = config.getStringArray(LANGUAGE_SPECIFIC_PARAMETERS);
+ for (String languageConfigIndex : languageConfigIndexes) {
+ String languagePropertyKey = LANGUAGE_SPECIFIC_PARAMETERS + "." + languageConfigIndex + "." + LANGUAGE_SPECIFIC_PARAMETERS_LANGUAGE_KEY;
+ String languageKey = config.get(languagePropertyKey)
+ .orElseThrow(() -> MessageException.of("Technical debt configuration is corrupted. At least one language specific parameter has no Language key. " +
+ "Contact your administrator to update this configuration in the global administration section of SonarQube."));
+ builder.put(languageKey, LanguageSpecificConfiguration.create(config, languageConfigIndex));
+ }
+ return builder.build();
+ }
+
+ private static long initDefaultDevelopmentCost(Configuration config) {
try {
return Long.parseLong(config.get(DEVELOPMENT_COST).get());
} catch (NumberFormatException e) {
@@ -98,11 +94,6 @@ public class RatingSettings {
}
}
- @CheckForNull
- private LanguageSpecificConfiguration getSpecificParametersForLanguage(String languageKey) {
- return languageSpecificConfigurationByLanguageKey.get(languageKey);
- }
-
@Immutable
private static class LanguageSpecificConfiguration {
private final String language;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitor.java
index 64c5ce3d279..b9d55d835ff 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitor.java
@@ -21,35 +21,23 @@ package org.sonar.server.computation.task.projectanalysis.qualitymodel;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
-import org.sonar.api.ce.measure.Issue;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.server.computation.task.projectanalysis.component.Component;
import org.sonar.server.computation.task.projectanalysis.component.PathAwareVisitorAdapter;
import org.sonar.server.computation.task.projectanalysis.formula.counter.RatingValue;
import org.sonar.server.computation.task.projectanalysis.issue.ComponentIssuesRepository;
-import org.sonar.server.computation.task.projectanalysis.measure.Measure;
import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepository;
import org.sonar.server.computation.task.projectanalysis.metric.Metric;
import org.sonar.server.computation.task.projectanalysis.metric.MetricRepository;
import static org.sonar.api.measures.CoreMetrics.RELIABILITY_RATING_KEY;
import static org.sonar.api.measures.CoreMetrics.SECURITY_RATING_KEY;
-import static org.sonar.api.rule.Severity.BLOCKER;
-import static org.sonar.api.rule.Severity.CRITICAL;
-import static org.sonar.api.rule.Severity.INFO;
-import static org.sonar.api.rule.Severity.MAJOR;
-import static org.sonar.api.rule.Severity.MINOR;
import static org.sonar.api.rules.RuleType.BUG;
import static org.sonar.api.rules.RuleType.VULNERABILITY;
import static org.sonar.server.computation.task.projectanalysis.component.ComponentVisitor.Order.POST_ORDER;
import static org.sonar.server.computation.task.projectanalysis.component.CrawlerDepthLimit.FILE;
import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.B;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.C;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.D;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.E;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.RATING_BY_SEVERITY;
/**
* Compute following measures for projects and descendants:
@@ -58,20 +46,8 @@ import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rat
*/
public class ReliabilityAndSecurityRatingMeasuresVisitor extends PathAwareVisitorAdapter<ReliabilityAndSecurityRatingMeasuresVisitor.Counter> {
- private static final Map<String, Rating> RATING_BY_SEVERITY = ImmutableMap.of(
- BLOCKER, E,
- CRITICAL, D,
- MAJOR, C,
- MINOR, B,
- INFO, A);
-
private final MeasureRepository measureRepository;
private final ComponentIssuesRepository componentIssuesRepository;
-
- // Output metrics
- private final Metric reliabilityRatingMetric;
- private final Metric securityRatingMetric;
-
private final Map<String, Metric> metricsByKey;
public ReliabilityAndSecurityRatingMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, ComponentIssuesRepository componentIssuesRepository) {
@@ -80,8 +56,8 @@ public class ReliabilityAndSecurityRatingMeasuresVisitor extends PathAwareVisito
this.componentIssuesRepository = componentIssuesRepository;
// Output metrics
- this.reliabilityRatingMetric = metricRepository.getByKey(RELIABILITY_RATING_KEY);
- this.securityRatingMetric = metricRepository.getByKey(SECURITY_RATING_KEY);
+ Metric reliabilityRatingMetric = metricRepository.getByKey(RELIABILITY_RATING_KEY);
+ Metric securityRatingMetric = metricRepository.getByKey(SECURITY_RATING_KEY);
this.metricsByKey = ImmutableMap.of(
RELIABILITY_RATING_KEY, reliabilityRatingMetric,
@@ -94,13 +70,13 @@ public class ReliabilityAndSecurityRatingMeasuresVisitor extends PathAwareVisito
}
@Override
- public void visitDirectory(Component directory, Path<Counter> path) {
- computeAndSaveMeasures(directory, path);
+ public void visitModule(Component module, Path<Counter> path) {
+ computeAndSaveMeasures(module, path);
}
@Override
- public void visitModule(Component module, Path<Counter> path) {
- computeAndSaveMeasures(module, path);
+ public void visitDirectory(Component directory, Path<Counter> path) {
+ computeAndSaveMeasures(directory, path);
}
@Override
@@ -110,27 +86,27 @@ public class ReliabilityAndSecurityRatingMeasuresVisitor extends PathAwareVisito
private void computeAndSaveMeasures(Component component, Path<Counter> path) {
processIssues(component, path);
- path.current().ratingValueByMetric.entrySet().forEach(
- entry -> measureRepository.add(component, metricsByKey.get(entry.getKey()), createRatingMeasure(entry.getValue().getValue())));
- addToParent(path);
+ path.current().ratingValueByMetric.forEach((key, value) -> {
+ Rating rating = value.getValue();
+ measureRepository.add(component, metricsByKey.get(key), newMeasureBuilder().create(rating.getIndex(), rating.name()));
+ });
+ if (!path.isRoot()) {
+ path.parent().add(path.current());
+ }
}
private void processIssues(Component component, Path<Counter> path) {
componentIssuesRepository.getIssues(component)
.stream()
.filter(issue -> issue.resolution() == null)
- .filter(issue -> issue.type().equals(BUG) || issue.type().equals(VULNERABILITY))
- .forEach(path.current()::processIssue);
- }
-
- private static void addToParent(Path<Counter> path) {
- if (!path.isRoot()) {
- path.parent().add(path.current());
- }
- }
-
- private static Measure createRatingMeasure(Rating rating) {
- return newMeasureBuilder().create(rating.getIndex(), rating.name());
+ .forEach(issue -> {
+ Rating rating = RATING_BY_SEVERITY.get(issue.severity());
+ if (issue.type().equals(BUG)) {
+ path.current().ratingValueByMetric.get(RELIABILITY_RATING_KEY).increment(rating);
+ } else if (issue.type().equals(VULNERABILITY)) {
+ path.current().ratingValueByMetric.get(SECURITY_RATING_KEY).increment(rating);
+ }
+ });
}
static final class Counter {
@@ -143,17 +119,9 @@ public class ReliabilityAndSecurityRatingMeasuresVisitor extends PathAwareVisito
}
void add(Counter otherCounter) {
- ratingValueByMetric.entrySet().forEach(e -> e.getValue().increment(otherCounter.ratingValueByMetric.get(e.getKey())));
+ ratingValueByMetric.forEach((key, value) -> value.increment(otherCounter.ratingValueByMetric.get(key)));
}
- void processIssue(Issue issue) {
- Rating rating = RATING_BY_SEVERITY.get(issue.severity());
- if (issue.type().equals(BUG)) {
- ratingValueByMetric.get(RELIABILITY_RATING_KEY).increment(rating);
- } else if (issue.type().equals(VULNERABILITY)) {
- ratingValueByMetric.get(SECURITY_RATING_KEY).increment(rating);
- }
- }
}
private static final class CounterFactory extends PathAwareVisitorAdapter.SimpleStackElementFactory<ReliabilityAndSecurityRatingMeasuresVisitor.Counter> {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java
index 6696baad527..7838e9091c2 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java
@@ -59,15 +59,11 @@ public class LoadQualityGateStep implements ComputationStep {
qualityGate = getProjectQualityGate();
if (!qualityGate.isPresent()) {
// No QG defined for the project, let's retrieve the QG on the organization
- qualityGate = getOrganizationDefaultQualityGate();
+ qualityGate = Optional.of(getOrganizationDefaultQualityGate());
}
}
- if (qualityGate.isPresent()) {
- qualityGateHolder.setQualityGate(qualityGate.get());
- } else {
- qualityGateHolder.setNoQualityGate();
- }
+ qualityGateHolder.setQualityGate(qualityGate.orElseThrow(() -> new IllegalStateException("Quality gate not present")));
}
private Optional<QualityGate> getShortLivingBranchQualityGate() {
@@ -100,7 +96,7 @@ public class LoadQualityGateStep implements ComputationStep {
}
}
- private Optional<QualityGate> getOrganizationDefaultQualityGate() {
+ private QualityGate getOrganizationDefaultQualityGate() {
return qualityGateService.findDefaultQualityGate(analysisMetadataHolder.getOrganization());
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java
index cd78e6a0e21..8fce93dbe39 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java
@@ -20,7 +20,6 @@
package org.sonar.server.computation.task.projectanalysis.step;
import com.google.common.base.Function;
-import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
@@ -28,6 +27,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -177,7 +177,7 @@ public class QualityGateMeasuresStep implements ComputationStep {
boolean ignoredConditions = false;
for (Map.Entry<Metric, Collection<Condition>> entry : conditionsPerMetric.asMap().entrySet()) {
Metric metric = entry.getKey();
- Optional<Measure> measure = measureRepository.getRawMeasure(project, metric);
+ com.google.common.base.Optional<Measure> measure = measureRepository.getRawMeasure(project, metric);
if (!measure.isPresent()) {
continue;
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java
index 16eea512659..576df71153e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTask.java
@@ -24,6 +24,7 @@ import java.util.Optional;
import java.util.Set;
import org.sonar.api.ce.posttask.PostProjectAnalysisTask;
import org.sonar.api.config.Configuration;
+import org.sonar.api.measures.Metric;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.server.computation.task.projectanalysis.component.ConfigurationRepository;
import org.sonar.server.qualitygate.Condition;
@@ -80,7 +81,7 @@ public class WebhookPostTask implements PostProjectAnalysisTask {
})
.collect(MoreCollectors.toSet());
return builder.setQualityGate(new org.sonar.server.qualitygate.QualityGate(qg.getId(), qg.getName(), conditions))
- .setStatus(EvaluatedQualityGate.Status.valueOf(qg.getStatus().name()))
+ .setStatus(Metric.Level.valueOf(qg.getStatus().name()))
.build();
})
.orElse(null);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexer.java
index 40e454e6de3..e236161d1da 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexer.java
@@ -40,7 +40,8 @@ public interface ProjectIndexer extends ResilientIndexer {
PROJECT_DELETION,
PROJECT_KEY_UPDATE,
PROJECT_TAGS_UPDATE,
- PERMISSION_CHANGE
+ PERMISSION_CHANGE,
+ MEASURE_CHANGE
}
/**
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java
index 4df063c29d9..ac81874ef50 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java
@@ -60,6 +60,11 @@ public abstract class AbstractChangeTagsAction extends Action {
protected abstract Collection<String> getTagsToSet(Context context, Collection<String> tagsFromParams);
+ @Override
+ public boolean shouldRefreshMeasures() {
+ return false;
+ }
+
private Set<String> parseTags(Map<String, Object> properties) {
Set<String> result = new HashSet<>();
String tagsString = (String) properties.get(TAGS_PARAMETER);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java b/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java
index 5177042d609..24f00e1895e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/Action.java
@@ -75,6 +75,8 @@ public abstract class Action {
public abstract boolean execute(Map<String, Object> properties, Context context);
+ public abstract boolean shouldRefreshMeasures();
+
public interface Context {
DefaultIssue issue();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java
index 7c522fc3286..90aa92e568b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/AssignAction.java
@@ -89,6 +89,11 @@ public class AssignAction extends Action {
return isAssigneeMemberOfIssueOrganization(assignee, properties, context) && issueFieldsSetter.assign(context.issue(), assignee, context.issueChangeContext());
}
+ @Override
+ public boolean shouldRefreshMeasures() {
+ return false;
+ }
+
private static boolean isAssigneeMemberOfIssueOrganization(@Nullable UserDto assignee, Map<String, Object> properties, Context context) {
return assignee == null || ((Set<String>) properties.get(ASSIGNEE_ORGANIZATIONS)).contains(context.project().getOrganizationUuid());
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java
index 45ceb028701..bab386354c5 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/CommentAction.java
@@ -51,6 +51,11 @@ public class CommentAction extends Action {
return true;
}
+ @Override
+ public boolean shouldRefreshMeasures() {
+ return false;
+ }
+
private static String comment(Map<String, Object> properties) {
String param = (String) properties.get(COMMENT_PROPERTY);
if (Strings.isNullOrEmpty(param)) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangePostProcessor.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangePostProcessor.java
new file mode 100644
index 00000000000..b0d28c613f2
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangePostProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.issue;
+
+import java.util.Collection;
+import java.util.List;
+import org.sonar.api.server.ServerSide;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+
+@ServerSide
+public interface IssueChangePostProcessor {
+
+ /**
+ * Refresh measures, quality gate status and send webhooks
+ *
+ * @param components the components of changed issues
+ */
+ void process(DbSession dbSession, List<DefaultIssue> changedIssues, Collection<ComponentDto> components);
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangePostProcessorImpl.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangePostProcessorImpl.java
new file mode 100644
index 00000000000..c19e9ba3b93
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueChangePostProcessorImpl.java
@@ -0,0 +1,46 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.issue;
+
+import java.util.Collection;
+import java.util.List;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.measure.live.LiveMeasureComputer;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
+
+public class IssueChangePostProcessorImpl implements IssueChangePostProcessor {
+
+ private final LiveMeasureComputer liveMeasureComputer;
+ private final QGChangeEventListeners qualityGateListeners;
+
+ public IssueChangePostProcessorImpl(LiveMeasureComputer liveMeasureComputer, QGChangeEventListeners qualityGateListeners) {
+ this.liveMeasureComputer = liveMeasureComputer;
+ this.qualityGateListeners = qualityGateListeners;
+ }
+
+ @Override
+ public void process(DbSession dbSession, List<DefaultIssue> changedIssues, Collection<ComponentDto> components) {
+ List<QGChangeEvent> gateChangeEvents = liveMeasureComputer.refresh(dbSession, components);
+ qualityGateListeners.broadcastOnIssueChange(changedIssues, gateChangeEvents);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueUpdater.java
index 8f3aa25c0b7..8dac9d11f00 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/IssueUpdater.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/IssueUpdater.java
@@ -19,12 +19,14 @@
*/
package org.sonar.server.issue;
+import java.util.List;
import java.util.Optional;
import javax.annotation.Nullable;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleStatus;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
@@ -42,38 +44,48 @@ public class IssueUpdater {
private final DbClient dbClient;
private final IssueStorage issueStorage;
private final NotificationManager notificationService;
+ private final IssueChangePostProcessor issueChangePostProcessor;
- public IssueUpdater(DbClient dbClient, IssueStorage issueStorage, NotificationManager notificationService) {
+ public IssueUpdater(DbClient dbClient, IssueStorage issueStorage, NotificationManager notificationService,
+ IssueChangePostProcessor issueChangePostProcessor) {
this.dbClient = dbClient;
this.issueStorage = issueStorage;
this.notificationService = notificationService;
+ this.issueChangePostProcessor = issueChangePostProcessor;
}
/**
* Same as {@link #saveIssue(DbSession, DefaultIssue, IssueChangeContext, String)} but populates the specified
* {@link SearchResponseData} with the DTOs (rule and components) retrieved from DB to save the issue.
*/
- public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession session, DefaultIssue issue, IssueChangeContext context, @Nullable String comment) {
- Optional<RuleDefinitionDto> rule = getRuleByKey(session, issue.getRuleKey());
- ComponentDto project = dbClient.componentDao().selectOrFailByUuid(session, issue.projectUuid());
- ComponentDto component = dbClient.componentDao().selectOrFailByUuid(session, issue.componentUuid());
- IssueDto issueDto = saveIssue(session, issue, context, comment, rule, project, component);
+ public SearchResponseData saveIssueAndPreloadSearchResponseData(DbSession dbSession, DefaultIssue issue, IssueChangeContext context,
+ @Nullable String comment, boolean refreshMeasures) {
+ Optional<RuleDefinitionDto> rule = getRuleByKey(dbSession, issue.getRuleKey());
+ ComponentDto project = dbClient.componentDao().selectOrFailByUuid(dbSession, issue.projectUuid());
+ ComponentDto component = dbClient.componentDao().selectOrFailByUuid(dbSession, issue.componentUuid());
+ IssueDto issueDto = doSaveIssue(dbSession, issue, context, comment, rule, project, component);
+
+ SearchResponseData result = new SearchResponseData(issueDto);
+ rule.ifPresent(r -> result.setRules(singletonList(r)));
+ result.addComponents(singleton(project));
+ result.addComponents(singleton(component));
+
+ if (refreshMeasures) {
+ List<DefaultIssue> changedIssues = result.getIssues().stream().map(IssueDto::toDefaultIssue).collect(MoreCollectors.toList(result.getIssues().size()));
+ issueChangePostProcessor.process(dbSession, changedIssues, singleton(component));
+ }
- SearchResponseData preloadedSearchResponseData = new SearchResponseData(issueDto);
- rule.ifPresent(r -> preloadedSearchResponseData.setRules(singletonList(r)));
- preloadedSearchResponseData.addComponents(singleton(project));
- preloadedSearchResponseData.addComponents(singleton(component));
- return preloadedSearchResponseData;
+ return result;
}
public IssueDto saveIssue(DbSession session, DefaultIssue issue, IssueChangeContext context, @Nullable String comment) {
Optional<RuleDefinitionDto> rule = getRuleByKey(session, issue.getRuleKey());
ComponentDto project = dbClient.componentDao().selectOrFailByUuid(session, issue.projectUuid());
ComponentDto component = dbClient.componentDao().selectOrFailByUuid(session, issue.componentUuid());
- return saveIssue(session, issue, context, comment, rule, project, component);
+ return doSaveIssue(session, issue, context, comment, rule, project, component);
}
- private IssueDto saveIssue(DbSession session, DefaultIssue issue, IssueChangeContext context, @Nullable String comment,
+ private IssueDto doSaveIssue(DbSession session, DefaultIssue issue, IssueChangeContext context, @Nullable String comment,
Optional<RuleDefinitionDto> rule, ComponentDto project, ComponentDto component) {
IssueDto issueDto = issueStorage.save(session, issue);
notificationService.scheduleForSending(new IssueChangeNotification()
@@ -87,7 +99,7 @@ public class IssueUpdater {
}
private Optional<RuleDefinitionDto> getRuleByKey(DbSession session, RuleKey ruleKey) {
- Optional<RuleDefinitionDto> rule = Optional.ofNullable(dbClient.ruleDao().selectDefinitionByKey(session, ruleKey).orElse(null));
+ Optional<RuleDefinitionDto> rule = dbClient.ruleDao().selectDefinitionByKey(session, ruleKey);
return (rule.isPresent() && rule.get().getStatus() != RuleStatus.REMOVED) ? rule : Optional.empty();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java
index d1909e9c1ec..3b905d1011f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/SetSeverityAction.java
@@ -61,6 +61,11 @@ public class SetSeverityAction extends Action {
return issueUpdater.setManualSeverity(context.issue(), verifySeverityParameter(properties), context.issueChangeContext());
}
+ @Override
+ public boolean shouldRefreshMeasures() {
+ return true;
+ }
+
private static String verifySeverityParameter(Map<String, Object> properties) {
String param = (String) properties.get(SEVERITY_PARAMETER);
checkArgument(!isNullOrEmpty(param), "Missing parameter : '%s'", SEVERITY_PARAMETER);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java
index 26909a4f867..193fe499247 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/SetTypeAction.java
@@ -61,6 +61,11 @@ public class SetTypeAction extends Action {
return issueUpdater.setType(context.issue(), RuleType.valueOf(type), context.issueChangeContext());
}
+ @Override
+ public boolean shouldRefreshMeasures() {
+ return true;
+ }
+
private static String verifyTypeParameter(Map<String, Object> properties) {
String type = (String) properties.get(TYPE_PARAMETER);
checkArgument(!isNullOrEmpty(type), "Missing parameter : '%s'", TYPE_PARAMETER);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java
index 23c44754b45..444b105017b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/TransitionAction.java
@@ -56,6 +56,11 @@ public class TransitionAction extends Action {
return canExecuteTransition(issue, transition) && transitionService.doTransition(context.issue(), context.issueChangeContext(), transition(properties));
}
+ @Override
+ public boolean shouldRefreshMeasures() {
+ return true;
+ }
+
private boolean canExecuteTransition(DefaultIssue issue, String transitionKey) {
return transitionService.listTransitions(issue)
.stream()
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndexer.java
index 9b430a5f25e..308afd0df57 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndexer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/index/IssueIndexer.java
@@ -108,10 +108,11 @@ public class IssueIndexer implements ProjectIndexer, NeedAuthorizationIndexer {
switch (cause) {
case PROJECT_CREATION:
// nothing to do, issues do not exist at project creation
+ case MEASURE_CHANGE:
case PROJECT_KEY_UPDATE:
case PROJECT_TAGS_UPDATE:
case PERMISSION_CHANGE:
- // nothing to do, permissions, project key and tags are not used in type issues/issue
+ // nothing to do. Measures, permissions, project key and tags are not used in type issues/issue
return emptyList();
case PROJECT_DELETION:
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java
index 4993dca94e1..d501ed05426 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AddCommentAction.java
@@ -98,7 +98,7 @@ public class AddCommentAction implements IssuesWsAction {
IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getLogin());
DefaultIssue defaultIssue = issueDto.toDefaultIssue();
issueFieldsSetter.addComment(defaultIssue, wsRequest.getText(), context);
- SearchResponseData preloadedSearchResponseData = issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context, wsRequest.getText());
+ SearchResponseData preloadedSearchResponseData = issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, defaultIssue, context, wsRequest.getText(), false);
responseWriter.write(defaultIssue.key(), preloadedSearchResponseData, request, response);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java
index a65810ac2a8..8cd574cca63 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/AssignAction.java
@@ -67,7 +67,7 @@ public class AssignAction implements IssuesWsAction {
private final OperationResponseWriter responseWriter;
public AssignAction(System2 system2, UserSession userSession, DbClient dbClient, IssueFinder issueFinder, IssueFieldsSetter issueFieldsSetter, IssueUpdater issueUpdater,
- OperationResponseWriter responseWriter) {
+ OperationResponseWriter responseWriter) {
this.system2 = system2;
this.userSession = userSession;
this.dbClient = dbClient;
@@ -121,7 +121,7 @@ public class AssignAction implements IssuesWsAction {
}
IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getLogin());
if (issueFieldsSetter.assign(issue, user, context)) {
- return issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, issue, context, null);
+ return issueUpdater.saveIssueAndPreloadSearchResponseData(dbSession, issue, context, null, false);
}
return new SearchResponseData(issueDto);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java
index 1edfaf4ab3e..11ed5626561 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java
@@ -28,9 +28,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
-import java.util.function.Function;
import java.util.function.Predicate;
-import java.util.stream.Stream;
+import java.util.stream.Collectors;
import org.sonar.api.issue.DefaultTransitions;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.Severity;
@@ -54,20 +53,15 @@ import org.sonar.db.rule.RuleDefinitionDto;
import org.sonar.server.issue.Action;
import org.sonar.server.issue.AddTagsAction;
import org.sonar.server.issue.AssignAction;
+import org.sonar.server.issue.IssueChangePostProcessor;
import org.sonar.server.issue.IssueStorage;
import org.sonar.server.issue.RemoveTagsAction;
-import org.sonar.server.issue.SetTypeAction;
-import org.sonar.server.issue.TransitionAction;
import org.sonar.server.issue.notification.IssueChangeNotification;
import org.sonar.server.notification.NotificationManager;
-import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Issues;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.collect.ImmutableList.copyOf;
import static com.google.common.collect.ImmutableMap.of;
import static java.lang.String.format;
import static java.util.function.Function.identity;
@@ -111,20 +105,18 @@ public class BulkChangeAction implements IssuesWsAction {
private final IssueStorage issueStorage;
private final NotificationManager notificationService;
private final List<Action> actions;
- private final QGChangeEventFactory qgChangeEventFactory;
- private final QGChangeEventListeners qgChangeEventListeners;
+ private final IssueChangePostProcessor issueChangePostProcessor;
public BulkChangeAction(System2 system2, UserSession userSession, DbClient dbClient, IssueStorage issueStorage,
NotificationManager notificationService, List<Action> actions,
- QGChangeEventFactory qgChangeEventFactory, QGChangeEventListeners qgChangeEventListeners) {
+ IssueChangePostProcessor issueChangePostProcessor) {
this.system2 = system2;
this.userSession = userSession;
this.dbClient = dbClient;
this.issueStorage = issueStorage;
this.notificationService = notificationService;
this.actions = actions;
- this.qgChangeEventFactory = qgChangeEventFactory;
- this.qgChangeEventListeners = qgChangeEventListeners;
+ this.issueChangePostProcessor = issueChangePostProcessor;
}
@Override
@@ -187,53 +179,41 @@ public class BulkChangeAction implements IssuesWsAction {
public void handle(Request request, Response response) throws Exception {
userSession.checkLoggedIn();
try (DbSession dbSession = dbClient.openSession(false)) {
- Issues.BulkChangeWsResponse wsResponse = Stream.of(request)
- .map(loadData(dbSession))
- .map(executeBulkChange())
- .map(toWsResponse())
- .collect(MoreCollectors.toOneElement());
- writeProtobuf(wsResponse, request, response);
+ BulkChangeResult result = executeBulkChange(dbSession, request);
+ writeProtobuf(toWsResponse(result), request, response);
}
}
- private Function<Request, BulkChangeData> loadData(DbSession dbSession) {
- return request -> new BulkChangeData(dbSession, request);
- }
+ private BulkChangeResult executeBulkChange(DbSession dbSession, Request request) {
+ BulkChangeData bulkChangeData = new BulkChangeData(dbSession, request);
+ BulkChangeResult result = new BulkChangeResult(bulkChangeData.issues.size());
+ IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(system2.now()), userSession.getLogin());
- private Function<BulkChangeData, BulkChangeResult> executeBulkChange() {
- return bulkChangeData -> {
- BulkChangeResult result = new BulkChangeResult(bulkChangeData.issues.size());
- IssueChangeContext issueChangeContext = IssueChangeContext.createUser(new Date(system2.now()), userSession.getLogin());
+ List<DefaultIssue> items = bulkChangeData.issues.stream()
+ .filter(bulkChange(issueChangeContext, bulkChangeData, result))
+ .collect(MoreCollectors.toList());
+ issueStorage.save(items);
- List<DefaultIssue> items = bulkChangeData.issues.stream()
- .filter(bulkChange(issueChangeContext, bulkChangeData, result))
- .collect(MoreCollectors.toList());
- issueStorage.save(items);
- items.forEach(sendNotification(issueChangeContext, bulkChangeData));
- buildWebhookIssueChange(bulkChangeData.propertiesByActions)
- .ifPresent(issueChange -> {
- QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(
- bulkChangeData.issues.stream().filter(i -> result.success.contains(i.key())).collect(MoreCollectors.toList()),
- copyOf(bulkChangeData.componentsByUuid.values()));
- List<QGChangeEvent> qgChangeEvents = qgChangeEventFactory.from(issueChangeData, issueChange, issueChangeContext);
- qgChangeEventListeners.broadcastOnIssueChange(issueChangeData, qgChangeEvents);
- });
- return result;
- };
+ refreshLiveMeasures(dbSession, bulkChangeData, result);
+
+ items.forEach(sendNotification(issueChangeContext, bulkChangeData));
+
+ return result;
}
- private static Optional<QGChangeEventFactory.IssueChange> buildWebhookIssueChange(Map<String, Map<String, Object>> propertiesByActions) {
- RuleType ruleType = Optional.ofNullable(propertiesByActions.get(SetTypeAction.SET_TYPE_KEY))
- .map(t -> (String) t.get(SetTypeAction.TYPE_PARAMETER))
- .map(RuleType::valueOf)
- .orElse(null);
- String transitionKey = Optional.ofNullable(propertiesByActions.get(TransitionAction.DO_TRANSITION_KEY))
- .map(t -> (String) t.get(TransitionAction.TRANSITION_PARAMETER))
- .orElse(null);
- if (ruleType == null && transitionKey == null) {
- return Optional.empty();
+ private void refreshLiveMeasures(DbSession dbSession, BulkChangeData data, BulkChangeResult result) {
+ if (!data.shouldRefreshMeasures()) {
+ return;
}
- return Optional.of(new QGChangeEventFactory.IssueChange(ruleType, transitionKey));
+ Set<String> touchedComponentUuids = result.success.stream()
+ .map(DefaultIssue::componentUuid)
+ .collect(Collectors.toSet());
+ List<ComponentDto> touchedComponents = touchedComponentUuids.stream()
+ .map(data.componentsByUuid::get)
+ .collect(MoreCollectors.toList(touchedComponentUuids.size()));
+
+ List<DefaultIssue> changedIssues = data.issues.stream().filter(result.success::contains).collect(MoreCollectors.toList());
+ issueChangePostProcessor.process(dbSession, changedIssues, touchedComponents);
}
private static Predicate<DefaultIssue> bulkChange(IssueChangeContext issueChangeContext, BulkChangeData bulkChangeData, BulkChangeResult result) {
@@ -241,7 +221,7 @@ public class BulkChangeAction implements IssuesWsAction {
ActionContext actionContext = new ActionContext(issue, issueChangeContext, bulkChangeData.projectsByUuid.get(issue.projectUuid()));
bulkChangeData.getActionsWithoutComment().forEach(applyAction(actionContext, bulkChangeData, result));
addCommentIfNeeded(actionContext, bulkChangeData);
- return result.success.contains(issue.key());
+ return result.success.contains(issue);
};
}
@@ -277,12 +257,12 @@ public class BulkChangeAction implements IssuesWsAction {
};
}
- private static Function<BulkChangeResult, Issues.BulkChangeWsResponse> toWsResponse() {
- return bulkChangeResult -> Issues.BulkChangeWsResponse.newBuilder()
- .setTotal(bulkChangeResult.getTotal())
- .setSuccess(bulkChangeResult.getSuccess())
- .setIgnored((long) bulkChangeResult.getTotal() - (bulkChangeResult.getSuccess() + bulkChangeResult.getFailures()))
- .setFailures(bulkChangeResult.getFailures())
+ private static Issues.BulkChangeWsResponse toWsResponse(BulkChangeResult result) {
+ return Issues.BulkChangeWsResponse.newBuilder()
+ .setTotal(result.countTotal())
+ .setSuccess(result.countSuccess())
+ .setIgnored((long) result.countTotal() - (result.countSuccess() + result.countFailures()))
+ .setFailures(result.countFailures())
.build();
}
@@ -391,11 +371,15 @@ public class BulkChangeAction implements IssuesWsAction {
long actionsDefined = actions.stream().filter(action -> !action.equals(COMMENT_KEY)).count();
checkArgument(actionsDefined > 0, "At least one action must be provided");
}
+
+ private boolean shouldRefreshMeasures() {
+ return availableActions.stream().anyMatch(Action::shouldRefreshMeasures);
+ }
}
private static class BulkChangeResult {
private final int total;
- private Set<String> success = new HashSet<>();
+ private final Set<DefaultIssue> success = new HashSet<>();
private int failures = 0;
BulkChangeResult(int total) {
@@ -403,22 +387,22 @@ public class BulkChangeAction implements IssuesWsAction {
}
void increaseSuccess(DefaultIssue issue) {
- this.success.add(issue.key());
+ this.success.add(issue);
}
void increaseFailure() {
this.failures++;
}
- public int getTotal() {
+ int countTotal() {
return total;
}
- public int getSuccess() {
+ int countSuccess() {
return success.size();
}
- public int getFailures() {
+ int countFailures() {
return failures;
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
index ba9b4a0c541..501d7f03fa7 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/DoTransitionAction.java
@@ -21,7 +21,6 @@ package org.sonar.server.issue.ws;
import com.google.common.io.Resources;
import java.util.Date;
-import java.util.List;
import org.sonar.api.issue.DefaultTransitions;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
@@ -37,13 +36,8 @@ import org.sonar.db.issue.IssueDto;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.IssueUpdater;
import org.sonar.server.issue.TransitionService;
-import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.user.UserSession;
-import static com.google.common.collect.ImmutableList.copyOf;
-import static org.sonar.core.util.stream.MoreCollectors.toList;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_DO_TRANSITION;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_TRANSITION;
@@ -57,11 +51,9 @@ public class DoTransitionAction implements IssuesWsAction {
private final TransitionService transitionService;
private final OperationResponseWriter responseWriter;
private final System2 system2;
- private final QGChangeEventFactory qgChangeEventFactory;
- private final QGChangeEventListeners qgChangeEventListeners;
public DoTransitionAction(DbClient dbClient, UserSession userSession, IssueFinder issueFinder, IssueUpdater issueUpdater, TransitionService transitionService,
- OperationResponseWriter responseWriter, System2 system2, QGChangeEventFactory qgChangeEventFactory, QGChangeEventListeners qgChangeEventListeners) {
+ OperationResponseWriter responseWriter, System2 system2) {
this.dbClient = dbClient;
this.userSession = userSession;
this.issueFinder = issueFinder;
@@ -69,8 +61,6 @@ public class DoTransitionAction implements IssuesWsAction {
this.transitionService = transitionService;
this.responseWriter = responseWriter;
this.system2 = system2;
- this.qgChangeEventFactory = qgChangeEventFactory;
- this.qgChangeEventListeners = qgChangeEventListeners;
}
@Override
@@ -112,13 +102,7 @@ public class DoTransitionAction implements IssuesWsAction {
IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getLogin());
transitionService.checkTransitionPermission(transitionKey, defaultIssue);
if (transitionService.doTransition(defaultIssue, context, transitionKey)) {
- SearchResponseData searchResponseData = issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context, null);
- QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(
- searchResponseData.getIssues().stream().map(IssueDto::toDefaultIssue).collect(toList(searchResponseData.getIssues().size())),
- copyOf(searchResponseData.getComponents()));
- List<QGChangeEvent> qgChangeEvents = qgChangeEventFactory.from(issueChangeData, new QGChangeEventFactory.IssueChange(transitionKey), context);
- qgChangeEventListeners.broadcastOnIssueChange(issueChangeData, qgChangeEvents);
- return searchResponseData;
+ return issueUpdater.saveIssueAndPreloadSearchResponseData(session, defaultIssue, context, null, true);
}
return new SearchResponseData(issueDto);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java
index 7d4e1985a96..a434b524eed 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/IssueWsModule.java
@@ -28,8 +28,6 @@ import org.sonar.server.issue.ServerIssueStorage;
import org.sonar.server.issue.TransitionService;
import org.sonar.server.issue.workflow.FunctionExecutor;
import org.sonar.server.issue.workflow.IssueWorkflow;
-import org.sonar.server.qualitygate.LiveQualityGateFactoryImpl;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventFactoryImpl;
import org.sonar.server.qualitygate.changeevent.QGChangeEventListenersImpl;
import org.sonar.server.settings.ProjectConfigurationLoaderImpl;
import org.sonar.server.webhook.WebhookQGChangeEventListener;
@@ -68,8 +66,6 @@ public class IssueWsModule extends Module {
ChangelogAction.class,
BulkChangeAction.class,
ProjectConfigurationLoaderImpl.class,
- LiveQualityGateFactoryImpl.class,
- QGChangeEventFactoryImpl.class,
WebhookQGChangeEventListener.class,
QGChangeEventListenersImpl.class);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
index d7407b48d44..c1cd538704b 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetSeverityAction.java
@@ -107,7 +107,7 @@ public class SetSeverityAction implements IssuesWsAction {
IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.getLogin());
if (issueFieldsSetter.setManualSeverity(issue, severity, context)) {
- return issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, null);
+ return issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, null, true);
}
return new SearchResponseData(issueDto);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java
index 778c03b681b..004e916a012 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTagsAction.java
@@ -105,7 +105,7 @@ public class SetTagsAction implements IssuesWsAction {
DefaultIssue issue = issueDto.toDefaultIssue();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), userSession.getLogin());
if (issueFieldsSetter.setTags(issue, tags, context)) {
- return issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, null);
+ return issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, null, false);
}
return new SearchResponseData(issueDto);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
index 4239996386e..d22d5fa26e0 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/issue/ws/SetTypeAction.java
@@ -21,7 +21,6 @@ package org.sonar.server.issue.ws;
import com.google.common.io.Resources;
import java.util.Date;
-import java.util.List;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
@@ -31,19 +30,14 @@ import org.sonar.api.utils.System2;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.core.util.Uuids;
-import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.issue.IssueDto;
import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.IssueUpdater;
-import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.user.UserSession;
-import static com.google.common.collect.ImmutableList.copyOf;
import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.ACTION_SET_TYPE;
import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ISSUE;
@@ -58,12 +52,9 @@ public class SetTypeAction implements IssuesWsAction {
private final IssueUpdater issueUpdater;
private final OperationResponseWriter responseWriter;
private final System2 system2;
- private final QGChangeEventFactory qgChangeEventFactory;
- private final QGChangeEventListeners qgChangeEventListeners;
public SetTypeAction(UserSession userSession, DbClient dbClient, IssueFinder issueFinder, IssueFieldsSetter issueFieldsSetter, IssueUpdater issueUpdater,
- OperationResponseWriter responseWriter, System2 system2,
- QGChangeEventFactory qgChangeEventFactory, QGChangeEventListeners qgChangeEventListeners) {
+ OperationResponseWriter responseWriter, System2 system2) {
this.userSession = userSession;
this.dbClient = dbClient;
this.issueFinder = issueFinder;
@@ -71,8 +62,6 @@ public class SetTypeAction implements IssuesWsAction {
this.issueUpdater = issueUpdater;
this.responseWriter = responseWriter;
this.system2 = system2;
- this.qgChangeEventFactory = qgChangeEventFactory;
- this.qgChangeEventListeners = qgChangeEventListeners;
}
@Override
@@ -121,13 +110,7 @@ public class SetTypeAction implements IssuesWsAction {
IssueChangeContext context = IssueChangeContext.createUser(new Date(system2.now()), userSession.getLogin());
if (issueFieldsSetter.setType(issue, ruleType, context)) {
- SearchResponseData searchResponseData = issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, null);
- QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(
- searchResponseData.getIssues().stream().map(IssueDto::toDefaultIssue).collect(MoreCollectors.toList(searchResponseData.getIssues().size())),
- copyOf(searchResponseData.getComponents()));
- List<QGChangeEvent> qgChangeEvents = qgChangeEventFactory.from(issueChangeData, new QGChangeEventFactory.IssueChange(ruleType), context);
- qgChangeEventListeners.broadcastOnIssueChange(issueChangeData, qgChangeEvents);
- return searchResponseData;
+ return issueUpdater.saveIssueAndPreloadSearchResponseData(session, issue, context, null, true);
}
return new SearchResponseData(issueDto);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java
index 7c4651cc453..bac783b89e3 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java
@@ -78,8 +78,8 @@ public class ProjectMeasuresIndexer implements ProjectIndexer, NeedAuthorization
}
@Override
- public void indexOnAnalysis(String branchUuid) {
- doIndex(Size.REGULAR, branchUuid);
+ public void indexOnAnalysis(String projectUuid) {
+ doIndex(Size.REGULAR, projectUuid);
}
@Override
@@ -88,7 +88,7 @@ public class ProjectMeasuresIndexer implements ProjectIndexer, NeedAuthorization
case PERMISSION_CHANGE:
// nothing to do, permissions are not used in type projectmeasures/projectmeasure
return Collections.emptyList();
-
+ case MEASURE_CHANGE:
case PROJECT_KEY_UPDATE:
// project must be re-indexed because key is used in this index
case PROJECT_CREATION:
@@ -106,6 +106,17 @@ public class ProjectMeasuresIndexer implements ProjectIndexer, NeedAuthorization
}
}
+ public IndexingResult commitAndIndex(DbSession dbSession, Collection<String> projectUuids) {
+ List<EsQueueDto> items = projectUuids.stream()
+ .map(projectUuid -> EsQueueDto.create(INDEX_TYPE_PROJECT_MEASURES.format(), projectUuid, null, projectUuid))
+ .collect(MoreCollectors.toArrayList(projectUuids.size()));
+ dbClient.esQueueDao().insert(dbSession, items);
+
+ dbSession.commit();
+
+ return index(dbSession, items);
+ }
+
@Override
public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) {
if (items.isEmpty()) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueCounter.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueCounter.java
new file mode 100644
index 00000000000..09e1e57a768
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueCounter.java
@@ -0,0 +1,153 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Collection;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.db.issue.IssueGroupDto;
+import org.sonar.db.rule.SeverityUtil;
+
+class IssueCounter {
+
+ private final Map<RuleType, HighestSeverity> highestSeverityOfUnresolved = new EnumMap<>(RuleType.class);
+ private final Map<RuleType, Effort> effortOfUnresolved = new EnumMap<>(RuleType.class);
+ private final Map<String, Count> unresolvedBySeverity = new HashMap<>();
+ private final Map<RuleType, Count> unresolvedByType = new EnumMap<>(RuleType.class);
+ private final Map<String, Count> byResolution = new HashMap<>();
+ private final Map<String, Count> byStatus = new HashMap<>();
+ private final Count unresolved = new Count();
+
+ IssueCounter(Collection<IssueGroupDto> groups) {
+ for (IssueGroupDto group : groups) {
+ RuleType ruleType = RuleType.valueOf(group.getRuleType());
+ if (group.getResolution() == null) {
+ highestSeverityOfUnresolved
+ .computeIfAbsent(ruleType, k -> new HighestSeverity())
+ .add(group);
+ effortOfUnresolved
+ .computeIfAbsent(ruleType, k -> new Effort())
+ .add(group);
+ unresolvedBySeverity
+ .computeIfAbsent(group.getSeverity(), k -> new Count())
+ .add(group);
+ unresolvedByType
+ .computeIfAbsent(ruleType, k -> new Count())
+ .add(group);
+ unresolved.add(group);
+ } else {
+ byResolution
+ .computeIfAbsent(group.getResolution(), k -> new Count())
+ .add(group);
+ }
+ if (group.getStatus() != null) {
+ byStatus
+ .computeIfAbsent(group.getStatus(), k -> new Count())
+ .add(group);
+ }
+ }
+ }
+
+ public Optional<String> getHighestSeverityOfUnresolved(RuleType ruleType, boolean onlyInLeak) {
+ return Optional.ofNullable(highestSeverityOfUnresolved.get(ruleType))
+ .map(hs -> hs.severity(onlyInLeak));
+ }
+
+ public double sumEffortOfUnresolved(RuleType type, boolean onlyInLeak) {
+ Effort effort = effortOfUnresolved.get(type);
+ if (effort == null) {
+ return 0.0;
+ }
+ return onlyInLeak ? effort.leak : effort.absolute;
+ }
+
+ public long countUnresolvedBySeverity(String severity, boolean onlyInLeak) {
+ return value(unresolvedBySeverity.get(severity), onlyInLeak);
+ }
+
+ public long countByResolution(String resolution, boolean onlyInLeak) {
+ return value(byResolution.get(resolution), onlyInLeak);
+ }
+
+ public long countUnresolvedByType(RuleType type, boolean onlyInLeak) {
+ return value(unresolvedByType.get(type), onlyInLeak);
+ }
+
+ public long countByStatus(String status, boolean onlyInLeak) {
+ return value(byStatus.get(status), onlyInLeak);
+ }
+
+ public long countUnresolved(boolean onlyInLeak) {
+ return value(unresolved, onlyInLeak);
+ }
+
+ private static long value(@Nullable Count count, boolean onlyInLeak) {
+ if (count == null) {
+ return 0;
+ }
+ return onlyInLeak ? count.leak : count.absolute;
+ }
+
+ private static class Count {
+ private long absolute = 0L;
+ private long leak = 0L;
+
+ void add(IssueGroupDto group) {
+ absolute += group.getCount();
+ if (group.isInLeak()) {
+ leak += group.getCount();
+ }
+ }
+ }
+
+ private static class Effort {
+ private double absolute = 0.0;
+ private double leak = 0.0;
+
+ void add(IssueGroupDto group) {
+ absolute += group.getEffort();
+ if (group.isInLeak()) {
+ leak += group.getEffort();
+ }
+ }
+ }
+
+ private static class HighestSeverity {
+ private int absolute = SeverityUtil.getOrdinalFromSeverity(Severity.INFO);
+ private int leak = SeverityUtil.getOrdinalFromSeverity(Severity.INFO);
+
+ void add(IssueGroupDto group) {
+ int severity = SeverityUtil.getOrdinalFromSeverity(group.getSeverity());
+ absolute = Math.max(severity, absolute);
+ if (group.isInLeak()) {
+ leak = Math.max(severity, leak);
+ }
+ }
+
+ String severity(boolean inLeak) {
+ return SeverityUtil.getSeverityFromOrdinal(inLeak ? leak : absolute);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormula.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormula.java
new file mode 100644
index 00000000000..521e65be629
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormula.java
@@ -0,0 +1,89 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Collection;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import org.sonar.api.measures.Metric;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.DebtRatingGrid;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
+
+import static java.util.Collections.emptyList;
+
+class IssueMetricFormula {
+
+ private final Metric metric;
+ private final boolean onLeak;
+ private final BiConsumer<Context, IssueCounter> formula;
+ private final Collection<Metric> dependentMetrics;
+
+ IssueMetricFormula(Metric metric, boolean onLeak, BiConsumer<Context, IssueCounter> formula) {
+ this(metric, onLeak, formula, emptyList());
+ }
+
+ IssueMetricFormula(Metric metric, boolean onLeak, BiConsumer<Context, IssueCounter> formula, Collection<Metric> dependentMetrics) {
+ this.metric = metric;
+ this.onLeak = onLeak;
+ this.formula = formula;
+ this.dependentMetrics = dependentMetrics;
+ }
+
+ Metric getMetric() {
+ return metric;
+ }
+
+ boolean isOnLeak() {
+ return onLeak;
+ }
+
+ Collection<Metric> getDependentMetrics() {
+ return dependentMetrics;
+ }
+
+ void compute(Context context, IssueCounter issues) {
+ formula.accept(context, issues);
+ }
+
+ interface Context {
+ ComponentDto getComponent();
+
+ DebtRatingGrid getDebtRatingGrid();
+
+ /**
+ * Value that was just refreshed, otherwise value as computed
+ * during last analysis.
+ * The metric must be declared in the formula dependencies
+ * (see {@link IssueMetricFormula#getDependentMetrics()}).
+ */
+ Optional<Double> getValue(Metric metric);
+
+ Optional<Double> getLeakValue(Metric metric);
+
+ void setValue(double value);
+
+ void setValue(Rating value);
+
+ void setLeakValue(double value);
+
+ void setLeakValue(Rating value);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactory.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactory.java
new file mode 100644
index 00000000000..9fc2578e9fe
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactory.java
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.server.ServerSide;
+
+@ServerSide
+public interface IssueMetricFormulaFactory {
+ List<IssueMetricFormula> getFormulas();
+
+ Set<Metric> getFormulaMetrics();
+
+ static Set<Metric> extractMetrics(List<IssueMetricFormula> formulas) {
+ return formulas.stream()
+ .flatMap(f -> Stream.concat(Stream.of(f.getMetric()), f.getDependentMetrics().stream()))
+ .collect(Collectors.toSet());
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java
new file mode 100644
index 00000000000..4044e5d7ea4
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImpl.java
@@ -0,0 +1,200 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.List;
+import java.util.Optional;
+import java.util.Set;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
+
+import static java.util.Arrays.asList;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.RATING_BY_SEVERITY;
+
+public class IssueMetricFormulaFactoryImpl implements IssueMetricFormulaFactory {
+
+ private static final List<IssueMetricFormula> FORMULAS = asList(
+ new IssueMetricFormula(CoreMetrics.CODE_SMELLS, false,
+ (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.CODE_SMELL, false))),
+
+ new IssueMetricFormula(CoreMetrics.BUGS, false,
+ (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.BUG, false))),
+
+ new IssueMetricFormula(CoreMetrics.VULNERABILITIES, false,
+ (context, issues) -> context.setValue(issues.countUnresolvedByType(RuleType.VULNERABILITY, false))),
+
+ new IssueMetricFormula(CoreMetrics.VIOLATIONS, false,
+ (context, issues) -> context.setValue(issues.countUnresolved(false))),
+
+ new IssueMetricFormula(CoreMetrics.BLOCKER_VIOLATIONS, false,
+ (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.BLOCKER, false))),
+
+ new IssueMetricFormula(CoreMetrics.CRITICAL_VIOLATIONS, false,
+ (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.CRITICAL, false))),
+
+ new IssueMetricFormula(CoreMetrics.MAJOR_VIOLATIONS, false,
+ (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.MAJOR, false))),
+
+ new IssueMetricFormula(CoreMetrics.MINOR_VIOLATIONS, false,
+ (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.MINOR, false))),
+
+ new IssueMetricFormula(CoreMetrics.INFO_VIOLATIONS, false,
+ (context, issues) -> context.setValue(issues.countUnresolvedBySeverity(Severity.INFO, false))),
+
+ new IssueMetricFormula(CoreMetrics.FALSE_POSITIVE_ISSUES, false,
+ (context, issues) -> context.setValue(issues.countByResolution(Issue.RESOLUTION_FALSE_POSITIVE, false))),
+
+ new IssueMetricFormula(CoreMetrics.WONT_FIX_ISSUES, false,
+ (context, issues) -> context.setValue(issues.countByResolution(Issue.RESOLUTION_WONT_FIX, false))),
+
+ new IssueMetricFormula(CoreMetrics.OPEN_ISSUES, false,
+ (context, issues) -> context.setValue(issues.countByStatus(Issue.STATUS_OPEN, false))),
+
+ new IssueMetricFormula(CoreMetrics.REOPENED_ISSUES, false,
+ (context, issues) -> context.setValue(issues.countByStatus(Issue.STATUS_REOPENED, false))),
+
+ new IssueMetricFormula(CoreMetrics.CONFIRMED_ISSUES, false,
+ (context, issues) -> context.setValue(issues.countByStatus(Issue.STATUS_CONFIRMED, false))),
+
+ new IssueMetricFormula(CoreMetrics.TECHNICAL_DEBT, false,
+ (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.CODE_SMELL, false))),
+
+ new IssueMetricFormula(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, false,
+ (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.BUG, false))),
+
+ new IssueMetricFormula(CoreMetrics.SECURITY_REMEDIATION_EFFORT, false,
+ (context, issues) -> context.setValue(issues.sumEffortOfUnresolved(RuleType.VULNERABILITY, false))),
+
+ new IssueMetricFormula(CoreMetrics.SQALE_DEBT_RATIO, false,
+ (context, issues) -> context.setValue(100.0 * debtDensity(context)),
+ asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)),
+
+ new IssueMetricFormula(CoreMetrics.SQALE_RATING, false,
+ (context, issues) -> context
+ .setValue(context.getDebtRatingGrid().getRatingForDensity(debtDensity(context))),
+ asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)),
+
+ new IssueMetricFormula(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, false,
+ (context, issues) -> context.setValue(effortToReachMaintainabilityRatingA(context)), asList(CoreMetrics.TECHNICAL_DEBT, CoreMetrics.DEVELOPMENT_COST)),
+
+ new IssueMetricFormula(CoreMetrics.RELIABILITY_RATING, false,
+ (context, issues) -> context.setValue(RATING_BY_SEVERITY.get(issues.getHighestSeverityOfUnresolved(RuleType.BUG, false).orElse(Severity.INFO)))),
+
+ 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.NEW_CODE_SMELLS, true,
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.CODE_SMELL, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_BUGS, true,
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.BUG, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_VULNERABILITIES, true,
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedByType(RuleType.VULNERABILITY, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_VIOLATIONS, true,
+ (context, issues) -> context.setLeakValue(issues.countUnresolved(true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_BLOCKER_VIOLATIONS, true,
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.BLOCKER, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_CRITICAL_VIOLATIONS, true,
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.CRITICAL, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_MAJOR_VIOLATIONS, true,
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.MAJOR, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_MINOR_VIOLATIONS, true,
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.MINOR, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_INFO_VIOLATIONS, true,
+ (context, issues) -> context.setLeakValue(issues.countUnresolvedBySeverity(Severity.INFO, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_TECHNICAL_DEBT, true,
+ (context, issues) -> context.setLeakValue(issues.sumEffortOfUnresolved(RuleType.CODE_SMELL, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, true,
+ (context, issues) -> context.setLeakValue(issues.sumEffortOfUnresolved(RuleType.BUG, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, true,
+ (context, issues) -> context.setLeakValue(issues.sumEffortOfUnresolved(RuleType.VULNERABILITY, true))),
+
+ new IssueMetricFormula(CoreMetrics.NEW_RELIABILITY_RATING, true,
+ (context, issues) -> {
+ String highestSeverity = issues.getHighestSeverityOfUnresolved(RuleType.BUG, true).orElse(Severity.INFO);
+ context.setLeakValue(RATING_BY_SEVERITY.get(highestSeverity));
+ }),
+
+ new IssueMetricFormula(CoreMetrics.NEW_SECURITY_RATING, true,
+ (context, issues) -> {
+ String highestSeverity = issues.getHighestSeverityOfUnresolved(RuleType.VULNERABILITY, true).orElse(Severity.INFO);
+ context.setLeakValue(RATING_BY_SEVERITY.get(highestSeverity));
+ }),
+
+ new IssueMetricFormula(CoreMetrics.NEW_SQALE_DEBT_RATIO, true,
+ (context, issues) -> context.setLeakValue(100.0 * newDebtDensity(context)),
+ asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST)),
+
+ new IssueMetricFormula(CoreMetrics.NEW_MAINTAINABILITY_RATING, true,
+ (context, issues) -> context.setLeakValue(context.getDebtRatingGrid().getRatingForDensity(
+ newDebtDensity(context))),
+ asList(CoreMetrics.NEW_TECHNICAL_DEBT, CoreMetrics.NEW_DEVELOPMENT_COST)));
+
+ private static final Set<Metric> FORMULA_METRICS = IssueMetricFormulaFactory.extractMetrics(FORMULAS);
+
+ private static double debtDensity(IssueMetricFormula.Context context) {
+ double debt = Math.max(context.getValue(CoreMetrics.TECHNICAL_DEBT).orElse(0.0), 0.0);
+ Optional<Double> devCost = context.getValue(CoreMetrics.DEVELOPMENT_COST);
+ if (devCost.isPresent() && Double.doubleToRawLongBits(devCost.get()) > 0L) {
+ return debt / devCost.get();
+ }
+ return 0d;
+ }
+
+ private static double newDebtDensity(IssueMetricFormula.Context context) {
+ double debt = Math.max(context.getLeakValue(CoreMetrics.NEW_TECHNICAL_DEBT).orElse(0.0), 0.0);
+ Optional<Double> devCost = context.getLeakValue(CoreMetrics.NEW_DEVELOPMENT_COST);
+ if (devCost.isPresent() && Double.doubleToRawLongBits(devCost.get()) > 0L) {
+ return debt / devCost.get();
+ }
+ return 0d;
+ }
+
+ private static double effortToReachMaintainabilityRatingA(IssueMetricFormula.Context context) {
+ double developmentCost = context.getValue(CoreMetrics.DEVELOPMENT_COST).orElse(0.0);
+ double effort = context.getValue(CoreMetrics.TECHNICAL_DEBT).orElse(0.0);
+ double upperGradeCost = context.getDebtRatingGrid().getGradeLowerBound(Rating.B) * developmentCost;
+ return upperGradeCost < effort ? (effort - upperGradeCost) : 0.0;
+ }
+
+ @Override
+ public List<IssueMetricFormula> getFormulas() {
+ return FORMULAS;
+ }
+
+ @Override
+ public Set<Metric> getFormulaMetrics() {
+ return FORMULA_METRICS;
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureComputer.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureComputer.java
new file mode 100644
index 00000000000..74b51a0e0be
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureComputer.java
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Collection;
+import java.util.List;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+
+/**
+ * Refresh and persist the measures of some files, directories, modules
+ * or projects. Measures include status of quality gate.
+ *
+ * Touching a file updates the related directory, module and project.
+ * Status of Quality gate is refreshed but webhooks are not triggered.
+ *
+ * Branches are supported.
+ */
+@ServerSide
+public interface LiveMeasureComputer {
+
+ List<QGChangeEvent> refresh(DbSession dbSession, Collection<ComponentDto> components);
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureComputerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureComputerImpl.java
new file mode 100644
index 00000000000..3f6f986c236
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureComputerImpl.java
@@ -0,0 +1,242 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.measures.Metric;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.SnapshotDto;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.DebtRatingGrid;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
+import org.sonar.server.es.ProjectIndexer;
+import org.sonar.server.es.ProjectIndexers;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+import org.sonar.server.qualitygate.QualityGate;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.settings.ProjectConfigurationLoader;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singleton;
+import static java.util.stream.Collectors.groupingBy;
+import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+
+public class LiveMeasureComputerImpl implements LiveMeasureComputer {
+
+ private final DbClient dbClient;
+ private final IssueMetricFormulaFactory formulaFactory;
+ private final LiveQualityGateComputer qGateComputer;
+ private final ProjectConfigurationLoader projectConfigurationLoader;
+ private final ProjectIndexers projectIndexer;
+
+ public LiveMeasureComputerImpl(DbClient dbClient, IssueMetricFormulaFactory formulaFactory,
+ LiveQualityGateComputer qGateComputer, ProjectConfigurationLoader projectConfigurationLoader, ProjectIndexers projectIndexer) {
+ this.dbClient = dbClient;
+ this.formulaFactory = formulaFactory;
+ this.qGateComputer = qGateComputer;
+ this.projectConfigurationLoader = projectConfigurationLoader;
+ this.projectIndexer = projectIndexer;
+ }
+
+ @Override
+ public List<QGChangeEvent> refresh(DbSession dbSession, Collection<ComponentDto> components) {
+ if (components.isEmpty()) {
+ return emptyList();
+ }
+
+ List<QGChangeEvent> result = new ArrayList<>();
+ Map<String, List<ComponentDto>> componentsByProjectUuid = components.stream().collect(groupingBy(ComponentDto::projectUuid));
+ for (List<ComponentDto> groupedComponents : componentsByProjectUuid.values()) {
+ Optional<QGChangeEvent> qgChangeEvent = refreshComponentsOnSameProject(dbSession, groupedComponents);
+ qgChangeEvent.ifPresent(result::add);
+ }
+ return result;
+ }
+
+ private Optional<QGChangeEvent> refreshComponentsOnSameProject(DbSession dbSession, List<ComponentDto> touchedComponents) {
+ // load all the components to be refreshed, including their ancestors
+ List<ComponentDto> components = loadTreeOfComponents(dbSession, touchedComponents);
+ ComponentDto project = findProject(components);
+ OrganizationDto organization = loadOrganization(dbSession, project);
+ BranchDto branch = loadBranch(dbSession, project);
+
+ Optional<SnapshotDto> lastAnalysis = dbClient.snapshotDao().selectLastAnalysisByRootComponentUuid(dbSession, project.uuid());
+ if (!lastAnalysis.isPresent()) {
+ return Optional.empty();
+ }
+ Optional<Long> beginningOfLeakPeriod = lastAnalysis.map(SnapshotDto::getPeriodDate);
+
+ QualityGate qualityGate = qGateComputer.loadQualityGate(dbSession, organization, project, branch);
+ Collection<String> metricKeys = getKeysOfAllInvolvedMetrics(qualityGate);
+
+ Map<Integer, MetricDto> metricsPerId = dbClient.metricDao().selectByKeys(dbSession, metricKeys).stream()
+ .collect(uniqueIndex(MetricDto::getId));
+ List<String> componentUuids = components.stream().map(ComponentDto::uuid).collect(toArrayList(components.size()));
+ List<LiveMeasureDto> dbMeasures = dbClient.liveMeasureDao().selectByComponentUuidsAndMetricIds(dbSession, componentUuids, metricsPerId.keySet());
+
+ Configuration config = projectConfigurationLoader.loadProjectConfiguration(dbSession, project);
+ DebtRatingGrid debtRatingGrid = new DebtRatingGrid(config);
+
+ MeasureMatrix matrix = new MeasureMatrix(components, metricsPerId.values(), dbMeasures);
+ components.forEach(c -> {
+ IssueCounter issueCounter = new IssueCounter(dbClient.issueDao().selectIssueGroupsByBaseComponent(dbSession, c, beginningOfLeakPeriod.orElse(Long.MAX_VALUE)));
+ FormulaContextImpl context = new FormulaContextImpl(matrix, debtRatingGrid);
+ for (IssueMetricFormula formula : formulaFactory.getFormulas()) {
+ // exclude leak formulas when leak period is not defined
+ if (beginningOfLeakPeriod.isPresent() || !formula.isOnLeak()) {
+ context.change(c, formula);
+ try {
+ formula.compute(context, issueCounter);
+ } catch (RuntimeException e) {
+ throw new IllegalStateException("Fail to compute " + formula.getMetric().getKey() + " on " + context.getComponent().getDbKey(), e);
+ }
+ }
+ }
+ });
+
+ EvaluatedQualityGate evaluatedQualityGate = qGateComputer.refreshGateStatus(project, qualityGate, matrix);
+
+ // persist the measures that have been created or updated
+ matrix.getChanged().forEach(m -> dbClient.liveMeasureDao().insertOrUpdate(dbSession, m, null));
+ projectIndexer.commitAndIndex(dbSession, singleton(project), ProjectIndexer.Cause.MEASURE_CHANGE);
+
+ return Optional.of(new QGChangeEvent(project, branch, lastAnalysis.get(), config, () -> Optional.of(evaluatedQualityGate)));
+ }
+
+ private List<ComponentDto> loadTreeOfComponents(DbSession dbSession, List<ComponentDto> touchedComponents) {
+ Set<String> componentUuids = new HashSet<>();
+ for (ComponentDto component : touchedComponents) {
+ componentUuids.add(component.uuid());
+ // ancestors, excluding self
+ componentUuids.addAll(component.getUuidPathAsList());
+ }
+ // Contrary to the formulas in Compute Engine,
+ // measures do not aggregate values of descendant components.
+ // As a consequence nodes do not need to be sorted. Formulas can be applied
+ // on components in any order.
+ return dbClient.componentDao().selectByUuids(dbSession, componentUuids);
+ }
+
+ private Set<String> getKeysOfAllInvolvedMetrics(QualityGate gate) {
+ Set<String> metricKeys = new HashSet<>();
+ for (Metric metric : formulaFactory.getFormulaMetrics()) {
+ metricKeys.add(metric.getKey());
+ }
+ metricKeys.addAll(qGateComputer.getMetricsRelatedTo(gate));
+ return metricKeys;
+ }
+
+ private static ComponentDto findProject(Collection<ComponentDto> components) {
+ return components.stream().filter(ComponentDto::isRootProject).findFirst()
+ .orElseThrow(() -> new IllegalStateException("No project found in " + components));
+ }
+
+ private BranchDto loadBranch(DbSession dbSession, ComponentDto project) {
+ return dbClient.branchDao().selectByUuid(dbSession, project.uuid())
+ .orElseThrow(() -> new IllegalStateException("Branch not found: " + project.uuid()));
+ }
+
+ private OrganizationDto loadOrganization(DbSession dbSession, ComponentDto project) {
+ String organizationUuid = project.getOrganizationUuid();
+ return dbClient.organizationDao().selectByUuid(dbSession, organizationUuid)
+ .orElseThrow(() -> new IllegalStateException("No organization with UUID " + organizationUuid));
+ }
+
+ private static class FormulaContextImpl implements IssueMetricFormula.Context {
+ private final MeasureMatrix matrix;
+ private final DebtRatingGrid debtRatingGrid;
+ private ComponentDto currentComponent;
+ private IssueMetricFormula currentFormula;
+
+ private FormulaContextImpl(MeasureMatrix matrix, DebtRatingGrid debtRatingGrid) {
+ this.matrix = matrix;
+ this.debtRatingGrid = debtRatingGrid;
+ }
+
+ private void change(ComponentDto component, IssueMetricFormula formula) {
+ this.currentComponent = component;
+ this.currentFormula = formula;
+ }
+
+ @Override
+ public ComponentDto getComponent() {
+ return currentComponent;
+ }
+
+ @Override
+ public DebtRatingGrid getDebtRatingGrid() {
+ return debtRatingGrid;
+ }
+
+ @Override
+ public Optional<Double> getValue(Metric metric) {
+ Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
+ return measure.map(LiveMeasureDto::getValue);
+ }
+
+ @Override
+ public Optional<Double> getLeakValue(Metric metric) {
+ Optional<LiveMeasureDto> measure = matrix.getMeasure(currentComponent, metric.getKey());
+ return measure.map(LiveMeasureDto::getVariation);
+ }
+
+ @Override
+ public void setValue(double value) {
+ String metricKey = currentFormula.getMetric().getKey();
+ checkState(!currentFormula.isOnLeak(), "Formula of metric %s accepts only leak values", metricKey);
+ matrix.setValue(currentComponent, metricKey, value);
+ }
+
+ @Override
+ public void setLeakValue(double value) {
+ String metricKey = currentFormula.getMetric().getKey();
+ checkState(currentFormula.isOnLeak(), "Formula of metric %s does not accept leak values", metricKey);
+ matrix.setLeakValue(currentComponent, metricKey, value);
+ }
+
+ @Override
+ public void setValue(Rating value) {
+ String metricKey = currentFormula.getMetric().getKey();
+ checkState(!currentFormula.isOnLeak(), "Formula of metric %s accepts only leak values", metricKey);
+ matrix.setValue(currentComponent, metricKey, value);
+ }
+
+ @Override
+ public void setLeakValue(Rating value) {
+ String metricKey = currentFormula.getMetric().getKey();
+ checkState(currentFormula.isOnLeak(), "Formula of metric %s does not accept leak values", metricKey);
+ matrix.setLeakValue(currentComponent, metricKey, value);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureModule.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureModule.java
new file mode 100644
index 00000000000..116bd907c09
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveMeasureModule.java
@@ -0,0 +1,32 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 org.sonar.core.platform.Module;
+
+public class LiveMeasureModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(
+ IssueMetricFormulaFactoryImpl.class,
+ LiveMeasureComputerImpl.class,
+ LiveQualityGateComputerImpl.class);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputer.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputer.java
new file mode 100644
index 00000000000..e59d5d359df
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputer.java
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Set;
+import org.sonar.api.server.ServerSide;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+import org.sonar.server.qualitygate.QualityGate;
+
+@ServerSide
+public interface LiveQualityGateComputer {
+
+ QualityGate loadQualityGate(DbSession dbSession, OrganizationDto organization, ComponentDto project, BranchDto branch);
+
+ EvaluatedQualityGate refreshGateStatus(ComponentDto project, QualityGate gate, MeasureMatrix measureMatrix);
+
+ Set<String> getMetricsRelatedTo(QualityGate gate);
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java
new file mode 100644
index 00000000000..b7cf692c7e8
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/LiveQualityGateComputerImpl.java
@@ -0,0 +1,151 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.OptionalDouble;
+import java.util.Set;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.qualitygate.QualityGateConditionDto;
+import org.sonar.db.qualitygate.QualityGateDto;
+import org.sonar.server.qualitygate.Condition;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+import org.sonar.server.qualitygate.QualityGate;
+import org.sonar.server.qualitygate.QualityGateConverter;
+import org.sonar.server.qualitygate.QualityGateEvaluator;
+import org.sonar.server.qualitygate.QualityGateFinder;
+import org.sonar.server.qualitygate.ShortLivingBranchQualityGate;
+
+import static org.sonar.core.util.stream.MoreCollectors.toHashSet;
+import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+
+public class LiveQualityGateComputerImpl implements LiveQualityGateComputer {
+
+ private final DbClient dbClient;
+ private final QualityGateFinder qGateFinder;
+ private final QualityGateEvaluator evaluator;
+
+ public LiveQualityGateComputerImpl(DbClient dbClient, QualityGateFinder qGateFinder, QualityGateEvaluator evaluator) {
+ this.dbClient = dbClient;
+ this.qGateFinder = qGateFinder;
+ this.evaluator = evaluator;
+ }
+
+ @Override
+ public QualityGate loadQualityGate(DbSession dbSession, OrganizationDto organization, ComponentDto project, BranchDto branch) {
+ if (branch.getBranchType() == BranchType.SHORT) {
+ return ShortLivingBranchQualityGate.GATE;
+ }
+
+ ComponentDto mainProject = project.getMainBranchProjectUuid() == null ? project : dbClient.componentDao().selectOrFailByKey(dbSession, project.getKey());
+ QualityGateDto gateDto = qGateFinder.getQualityGate(dbSession, organization, mainProject).getQualityGate();
+ Collection<QualityGateConditionDto> conditionDtos = dbClient.gateConditionDao().selectForQualityGate(dbSession, gateDto.getId());
+ Set<Integer> metricIds = conditionDtos.stream().map(c -> (int) c.getMetricId())
+ .collect(toHashSet(conditionDtos.size()));
+ Map<Integer, MetricDto> metricsById = dbClient.metricDao().selectByIds(dbSession, metricIds).stream()
+ .collect(uniqueIndex(MetricDto::getId));
+
+ Set<Condition> conditions = conditionDtos.stream().map(conditionDto -> {
+ String metricKey = metricsById.get((int) conditionDto.getMetricId()).getKey();
+ Condition.Operator operator = Condition.Operator.fromDbValue(conditionDto.getOperator());
+ boolean onLeak = Objects.equals(conditionDto.getPeriod(), 1);
+ return new Condition(metricKey, operator, conditionDto.getErrorThreshold(), conditionDto.getWarningThreshold(), onLeak);
+ }).collect(toHashSet(conditionDtos.size()));
+
+ return new QualityGate(String.valueOf(gateDto.getId()), gateDto.getName(), conditions);
+ }
+
+ @Override
+ public EvaluatedQualityGate refreshGateStatus(ComponentDto project, QualityGate gate, MeasureMatrix measureMatrix) {
+ QualityGateEvaluator.Measures measures = metricKey -> {
+ Optional<LiveMeasureDto> liveMeasureDto = measureMatrix.getMeasure(project, metricKey);
+ if (!liveMeasureDto.isPresent()) {
+ return Optional.empty();
+ }
+ MetricDto metric = measureMatrix.getMetric(liveMeasureDto.get().getMetricId());
+ return Optional.of(new LiveMeasure(liveMeasureDto.get(), metric));
+ };
+
+ EvaluatedQualityGate evaluatedGate = evaluator.evaluate(gate, measures);
+
+ measureMatrix.setValue(project, CoreMetrics.ALERT_STATUS_KEY, evaluatedGate.getStatus().name());
+ measureMatrix.setValue(project, CoreMetrics.QUALITY_GATE_DETAILS_KEY, QualityGateConverter.toJson(evaluatedGate));
+
+ return evaluatedGate;
+ }
+
+ @Override
+ public Set<String> getMetricsRelatedTo(QualityGate gate) {
+ Set<String> metricKeys = new HashSet<>();
+ metricKeys.add(CoreMetrics.ALERT_STATUS_KEY);
+ metricKeys.add(CoreMetrics.QUALITY_GATE_DETAILS_KEY);
+ metricKeys.addAll(evaluator.getMetricKeys(gate));
+ return metricKeys;
+ }
+
+ private static class LiveMeasure implements QualityGateEvaluator.Measure {
+ private final LiveMeasureDto dto;
+ private final MetricDto metric;
+
+ LiveMeasure(LiveMeasureDto dto, MetricDto metric) {
+ this.dto = dto;
+ this.metric = metric;
+ }
+
+ @Override
+ public Metric.ValueType getType() {
+ return Metric.ValueType.valueOf(metric.getValueType());
+ }
+
+ @Override
+ public OptionalDouble getValue() {
+ if (dto.getValue() == null) {
+ return OptionalDouble.empty();
+ }
+ return OptionalDouble.of(dto.getValue());
+ }
+
+ @Override
+ public Optional<String> getStringValue() {
+ return Optional.ofNullable(dto.getTextValue());
+ }
+
+ @Override
+ public OptionalDouble getLeakValue() {
+ if (dto.getVariation() == null) {
+ return OptionalDouble.empty();
+ }
+ return OptionalDouble.of(dto.getVariation());
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/MeasureMatrix.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/MeasureMatrix.java
new file mode 100644
index 00000000000..0dad8f5b745
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/MeasureMatrix.java
@@ -0,0 +1,203 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 com.google.common.collect.ArrayTable;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Table;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Keep the measures in memory during refresh of live measures:
+ * <ul>
+ * <li>the values of last analysis, restricted to the needed metrics</li>
+ * <li>the refreshed values</li>
+ * </ul>
+ */
+class MeasureMatrix {
+
+ // component uuid -> metric key -> measure
+ private final Table<String, String, MeasureCell> table;
+
+ private final Map<String, MetricDto> metricsByKeys = new HashMap<>();
+ private final Map<Integer, MetricDto> metricsByIds = new HashMap<>();
+
+ MeasureMatrix(Collection<ComponentDto> components, Collection<MetricDto> metrics, List<LiveMeasureDto> dbMeasures) {
+ for (MetricDto metric : metrics) {
+ this.metricsByKeys.put(metric.getKey(), metric);
+ this.metricsByIds.put(metric.getId(), metric);
+ }
+ this.table = ArrayTable.create(Collections2.transform(components, ComponentDto::uuid), metricsByKeys.keySet());
+ for (LiveMeasureDto dbMeasure : dbMeasures) {
+ table.put(dbMeasure.getComponentUuid(), metricsByIds.get(dbMeasure.getMetricId()).getKey(), new MeasureCell(dbMeasure, false));
+ }
+ }
+
+ MetricDto getMetric(int id) {
+ return requireNonNull(metricsByIds.get(id), () -> String.format("Metric with id %d not found", id));
+ }
+
+ private MetricDto getMetric(String key) {
+ return requireNonNull(metricsByKeys.get(key), () -> String.format("Metric with key %s not found", key));
+ }
+
+ Optional<LiveMeasureDto> getMeasure(ComponentDto component, String metricKey) {
+ checkArgument(table.containsColumn(metricKey), "Metric with key %s is not registered", metricKey);
+ MeasureCell cell = table.get(component.uuid(), metricKey);
+ return cell == null ? Optional.empty() : Optional.of(cell.measure);
+ }
+
+ void setValue(ComponentDto component, String metricKey, double value) {
+ changeCell(component, metricKey, m -> {
+ MetricDto metric = getMetric(metricKey);
+ double newValue = scale(metric, value);
+
+ Double initialValue = m.getValue();
+ if (initialValue != null && Double.compare(initialValue, newValue) == 0) {
+ return false;
+ }
+ m.setValue(newValue);
+ Double initialVariation = m.getVariation();
+ if (initialValue != null && initialVariation != null) {
+ double leakInitialValue = initialValue - initialVariation;
+ m.setVariation(scale(metric, value - leakInitialValue));
+ }
+ return true;
+ });
+ }
+
+ void setValue(ComponentDto component, String metricKey, Rating value) {
+ changeCell(component, metricKey, m -> {
+ Double initialValue = m.getValue();
+ if (initialValue != null && Double.compare(initialValue, (double) value.getIndex()) == 0) {
+ return false;
+ }
+ m.setData(value.name());
+ m.setValue((double) value.getIndex());
+
+ Double initialVariation = m.getVariation();
+ if (initialValue != null && initialVariation != null) {
+ double leakInitialValue = initialValue - initialVariation;
+ m.setVariation(value.getIndex() - leakInitialValue);
+ }
+ return true;
+ });
+ }
+
+ void setValue(ComponentDto component, String metricKey, @Nullable String data) {
+ changeCell(component, metricKey, m -> {
+ if (Objects.equals(m.getDataAsString(), data)) {
+ return false;
+ }
+ m.setData(data);
+ return true;
+ });
+ }
+
+ void setLeakValue(ComponentDto component, String metricKey, double variation) {
+ changeCell(component, metricKey, c -> {
+ double newVariation = scale(getMetric(metricKey), variation);
+ if (c.getVariation() != null && Double.compare(c.getVariation(), newVariation) == 0) {
+ return false;
+ }
+ MetricDto metric = metricsByKeys.get(metricKey);
+ c.setVariation(scale(metric, variation));
+ return true;
+ });
+ }
+
+ void setLeakValue(ComponentDto component, String metricKey, Rating variation) {
+ setLeakValue(component, metricKey, (double) variation.getIndex());
+ }
+
+ Stream<LiveMeasureDto> getChanged() {
+ return table.values()
+ .stream()
+ .filter(Objects::nonNull)
+ .filter(MeasureCell::isChanged)
+ .map(MeasureCell::getMeasure);
+ }
+
+ private void changeCell(ComponentDto component, String metricKey, Function<LiveMeasureDto, Boolean> changer) {
+ MeasureCell cell = table.get(component.uuid(), metricKey);
+ if (cell == null) {
+ LiveMeasureDto measure = new LiveMeasureDto()
+ .setComponentUuid(component.uuid())
+ .setProjectUuid(component.projectUuid())
+ .setMetricId(metricsByKeys.get(metricKey).getId());
+ cell = new MeasureCell(measure, true);
+ table.put(component.uuid(), metricKey, cell);
+ changer.apply(cell.getMeasure());
+ } else if (changer.apply(cell.getMeasure())) {
+ cell.setChanged(true);
+ }
+ }
+
+ /**
+ * Round a measure value by applying the scale defined on the metric.
+ * Example: scale(0.1234) returns 0.12 if metric scale is 2
+ */
+ private static double scale(MetricDto metric, double value) {
+ if (metric.getDecimalScale() == null) {
+ return value;
+ }
+ BigDecimal bd = BigDecimal.valueOf(value);
+ return bd.setScale(metric.getDecimalScale(), RoundingMode.HALF_UP).doubleValue();
+ }
+
+ private static class MeasureCell {
+ private final LiveMeasureDto measure;
+ private boolean changed;
+
+ private MeasureCell(LiveMeasureDto measure, boolean changed) {
+ this.measure = measure;
+ this.changed = changed;
+ }
+
+ public LiveMeasureDto getMeasure() {
+ return measure;
+ }
+
+ public boolean isChanged() {
+ return changed;
+ }
+
+ public void setChanged(boolean b) {
+ this.changed = b;
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/live/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/measure/live/package-info.java
new file mode 100644
index 00000000000..4e4f17543be
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/live/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.measure.live;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java
index a4a59852c7f..8ede31ea203 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/ComponentAction.java
@@ -216,7 +216,7 @@ public class ComponentAction implements MeasuresWsAction {
private List<LiveMeasureDto> searchMeasures(DbSession dbSession, ComponentDto component, List<MetricDto> metrics) {
List<Integer> metricIds = Lists.transform(metrics, MetricDto::getId);
- List<LiveMeasureDto> measures = dbClient.liveMeasureDao().selectByComponentUuids(dbSession, singletonList(component.uuid()), metricIds);
+ List<LiveMeasureDto> measures = dbClient.liveMeasureDao().selectByComponentUuidsAndMetricIds(dbSession, singletonList(component.uuid()), metricIds);
addBestValuesToMeasures(measures, component, metrics);
return measures;
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java
index 30424bc006b..b58abdfb97f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/measure/ws/SearchAction.java
@@ -162,7 +162,7 @@ public class SearchAction implements MeasuresWsAction {
}
private List<LiveMeasureDto> searchMeasures() {
- return dbClient.liveMeasureDao().selectByComponentUuids(dbSession,
+ return dbClient.liveMeasureDao().selectByComponentUuidsAndMetricIds(dbSession,
projects.stream().map(ComponentDto::uuid).collect(MoreCollectors.toArrayList(projects.size())),
metrics.stream().map(MetricDto::getId).collect(MoreCollectors.toArrayList(metrics.size())));
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java
index c3b7f556000..1a9ab35efec 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/permission/index/PermissionIndexer.java
@@ -45,7 +45,6 @@ import org.sonar.server.permission.index.PermissionIndexerDao.Dto;
import static java.util.Collections.emptyList;
import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
import static org.sonar.core.util.stream.MoreCollectors.toSet;
-import static org.sonar.server.es.DefaultIndexSettings.REFRESH_IMMEDIATE;
/**
* Populates the types "authorization" of each index requiring project
@@ -100,9 +99,10 @@ public class PermissionIndexer implements ProjectIndexer {
@Override
public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) {
switch (cause) {
+ case MEASURE_CHANGE:
case PROJECT_KEY_UPDATE:
case PROJECT_TAGS_UPDATE:
- // nothing to change, project key and tags are not part of this index
+ // nothing to change. Measures, project key and tags are not part of this index
return emptyList();
case PROJECT_CREATION:
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index dfbee585adf..db08d7499b5 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -69,6 +69,7 @@ import org.sonar.server.health.NodeHealthModule;
import org.sonar.server.issue.AddTagsAction;
import org.sonar.server.issue.AssignAction;
import org.sonar.server.issue.CommentAction;
+import org.sonar.server.issue.IssueChangePostProcessorImpl;
import org.sonar.server.issue.RemoveTagsAction;
import org.sonar.server.issue.SetSeverityAction;
import org.sonar.server.issue.SetTypeAction;
@@ -88,6 +89,7 @@ import org.sonar.server.issue.ws.IssueWsModule;
import org.sonar.server.language.ws.LanguageWs;
import org.sonar.server.measure.custom.ws.CustomMeasuresWsModule;
import org.sonar.server.measure.index.ProjectsEsModule;
+import org.sonar.server.measure.live.LiveMeasureModule;
import org.sonar.server.measure.ws.MeasuresWsModule;
import org.sonar.server.measure.ws.TimeMachineWs;
import org.sonar.server.metric.CoreCustomMetrics;
@@ -409,6 +411,7 @@ public class PlatformLevel4 extends PlatformLevel {
ComponentIndexDefinition.class,
ComponentIndex.class,
ComponentIndexer.class,
+ LiveMeasureModule.class,
FavoriteModule.class,
@@ -444,6 +447,7 @@ public class PlatformLevel4 extends PlatformLevel {
TransitionAction.class,
AddTagsAction.class,
RemoveTagsAction.class,
+ IssueChangePostProcessorImpl.class,
// technical debt
DebtModelPluginRepository.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java
index 26a2691d5ef..3319b92dd4a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/Condition.java
@@ -21,10 +21,13 @@ package org.sonar.server.qualitygate;
import java.util.Objects;
import java.util.Optional;
+import java.util.stream.Stream;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
+import org.sonar.db.qualitygate.QualityGateConditionDto;
+import static com.google.common.base.Strings.emptyToNull;
import static java.util.Objects.requireNonNull;
@Immutable
@@ -44,8 +47,8 @@ public class Condition {
this.metricKey = requireNonNull(metricKey, "metricKey can't be null");
this.operator = requireNonNull(operator, "operator can't be null");
this.onLeakPeriod = onLeakPeriod;
- this.errorThreshold = errorThreshold;
- this.warningThreshold = warningThreshold;
+ this.errorThreshold = emptyToNull(errorThreshold);
+ this.warningThreshold = emptyToNull(warningThreshold);
}
public String getMetricKey() {
@@ -108,7 +111,10 @@ public class Condition {
}
public enum Operator {
- EQUALS("EQ"), NOT_EQUALS("NE"), GREATER_THAN("GT"), LESS_THAN("LT");
+ EQUALS(QualityGateConditionDto.OPERATOR_EQUALS),
+ NOT_EQUALS(QualityGateConditionDto.OPERATOR_NOT_EQUALS),
+ GREATER_THAN(QualityGateConditionDto.OPERATOR_GREATER_THAN),
+ LESS_THAN(QualityGateConditionDto.OPERATOR_LESS_THAN);
private final String dbValue;
@@ -119,5 +125,12 @@ public class Condition {
public String getDbValue() {
return dbValue;
}
+
+ public static Operator fromDbValue(String s) {
+ return Stream.of(values())
+ .filter(o -> o.getDbValue().equals(s))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("Unsupported operator db value: " + s));
+ }
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java
new file mode 100644
index 00000000000..f154d3414fd
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ConditionEvaluator.java
@@ -0,0 +1,181 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.EnumSet;
+import java.util.Optional;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import org.sonar.api.measures.Metric.ValueType;
+import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus;
+
+import static java.util.Optional.of;
+import static org.sonar.api.measures.Metric.ValueType.BOOL;
+import static org.sonar.api.measures.Metric.ValueType.FLOAT;
+import static org.sonar.api.measures.Metric.ValueType.INT;
+import static org.sonar.api.measures.Metric.ValueType.MILLISEC;
+import static org.sonar.api.measures.Metric.ValueType.PERCENT;
+import static org.sonar.api.measures.Metric.ValueType.RATING;
+import static org.sonar.api.measures.Metric.ValueType.WORK_DUR;
+
+class ConditionEvaluator {
+
+ private static final Set<ValueType> NUMERICAL_TYPES = EnumSet.of(BOOL, INT, RATING, FLOAT, MILLISEC, PERCENT, WORK_DUR);
+
+ private ConditionEvaluator() {
+ // prevent instantiation
+ }
+
+ /**
+ * Evaluates the condition for the specified measure
+ */
+ static EvaluatedCondition evaluate(Condition condition, QualityGateEvaluator.Measures measures) {
+ Optional<QualityGateEvaluator.Measure> measure = measures.get(condition.getMetricKey());
+ if (!measure.isPresent()) {
+ return new EvaluatedCondition(condition, EvaluationStatus.OK, null);
+ }
+
+ Optional<Comparable> value = getMeasureValue(condition, measure.get());
+ if (!value.isPresent()) {
+ return new EvaluatedCondition(condition, EvaluationStatus.OK, null);
+ }
+
+ ValueType type = measure.get().getType();
+ return evaluateCondition(condition, type, value.get(), true)
+ .orElseGet(() -> evaluateCondition(condition, type, value.get(), false)
+ .orElseGet(() -> new EvaluatedCondition(condition, EvaluationStatus.OK, value.get().toString())));
+ }
+
+ /**
+ * Evaluates the error or warning condition. Returns empty if threshold or measure value is not defined.
+ */
+ private static Optional<EvaluatedCondition> evaluateCondition(Condition condition, ValueType type, Comparable value, boolean error) {
+ Optional<Comparable> threshold = getThreshold(condition, type, error);
+ if (!threshold.isPresent()) {
+ return Optional.empty();
+ }
+
+ if (reachThreshold(value, threshold.get(), condition)) {
+ EvaluationStatus status = error ? EvaluationStatus.ERROR : EvaluationStatus.WARN;
+ return of(new EvaluatedCondition(condition, status, value.toString()));
+ }
+ return Optional.empty();
+ }
+
+ private static Optional<Comparable> getThreshold(Condition condition, ValueType valueType, boolean error) {
+ Optional<String> valString = error ? condition.getErrorThreshold() : condition.getWarningThreshold();
+ return valString.map(s -> {
+ try {
+ switch (valueType) {
+ case BOOL:
+ return parseInteger(s) == 1;
+ case INT:
+ case RATING:
+ return parseInteger(s);
+ case MILLISEC:
+ case WORK_DUR:
+ return Long.parseLong(s);
+ case FLOAT:
+ case PERCENT:
+ return Double.parseDouble(s);
+ case STRING:
+ case LEVEL:
+ return s;
+ default:
+ throw new IllegalArgumentException(String.format("Unsupported value type %s. Cannot convert condition value", valueType));
+ }
+ } catch (NumberFormatException badValueFormat) {
+ throw new IllegalArgumentException(String.format(
+ "Quality Gate: unable to parse threshold '%s' to compare against %s", s, condition.getMetricKey()));
+ }
+ });
+ }
+
+ private static Optional<Comparable> getMeasureValue(Condition condition, QualityGateEvaluator.Measure measure) {
+ if (condition.isOnLeakPeriod()) {
+ return Optional.ofNullable(getLeakValue(measure));
+ }
+
+ return Optional.ofNullable(getValue(measure));
+ }
+
+ @CheckForNull
+ private static Comparable getValue(QualityGateEvaluator.Measure measure) {
+ if (NUMERICAL_TYPES.contains(measure.getType())) {
+ return measure.getValue().isPresent() ? getNumericValue(measure.getType(), measure.getValue().getAsDouble()) : null;
+ }
+
+ switch (measure.getType()) {
+ case LEVEL:
+ case STRING:
+ case DISTRIB:
+ return measure.getStringValue().orElse(null);
+ default:
+ throw new IllegalArgumentException("Condition on leak period is not allowed for type " + measure.getType());
+ }
+ }
+
+ @CheckForNull
+ private static Comparable getLeakValue(QualityGateEvaluator.Measure measure) {
+ if (NUMERICAL_TYPES.contains(measure.getType())) {
+ return measure.getLeakValue().isPresent() ? getNumericValue(measure.getType(), measure.getLeakValue().getAsDouble()) : null;
+ }
+
+ throw new IllegalArgumentException("Condition on leak period is not allowed for type " + measure.getType());
+ }
+
+ private static Comparable getNumericValue(ValueType type, double value) {
+ switch (type) {
+ case BOOL:
+ return Double.compare(value, 1.0) == 1;
+ case INT:
+ case RATING:
+ return (int) value;
+ case FLOAT:
+ case PERCENT:
+ return value;
+ case MILLISEC:
+ case WORK_DUR:
+ return (long) value;
+ default:
+ throw new IllegalArgumentException("Condition on numeric value is not allowed for type " + type);
+ }
+ }
+
+ private static int parseInteger(String value) {
+ return value.contains(".") ? Integer.parseInt(value.substring(0, value.indexOf('.'))) : Integer.parseInt(value);
+ }
+
+ private static boolean reachThreshold(Comparable measureValue, Comparable threshold, Condition condition) {
+ int comparison = measureValue.compareTo(threshold);
+ switch (condition.getOperator()) {
+ case EQUALS:
+ return comparison == 0;
+ case NOT_EQUALS:
+ return comparison != 0;
+ case GREATER_THAN:
+ return comparison > 0;
+ case LESS_THAN:
+ return comparison < 0;
+ default:
+ throw new IllegalArgumentException(String.format("Unsupported operator '%s'", condition.getOperator()));
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java
index 97aa900139e..b47c176ac92 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedCondition.java
@@ -30,6 +30,7 @@ import static java.util.Objects.requireNonNull;
public class EvaluatedCondition {
private final Condition condition;
private final EvaluationStatus status;
+ @Nullable
private final String value;
public EvaluatedCondition(Condition condition, EvaluationStatus status, @Nullable String value) {
@@ -74,7 +75,7 @@ public class EvaluatedCondition {
return "EvaluatedCondition{" +
"condition=" + condition +
", status=" + status +
- ", value=" + (value == null ? null : '\'' + value + '\'') +
+ ", value=" + (value == null ? null : ('\'' + value + '\'')) +
'}';
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java
index e262edcfb3b..f568d242c76 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/EvaluatedQualityGate.java
@@ -27,6 +27,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
+import org.sonar.api.measures.Metric;
import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus;
import static com.google.common.base.Preconditions.checkArgument;
@@ -35,20 +36,22 @@ import static java.util.Objects.requireNonNull;
@Immutable
public class EvaluatedQualityGate {
private final QualityGate qualityGate;
- private final Status status;
+ private final Metric.Level status;
private final Set<EvaluatedCondition> evaluatedConditions;
+ private final boolean ignoredConditionsOnSmallChangeset;
- private EvaluatedQualityGate(QualityGate qualityGate, Status status, Set<EvaluatedCondition> evaluatedConditions) {
- this.qualityGate = qualityGate;
- this.status = status;
+ private EvaluatedQualityGate(QualityGate qualityGate, Metric.Level status, Set<EvaluatedCondition> evaluatedConditions, boolean ignoredConditionsOnSmallChangeset) {
+ this.qualityGate = requireNonNull(qualityGate, "qualityGate can't be null");
+ this.status = requireNonNull(status, "status can't be null");
this.evaluatedConditions = evaluatedConditions;
+ this.ignoredConditionsOnSmallChangeset = ignoredConditionsOnSmallChangeset;
}
public QualityGate getQualityGate() {
return qualityGate;
}
- public Status getStatus() {
+ public Metric.Level getStatus() {
return status;
}
@@ -56,6 +59,10 @@ public class EvaluatedQualityGate {
return evaluatedConditions;
}
+ public boolean hasIgnoredConditionsOnSmallChangeset() {
+ return ignoredConditionsOnSmallChangeset;
+ }
+
public static Builder newBuilder() {
return new Builder();
}
@@ -90,20 +97,26 @@ public class EvaluatedQualityGate {
public static final class Builder {
private QualityGate qualityGate;
- private Status status;
+ private Metric.Level status;
private final Map<Condition, EvaluatedCondition> evaluatedConditions = new HashMap<>();
+ private boolean ignoredConditionsOnSmallChangeset = false;
private Builder() {
// use static factory method
}
public Builder setQualityGate(QualityGate qualityGate) {
- this.qualityGate = checkQualityGate(qualityGate);
+ this.qualityGate = qualityGate;
+ return this;
+ }
+
+ public Builder setStatus(Metric.Level status) {
+ this.status = status;
return this;
}
- public Builder setStatus(Status status) {
- this.status = checkStatus(status);
+ public Builder setIgnoredConditionsOnSmallChangeset(boolean b) {
+ this.ignoredConditionsOnSmallChangeset = b;
return this;
}
@@ -112,18 +125,21 @@ public class EvaluatedQualityGate {
return this;
}
+ public Builder addCondition(EvaluatedCondition c) {
+ evaluatedConditions.put(c.getCondition(), c);
+ return this;
+ }
+
public Set<EvaluatedCondition> getEvaluatedConditions() {
return ImmutableSet.copyOf(evaluatedConditions.values());
}
public EvaluatedQualityGate build() {
- checkQualityGate(this.qualityGate);
- checkStatus(this.status);
-
return new EvaluatedQualityGate(
this.qualityGate,
this.status,
- checkEvaluatedConditions(qualityGate, evaluatedConditions));
+ checkEvaluatedConditions(qualityGate, evaluatedConditions),
+ ignoredConditionsOnSmallChangeset);
}
private static Set<EvaluatedCondition> checkEvaluatedConditions(QualityGate qualityGate, Map<Condition, EvaluatedCondition> evaluatedConditions) {
@@ -141,19 +157,5 @@ public class EvaluatedQualityGate {
return ImmutableSet.copyOf(evaluatedConditions.values());
}
-
- private static QualityGate checkQualityGate(@Nullable QualityGate qualityGate) {
- return requireNonNull(qualityGate, "qualityGate can't be null");
- }
-
- private static Status checkStatus(@Nullable Status status) {
- return requireNonNull(status, "status can't be null");
- }
- }
-
- public enum Status {
- OK,
- WARN,
- ERROR
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/LiveQualityGateFactoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/LiveQualityGateFactoryImpl.java
deleted file mode 100644
index 559827250bf..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/LiveQualityGateFactoryImpl.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-import org.elasticsearch.action.search.SearchResponse;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.System2;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.qualitygate.QualityGateConditionDto;
-import org.sonar.server.es.Facets;
-import org.sonar.server.es.SearchOptions;
-import org.sonar.server.issue.IssueQuery;
-import org.sonar.server.issue.index.IssueIndex;
-import org.sonar.server.rule.index.RuleIndex;
-
-import static java.lang.String.format;
-import static java.lang.String.valueOf;
-import static java.util.Collections.singletonList;
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
-
-public class LiveQualityGateFactoryImpl implements LiveQualityGateFactory {
-
- private final IssueIndex issueIndex;
- private final System2 system2;
-
- public LiveQualityGateFactoryImpl(IssueIndex issueIndex, System2 system2) {
- this.issueIndex = issueIndex;
- this.system2 = system2;
- }
-
- @Override
- public EvaluatedQualityGate buildForShortLivedBranch(ComponentDto componentDto) {
- return createQualityGate(componentDto, issueIndex);
- }
-
- private EvaluatedQualityGate createQualityGate(ComponentDto project, IssueIndex issueIndex) {
- SearchResponse searchResponse = issueIndex.search(IssueQuery.builder()
- .projectUuids(singletonList(project.getMainBranchProjectUuid()))
- .branchUuid(project.uuid())
- .mainBranch(false)
- .resolved(false)
- .checkAuthorization(false)
- .build(),
- new SearchOptions().addFacets(RuleIndex.FACET_TYPES));
- LinkedHashMap<String, Long> typeFacet = new Facets(searchResponse, system2.getDefaultTimeZone())
- .get(RuleIndex.FACET_TYPES);
-
- EvaluatedQualityGate.Builder builder = EvaluatedQualityGate.newBuilder();
- Set<Condition> conditions = ShortLivingBranchQualityGate.CONDITIONS.stream()
- .map(c -> {
- long measure = getMeasure(typeFacet, c);
- EvaluatedCondition.EvaluationStatus status = measure > 0 ? EvaluatedCondition.EvaluationStatus.ERROR : EvaluatedCondition.EvaluationStatus.OK;
- Condition condition = new Condition(c.getMetricKey(), toOperator(c), c.getErrorThreshold(), c.getWarnThreshold(), c.isOnLeak());
- builder.addCondition(condition, status, valueOf(measure));
- return condition;
- })
- .collect(toSet(ShortLivingBranchQualityGate.CONDITIONS.size()));
- builder
- .setQualityGate(
- new org.sonar.server.qualitygate.QualityGate(
- valueOf(ShortLivingBranchQualityGate.ID),
- ShortLivingBranchQualityGate.NAME,
- conditions))
- .setStatus(qgStatusFrom(builder.getEvaluatedConditions()));
-
- return builder.build();
- }
-
- private static Condition.Operator toOperator(ShortLivingBranchQualityGate.Condition c) {
- String operator = c.getOperator();
- switch (operator) {
- case QualityGateConditionDto.OPERATOR_GREATER_THAN:
- return Condition.Operator.GREATER_THAN;
- case QualityGateConditionDto.OPERATOR_LESS_THAN:
- return Condition.Operator.LESS_THAN;
- case QualityGateConditionDto.OPERATOR_EQUALS:
- return Condition.Operator.EQUALS;
- case QualityGateConditionDto.OPERATOR_NOT_EQUALS:
- return Condition.Operator.NOT_EQUALS;
- default:
- throw new IllegalArgumentException(format("Unsupported Condition operator '%s'", operator));
- }
- }
-
- private static EvaluatedQualityGate.Status qgStatusFrom(Set<EvaluatedCondition> conditions) {
- if (conditions.stream().anyMatch(c -> c.getStatus() == EvaluatedCondition.EvaluationStatus.ERROR)) {
- return EvaluatedQualityGate.Status.ERROR;
- }
- return EvaluatedQualityGate.Status.OK;
- }
-
- private static long getMeasure(LinkedHashMap<String, Long> typeFacet, ShortLivingBranchQualityGate.Condition c) {
- String metricKey = c.getMetricKey();
- switch (metricKey) {
- case CoreMetrics.BUGS_KEY:
- return getValueForRuleType(typeFacet, RuleType.BUG);
- case CoreMetrics.VULNERABILITIES_KEY:
- return getValueForRuleType(typeFacet, RuleType.VULNERABILITY);
- case CoreMetrics.CODE_SMELLS_KEY:
- return getValueForRuleType(typeFacet, RuleType.CODE_SMELL);
- default:
- throw new IllegalArgumentException(format("Unsupported metric key '%s' in hardcoded quality gate", metricKey));
- }
- }
-
- private static long getValueForRuleType(Map<String, Long> facet, RuleType ruleType) {
- Long res = facet.get(ruleType.name());
- if (res == null) {
- return 0L;
- }
- return res;
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java
index a269d519ad2..9d56caabfb7 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java
@@ -34,7 +34,7 @@ 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.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
import org.sonar.server.exceptions.NotFoundException;
import static com.google.common.base.Strings.isNullOrEmpty;
@@ -44,7 +44,7 @@ import static java.util.Arrays.stream;
import static org.sonar.api.measures.Metric.ValueType.RATING;
import static org.sonar.api.measures.Metric.ValueType.valueOf;
import static org.sonar.db.qualitygate.QualityGateConditionDto.isOperatorAllowed;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.E;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.E;
import static org.sonar.server.qualitygate.ValidRatingMetrics.isCoreRatingMetric;
import static org.sonar.server.ws.WsUtils.checkRequest;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateConverter.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateConverter.java
new file mode 100644
index 00000000000..785ac31bb40
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateConverter.java
@@ -0,0 +1,61 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+public class QualityGateConverter {
+
+ private static final String FIELD_LEVEL = "level";
+ private static final String FIELD_IGNORED_CONDITIONS = "ignoredConditions";
+
+ private QualityGateConverter() {
+ // prevent instantiation
+ }
+
+ public static String toJson(EvaluatedQualityGate gate) {
+ JsonObject details = new JsonObject();
+ details.addProperty(FIELD_LEVEL, gate.getStatus().name());
+ JsonArray conditionResults = new JsonArray();
+ for (EvaluatedCondition condition : gate.getEvaluatedConditions()) {
+ conditionResults.add(toJson(condition));
+ }
+ details.add("conditions", conditionResults);
+ details.addProperty(FIELD_IGNORED_CONDITIONS, gate.hasIgnoredConditionsOnSmallChangeset());
+ return details.toString();
+ }
+
+ private static JsonObject toJson(EvaluatedCondition evaluatedCondition) {
+ Condition condition = evaluatedCondition.getCondition();
+
+ JsonObject result = new JsonObject();
+ result.addProperty("metric", condition.getMetricKey());
+ result.addProperty("op", condition.getOperator().getDbValue());
+ if (condition.isOnLeakPeriod()) {
+ result.addProperty("period", 1);
+ }
+ condition.getWarningThreshold().ifPresent(t -> result.addProperty("warning", t));
+ condition.getErrorThreshold().ifPresent(t -> result.addProperty("error", t));
+ evaluatedCondition.getValue().ifPresent(v -> result.addProperty("actual", v));
+ result.addProperty(FIELD_LEVEL, evaluatedCondition.getStatus().name());
+ return result;
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluator.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluator.java
new file mode 100644
index 00000000000..3ecf03d08dd
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluator.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Optional;
+import java.util.OptionalDouble;
+import java.util.Set;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.server.ServerSide;
+
+@ComputeEngineSide
+@ServerSide
+public interface QualityGateEvaluator {
+
+ /**
+ * @param measures must provide the measures related to the metrics
+ * defined by {@link #getMetricKeys(QualityGate)}
+ */
+ EvaluatedQualityGate evaluate(QualityGate gate, Measures measures);
+
+ /**
+ * Keys of the metrics involved in the computation of gate status.
+ * It may include metrics that are not part of conditions,
+ * for instance "new_lines" for the circuit-breaker on
+ * small changesets.
+ */
+ Set<String> getMetricKeys(QualityGate gate);
+
+ interface Measures {
+ Optional<Measure> get(String metricKey);
+ }
+
+ interface Measure {
+ Metric.ValueType getType();
+
+ OptionalDouble getValue();
+
+ Optional<String> getStringValue();
+
+ OptionalDouble getLeakValue();
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java
new file mode 100644
index 00000000000..f0ce9449320
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateEvaluatorImpl.java
@@ -0,0 +1,133 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric.Level;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.server.qualitygate.EvaluatedCondition.EvaluationStatus;
+
+import static java.util.Objects.requireNonNull;
+import static org.sonar.core.util.stream.MoreCollectors.toEnumSet;
+
+public class QualityGateEvaluatorImpl implements QualityGateEvaluator {
+
+ private static final int MAXIMUM_NEW_LINES_FOR_SMALL_CHANGESETS = 20;
+ /**
+ * Some metrics will be ignored on very small change sets.
+ */
+ private static final Set<String> METRICS_TO_IGNORE_ON_SMALL_CHANGESETS = ImmutableSet.of(
+ CoreMetrics.NEW_COVERAGE_KEY,
+ CoreMetrics.NEW_LINE_COVERAGE_KEY,
+ CoreMetrics.NEW_BRANCH_COVERAGE_KEY,
+ CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY,
+ CoreMetrics.NEW_DUPLICATED_LINES_KEY,
+ CoreMetrics.NEW_BLOCKS_DUPLICATED_KEY);
+
+ @Override
+ public EvaluatedQualityGate evaluate(QualityGate gate, Measures measures) {
+ EvaluatedQualityGate.Builder result = EvaluatedQualityGate.newBuilder()
+ .setQualityGate(gate);
+
+ boolean isSmallChangeset = isSmallChangeset(measures);
+ Multimap<String, Condition> conditionsPerMetric = gate.getConditions().stream()
+ .collect(MoreCollectors.index(Condition::getMetricKey, Function.identity()));
+
+ for (Map.Entry<String, Collection<Condition>> entry : conditionsPerMetric.asMap().entrySet()) {
+ String metricKey = entry.getKey();
+ Collection<Condition> conditionsOnSameMetric = entry.getValue();
+
+ EvaluatedCondition evaluation = evaluateConditionsOnMetric(conditionsOnSameMetric, measures);
+
+ if (isSmallChangeset && evaluation.getStatus() != EvaluationStatus.OK && METRICS_TO_IGNORE_ON_SMALL_CHANGESETS.contains(metricKey)) {
+ result.addCondition(new EvaluatedCondition(evaluation.getCondition(), EvaluationStatus.OK, evaluation.getValue().orElse(null)));
+ result.setIgnoredConditionsOnSmallChangeset(true);
+ } else {
+ result.addCondition(evaluation);
+ }
+ }
+
+ result.setStatus(overallStatusOf(result.getEvaluatedConditions()));
+
+ return result.build();
+ }
+
+ @Override
+ public Set<String> getMetricKeys(QualityGate gate) {
+ Set<String> metricKeys = new HashSet<>();
+ metricKeys.add(CoreMetrics.NEW_LINES_KEY);
+ for (Condition condition : gate.getConditions()) {
+ metricKeys.add(condition.getMetricKey());
+ }
+ return metricKeys;
+ }
+
+ private static boolean isSmallChangeset(Measures measures) {
+ Optional<Measure> newLines = measures.get(CoreMetrics.NEW_LINES_KEY);
+ return newLines.isPresent() &&
+ newLines.get().getLeakValue().isPresent() &&
+ newLines.get().getLeakValue().getAsDouble() < MAXIMUM_NEW_LINES_FOR_SMALL_CHANGESETS;
+ }
+
+ private static EvaluatedCondition evaluateConditionsOnMetric(Collection<Condition> conditionsOnSameMetric, Measures measures) {
+ EvaluatedCondition leakEvaluation = null;
+ EvaluatedCondition absoluteEvaluation = null;
+ for (Condition condition : conditionsOnSameMetric) {
+ if (condition.isOnLeakPeriod()) {
+ leakEvaluation = ConditionEvaluator.evaluate(condition, measures);
+ } else {
+ absoluteEvaluation = ConditionEvaluator.evaluate(condition, measures);
+ }
+ }
+
+ if (leakEvaluation == null) {
+ return requireNonNull(absoluteEvaluation, "Evaluation of absolute value can't be null on conditions " + conditionsOnSameMetric);
+ }
+ if (absoluteEvaluation == null) {
+ return requireNonNull(leakEvaluation, "Evaluation of leak value can't be null on conditions " + conditionsOnSameMetric);
+ }
+ // both conditions are present. Take the worse one. In case of equality, take
+ // the one on the leak period
+ if (absoluteEvaluation.getStatus().compareTo(leakEvaluation.getStatus()) > 0) {
+ return absoluteEvaluation;
+ }
+ return leakEvaluation;
+ }
+
+ private static Level overallStatusOf(Set<EvaluatedCondition> conditions) {
+ Set<EvaluationStatus> statuses = conditions.stream().map(EvaluatedCondition::getStatus).collect(toEnumSet(EvaluationStatus.class));
+ if (statuses.contains(EvaluationStatus.ERROR)) {
+ return Level.ERROR;
+ }
+ if (statuses.contains(EvaluationStatus.WARN)) {
+ return Level.WARN;
+ }
+ return Level.OK;
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java
index 68dc868d3ef..d1abced201e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateFinder.java
@@ -22,6 +22,7 @@ package org.sonar.server.qualitygate;
import java.util.Optional;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.qualitygate.QGateWithOrgDto;
import org.sonar.db.qualitygate.QualityGateDto;
@@ -44,16 +45,15 @@ public class QualityGateFinder {
*
* It will first try to get the quality gate explicitly defined on a project, if none it will try to return default quality gate ofI the organization
*/
- public QualityGateData getQualityGate(DbSession dbSession, OrganizationDto organization, long componentId) {
- Optional<Long> qualityGateId = dbClient.projectQgateAssociationDao().selectQGateIdByComponentId(dbSession, componentId);
+ public QualityGateData getQualityGate(DbSession dbSession, OrganizationDto organization, ComponentDto component) {
+ Optional<Long> qualityGateId = dbClient.projectQgateAssociationDao().selectQGateIdByComponentId(dbSession, component.getId());
if (qualityGateId.isPresent()) {
QualityGateDto qualityGate = checkFound(dbClient.qualityGateDao().selectById(dbSession, qualityGateId.get()), "No quality gate has been found for id %s", qualityGateId);
return new QualityGateData(qualityGate, false);
- } else {
- QualityGateDto defaultQualityGate = dbClient.qualityGateDao().selectByOrganizationAndUuid(dbSession, organization, organization.getDefaultQualityGateUuid());
- checkState(defaultQualityGate != null, "Unable to find the quality gate [%s] for organization [%s]", organization.getDefaultQualityGateUuid(), organization.getUuid());
- return new QualityGateData(defaultQualityGate, true);
}
+ QualityGateDto defaultQualityGate = dbClient.qualityGateDao().selectByOrganizationAndUuid(dbSession, organization, organization.getDefaultQualityGateUuid());
+ checkState(defaultQualityGate != null, "Unable to find the quality gate [%s] for organization [%s]", organization.getDefaultQualityGateUuid(), organization.getUuid());
+ return new QualityGateData(defaultQualityGate, true);
}
public QGateWithOrgDto getByOrganizationAndId(DbSession dbSession, OrganizationDto organization, long qualityGateId) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java
index 6a7626518b0..46f48b32c98 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/QualityGateModule.java
@@ -45,6 +45,7 @@ public class QualityGateModule extends Module {
QualityGateUpdater.class,
QualityGateConditionsUpdater.class,
QualityGateFinder.class,
+ QualityGateEvaluatorImpl.class,
// WS
QualityGatesWsSupport.class,
QualityGatesWs.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java
index 9cc85232103..3effb8aa841 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/RegisterQualityGates.java
@@ -39,7 +39,7 @@ import org.sonar.db.qualitygate.QualityGateConditionDao;
import org.sonar.db.qualitygate.QualityGateConditionDto;
import org.sonar.db.qualitygate.QualityGateDao;
import org.sonar.db.qualitygate.QualityGateDto;
-import org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toMap;
@@ -57,7 +57,7 @@ public class RegisterQualityGates implements Startable {
private static final String BUILTIN_QUALITY_GATE_NAME = "Sonar way";
private static final int LEAK_PERIOD = 1;
- private static final String A_RATING = Integer.toString(RatingGrid.Rating.A.getIndex());
+ private static final String A_RATING = Integer.toString(Rating.A.getIndex());
private static final List<QualityGateCondition> QUALITY_GATE_CONDITIONS = asList(
new QualityGateCondition().setMetricKey(NEW_SECURITY_RATING_KEY).setOperator(OPERATOR_GREATER_THAN).setPeriod(LEAK_PERIOD).setErrorThreshold(A_RATING),
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java
index 336568df8eb..b086408aa92 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ShortLivingBranchQualityGate.java
@@ -20,6 +20,7 @@
package org.sonar.server.qualitygate;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import java.util.List;
import javax.annotation.CheckForNull;
import org.sonar.api.measures.CoreMetrics;
@@ -37,6 +38,11 @@ public final class ShortLivingBranchQualityGate {
new Condition(CoreMetrics.VULNERABILITIES_KEY, OPERATOR_GREATER_THAN, "0", false),
new Condition(CoreMetrics.CODE_SMELLS_KEY, OPERATOR_GREATER_THAN, "0", false));
+ public static final QualityGate GATE = new QualityGate(String.valueOf(ID), NAME, ImmutableSet.of(
+ new org.sonar.server.qualitygate.Condition(CoreMetrics.BUGS_KEY, org.sonar.server.qualitygate.Condition.Operator.GREATER_THAN, "0", null, false),
+ new org.sonar.server.qualitygate.Condition(CoreMetrics.VULNERABILITIES_KEY, org.sonar.server.qualitygate.Condition.Operator.GREATER_THAN, "0", null, false),
+ new org.sonar.server.qualitygate.Condition(CoreMetrics.CODE_SMELLS_KEY, org.sonar.server.qualitygate.Condition.Operator.GREATER_THAN, "0", null, false)));
+
private ShortLivingBranchQualityGate() {
// prevents instantiation
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactory.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactory.java
deleted file mode 100644
index 007204554ac..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactory.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.changeevent;
-
-import com.google.common.collect.ImmutableList;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import org.sonar.api.rules.RuleType;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.server.issue.ws.SearchResponseData;
-
-import static com.google.common.base.Preconditions.checkArgument;
-
-public interface QGChangeEventFactory {
- /**
- * Will call webhooks once for any short living branch which has at least one issue in {@link SearchResponseData} and
- * if change described in {@link IssueChange} can alter the status of the short living branch.
- */
- List<QGChangeEvent> from(IssueChangeData issueChangeData, IssueChange issueChange, IssueChangeContext context);
-
- final class IssueChange {
- private final RuleType ruleType;
- private final String transitionKey;
-
- public IssueChange(RuleType ruleType) {
- this(ruleType, null);
- }
-
- public IssueChange(String transitionKey) {
- this(null, transitionKey);
- }
-
- public IssueChange(@Nullable RuleType ruleType, @Nullable String transitionKey) {
- checkArgument(ruleType != null || transitionKey != null, "At least one of ruleType and transitionKey must be non null");
- this.ruleType = ruleType;
- this.transitionKey = transitionKey;
- }
-
- public Optional<RuleType> getRuleType() {
- return Optional.ofNullable(ruleType);
- }
-
- public Optional<String> getTransitionKey() {
- return Optional.ofNullable(transitionKey);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- IssueChange that = (IssueChange) o;
- return ruleType == that.ruleType &&
- Objects.equals(transitionKey, that.transitionKey);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(ruleType, transitionKey);
- }
-
- @Override
- public String toString() {
- return "IssueChange{" +
- "ruleType=" + ruleType +
- ", transitionKey='" + transitionKey + '\'' +
- '}';
- }
- }
-
- final class IssueChangeData {
- private final List<DefaultIssue> issues;
- private final List<ComponentDto> components;
-
- public IssueChangeData(List<DefaultIssue> issues, List<ComponentDto> components) {
- this.issues = ImmutableList.copyOf(issues);
- this.components = ImmutableList.copyOf(components);
- }
-
- public List<DefaultIssue> getIssues() {
- return issues;
- }
-
- public List<ComponentDto> getComponents() {
- return components;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- IssueChangeData that = (IssueChangeData) o;
- return Objects.equals(issues, that.issues) &&
- Objects.equals(components, that.components);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(issues, components);
- }
-
- @Override
- public String toString() {
- return "IssueChangeData{" +
- "issues=" + issues +
- ", components=" + components +
- '}';
- }
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryImpl.java
deleted file mode 100644
index b1f32639bd8..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryImpl.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.changeevent;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
-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 org.sonar.api.config.Configuration;
-import org.sonar.api.issue.DefaultTransitions;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.server.qualitygate.LiveQualityGateFactory;
-import org.sonar.server.settings.ProjectConfigurationLoader;
-
-import static java.util.Collections.emptyList;
-import static org.sonar.core.util.stream.MoreCollectors.toSet;
-import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
-
-public class QGChangeEventFactoryImpl implements QGChangeEventFactory {
- private static final Set<String> MEANINGFUL_TRANSITIONS = ImmutableSet.of(
- DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
- private final DbClient dbClient;
- private final ProjectConfigurationLoader projectConfigurationLoader;
- private final LiveQualityGateFactory liveQualityGateFactory;
-
- public QGChangeEventFactoryImpl(DbClient dbClient, ProjectConfigurationLoader projectConfigurationLoader,
- LiveQualityGateFactory liveQualityGateFactory) {
- this.dbClient = dbClient;
- this.projectConfigurationLoader = projectConfigurationLoader;
- this.liveQualityGateFactory = liveQualityGateFactory;
- }
-
- @Override
- public List<QGChangeEvent> from(IssueChangeData issueChangeData, IssueChange issueChange, IssueChangeContext context) {
- if (isEmpty(issueChangeData) || !isUserChangeContext(context) || !isRelevant(issueChange)) {
- return emptyList();
- }
-
- return from(issueChangeData);
- }
-
- private static boolean isRelevant(IssueChange issueChange) {
- return issueChange.getTransitionKey().map(QGChangeEventFactoryImpl::isMeaningfulTransition).orElse(true);
- }
-
- private static boolean isEmpty(IssueChangeData issueChangeData) {
- return issueChangeData.getIssues().isEmpty();
- }
-
- private static boolean isUserChangeContext(IssueChangeContext context) {
- return context.login() != null;
- }
-
- private static boolean isMeaningfulTransition(String transitionKey) {
- return MEANINGFUL_TRANSITIONS.contains(transitionKey);
- }
-
- private List<QGChangeEvent> from(IssueChangeData issueChangeData) {
- try (DbSession dbSession = dbClient.openSession(false)) {
- Map<String, ComponentDto> branchesByUuid = getBranchComponents(dbSession, issueChangeData);
- if (branchesByUuid.isEmpty()) {
- return emptyList();
- }
-
- Set<String> branchProjectUuids = branchesByUuid.values().stream()
- .map(ComponentDto::uuid)
- .collect(toSet(branchesByUuid.size()));
- Set<BranchDto> shortBranches = dbClient.branchDao().selectByUuids(dbSession, branchProjectUuids)
- .stream()
- .filter(branchDto -> branchDto.getBranchType() == BranchType.SHORT)
- .collect(toSet(branchesByUuid.size()));
- if (shortBranches.isEmpty()) {
- return emptyList();
- }
-
- Map<String, Configuration> configurationByUuid = projectConfigurationLoader.loadProjectConfigurations(dbSession,
- shortBranches.stream().map(shortBranch -> branchesByUuid.get(shortBranch.getUuid())).collect(Collectors.toSet()));
- Set<String> shortBranchesComponentUuids = shortBranches.stream().map(BranchDto::getUuid).collect(toSet(shortBranches.size()));
- Map<String, SnapshotDto> analysisByProjectUuid = dbClient.snapshotDao().selectLastAnalysesByRootComponentUuids(
- dbSession,
- shortBranchesComponentUuids)
- .stream()
- .collect(uniqueIndex(SnapshotDto::getComponentUuid));
-
- return shortBranches
- .stream()
- .map(shortBranch -> {
- ComponentDto branch = branchesByUuid.get(shortBranch.getUuid());
- SnapshotDto analysis = analysisByProjectUuid.get(shortBranch.getUuid());
- if (branch != null && analysis != null) {
- Configuration configuration = configurationByUuid.get(shortBranch.getUuid());
-
- return new QGChangeEvent(branch, shortBranch, analysis, configuration,
- () -> Optional.of(liveQualityGateFactory.buildForShortLivedBranch(branch)));
- }
- return null;
- })
- .filter(Objects::nonNull)
- .collect(MoreCollectors.toList(shortBranches.size()));
- }
- }
-
- private Map<String, ComponentDto> getBranchComponents(DbSession dbSession, IssueChangeData issueChangeData) {
- Set<String> projectUuids = issueChangeData.getIssues().stream()
- .map(DefaultIssue::projectUuid)
- .collect(toSet());
- Set<String> missingProjectUuids = ImmutableSet.copyOf(Sets.difference(
- projectUuids,
- issueChangeData.getComponents()
- .stream()
- .map(ComponentDto::uuid)
- .collect(Collectors.toSet())));
- if (missingProjectUuids.isEmpty()) {
- return issueChangeData.getComponents()
- .stream()
- .filter(c -> projectUuids.contains(c.uuid()))
- .filter(componentDto -> componentDto.getMainBranchProjectUuid() != null)
- .collect(uniqueIndex(ComponentDto::uuid));
- }
- return Stream.concat(
- issueChangeData.getComponents().stream().filter(c -> projectUuids.contains(c.uuid())),
- dbClient.componentDao().selectByUuids(dbSession, missingProjectUuids).stream())
- .filter(componentDto -> componentDto.getMainBranchProjectUuid() != null)
- .collect(uniqueIndex(ComponentDto::uuid));
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListeners.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListeners.java
index 791164c7cd9..46921a78348 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListeners.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListeners.java
@@ -20,8 +20,10 @@
package org.sonar.server.qualitygate.changeevent;
import java.util.Collection;
+import java.util.List;
+import org.sonar.core.issue.DefaultIssue;
public interface QGChangeEventListeners {
- void broadcastOnIssueChange(QGChangeEventFactory.IssueChangeData issueChangeData, Collection<QGChangeEvent> qgChangeEvents);
+ void broadcastOnIssueChange(List<DefaultIssue> changedIssues, Collection<QGChangeEvent> qgChangeEvents);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java
index f471916f41c..b2fe05c836f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImpl.java
@@ -22,6 +22,7 @@ package org.sonar.server.qualitygate.changeevent;
import com.google.common.collect.Multimap;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import java.util.Set;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.log.Logger;
@@ -57,15 +58,15 @@ public class QGChangeEventListenersImpl implements QGChangeEventListeners {
}
@Override
- public void broadcastOnIssueChange(QGChangeEventFactory.IssueChangeData issueChangeData, Collection<QGChangeEvent> changeEvents) {
- if (listeners.length == 0 || issueChangeData.getComponents().isEmpty() || issueChangeData.getIssues().isEmpty() || changeEvents.isEmpty()) {
+ public void broadcastOnIssueChange(List<DefaultIssue> issues, Collection<QGChangeEvent> changeEvents) {
+ if (listeners.length == 0 || issues.isEmpty() || changeEvents.isEmpty()) {
return;
}
try {
Multimap<String, QGChangeEvent> eventsByComponentUuid = changeEvents.stream()
.collect(MoreCollectors.index(t -> t.getProject().uuid()));
- Multimap<String, DefaultIssue> issueByComponentUuid = issueChangeData.getIssues().stream()
+ Multimap<String, DefaultIssue> issueByComponentUuid = issues.stream()
.collect(MoreCollectors.index(DefaultIssue::projectUuid));
issueByComponentUuid.asMap()
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/GetByProjectAction.java b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/GetByProjectAction.java
index c5d07861053..5b78ed31c9f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/GetByProjectAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/qualitygate/ws/GetByProjectAction.java
@@ -99,7 +99,7 @@ public class GetByProjectAction implements QualityGatesWsAction {
throw insufficientPrivilegesException();
}
- QualityGateData data = qualityGateFinder.getQualityGate(dbSession, organization, project.getId());
+ QualityGateData data = qualityGateFinder.getQualityGate(dbSession, organization, project);
writeProtobuf(buildResponse(data), request, response);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/settings/ProjectConfigurationLoader.java b/server/sonar-server/src/main/java/org/sonar/server/settings/ProjectConfigurationLoader.java
index 356eb87c43b..32123e9ac05 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/settings/ProjectConfigurationLoader.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/settings/ProjectConfigurationLoader.java
@@ -19,12 +19,16 @@
*/
package org.sonar.server.settings;
+import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.sonar.api.config.Configuration;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
public interface ProjectConfigurationLoader {
/**
* Loads configuration for the specified components.
@@ -37,4 +41,9 @@ public interface ProjectConfigurationLoader {
* Any component is accepted but SQ only supports specific properties for projects and branches.
*/
Map<String, Configuration> loadProjectConfigurations(DbSession dbSession, Set<ComponentDto> projects);
+
+ default Configuration loadProjectConfiguration(DbSession dbSession, ComponentDto project) {
+ Map<String, Configuration> configurations = loadProjectConfigurations(dbSession, Collections.singleton(project));
+ return requireNonNull(configurations.get(project.uuid()), () -> format("Configuration for project '%s' is not found", project.getKey()));
+ }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/test/index/TestIndexer.java b/server/sonar-server/src/main/java/org/sonar/server/test/index/TestIndexer.java
index 5c96aa6479d..01e8bcbaa08 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/test/index/TestIndexer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/test/index/TestIndexer.java
@@ -93,12 +93,11 @@ public class TestIndexer implements ProjectIndexer {
switch (cause) {
case PROJECT_CREATION:
// no tests at that time
- return emptyList();
-
+ case MEASURE_CHANGE:
case PROJECT_KEY_UPDATE:
case PROJECT_TAGS_UPDATE:
case PERMISSION_CHANGE:
- // project key, tags and permissions are not part of tests/test
+ // Measures, project key, tags and permissions are not part of tests/test
return emptyList();
case PROJECT_DELETION:
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentAction.java b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentAction.java
index 79ea6d75af9..580a62cd94a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/ui/ws/ComponentAction.java
@@ -207,7 +207,7 @@ public class ComponentAction implements NavigationWsAction {
}
private void writeQualityGate(JsonWriter json, DbSession session, OrganizationDto organization, ComponentDto component) {
- QualityGateFinder.QualityGateData qualityGateData = qualityGateFinder.getQualityGate(session, organization, component.getId());
+ QualityGateFinder.QualityGateData qualityGateData = qualityGateFinder.getQualityGate(session, organization, component);
QualityGateDto qualityGateDto = qualityGateData.getQualityGate();
json.name("qualityGate").beginObject()
.prop("key", qualityGateDto.getId())
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookQGChangeEventListener.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookQGChangeEventListener.java
index 5f101568583..85c1a460e95 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookQGChangeEventListener.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/WebhookQGChangeEventListener.java
@@ -22,6 +22,7 @@ package org.sonar.server.webhook;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
+import org.apache.commons.lang.StringUtils;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.AnalysisPropertyDto;
@@ -30,6 +31,7 @@ import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.SnapshotDto;
import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
import org.sonar.server.qualitygate.changeevent.QGChangeEventListener;
+import org.sonar.server.webhook.Branch.Type;
public class WebhookQGChangeEventListener implements QGChangeEventListener {
private final WebHooks webhooks;
@@ -61,17 +63,18 @@ public class WebhookQGChangeEventListener implements QGChangeEventListener {
}
private WebhookPayload buildWebHookPayload(DbSession dbSession, QGChangeEvent event) {
- ComponentDto branch = event.getProject();
- BranchDto shortBranch = event.getBranch();
+ ComponentDto project = event.getProject();
+ BranchDto branch = event.getBranch();
SnapshotDto analysis = event.getAnalysis();
Map<String, String> analysisProperties = dbClient.analysisPropertiesDao().selectBySnapshotUuid(dbSession, analysis.getUuid())
.stream()
.collect(Collectors.toMap(AnalysisPropertyDto::getKey, AnalysisPropertyDto::getValue));
+ String projectUuid = StringUtils.defaultString(project.getMainBranchProjectUuid(), project.projectUuid());
ProjectAnalysis projectAnalysis = new ProjectAnalysis(
- new Project(branch.getMainBranchProjectUuid(), branch.getKey(), branch.name()),
+ new Project(projectUuid, project.getKey(), project.name()),
null,
new Analysis(analysis.getUuid(), analysis.getCreatedAt()),
- new Branch(false, shortBranch.getKey(), Branch.Type.SHORT),
+ new Branch(branch.isMain(), branch.getKey(), Type.valueOf(branch.getBranchType().name())),
event.getQualityGateSupplier().get().orElse(null),
null,
analysisProperties);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookWsSupport.java b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookWsSupport.java
index 48b5b99bd70..8eab7f02c95 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookWsSupport.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/webhook/ws/WebhookWsSupport.java
@@ -19,6 +19,7 @@
*/
package org.sonar.server.webhook.ws;
+import org.sonar.core.util.Protobuf;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.webhook.WebhookDeliveryDto;
import org.sonar.db.webhook.WebhookDeliveryLiteDto;
@@ -39,23 +40,17 @@ class WebhookWsSupport {
.setName(dto.getName())
.setUrl(dto.getUrl())
.setSuccess(dto.isSuccess())
- .setCeTaskId(dto.getCeTaskUuid())
.setComponentKey(component.getDbKey());
- if (dto.getHttpStatus() != null) {
- builder.setHttpStatus(dto.getHttpStatus());
- }
- if (dto.getDurationMs() != null) {
- builder.setDurationMs(dto.getDurationMs());
- }
+ Protobuf.setNullable(dto.getCeTaskUuid(), builder::setCeTaskId);
+ Protobuf.setNullable(dto.getHttpStatus(), builder::setHttpStatus);
+ Protobuf.setNullable(dto.getDurationMs(), builder::setDurationMs);
return builder;
}
static Webhooks.Delivery.Builder copyDtoToProtobuf(ComponentDto component, WebhookDeliveryDto dto, Webhooks.Delivery.Builder builder) {
copyDtoToProtobuf(component, (WebhookDeliveryLiteDto) dto, builder);
builder.setPayload(dto.getPayload());
- if (dto.getErrorStacktrace() != null) {
- builder.setErrorStacktrace(dto.getErrorStacktrace());
- }
+ Protobuf.setNullable(dto.getErrorStacktrace(), builder::setErrorStacktrace);
return builder;
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/formula/counter/RatingValueTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/formula/counter/RatingValueTest.java
index a456e2b84ed..742bb1d35e9 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/formula/counter/RatingValueTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/formula/counter/RatingValueTest.java
@@ -20,13 +20,13 @@
package org.sonar.server.computation.task.projectanalysis.formula.counter;
import org.junit.Test;
-import org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.B;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.C;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.D;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.A;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.B;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.C;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.D;
public class RatingValueTest {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/measure/BestValueOptimizationTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/measure/BestValueOptimizationTest.java
index ac7f9efdfdc..e023123bc95 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/measure/BestValueOptimizationTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/measure/BestValueOptimizationTest.java
@@ -28,8 +28,8 @@ import org.sonar.server.computation.task.projectanalysis.metric.MetricImpl;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.B;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.A;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.B;
public class BestValueOptimizationTest {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolderRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolderRule.java
index 5db2a72cc8c..cd1c045d9d3 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolderRule.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/MutableQualityGateHolderRule.java
@@ -19,8 +19,9 @@
*/
package org.sonar.server.computation.task.projectanalysis.qualitygate;
-import com.google.common.base.Optional;
+import java.util.Optional;
import org.junit.rules.ExternalResource;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
public class MutableQualityGateHolderRule extends ExternalResource implements MutableQualityGateHolder {
private MutableQualityGateHolder delegate = new QualityGateHolderImpl();
@@ -31,13 +32,18 @@ public class MutableQualityGateHolderRule extends ExternalResource implements Mu
}
@Override
- public void setNoQualityGate() {
- delegate.setNoQualityGate();
+ public Optional<QualityGate> getQualityGate() {
+ return delegate.getQualityGate();
}
@Override
- public Optional<QualityGate> getQualityGate() {
- return delegate.getQualityGate();
+ public void setEvaluation(EvaluatedQualityGate evaluation) {
+ delegate.setEvaluation(evaluation);
+ }
+
+ @Override
+ public Optional<EvaluatedQualityGate> getEvaluation() {
+ return delegate.getEvaluation();
}
@Override
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImplTest.java
index 24e6a1a7be3..a53912856e3 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImplTest.java
@@ -19,15 +19,16 @@
*/
package org.sonar.server.computation.task.projectanalysis.qualitygate;
-import java.util.Collections;
import org.junit.Test;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.guava.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
public class QualityGateHolderImplTest {
- public static final QualityGate QUALITY_GATE = new QualityGate(4612, "name", Collections.<Condition>emptyList());
+ private static final QualityGate QUALITY_GATE = new QualityGate(4612, "name", emptyList());
@Test(expected = IllegalStateException.class)
public void getQualityGate_throws_ISE_if_QualityGate_not_set() {
@@ -56,13 +57,33 @@ public class QualityGateHolderImplTest {
assertThat(holder.getQualityGate().get()).isSameAs(QUALITY_GATE);
}
+ @Test(expected = IllegalStateException.class)
+ public void getEvaluation_throws_ISE_if_QualityGate_not_set() {
+ new QualityGateHolderImpl().getEvaluation();
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void setEvaluation_throws_NPE_if_argument_is_null() {
+ new QualityGateHolderImpl().setEvaluation(null);
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void setEvaluation_throws_ISE_if_called_twice() {
+ QualityGateHolderImpl holder = new QualityGateHolderImpl();
+
+ EvaluatedQualityGate evaluation = mock(EvaluatedQualityGate.class);
+ holder.setEvaluation(evaluation);
+ holder.setEvaluation(evaluation);
+ }
+
@Test
- public void getQualityGate_returns_absent_if_holder_initialized_with_setNoQualityGate() {
+ public void getEvaluation_returns_QualityGate_set_by_setQualityGate() {
QualityGateHolderImpl holder = new QualityGateHolderImpl();
- holder.setNoQualityGate();
+ EvaluatedQualityGate evaluation = mock(EvaluatedQualityGate.class);
+ holder.setEvaluation(evaluation);
- assertThat(holder.getQualityGate()).isAbsent();
+ assertThat(holder.getEvaluation().get()).isSameAs(evaluation);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderRule.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderRule.java
index 16485fd5d1a..10a0c320eb5 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderRule.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderRule.java
@@ -19,17 +19,21 @@
*/
package org.sonar.server.computation.task.projectanalysis.qualitygate;
-import com.google.common.base.Optional;
+import java.util.Optional;
import javax.annotation.Nullable;
import org.junit.rules.ExternalResource;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
import static com.google.common.base.Preconditions.checkState;
public class QualityGateHolderRule extends ExternalResource implements QualityGateHolder {
+ @Nullable
private Optional<QualityGate> qualityGate;
+ @Nullable
+ private Optional<EvaluatedQualityGate> evaluation;
public void setQualityGate(@Nullable QualityGate qualityGate) {
- this.qualityGate = Optional.fromNullable(qualityGate);
+ this.qualityGate = Optional.ofNullable(qualityGate);
}
@Override
@@ -38,6 +42,16 @@ public class QualityGateHolderRule extends ExternalResource implements QualityGa
return qualityGate;
}
+ public void setEvaluation(@Nullable EvaluatedQualityGate e) {
+ this.evaluation = Optional.ofNullable(e);
+ }
+
+ @Override
+ public Optional<EvaluatedQualityGate> getEvaluation() {
+ checkState(evaluation != null, "EvaluatedQualityGate has not been initialized");
+ return evaluation;
+ }
+
@Override
protected void after() {
reset();
@@ -45,5 +59,6 @@ public class QualityGateHolderRule extends ExternalResource implements QualityGa
public void reset() {
this.qualityGate = null;
+ this.evaluation = null;
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingGridTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/DebtRatingGridTest.java
index 7149a2e9ca1..596697900d9 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingGridTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/DebtRatingGridTest.java
@@ -25,16 +25,16 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.B;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.C;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.D;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.E;
-public class RatingGridTest {
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.A;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.B;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.C;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.D;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.E;
- private RatingGrid ratingGrid;
+public class DebtRatingGridTest {
+
+ private DebtRatingGrid ratingGrid;
@Rule
public ExpectedException throwable = ExpectedException.none();
@@ -42,7 +42,7 @@ public class RatingGridTest {
@Before
public void setUp() {
double[] gridValues = new double[] {0.1, 0.2, 0.5, 1};
- ratingGrid = new RatingGrid(gridValues);
+ ratingGrid = new DebtRatingGrid(gridValues);
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java
index f3b31742c57..b77f3a0780b 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/MaintainabilityMeasuresVisitorTest.java
@@ -31,7 +31,6 @@ import org.sonar.server.computation.task.projectanalysis.issue.ComponentIssuesRe
import org.sonar.server.computation.task.projectanalysis.measure.Measure;
import org.sonar.server.computation.task.projectanalysis.measure.MeasureRepositoryRule;
import org.sonar.server.computation.task.projectanalysis.metric.MetricRepositoryRule;
-import org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
@@ -57,9 +56,9 @@ import static org.sonar.server.computation.task.projectanalysis.component.Report
import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder;
import static org.sonar.server.computation.task.projectanalysis.measure.MeasureRepoEntry.entryOf;
import static org.sonar.server.computation.task.projectanalysis.measure.MeasureRepoEntry.toEntries;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.C;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.E;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.A;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.C;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.E;
public class MaintainabilityMeasuresVisitorTest {
@@ -114,7 +113,7 @@ public class MaintainabilityMeasuresVisitorTest {
@Before
public void setUp() {
// assumes rating configuration is consistent
- when(ratingSettings.getRatingGrid()).thenReturn(new RatingGrid(RATING_GRID));
+ when(ratingSettings.getDebtRatingGrid()).thenReturn(new DebtRatingGrid(RATING_GRID));
when(ratingSettings.getDevCost(LANGUAGE_KEY_1)).thenReturn(DEV_COST_LANGUAGE_1);
when(ratingSettings.getDevCost(LANGUAGE_KEY_2)).thenReturn(DEV_COST_LANGUAGE_2);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java
index 8d0d84ee4a0..c39cd3146db 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitorTest.java
@@ -39,7 +39,6 @@ import org.sonar.server.computation.task.projectanalysis.measure.MeasureReposito
import org.sonar.server.computation.task.projectanalysis.metric.MetricRepositoryRule;
import org.sonar.server.computation.task.projectanalysis.period.Period;
import org.sonar.server.computation.task.projectanalysis.period.PeriodHolderRule;
-import org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
import org.sonar.server.computation.task.projectanalysis.scm.Changeset;
import org.sonar.server.computation.task.projectanalysis.scm.ScmInfoRepositoryRule;
@@ -48,6 +47,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA;
import static org.sonar.api.measures.CoreMetrics.NCLOC_DATA_KEY;
+import static org.sonar.api.measures.CoreMetrics.NEW_DEVELOPMENT_COST;
+import static org.sonar.api.measures.CoreMetrics.NEW_DEVELOPMENT_COST_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING;
import static org.sonar.api.measures.CoreMetrics.NEW_MAINTAINABILITY_RATING_KEY;
import static org.sonar.api.measures.CoreMetrics.NEW_SQALE_DEBT_RATIO;
@@ -60,8 +61,8 @@ import static org.sonar.server.computation.task.projectanalysis.component.Compon
import static org.sonar.server.computation.task.projectanalysis.component.Component.Type.PROJECT;
import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder;
import static org.sonar.server.computation.task.projectanalysis.measure.MeasureAssert.assertThat;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.D;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.A;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.D;
public class NewMaintainabilityMeasuresVisitorTest {
@@ -85,7 +86,8 @@ public class NewMaintainabilityMeasuresVisitorTest {
.add(NEW_TECHNICAL_DEBT)
.add(NCLOC_DATA)
.add(NEW_SQALE_DEBT_RATIO)
- .add(NEW_MAINTAINABILITY_RATING);
+ .add(NEW_MAINTAINABILITY_RATING)
+ .add(NEW_DEVELOPMENT_COST);
@Rule
public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
@Rule
@@ -97,7 +99,7 @@ public class NewMaintainabilityMeasuresVisitorTest {
@Before
public void setUp() throws Exception {
- when(ratingSettings.getRatingGrid()).thenReturn(new RatingGrid(RATING_GRID));
+ when(ratingSettings.getDebtRatingGrid()).thenReturn(new DebtRatingGrid(RATING_GRID));
underTest = new VisitorsCrawler(Arrays.asList(new NewMaintainabilityMeasuresVisitor(metricRepository, measureRepository, scmInfoRepository,
periodsHolder, ratingSettings)));
}
@@ -320,7 +322,7 @@ public class NewMaintainabilityMeasuresVisitorTest {
}
@Test
- public void compute_new_maintainability_rating() throws Exception {
+ public void compute_new_maintainability_rating() {
setPeriod();
when(ratingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
treeRootHolder.setRoot(
@@ -354,7 +356,41 @@ public class NewMaintainabilityMeasuresVisitorTest {
}
@Test
- public void compute_new_maintainability_rating_to_A_when_no_debt() throws Exception {
+ public void compute_new_development_cost() {
+ setPeriod();
+ when(ratingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
+ treeRootHolder.setRoot(
+ builder(PROJECT, ROOT_REF)
+ .addChildren(
+ builder(MODULE, 11)
+ .addChildren(
+ builder(DIRECTORY, 111)
+ .addChildren(
+ builder(FILE, LANGUAGE_1_FILE_REF).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 4)).build(),
+ builder(FILE, 22_222).setFileAttributes(new FileAttributes(false, LANGUAGE_1_KEY, 6)).build()
+ )
+ .build())
+ .build())
+ .build());
+
+ // 4 lines file, only first one is not ncloc
+ measureRepository.addRawMeasure(LANGUAGE_1_FILE_REF, NCLOC_DATA_KEY, createNclocDataMeasure(2, 3, 4));
+ // first 2 lines are before all snapshots, 2 last lines are after PERIOD 2's snapshot date
+ scmInfoRepository.setScmInfo(LANGUAGE_1_FILE_REF, createChangesets(PERIOD_SNAPSHOT_DATE - 100, 2, PERIOD_SNAPSHOT_DATE + 100, 2));
+ // 6 lines file, only last one is not ncloc
+ measureRepository.addRawMeasure(22_222, NCLOC_DATA_KEY, createNclocDataMeasure(1, 2, 3, 4, 5));
+ // first 2 lines are before all snapshots, 4 last lines are after PERIOD 2's snapshot date
+ scmInfoRepository.setScmInfo(22_222, createChangesets(PERIOD_SNAPSHOT_DATE - 100, 2, PERIOD_SNAPSHOT_DATE + 100, 4));
+
+ underTest.visit(treeRootHolder.getRoot());
+
+ assertNewDevelopmentCostValues(ROOT_REF, 5 * LANGUAGE_1_DEV_COST);
+ assertNewDevelopmentCostValues(LANGUAGE_1_FILE_REF, 2 * LANGUAGE_1_DEV_COST);
+ assertNewDevelopmentCostValues(22_222, 3 * LANGUAGE_1_DEV_COST);
+ }
+
+ @Test
+ public void compute_new_maintainability_rating_to_A_when_no_debt() {
setPeriod();
when(ratingSettings.getDevCost(LANGUAGE_1_KEY)).thenReturn(LANGUAGE_1_DEV_COST);
treeRootHolder.setRoot(
@@ -471,6 +507,10 @@ public class NewMaintainabilityMeasuresVisitorTest {
assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_SQALE_DEBT_RATIO_KEY)).hasVariation(expectedVariation, VARIATION_COMPARISON_OFFSET);
}
+ private void assertNewDevelopmentCostValues(int componentRef, long expectedVariation) {
+ assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_DEVELOPMENT_COST_KEY)).hasVariation(expectedVariation, VARIATION_COMPARISON_OFFSET);
+ }
+
private void assertNewMaintainability(int componentRef, Rating expectedVariation) {
assertThat(measureRepository.getAddedRawMeasure(componentRef, NEW_MAINTAINABILITY_RATING_KEY)).hasVariation(expectedVariation.getIndex());
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java
index 89ad11a779e..51393a717e2 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitorTest.java
@@ -59,12 +59,12 @@ import static org.sonar.server.computation.task.projectanalysis.component.Compon
import static org.sonar.server.computation.task.projectanalysis.component.Component.Type.MODULE;
import static org.sonar.server.computation.task.projectanalysis.component.Component.Type.PROJECT;
import static org.sonar.server.computation.task.projectanalysis.component.ReportComponent.builder;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.B;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.C;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.D;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.E;
+
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.A;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.B;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.C;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.D;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.E;
public class NewReliabilityAndSecurityRatingMeasuresVisitorTest {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingSettingsTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingSettingsTest.java
index 5bcbc3fb662..2fb023fa235 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingSettingsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingSettingsTest.java
@@ -19,14 +19,15 @@
*/
package org.sonar.server.computation.task.projectanalysis.qualitymodel;
-import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.CoreProperties;
+import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.utils.MessageException;
+import org.sonar.core.config.CorePropertyDefinitions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.CoreProperties.DEVELOPMENT_COST;
@@ -36,22 +37,17 @@ import static org.sonar.api.CoreProperties.LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS
public class RatingSettingsTest {
- private MapSettings settings;
+ private MapSettings settings = new MapSettings(new PropertyDefinitions(CorePropertyDefinitions.all()));
@Rule
- public ExpectedException throwable = ExpectedException.none();
-
- @Before
- public void setUp() {
- settings = new MapSettings();
- }
+ public ExpectedException expectedException = ExpectedException.none();
@Test
public void load_rating_grid() {
settings.setProperty(CoreProperties.RATING_GRID, "1,3.4,8,50");
RatingSettings configurationLoader = new RatingSettings(settings.asConfig());
- double[] grid = configurationLoader.getRatingGrid().getGridValues();
+ double[] grid = configurationLoader.getDebtRatingGrid().getGridValues();
assertThat(grid).hasSize(4);
assertThat(grid[0]).isEqualTo(1.0);
assertThat(grid[1]).isEqualTo(3.4);
@@ -89,12 +85,10 @@ public class RatingSettingsTest {
@Test
public void fail_on_invalid_rating_grid_configuration() {
- RatingSettings configurationLoader = new RatingSettings(settings.asConfig());
-
- throwable.expect(IllegalArgumentException.class);
+ expectedException.expect(IllegalArgumentException.class);
settings.setProperty(CoreProperties.RATING_GRID, "a b c");
- configurationLoader.getRatingGrid();
+ new RatingSettings(settings.asConfig());
}
@Test
@@ -117,8 +111,8 @@ public class RatingSettingsTest {
settings.setProperty(LANGUAGE_SPECIFIC_PARAMETERS, "0");
settings.setProperty(LANGUAGE_SPECIFIC_PARAMETERS + "." + "0" + "." + LANGUAGE_SPECIFIC_PARAMETERS_MAN_DAYS_KEY, "40");
- throwable.expect(MessageException.class);
- throwable.expectMessage("Technical debt configuration is corrupted. At least one language specific parameter has no Language key. " +
+ expectedException.expect(MessageException.class);
+ expectedException.expectMessage("Technical debt configuration is corrupted. At least one language specific parameter has no Language key. " +
"Contact your administrator to update this configuration in the global administration section of SonarQube.");
new RatingSettings(settings.asConfig());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitorTest.java
index bc445fb7ae3..0d0ac7d8102 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitorTest.java
@@ -60,12 +60,12 @@ import static org.sonar.server.computation.task.projectanalysis.component.Report
import static org.sonar.server.computation.task.projectanalysis.measure.Measure.newMeasureBuilder;
import static org.sonar.server.computation.task.projectanalysis.measure.MeasureRepoEntry.entryOf;
import static org.sonar.server.computation.task.projectanalysis.measure.MeasureRepoEntry.toEntries;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.A;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.B;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.C;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.D;
-import static org.sonar.server.computation.task.projectanalysis.qualitymodel.RatingGrid.Rating.E;
+
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.A;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.B;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.C;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.D;
+import static org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating.E;
public class ReliabilityAndSecurityRatingMeasuresVisitorTest {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStepTest.java
index 69a9f29b7c7..016f1c1d751 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStepTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStepTest.java
@@ -19,7 +19,6 @@
*/
package org.sonar.server.computation.task.projectanalysis.step;
-import java.util.Collections;
import java.util.Optional;
import org.junit.Before;
import org.junit.Rule;
@@ -33,9 +32,8 @@ import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGate
import org.sonar.server.computation.task.projectanalysis.qualitygate.QualityGateService;
import org.sonar.server.qualitygate.ShortLivingBranchQualityGate;
-import static java.lang.String.format;
+import static java.util.Collections.emptyList;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.guava.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -72,18 +70,18 @@ public class LoadQualityGateStepTest {
@Test
public void execute_sets_default_QualityGate_when_project_has_no_settings() {
when(settingsRepository.getConfiguration()).thenReturn(new MapSettings().asConfig());
- QualityGate qualityGate = mock(QualityGate.class);
- when(qualityGateService.findDefaultQualityGate(any())).thenReturn(Optional.of(qualityGate));
+ QualityGate defaultGate = mock(QualityGate.class);
+ when(qualityGateService.findDefaultQualityGate(any())).thenReturn(defaultGate);
underTest.execute();
- assertThat(mutableQualityGateHolder.getQualityGate().get()).isSameAs(qualityGate);
+ assertThat(mutableQualityGateHolder.getQualityGate().get()).isSameAs(defaultGate);
}
@Test
public void execute_sets_default_QualityGate_when_property_value_is_not_a_long() {
expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage(format("Unsupported value (%s) in property sonar.qualitygate", "10 sds"));
+ expectedException.expectMessage("Unsupported value (10 sds) in property sonar.qualitygate");
when(settingsRepository.getConfiguration()).thenReturn(new MapSettings().setProperty("sonar.qualitygate", "10 sds").asConfig());
@@ -91,18 +89,8 @@ public class LoadQualityGateStepTest {
}
@Test
- public void execute_sets_default_QualityGate_if_no_default_quality_gate_on_organization() {
- when(settingsRepository.getConfiguration()).thenReturn(new MapSettings().asConfig());
- when(qualityGateService.findDefaultQualityGate(any())).thenReturn(Optional.empty());
-
- underTest.execute();
-
- verifyNoQualityGate();
- }
-
- @Test
public void execute_sets_QualityGate_if_it_can_be_found_by_service() {
- QualityGate qualityGate = new QualityGate(10, "name", Collections.emptyList());
+ QualityGate qualityGate = new QualityGate(10, "name", emptyList());
when(settingsRepository.getConfiguration()).thenReturn(new MapSettings().setProperty("sonar.qualitygate", 10).asConfig());
when(qualityGateService.findById(10)).thenReturn(Optional.of(qualityGate));
@@ -112,8 +100,4 @@ public class LoadQualityGateStepTest {
assertThat(mutableQualityGateHolder.getQualityGate().get()).isSameAs(qualityGate);
}
- private void verifyNoQualityGate() {
- assertThat(mutableQualityGateHolder.getQualityGate()).isAbsent();
- }
-
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java
index d92e79d0861..511e460d07d 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookPostTaskTest.java
@@ -35,6 +35,7 @@ import org.sonar.api.ce.posttask.PostProjectAnalysisTaskTester;
import org.sonar.api.ce.posttask.Project;
import org.sonar.api.ce.posttask.QualityGate;
import org.sonar.api.config.Configuration;
+import org.sonar.api.measures.Metric;
import org.sonar.server.computation.task.projectanalysis.component.ConfigurationRepository;
import org.sonar.server.qualitygate.Condition;
import org.sonar.server.qualitygate.EvaluatedCondition;
@@ -153,7 +154,7 @@ public class WebhookPostTaskTest {
condition.isOnLeakPeriod());
webQualityGate = EvaluatedQualityGate.newBuilder()
.setQualityGate(new org.sonar.server.qualitygate.QualityGate(qualityGate.getId(), qualityGate.getName(), Collections.singleton(qgCondition)))
- .setStatus(EvaluatedQualityGate.Status.valueOf(qualityGate.getStatus().name()))
+ .setStatus(Metric.Level.valueOf(qualityGate.getStatus().name()))
.addCondition(qgCondition, EvaluatedCondition.EvaluationStatus.valueOf(condition.getStatus().name()), condition.getValue())
.build();
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ActionTest.java
index d7f50738fbb..d082951d143 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ActionTest.java
@@ -21,49 +21,52 @@ package org.sonar.server.issue;
import java.util.Collection;
import java.util.Map;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.server.user.UserSession;
-import static org.assertj.core.api.Assertions.assertThat;
-
public class ActionTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
@Test
public void key_should_not_be_empty() {
- try {
- new Action("") {
- @Override
- public boolean verify(Map<String, Object> properties, Collection<DefaultIssue> issues, UserSession userSession) {
- return false;
- }
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Action key must be set");
- @Override
- public boolean execute(Map<String, Object> properties, Context context) {
- return false;
- }
- };
- } catch (Exception e) {
- assertThat(e).hasMessage("Action key must be set").isInstanceOf(IllegalArgumentException.class);
- }
+ new FakeAction("");
}
@Test
public void key_should_not_be_null() {
- try {
- new Action(null) {
- @Override
- public boolean verify(Map<String, Object> properties, Collection<DefaultIssue> issues, UserSession userSession) {
- return false;
- }
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Action key must be set");
+
+ new FakeAction(null);
+ }
+
+ private static class FakeAction extends Action {
+
+ FakeAction(String key) {
+ super(key);
+ }
+
+ @Override
+ public boolean verify(Map<String, Object> properties, Collection<DefaultIssue> issues, UserSession userSession) {
+ return false;
+ }
+
+ @Override
+ public boolean execute(Map<String, Object> properties, Context context) {
+ return false;
+ }
- @Override
- public boolean execute(Map<String, Object> properties, Context context) {
- return false;
- }
- };
- } catch (Exception e) {
- assertThat(e).hasMessage("Action key must be set").isInstanceOf(IllegalArgumentException.class);
+ @Override
+ public boolean shouldRefreshMeasures() {
+ return false;
}
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueUpdaterTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueUpdaterTest.java
index 8eb00e90541..f9f42cfc2d6 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/IssueUpdaterTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/IssueUpdaterTest.java
@@ -83,11 +83,12 @@ public class IssueUpdaterTest {
private ArgumentCaptor<IssueChangeNotification> notificationArgumentCaptor = ArgumentCaptor.forClass(IssueChangeNotification.class);
private IssueIndexer issueIndexer = new IssueIndexer(esTester.client(), dbClient, new IssueIteratorFactory(dbClient));
+ private TestIssueChangePostProcessor issueChangePostProcessor = new TestIssueChangePostProcessor();
private IssueUpdater underTest = new IssueUpdater(dbClient,
- new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), notificationManager);
+ new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), notificationManager, issueChangePostProcessor);
@Test
- public void update_issue() throws Exception {
+ public void update_issue() {
DefaultIssue issue = issueDbTester.insertIssue(newIssue().setSeverity(MAJOR)).toDefaultIssue();
IssueChangeContext context = IssueChangeContext.createUser(new Date(), "john");
issueFieldsSetter.setSeverity(issue, BLOCKER, context);
@@ -99,7 +100,7 @@ public class IssueUpdaterTest {
}
@Test
- public void verify_notification() throws Exception {
+ public void verify_notification() {
RuleDto rule = ruleDbTester.insertRule(newRuleDto());
ComponentDto project = componentDbTester.insertPrivateProject();
ComponentDto file = componentDbTester.insertComponent(newFileDto(project));
@@ -124,7 +125,7 @@ public class IssueUpdaterTest {
}
@Test
- public void verify_notification_on_branch() throws Exception {
+ public void verify_notification_on_branch() {
RuleDto rule = ruleDbTester.insertRule(newRuleDto());
ComponentDto project = componentDbTester.insertMainBranch();
ComponentDto branch = componentDbTester.insertProjectBranch(project);
@@ -144,7 +145,7 @@ public class IssueUpdaterTest {
}
@Test
- public void verify_notification_when_issue_is_linked_on_removed_rule() throws Exception {
+ public void verify_notification_when_issue_is_linked_on_removed_rule() {
RuleDto rule = ruleDbTester.insertRule(newRuleDto().setStatus(RuleStatus.REMOVED));
ComponentDto project = componentDbTester.insertPrivateProject();
ComponentDto file = componentDbTester.insertComponent(newFileDto(project));
@@ -175,7 +176,7 @@ public class IssueUpdaterTest {
IssueChangeContext context = IssueChangeContext.createUser(new Date(), "john");
issueFieldsSetter.setSeverity(issue, BLOCKER, context);
- SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(dbTester.getSession(), issue, context, null);
+ SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(dbTester.getSession(), issue, context, null, true);
assertThat(preloadedSearchResponseData.getIssues())
.hasSize(1);
@@ -187,6 +188,7 @@ public class IssueUpdaterTest {
assertThat(preloadedSearchResponseData.getComponents())
.extracting(ComponentDto::uuid)
.containsOnly(project.uuid(), file.uuid());
+ assertThat(issueChangePostProcessor.calledComponents()).containsExactlyInAnyOrder(file);
}
@Test
@@ -199,7 +201,7 @@ public class IssueUpdaterTest {
IssueChangeContext context = IssueChangeContext.createUser(new Date(), "john");
issueFieldsSetter.setSeverity(issue, BLOCKER, context);
- SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(dbTester.getSession(), issue, context, null);
+ SearchResponseData preloadedSearchResponseData = underTest.saveIssueAndPreloadSearchResponseData(dbTester.getSession(), issue, context, null, false);
assertThat(preloadedSearchResponseData.getIssues())
.hasSize(1);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/TestIssueChangePostProcessor.java b/server/sonar-server/src/test/java/org/sonar/server/issue/TestIssueChangePostProcessor.java
new file mode 100644
index 00000000000..e18cf411848
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/TestIssueChangePostProcessor.java
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.issue;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+
+public class TestIssueChangePostProcessor implements IssueChangePostProcessor {
+
+ private boolean called = false;
+ private final List<ComponentDto> calledComponents = new ArrayList<>();
+
+ @Override
+ public void process(DbSession dbSession, List<DefaultIssue> changedIssues, Collection<ComponentDto> components) {
+ called = true;
+ calledComponents.addAll(components);
+ }
+
+ public boolean wasCalled() {
+ return called;
+ }
+
+ public List<ComponentDto> calledComponents() {
+ return calledComponents;
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AddCommentActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AddCommentActionTest.java
index d731f926115..67b52acfd0c 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AddCommentActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AddCommentActionTest.java
@@ -45,6 +45,7 @@ import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.IssueUpdater;
import org.sonar.server.issue.ServerIssueStorage;
+import org.sonar.server.issue.TestIssueChangePostProcessor;
import org.sonar.server.issue.index.IssueIndexDefinition;
import org.sonar.server.issue.index.IssueIndexer;
import org.sonar.server.issue.index.IssueIteratorFactory;
@@ -94,7 +95,8 @@ public class AddCommentActionTest {
private IssueIndexer issueIndexer = new IssueIndexer(esTester.client(), dbClient, new IssueIteratorFactory(dbClient));
private ServerIssueStorage serverIssueStorage = new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer);
- private IssueUpdater issueUpdater = new IssueUpdater(dbClient, serverIssueStorage, mock(NotificationManager.class));
+ private TestIssueChangePostProcessor issueChangePostProcessor = new TestIssueChangePostProcessor();
+ private IssueUpdater issueUpdater = new IssueUpdater(dbClient, serverIssueStorage, mock(NotificationManager.class), issueChangePostProcessor);
private OperationResponseWriter responseWriter = mock(OperationResponseWriter.class);
private ArgumentCaptor<SearchResponseData> preloadedSearchResponseDataCaptor = ArgumentCaptor.forClass(SearchResponseData.class);
@@ -107,7 +109,7 @@ public class AddCommentActionTest {
}
@Test
- public void add_comment() throws Exception {
+ public void add_comment() {
IssueDto issueDto = issueDbTester.insertIssue();
loginWithBrowsePermission(issueDto, USER);
@@ -128,10 +130,11 @@ public class AddCommentActionTest {
IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get();
assertThat(issueReloaded.getIssueUpdateTime()).isEqualTo(NOW);
+ assertThat(issueChangePostProcessor.wasCalled()).isFalse();
}
@Test
- public void fail_when_missing_issue_key() throws Exception {
+ public void fail_when_missing_issue_key() {
userSession.logIn("john");
expectedException.expect(IllegalArgumentException.class);
@@ -139,7 +142,7 @@ public class AddCommentActionTest {
}
@Test
- public void fail_when_issue_does_not_exist() throws Exception {
+ public void fail_when_issue_does_not_exist() {
userSession.logIn("john");
expectedException.expect(NotFoundException.class);
@@ -147,7 +150,7 @@ public class AddCommentActionTest {
}
@Test
- public void fail_when_missing_comment_text() throws Exception {
+ public void fail_when_missing_comment_text() {
userSession.logIn("john");
expectedException.expect(IllegalArgumentException.class);
@@ -155,7 +158,7 @@ public class AddCommentActionTest {
}
@Test
- public void fail_when_empty_comment_text() throws Exception {
+ public void fail_when_empty_comment_text() {
IssueDto issueDto = issueDbTester.insertIssue();
loginWithBrowsePermission(issueDto, USER);
@@ -164,13 +167,13 @@ public class AddCommentActionTest {
}
@Test
- public void fail_when_not_authenticated() throws Exception {
+ public void fail_when_not_authenticated() {
expectedException.expect(UnauthorizedException.class);
call("ABCD", "please fix it");
}
@Test
- public void fail_when_not_enough_permission() throws Exception {
+ public void fail_when_not_enough_permission() {
IssueDto issueDto = issueDbTester.insertIssue();
loginWithBrowsePermission(issueDto, CODEVIEWER);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java
index 6def1097474..aed5de8eb82 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/AssignActionTest.java
@@ -42,6 +42,7 @@ import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.IssueUpdater;
import org.sonar.server.issue.ServerIssueStorage;
+import org.sonar.server.issue.TestIssueChangePostProcessor;
import org.sonar.server.issue.index.IssueIndexDefinition;
import org.sonar.server.issue.index.IssueIndexer;
import org.sonar.server.issue.index.IssueIteratorFactory;
@@ -82,16 +83,17 @@ public class AssignActionTest {
private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db);
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()));
private OperationResponseWriter responseWriter = mock(OperationResponseWriter.class);
+ private TestIssueChangePostProcessor issueChangePostProcessor = new TestIssueChangePostProcessor();
private AssignAction underTest = new AssignAction(system2, userSession, db.getDbClient(), new IssueFinder(db.getDbClient(), userSession), new IssueFieldsSetter(),
new IssueUpdater(db.getDbClient(),
new ServerIssueStorage(system2, new DefaultRuleFinder(db.getDbClient(), defaultOrganizationProvider), db.getDbClient(), issueIndexer),
- mock(NotificationManager.class)),
+ mock(NotificationManager.class), issueChangePostProcessor),
responseWriter);
private ArgumentCaptor<SearchResponseData> preloadedSearchResponseDataCaptor = ArgumentCaptor.forClass(SearchResponseData.class);
private WsActionTester ws = new WsActionTester(underTest);
@Test
- public void assign_to_someone() throws Exception {
+ public void assign_to_someone() {
IssueDto issue = newIssueWithBrowsePermission();
insertUser("arthur");
@@ -103,10 +105,11 @@ public class AssignActionTest {
checkIssueAssignee(issue.getKey(), "arthur");
verify(responseWriter).write(eq(issue.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class));
verifyContentOfPreloadedSearchResponseData(issue);
+ assertThat(issueChangePostProcessor.wasCalled()).isFalse();
}
@Test
- public void assign_to_me() throws Exception {
+ public void assign_to_me() {
IssueDto issue = newIssueWithBrowsePermission();
ws.newRequest()
@@ -117,10 +120,11 @@ public class AssignActionTest {
checkIssueAssignee(issue.getKey(), CURRENT_USER_LOGIN);
verify(responseWriter).write(eq(issue.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class));
verifyContentOfPreloadedSearchResponseData(issue);
+ assertThat(issueChangePostProcessor.wasCalled()).isFalse();
}
@Test
- public void assign_to_me_using_deprecated_me_param() throws Exception {
+ public void assign_to_me_using_deprecated_me_param() {
IssueDto issue = newIssueWithBrowsePermission();
ws.newRequest()
@@ -134,7 +138,7 @@ public class AssignActionTest {
}
@Test
- public void unassign() throws Exception {
+ public void unassign() {
IssueDto issue = newIssueWithBrowsePermission();
ws.newRequest()
@@ -144,10 +148,11 @@ public class AssignActionTest {
checkIssueAssignee(issue.getKey(), null);
verify(responseWriter).write(eq(issue.getKey()), preloadedSearchResponseDataCaptor.capture(), any(Request.class), any(Response.class));
verifyContentOfPreloadedSearchResponseData(issue);
+ assertThat(issueChangePostProcessor.wasCalled()).isFalse();
}
@Test
- public void unassign_with_empty_assignee_param() throws Exception {
+ public void unassign_with_empty_assignee_param() {
IssueDto issue = newIssueWithBrowsePermission();
ws.newRequest()
@@ -161,7 +166,7 @@ public class AssignActionTest {
}
@Test
- public void nothing_to_do_when_new_assignee_is_same_as_old_one() throws Exception {
+ public void nothing_to_do_when_new_assignee_is_same_as_old_one() {
IssueDto issue = newIssueWithBrowsePermission();
insertUser(PREVIOUS_ASSIGNEE);
@@ -177,7 +182,7 @@ public class AssignActionTest {
}
@Test
- public void fail_when_assignee_does_not_exist() throws Exception {
+ public void fail_when_assignee_does_not_exist() {
IssueDto issue = newIssueWithBrowsePermission();
expectedException.expect(NotFoundException.class);
@@ -189,7 +194,7 @@ public class AssignActionTest {
}
@Test
- public void fail_when_assignee_is_disabled() throws Exception {
+ public void fail_when_assignee_is_disabled() {
IssueDto issue = newIssueWithBrowsePermission();
db.users().insertUser(user -> user.setActive(false));
@@ -202,7 +207,7 @@ public class AssignActionTest {
}
@Test
- public void fail_when_not_authenticated() throws Exception {
+ public void fail_when_not_authenticated() {
IssueDto issue = newIssue();
userSession.anonymous();
@@ -215,7 +220,7 @@ public class AssignActionTest {
}
@Test
- public void fail_when_missing_browse_permission() throws Exception {
+ public void fail_when_missing_browse_permission() {
IssueDto issue = newIssue();
setUserWithPermission(issue, CODEVIEWER);
@@ -228,7 +233,7 @@ public class AssignActionTest {
}
@Test
- public void fail_when_assignee_is_not_member_of_organization_of_project_issue() throws Exception {
+ public void fail_when_assignee_is_not_member_of_organization_of_project_issue() {
OrganizationDto org = db.organizations().insert(organizationDto -> organizationDto.setKey("Organization key"));
IssueDto issueDto = db.issues().insertIssue(org);
setUserWithBrowsePermission(issueDto);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/BulkChangeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/BulkChangeActionTest.java
index e75761f7351..764ebe56070 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/BulkChangeActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/BulkChangeActionTest.java
@@ -20,9 +20,6 @@
package org.sonar.server.issue.ws;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -37,8 +34,6 @@ import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
@@ -53,6 +48,7 @@ import org.sonar.server.issue.Action;
import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueStorage;
import org.sonar.server.issue.ServerIssueStorage;
+import org.sonar.server.issue.TestIssueChangePostProcessor;
import org.sonar.server.issue.TransitionService;
import org.sonar.server.issue.index.IssueIndexDefinition;
import org.sonar.server.issue.index.IssueIndexer;
@@ -63,9 +59,6 @@ import org.sonar.server.issue.workflow.IssueWorkflow;
import org.sonar.server.notification.NotificationManager;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
-import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.rule.DefaultRuleFinder;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
@@ -79,12 +72,8 @@ import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.STATUS_CLOSED;
@@ -92,7 +81,6 @@ import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.rule.Severity.MAJOR;
import static org.sonar.api.rule.Severity.MINOR;
import static org.sonar.api.rules.RuleType.BUG;
-import static org.sonar.api.rules.RuleType.CODE_SMELL;
import static org.sonar.api.rules.RuleType.VULNERABILITY;
import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
import static org.sonar.api.web.UserRole.USER;
@@ -125,8 +113,7 @@ public class BulkChangeActionTest {
private IssueStorage issueStorage = new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient,
new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient)));
private NotificationManager notificationManager = mock(NotificationManager.class);
- private QGChangeEventFactory qgChangeEventFactory = mock(QGChangeEventFactory.class);
- private QGChangeEventListeners qgChangeEventListeners = mock(QGChangeEventListeners.class);
+ private TestIssueChangePostProcessor issueChangePostProcessor = new TestIssueChangePostProcessor();
private List<Action> actions = new ArrayList<>();
private RuleDto rule;
@@ -135,8 +122,7 @@ public class BulkChangeActionTest {
private ComponentDto file;
private UserDto user;
- private WsActionTester tester = new WsActionTester(
- new BulkChangeAction(system2, userSession, dbClient, issueStorage, notificationManager, actions, qgChangeEventFactory, qgChangeEventListeners));
+ private WsActionTester tester = new WsActionTester(new BulkChangeAction(system2, userSession, dbClient, issueStorage, notificationManager, actions, issueChangePostProcessor));
@Before
public void setUp() {
@@ -154,7 +140,6 @@ public class BulkChangeActionTest {
public void set_type() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
- List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(CODE_SMELL, null);
BulkChangeWsResponse response = call(builder()
.setIssues(singletonList(issueDto.getKey()))
@@ -166,8 +151,7 @@ public class BulkChangeActionTest {
assertThat(reloaded.getType()).isEqualTo(RuleType.CODE_SMELL.getDbConstant());
assertThat(reloaded.getUpdatedAt()).isEqualTo(NOW);
- QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(CODE_SMELL, null, new String[] {file.uuid()}, issueDto);
- verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ verifyPostProcessorCalled(file);
}
@Test
@@ -185,7 +169,7 @@ public class BulkChangeActionTest {
assertThat(reloaded.getSeverity()).isEqualTo(MINOR);
assertThat(reloaded.getUpdatedAt()).isEqualTo(NOW);
- verifyZeroInteractions(qgChangeEventFactory);
+ verifyPostProcessorCalled(file);
}
@Test
@@ -203,7 +187,8 @@ public class BulkChangeActionTest {
assertThat(reloaded.getTags()).containsOnly("tag1", "tag2", "tag3");
assertThat(reloaded.getUpdatedAt()).isEqualTo(NOW);
- verifyZeroInteractions(qgChangeEventFactory);
+ // no need to refresh measures
+ verifyPostProcessorNotCalled();
}
@Test
@@ -221,14 +206,14 @@ public class BulkChangeActionTest {
assertThat(reloaded.getAssignee()).isNull();
assertThat(reloaded.getUpdatedAt()).isEqualTo(NOW);
- verifyZeroInteractions(qgChangeEventFactory);
+ // no need to refresh measures
+ verifyPostProcessorNotCalled();
}
@Test
public void bulk_change_with_comment() {
setUserProjectPermissions(USER);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
- List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(null, "confirm");
BulkChangeWsResponse response = call(builder()
.setIssues(singletonList(issueDto.getKey()))
@@ -241,8 +226,7 @@ public class BulkChangeActionTest {
assertThat(issueComment.getUserLogin()).isEqualTo("john");
assertThat(issueComment.getChangeData()).isEqualTo("type was badly defined");
- QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(null, "confirm", new String[] {file.uuid()}, issueDto);
- verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ verifyPostProcessorCalled(file);
}
@Test
@@ -254,7 +238,6 @@ public class BulkChangeActionTest {
IssueDto issue1 = db.issues().insertIssue(newUnresolvedIssue().setAssignee(user.getLogin())).setType(BUG).setSeverity(MINOR);
IssueDto issue2 = db.issues().insertIssue(newUnresolvedIssue().setAssignee(userToAssign.getLogin())).setType(BUG).setSeverity(MAJOR);
IssueDto issue3 = db.issues().insertIssue(newUnresolvedIssue().setAssignee(null)).setType(VULNERABILITY).setSeverity(MAJOR);
- List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(VULNERABILITY, null);
BulkChangeWsResponse response = call(builder()
.setIssues(asList(issue1.getKey(), issue2.getKey(), issue3.getKey()))
@@ -271,15 +254,13 @@ public class BulkChangeActionTest {
tuple(issue2.getKey(), userToAssign.getLogin(), VULNERABILITY.getDbConstant(), MINOR, NOW),
tuple(issue3.getKey(), userToAssign.getLogin(), VULNERABILITY.getDbConstant(), MINOR, NOW));
- QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(VULNERABILITY, null, new String[] {file.uuid()}, issue1, issue2, issue3);
- verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ verifyPostProcessorCalled(file);
}
@Test
public void send_notification() {
setUserProjectPermissions(USER);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
- List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(null, "confirm");
BulkChangeWsResponse response = call(builder()
.setIssues(singletonList(issueDto.getKey()))
@@ -298,9 +279,6 @@ public class BulkChangeActionTest {
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("ruleName")).isEqualTo(rule.getName());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("changeAuthor")).isEqualTo(user.getLogin());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("branch")).isNull();
-
- QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(null, "confirm", new String[] {file.uuid()}, issueDto);
- verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
}
@Test
@@ -311,7 +289,6 @@ public class BulkChangeActionTest {
ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey(branchName));
ComponentDto fileOnBranch = db.components().insertComponent(newFileDto(branch));
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue(rule, fileOnBranch, branch).setType(BUG));
- List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(null, "confirm");
BulkChangeWsResponse response = call(builder()
.setIssues(singletonList(issueDto.getKey()))
@@ -331,8 +308,7 @@ public class BulkChangeActionTest {
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("changeAuthor")).isEqualTo(user.getLogin());
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("branch")).isEqualTo(branchName);
- QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(null, "confirm", new String[] {fileOnBranch.uuid()}, issueDto);
- verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ verifyPostProcessorCalled(fileOnBranch);
}
@Test
@@ -342,7 +318,6 @@ public class BulkChangeActionTest {
IssueDto issue2 = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
IssueDto issue3 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY));
ArgumentCaptor<IssueChangeNotification> issueChangeNotificationCaptor = ArgumentCaptor.forClass(IssueChangeNotification.class);
- List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(BUG, null);
BulkChangeWsResponse response = call(builder()
.setIssues(asList(issue1.getKey(), issue2.getKey(), issue3.getKey()))
@@ -355,18 +330,17 @@ public class BulkChangeActionTest {
assertThat(issueChangeNotificationCaptor.getAllValues()).hasSize(1);
assertThat(issueChangeNotificationCaptor.getValue().getFieldValue("key")).isEqualTo(issue3.getKey());
- QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(BUG, null, new String[] {file.uuid()}, issue3);
- verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ verifyPostProcessorCalled(file);
}
@Test
- public void ignore_issues_when_condition_does_not_match() {
+ public void ignore_the_issues_that_do_not_match_conditions() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
+ ComponentDto file2 = db.components().insertComponent(newFileDto(project));
IssueDto issue1 = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
// These 2 issues will be ignored as they are resolved, changing type is not possible
IssueDto issue2 = db.issues().insertIssue(newResolvedIssue().setType(BUG));
- IssueDto issue3 = db.issues().insertIssue(newResolvedIssue().setType(BUG));
- List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(VULNERABILITY, null);
+ IssueDto issue3 = db.issues().insertIssue(newResolvedIssue().setType(BUG).setComponent(file2));
BulkChangeWsResponse response = call(builder()
.setIssues(asList(issue1.getKey(), issue2.getKey(), issue3.getKey()))
@@ -381,18 +355,18 @@ public class BulkChangeActionTest {
tuple(issue3.getKey(), BUG.getDbConstant(), issue2.getUpdatedAt()),
tuple(issue2.getKey(), BUG.getDbConstant(), issue3.getUpdatedAt()));
- QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(VULNERABILITY, null, new String[] {file.uuid()}, issue1);
- verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ // file2 is not refreshed
+ verifyPostProcessorCalled(file);
}
@Test
public void ignore_issues_when_there_is_nothing_to_do() {
setUserProjectPermissions(USER, ISSUE_ADMIN);
+ ComponentDto file2 = db.components().insertComponent(newFileDto(project));
IssueDto issue1 = db.issues().insertIssue(newUnresolvedIssue().setType(BUG).setSeverity(MINOR));
// These 2 issues will be ignored as there's nothing to do
IssueDto issue2 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY));
- IssueDto issue3 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY));
- List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(VULNERABILITY, null);
+ IssueDto issue3 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY).setComponent(file2));
BulkChangeWsResponse response = call(builder()
.setIssues(asList(issue1.getKey(), issue2.getKey(), issue3.getKey()))
@@ -407,8 +381,8 @@ public class BulkChangeActionTest {
tuple(issue2.getKey(), VULNERABILITY.getDbConstant(), issue2.getUpdatedAt()),
tuple(issue3.getKey(), VULNERABILITY.getDbConstant(), issue3.getUpdatedAt()));
- QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(VULNERABILITY, null, new String[] {file.uuid()}, issue1);
- verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ // file2 is not refreshed
+ verifyPostProcessorCalled(file);
}
@Test
@@ -418,7 +392,6 @@ public class BulkChangeActionTest {
// These 2 issues will be ignored as there's nothing to do
IssueDto issue2 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY));
IssueDto issue3 = db.issues().insertIssue(newUnresolvedIssue().setType(VULNERABILITY));
- List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(VULNERABILITY, null);
BulkChangeWsResponse response = call(builder()
.setIssues(asList(issue1.getKey(), issue2.getKey(), issue3.getKey()))
@@ -431,8 +404,7 @@ public class BulkChangeActionTest {
assertThat(dbClient.issueChangeDao().selectByTypeAndIssueKeys(db.getSession(), singletonList(issue2.getKey()), TYPE_COMMENT)).isEmpty();
assertThat(dbClient.issueChangeDao().selectByTypeAndIssueKeys(db.getSession(), singletonList(issue3.getKey()), TYPE_COMMENT)).isEmpty();
- QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(VULNERABILITY, null, new String[] {file.uuid()}, issue1);
- verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ verifyPostProcessorCalled(file);
}
@Test
@@ -444,7 +416,6 @@ public class BulkChangeActionTest {
// User has not browse permission on these 2 issues
IssueDto notAuthorizedIssue1 = db.issues().insertIssue(newUnresolvedIssue(rule, anotherFile, anotherProject).setType(BUG));
IssueDto notAuthorizedIssue2 = db.issues().insertIssue(newUnresolvedIssue(rule, anotherFile, anotherProject).setType(BUG));
- List<QGChangeEvent> qgChangeEvents = mockQGChangeEvents(VULNERABILITY, null);
BulkChangeWsResponse response = call(builder()
.setIssues(asList(authorizedIssue.getKey(), notAuthorizedIssue1.getKey(), notAuthorizedIssue2.getKey()))
@@ -459,8 +430,7 @@ public class BulkChangeActionTest {
tuple(notAuthorizedIssue1.getKey(), BUG.getDbConstant(), notAuthorizedIssue1.getUpdatedAt()),
tuple(notAuthorizedIssue2.getKey(), BUG.getDbConstant(), notAuthorizedIssue2.getUpdatedAt()));
- QGChangeEventFactory.IssueChangeData issueChangeData = verifyIssueChangeData(VULNERABILITY, null, new String[] {file.uuid()}, authorizedIssue);
- verifyBroadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ verifyPostProcessorCalled(file);
}
@Test
@@ -487,6 +457,7 @@ public class BulkChangeActionTest {
tuple(authorizedIssue1.getKey(), VULNERABILITY.getDbConstant(), NOW),
tuple(notAuthorizedIssue1.getKey(), BUG.getDbConstant(), notAuthorizedIssue1.getUpdatedAt()),
tuple(notAuthorizedIssue2.getKey(), BUG.getDbConstant(), notAuthorizedIssue2.getUpdatedAt()));
+ verifyPostProcessorCalled(file);
}
@Test
@@ -513,12 +484,15 @@ public class BulkChangeActionTest {
tuple(authorizedIssue1.getKey(), MINOR, NOW),
tuple(notAuthorizedIssue1.getKey(), MAJOR, notAuthorizedIssue1.getUpdatedAt()),
tuple(notAuthorizedIssue2.getKey(), MAJOR, notAuthorizedIssue2.getUpdatedAt()));
+
+ verifyPostProcessorCalled(file);
}
@Test
public void fail_when_only_comment_action() {
setUserProjectPermissions(USER);
IssueDto issueDto = db.issues().insertIssue(newUnresolvedIssue().setType(BUG));
+
expectedException.expectMessage("At least one action must be provided");
expectedException.expect(IllegalArgumentException.class);
@@ -531,6 +505,7 @@ public class BulkChangeActionTest {
@Test
public void fail_when_number_of_issues_is_more_than_500() {
userSession.logIn("john");
+
expectedException.expectMessage("Number of issues is limited to 500");
expectedException.expect(IllegalArgumentException.class);
@@ -557,60 +532,6 @@ public class BulkChangeActionTest {
assertThat(action.responseExample()).isNotNull();
}
- private void verifyIssueChangeWebhookCalled(@Nullable RuleType expectedRuleType, @Nullable String transitionKey,
- String[] componentUUids,
- IssueDto... issueDtos) {
- QGChangeEventFactory.IssueChange issueChange = new QGChangeEventFactory.IssueChange(expectedRuleType, transitionKey);
- IssueChangeContext user = IssueChangeContext.createUser(new Date(NOW), userSession.getLogin());
- List<QGChangeEvent> changeEvents = Collections.singletonList(mock(QGChangeEvent.class));
- when(qgChangeEventFactory.from(any(QGChangeEventFactory.IssueChangeData.class), eq(issueChange), eq(user))).thenReturn(changeEvents);
-
- ArgumentCaptor<QGChangeEventFactory.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(QGChangeEventFactory.IssueChangeData.class);
- verify(qgChangeEventFactory).from(issueChangeDataCaptor.capture(), eq(issueChange), eq(user));
-
- QGChangeEventFactory.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
- assertThat(issueChangeData.getIssues())
- .extracting(DefaultIssue::key)
- .containsOnly(Arrays.stream(issueDtos).map(IssueDto::getKey).toArray(String[]::new));
- assertThat(issueChangeData.getComponents())
- .extracting(ComponentDto::uuid)
- .containsOnly(componentUUids);
-
- verify(qgChangeEventListeners).broadcastOnIssueChange(same(issueChangeData), same(changeEvents));
- }
-
- private List<QGChangeEvent> mockQGChangeEvents(@Nullable RuleType expectedRuleType, @Nullable String transitionKey) {
- QGChangeEventFactory.IssueChange issueChange = new QGChangeEventFactory.IssueChange(expectedRuleType, transitionKey);
- IssueChangeContext user = IssueChangeContext.createUser(new Date(NOW), userSession.getLogin());
- List<QGChangeEvent> changeEvents = Collections.singletonList(mock(QGChangeEvent.class));
- when(qgChangeEventFactory.from(any(QGChangeEventFactory.IssueChangeData.class), eq(issueChange), eq(user))).thenReturn(changeEvents);
-
- return changeEvents;
- }
-
- private QGChangeEventFactory.IssueChangeData verifyIssueChangeData(@Nullable RuleType expectedRuleType, @Nullable String transitionKey,
- String[] componentUUids,
- IssueDto... issueDtos) {
- QGChangeEventFactory.IssueChange issueChange = new QGChangeEventFactory.IssueChange(expectedRuleType, transitionKey);
- IssueChangeContext user = IssueChangeContext.createUser(new Date(NOW), userSession.getLogin());
-
- ArgumentCaptor<QGChangeEventFactory.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(QGChangeEventFactory.IssueChangeData.class);
- verify(qgChangeEventFactory).from(issueChangeDataCaptor.capture(), eq(issueChange), eq(user));
-
- QGChangeEventFactory.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
- assertThat(issueChangeData.getIssues())
- .extracting(DefaultIssue::key)
- .containsOnly(Arrays.stream(issueDtos).map(IssueDto::getKey).toArray(String[]::new));
- assertThat(issueChangeData.getComponents())
- .extracting(ComponentDto::uuid)
- .containsOnly(componentUUids);
- return issueChangeData;
- }
-
- private void verifyBroadcastOnIssueChange(QGChangeEventFactory.IssueChangeData issueChangeData, List<QGChangeEvent> changeEvents) {
- verify(qgChangeEventListeners).broadcastOnIssueChange(same(issueChangeData), same(changeEvents));
- }
-
private BulkChangeWsResponse call(BulkChangeRequest bulkChangeRequest) {
TestRequest request = tester.newRequest();
setNullable(bulkChangeRequest.getIssues(), value -> request.setParam("issues", String.join(",", value)));
@@ -652,6 +573,14 @@ public class BulkChangeActionTest {
return db.getDbClient().issueDao().selectByKeys(db.getSession(), asList(issueKeys));
}
+ private void verifyPostProcessorCalled(ComponentDto... components) {
+ assertThat(issueChangePostProcessor.calledComponents()).containsExactlyInAnyOrder(components);
+ }
+
+ private void verifyPostProcessorNotCalled() {
+ assertThat(issueChangePostProcessor.wasCalled()).isFalse();
+ }
+
private IssueDto newUnresolvedIssue(RuleDto rule, ComponentDto file, ComponentDto project) {
return newDto(rule, file, project).setStatus(STATUS_OPEN).setResolution(null);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/DoTransitionActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/DoTransitionActionTest.java
index 42b9de08966..d3c69b8d696 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/DoTransitionActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/DoTransitionActionTest.java
@@ -19,9 +19,6 @@
*/
package org.sonar.server.issue.ws;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
@@ -32,8 +29,6 @@ import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.utils.System2;
-import org.sonar.core.issue.DefaultIssue;
-import org.sonar.core.issue.IssueChangeContext;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDbTester;
@@ -51,6 +46,7 @@ import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.IssueUpdater;
import org.sonar.server.issue.ServerIssueStorage;
+import org.sonar.server.issue.TestIssueChangePostProcessor;
import org.sonar.server.issue.TransitionService;
import org.sonar.server.issue.index.IssueIndexDefinition;
import org.sonar.server.issue.index.IssueIndexer;
@@ -60,9 +56,6 @@ import org.sonar.server.issue.workflow.IssueWorkflow;
import org.sonar.server.notification.NotificationManager;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
-import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.rule.DefaultRuleFinder;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
@@ -113,16 +106,15 @@ public class DoTransitionActionTest {
private TransitionService transitionService = new TransitionService(userSession, workflow);
private OperationResponseWriter responseWriter = mock(OperationResponseWriter.class);
private IssueIndexer issueIndexer = new IssueIndexer(esTester.client(), dbClient, new IssueIteratorFactory(dbClient));
+ private TestIssueChangePostProcessor issueChangePostProcessor = new TestIssueChangePostProcessor();
private IssueUpdater issueUpdater = new IssueUpdater(dbClient,
- new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), mock(NotificationManager.class));
+ new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), mock(NotificationManager.class),
+ issueChangePostProcessor);
private ComponentDto project;
private ComponentDto file;
private ArgumentCaptor<SearchResponseData> preloadedSearchResponseDataCaptor = ArgumentCaptor.forClass(SearchResponseData.class);
- private QGChangeEventFactory qgChangeEventFactory = mock(QGChangeEventFactory.class);
- private QGChangeEventListeners qgChangeEventListeners = mock(QGChangeEventListeners.class);
- private WsAction underTest = new DoTransitionAction(dbClient, userSession, new IssueFinder(dbClient, userSession), issueUpdater, transitionService, responseWriter, system2,
- qgChangeEventFactory, qgChangeEventListeners);
+ private WsAction underTest = new DoTransitionAction(dbClient, userSession, new IssueFinder(dbClient, userSession), issueUpdater, transitionService, responseWriter, system2);
private WsActionTester tester = new WsActionTester(underTest);
@Before
@@ -137,11 +129,6 @@ public class DoTransitionActionTest {
when(system2.now()).thenReturn(NOW);
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setStatus(STATUS_OPEN).setResolution(null));
userSession.logIn("john").addProjectPermission(USER, project, file);
- IssueChangeContext changeContext = IssueChangeContext.createUser(new Date(NOW), userSession.getLogin());
- QGChangeEventFactory.IssueChange issueChange = new QGChangeEventFactory.IssueChange(null, "confirm");
- List<QGChangeEvent> qgChangeEvents = Collections.singletonList(mock(QGChangeEvent.class));
- when(qgChangeEventFactory.from(any(QGChangeEventFactory.IssueChangeData.class), eq(issueChange), eq(changeContext)))
- .thenReturn(qgChangeEvents);
call(issueDto.getKey(), "confirm");
@@ -149,17 +136,7 @@ public class DoTransitionActionTest {
verifyContentOfPreloadedSearchResponseData(issueDto);
IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get();
assertThat(issueReloaded.getStatus()).isEqualTo(STATUS_CONFIRMED);
-
- ArgumentCaptor<QGChangeEventFactory.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(QGChangeEventFactory.IssueChangeData.class);
- verify(qgChangeEventFactory).from(issueChangeDataCaptor.capture(), eq(issueChange), eq(changeContext));
- QGChangeEventFactory.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
- assertThat(issueChangeData.getIssues())
- .extracting(DefaultIssue::key)
- .containsOnly(issueDto.getKey());
- assertThat(issueChangeData.getComponents())
- .extracting(ComponentDto::uuid)
- .containsOnly(issueDto.getComponentUuid(), issueDto.getProjectUuid());
- verify(qgChangeEventListeners).broadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ assertThat(issueChangePostProcessor.calledComponents()).containsExactlyInAnyOrder(file);
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java
index 267c06f989f..32bfa672ff4 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/IssueWsModuleTest.java
@@ -30,7 +30,7 @@ public class IssueWsModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new IssueWsModule().configure(container);
- assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 33);
+ assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 31);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetSeverityActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetSeverityActionTest.java
index 30d2a740ba7..545ce4d5ec7 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetSeverityActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetSeverityActionTest.java
@@ -45,6 +45,7 @@ import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.IssueUpdater;
import org.sonar.server.issue.ServerIssueStorage;
+import org.sonar.server.issue.TestIssueChangePostProcessor;
import org.sonar.server.issue.index.IssueIndexDefinition;
import org.sonar.server.issue.index.IssueIndexer;
import org.sonar.server.issue.index.IssueIteratorFactory;
@@ -91,13 +92,14 @@ public class SetSeverityActionTest {
private ArgumentCaptor<SearchResponseData> preloadedSearchResponseDataCaptor = ArgumentCaptor.forClass(SearchResponseData.class);
private IssueIndexer issueIndexer = new IssueIndexer(esTester.client(), dbClient, new IssueIteratorFactory(dbClient));
+ private TestIssueChangePostProcessor issueChangePostProcessor = new TestIssueChangePostProcessor();
private WsActionTester tester = new WsActionTester(new SetSeverityAction(userSession, dbClient, new IssueFinder(dbClient, userSession), new IssueFieldsSetter(),
new IssueUpdater(dbClient,
- new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), mock(NotificationManager.class)),
+ new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), mock(NotificationManager.class), issueChangePostProcessor),
responseWriter));
@Test
- public void set_severity() throws Exception {
+ public void set_severity() {
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setSeverity(MAJOR));
setUserWithBrowseAndAdministerIssuePermission(issueDto);
@@ -109,10 +111,13 @@ public class SetSeverityActionTest {
IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get();
assertThat(issueReloaded.getSeverity()).isEqualTo(MINOR);
assertThat(issueReloaded.isManualSeverity()).isTrue();
+ assertThat(issueChangePostProcessor.calledComponents())
+ .extracting(ComponentDto::uuid)
+ .containsExactlyInAnyOrder(issueDto.getComponentUuid());
}
@Test
- public void insert_entry_in_changelog_when_setting_severity() throws Exception {
+ public void insert_entry_in_changelog_when_setting_severity() {
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setSeverity(MAJOR));
setUserWithBrowseAndAdministerIssuePermission(issueDto);
@@ -136,13 +141,13 @@ public class SetSeverityActionTest {
}
@Test
- public void fail_when_not_authenticated() throws Exception {
+ public void fail_when_not_authenticated() {
expectedException.expect(UnauthorizedException.class);
call("ABCD", MAJOR);
}
@Test
- public void fail_when_missing_browse_permission() throws Exception {
+ public void fail_when_missing_browse_permission() {
IssueDto issueDto = issueDbTester.insertIssue();
logInAndAddProjectPermission(issueDto, ISSUE_ADMIN);
@@ -151,7 +156,7 @@ public class SetSeverityActionTest {
}
@Test
- public void fail_when_missing_administer_issue_permission() throws Exception {
+ public void fail_when_missing_administer_issue_permission() {
IssueDto issueDto = issueDbTester.insertIssue();
logInAndAddProjectPermission(issueDto, USER);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTagsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTagsActionTest.java
index 5df04b4ee93..22e9634f98d 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTagsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTagsActionTest.java
@@ -47,6 +47,7 @@ import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.IssueUpdater;
import org.sonar.server.issue.ServerIssueStorage;
+import org.sonar.server.issue.TestIssueChangePostProcessor;
import org.sonar.server.issue.index.IssueIndexDefinition;
import org.sonar.server.issue.index.IssueIndexer;
import org.sonar.server.issue.index.IssueIteratorFactory;
@@ -87,10 +88,11 @@ public class SetTagsActionTest {
private OperationResponseWriter responseWriter = mock(OperationResponseWriter.class);
private IssueIndexer issueIndexer = new IssueIndexer(esTester.client(), dbClient, new IssueIteratorFactory(dbClient));
private ArgumentCaptor<SearchResponseData> preloadedSearchResponseDataCaptor = ArgumentCaptor.forClass(SearchResponseData.class);
+ private TestIssueChangePostProcessor issueChangePostProcessor = new TestIssueChangePostProcessor();
private WsActionTester ws = new WsActionTester(new SetTagsAction(userSession, dbClient, new IssueFinder(dbClient, userSession), new IssueFieldsSetter(),
new IssueUpdater(dbClient,
- new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), mock(NotificationManager.class)),
+ new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), mock(NotificationManager.class), issueChangePostProcessor),
responseWriter));
@Test
@@ -104,6 +106,7 @@ public class SetTagsActionTest {
verifyContentOfPreloadedSearchResponseData(issueDto);
IssueDto issueReloaded = dbClient.issueDao().selectByKey(db.getSession(), issueDto.getKey()).get();
assertThat(issueReloaded.getTags()).containsOnly("bug", "todo");
+ assertThat(issueChangePostProcessor.wasCalled()).isFalse();
}
@Test
@@ -115,6 +118,7 @@ public class SetTagsActionTest {
IssueDto issueReloaded = dbClient.issueDao().selectByKey(db.getSession(), issueDto.getKey()).get();
assertThat(issueReloaded.getTags()).isEmpty();
+ assertThat(issueChangePostProcessor.wasCalled()).isFalse();
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java
index c7edce5e1f5..e51d37abab2 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java
@@ -19,8 +19,6 @@
*/
package org.sonar.server.issue.ws;
-import java.util.Collections;
-import java.util.Date;
import java.util.List;
import javax.annotation.Nullable;
import org.junit.Rule;
@@ -32,9 +30,7 @@ import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
-import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
-import org.sonar.core.issue.IssueChangeContext;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
@@ -49,15 +45,13 @@ import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
import org.sonar.server.issue.IssueUpdater;
import org.sonar.server.issue.ServerIssueStorage;
+import org.sonar.server.issue.TestIssueChangePostProcessor;
import org.sonar.server.issue.index.IssueIndexDefinition;
import org.sonar.server.issue.index.IssueIndexer;
import org.sonar.server.issue.index.IssueIteratorFactory;
import org.sonar.server.notification.NotificationManager;
import org.sonar.server.organization.DefaultOrganizationProvider;
import org.sonar.server.organization.TestDefaultOrganizationProvider;
-import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventListeners;
import org.sonar.server.rule.DefaultRuleFinder;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
@@ -100,12 +94,12 @@ public class SetTypeActionTest {
private ArgumentCaptor<SearchResponseData> preloadedSearchResponseDataCaptor = ArgumentCaptor.forClass(SearchResponseData.class);
private IssueIndexer issueIndexer = new IssueIndexer(esTester.client(), dbClient, new IssueIteratorFactory(dbClient));
- private QGChangeEventFactory qgChangeEventFactory = mock(QGChangeEventFactory.class);
- private QGChangeEventListeners qgChangeEventListeners = mock(QGChangeEventListeners.class);
+ private TestIssueChangePostProcessor issueChangePostProcessor = new TestIssueChangePostProcessor();
private WsActionTester tester = new WsActionTester(new SetTypeAction(userSession, dbClient, new IssueFinder(dbClient, userSession), new IssueFieldsSetter(),
new IssueUpdater(dbClient,
- new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), mock(NotificationManager.class)),
- responseWriter, system2, qgChangeEventFactory, qgChangeEventListeners));
+ new ServerIssueStorage(system2, new DefaultRuleFinder(dbClient, defaultOrganizationProvider), dbClient, issueIndexer), mock(NotificationManager.class),
+ issueChangePostProcessor),
+ responseWriter, system2));
@Test
public void set_type() {
@@ -113,11 +107,6 @@ public class SetTypeActionTest {
when(system2.now()).thenReturn(now);
IssueDto issueDto = issueDbTester.insertIssue(newIssue().setType(CODE_SMELL));
setUserWithBrowseAndAdministerIssuePermission(issueDto);
- QGChangeEventFactory.IssueChange issueChange = new QGChangeEventFactory.IssueChange(BUG, null);
- IssueChangeContext changeContext = IssueChangeContext.createUser(new Date(now), userSession.getLogin());
- List<QGChangeEvent> qgChangeEvents = Collections.singletonList(mock(QGChangeEvent.class));
- when(qgChangeEventFactory.from(any(QGChangeEventFactory.IssueChangeData.class), eq(issueChange), eq(changeContext)))
- .thenReturn(qgChangeEvents);
call(issueDto.getKey(), BUG.name());
@@ -126,16 +115,9 @@ public class SetTypeActionTest {
IssueDto issueReloaded = dbClient.issueDao().selectByKey(dbTester.getSession(), issueDto.getKey()).get();
assertThat(issueReloaded.getType()).isEqualTo(BUG.getDbConstant());
- ArgumentCaptor<QGChangeEventFactory.IssueChangeData> issueChangeDataCaptor = ArgumentCaptor.forClass(QGChangeEventFactory.IssueChangeData.class);
- verify(qgChangeEventFactory).from(issueChangeDataCaptor.capture(), eq(issueChange), eq(changeContext));
- QGChangeEventFactory.IssueChangeData issueChangeData = issueChangeDataCaptor.getValue();
- assertThat(issueChangeData.getIssues())
- .extracting(DefaultIssue::key)
- .containsOnly(issueDto.getKey());
- assertThat(issueChangeData.getComponents())
+ assertThat(issueChangePostProcessor.calledComponents())
.extracting(ComponentDto::uuid)
- .containsOnly(issueDto.getComponentUuid(), issueDto.getProjectUuid());
- verify(qgChangeEventListeners).broadcastOnIssueChange(issueChangeData, qgChangeEvents);
+ .containsExactlyInAnyOrder(issueDto.getComponentUuid());
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImplTest.java
new file mode 100644
index 00000000000..8e7d34e9f53
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/live/IssueMetricFormulaFactoryImplTest.java
@@ -0,0 +1,832 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.issue.Issue;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.issue.IssueGroupDto;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.DebtRatingGrid;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class IssueMetricFormulaFactoryImplTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private IssueMetricFormulaFactoryImpl underTest = new IssueMetricFormulaFactoryImpl();
+
+ @Test
+ public void getFormulaMetrics_include_the_dependent_metrics() {
+ for (IssueMetricFormula formula : underTest.getFormulas()) {
+ assertThat(underTest.getFormulaMetrics()).contains(formula.getMetric());
+ for (Metric dependentMetric : formula.getDependentMetrics()) {
+ assertThat(underTest.getFormulaMetrics()).contains(dependentMetric);
+ }
+ }
+ }
+
+ @Test
+ public void test_violations() {
+ withNoIssues().assertThatValueIs(CoreMetrics.VIOLATIONS, 0);
+ with(newGroup(), newGroup().setCount(4)).assertThatValueIs(CoreMetrics.VIOLATIONS, 5);
+
+ // exclude resolved
+ IssueGroupDto resolved = newResolvedGroup(Issue.RESOLUTION_FIXED, Issue.STATUS_RESOLVED);
+ with(newGroup(), newGroup(), resolved).assertThatValueIs(CoreMetrics.VIOLATIONS, 2);
+
+ // include issues on leak
+ IssueGroupDto onLeak = newGroup().setCount(11).setInLeak(true);
+ with(newGroup(), newGroup(), onLeak).assertThatValueIs(CoreMetrics.VIOLATIONS, 1 + 1 + 11);
+ }
+
+ @Test
+ public void test_bugs() {
+ withNoIssues().assertThatValueIs(CoreMetrics.BUGS, 0);
+ with(
+ newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(5),
+ // exclude resolved
+ newResolvedGroup(RuleType.BUG).setCount(7),
+ // not bugs
+ newGroup(RuleType.CODE_SMELL).setCount(11))
+ .assertThatValueIs(CoreMetrics.BUGS, 3 + 5);
+ }
+
+ @Test
+ public void test_code_smells() {
+ withNoIssues().assertThatValueIs(CoreMetrics.CODE_SMELLS, 0);
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setCount(5),
+ // exclude resolved
+ newResolvedGroup(RuleType.CODE_SMELL).setCount(7),
+ // not code smells
+ newGroup(RuleType.BUG).setCount(11))
+ .assertThatValueIs(CoreMetrics.CODE_SMELLS, 3 + 5);
+ }
+
+ @Test
+ public void test_vulnerabilities() {
+ withNoIssues().assertThatValueIs(CoreMetrics.VULNERABILITIES, 0);
+ with(
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(5),
+ // exclude resolved
+ newResolvedGroup(RuleType.VULNERABILITY).setCount(7),
+ // not vulnerabilities
+ newGroup(RuleType.BUG).setCount(11))
+ .assertThatValueIs(CoreMetrics.VULNERABILITIES, 3 + 5);
+ }
+
+ @Test
+ public void count_unresolved_by_severity() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.BLOCKER_VIOLATIONS, 0)
+ .assertThatValueIs(CoreMetrics.CRITICAL_VIOLATIONS, 0)
+ .assertThatValueIs(CoreMetrics.MAJOR_VIOLATIONS, 0)
+ .assertThatValueIs(CoreMetrics.MINOR_VIOLATIONS, 0)
+ .assertThatValueIs(CoreMetrics.INFO_VIOLATIONS, 0);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(5),
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(7),
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(11),
+ // include leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(13),
+ // exclude resolved
+ newResolvedGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setCount(17),
+ newResolvedGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setCount(19))
+ .assertThatValueIs(CoreMetrics.BLOCKER_VIOLATIONS, 11 + 13)
+ .assertThatValueIs(CoreMetrics.CRITICAL_VIOLATIONS, 7)
+ .assertThatValueIs(CoreMetrics.MAJOR_VIOLATIONS, 3 + 5)
+ .assertThatValueIs(CoreMetrics.MINOR_VIOLATIONS, 0)
+ .assertThatValueIs(CoreMetrics.INFO_VIOLATIONS, 0);
+ }
+
+ @Test
+ public void count_resolved() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.FALSE_POSITIVE_ISSUES, 0)
+ .assertThatValueIs(CoreMetrics.WONT_FIX_ISSUES, 0);
+
+ with(
+ newResolvedGroup(Issue.RESOLUTION_FIXED, Issue.STATUS_RESOLVED).setCount(3),
+ newResolvedGroup(Issue.RESOLUTION_FALSE_POSITIVE, Issue.STATUS_CLOSED).setCount(5),
+ newResolvedGroup(Issue.RESOLUTION_WONT_FIX, Issue.STATUS_CLOSED).setSeverity(Severity.MAJOR).setCount(7),
+ newResolvedGroup(Issue.RESOLUTION_WONT_FIX, Issue.STATUS_CLOSED).setSeverity(Severity.BLOCKER).setCount(11),
+ newResolvedGroup(Issue.RESOLUTION_REMOVED, Issue.STATUS_CLOSED).setCount(13),
+ // exclude unresolved
+ newGroup(RuleType.VULNERABILITY).setCount(17),
+ newGroup(RuleType.BUG).setCount(19))
+ .assertThatValueIs(CoreMetrics.FALSE_POSITIVE_ISSUES, 5)
+ .assertThatValueIs(CoreMetrics.WONT_FIX_ISSUES, 7 + 11);
+ }
+
+ @Test
+ public void count_by_status() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.CONFIRMED_ISSUES, 0)
+ .assertThatValueIs(CoreMetrics.OPEN_ISSUES, 0)
+ .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 0);
+
+ with(
+ newGroup().setStatus(Issue.STATUS_CONFIRMED).setSeverity(Severity.BLOCKER).setCount(3),
+ newGroup().setStatus(Issue.STATUS_CONFIRMED).setSeverity(Severity.INFO).setCount(5),
+ newGroup().setStatus(Issue.STATUS_REOPENED).setCount(7),
+ newGroup(RuleType.CODE_SMELL).setStatus(Issue.STATUS_OPEN).setCount(9),
+ newGroup(RuleType.BUG).setStatus(Issue.STATUS_OPEN).setCount(11),
+ newResolvedGroup(Issue.RESOLUTION_FALSE_POSITIVE, Issue.STATUS_CLOSED).setCount(13))
+ .assertThatValueIs(CoreMetrics.CONFIRMED_ISSUES, 3 + 5)
+ .assertThatValueIs(CoreMetrics.OPEN_ISSUES, 9 + 11)
+ .assertThatValueIs(CoreMetrics.REOPENED_ISSUES, 7);
+ }
+
+ @Test
+ public void test_technical_debt() {
+ withNoIssues().assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setEffort(3.0).setInLeak(false),
+ newGroup(RuleType.CODE_SMELL).setEffort(5.0).setInLeak(true),
+ // not code smells
+ newGroup(RuleType.BUG).setEffort(7.0),
+ // exclude resolved
+ newResolvedGroup(RuleType.CODE_SMELL).setEffort(17.0))
+ .assertThatValueIs(CoreMetrics.TECHNICAL_DEBT, 3.0 + 5.0);
+ }
+
+ @Test
+ public void test_reliability_remediation_effort() {
+ withNoIssues().assertThatValueIs(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, 0);
+
+ with(
+ newGroup(RuleType.BUG).setEffort(3.0),
+ newGroup(RuleType.BUG).setEffort(5.0).setSeverity(Severity.BLOCKER),
+ // not bugs
+ newGroup(RuleType.CODE_SMELL).setEffort(7.0),
+ // exclude resolved
+ newResolvedGroup(RuleType.BUG).setEffort(17.0))
+ .assertThatValueIs(CoreMetrics.RELIABILITY_REMEDIATION_EFFORT, 3.0 + 5.0);
+ }
+
+ @Test
+ public void test_security_remediation_effort() {
+ withNoIssues().assertThatValueIs(CoreMetrics.SECURITY_REMEDIATION_EFFORT, 0);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setEffort(3.0),
+ newGroup(RuleType.VULNERABILITY).setEffort(5.0).setSeverity(Severity.BLOCKER),
+ // not vulnerability
+ newGroup(RuleType.CODE_SMELL).setEffort(7.0),
+ // exclude resolved
+ newResolvedGroup(RuleType.VULNERABILITY).setEffort(17.0))
+ .assertThatValueIs(CoreMetrics.SECURITY_REMEDIATION_EFFORT, 3.0 + 5.0);
+ }
+
+ @Test
+ public void test_sqale_debt_ratio_and_sqale_rating() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ // technical_debt not computed
+ with(CoreMetrics.DEVELOPMENT_COST, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+ with(CoreMetrics.DEVELOPMENT_COST, 20)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ // development_cost not computed
+ with(CoreMetrics.TECHNICAL_DEBT, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+ with(CoreMetrics.TECHNICAL_DEBT, 20)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ // input measures are available
+ with(CoreMetrics.TECHNICAL_DEBT, 20.0)
+ .and(CoreMetrics.DEVELOPMENT_COST, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ with(CoreMetrics.TECHNICAL_DEBT, 20.0)
+ .and(CoreMetrics.DEVELOPMENT_COST, 160.0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 12.5)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.C);
+
+ with(CoreMetrics.TECHNICAL_DEBT, 20.0)
+ .and(CoreMetrics.DEVELOPMENT_COST, 10.0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 200.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.E);
+
+ // B is 5% --> min debt is exactly 200*0.05=10
+ with(CoreMetrics.DEVELOPMENT_COST, 200.0)
+ .and(CoreMetrics.TECHNICAL_DEBT, 10.0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 5.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.B);
+
+ with(CoreMetrics.TECHNICAL_DEBT, 0.0)
+ .and(CoreMetrics.DEVELOPMENT_COST, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ with(CoreMetrics.TECHNICAL_DEBT, 0.0)
+ .and(CoreMetrics.DEVELOPMENT_COST, 80.0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0);
+
+ with(CoreMetrics.TECHNICAL_DEBT, -20.0)
+ .and(CoreMetrics.DEVELOPMENT_COST, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ // bug, debt can't be negative
+ with(CoreMetrics.TECHNICAL_DEBT, -20.0)
+ .and(CoreMetrics.DEVELOPMENT_COST, 80.0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+
+ // bug, cost can't be negative
+ with(CoreMetrics.TECHNICAL_DEBT, 20.0)
+ .and(CoreMetrics.DEVELOPMENT_COST, -80.0)
+ .assertThatValueIs(CoreMetrics.SQALE_DEBT_RATIO, 0.0)
+ .assertThatValueIs(CoreMetrics.SQALE_RATING, Rating.A);
+ }
+
+ @Test
+ public void test_effort_to_reach_maintainability_rating_A() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+
+ // technical_debt not computed
+ with(CoreMetrics.DEVELOPMENT_COST, 0.0)
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+ with(CoreMetrics.DEVELOPMENT_COST, 20.0)
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+
+ // development_cost not computed
+ with(CoreMetrics.TECHNICAL_DEBT, 0.0)
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+ with(CoreMetrics.TECHNICAL_DEBT, 20.0)
+ // development cost is considered as zero, so the effort is to reach... zero
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 20.0);
+
+ // B to A
+ with(CoreMetrics.DEVELOPMENT_COST, 200.0)
+ .and(CoreMetrics.TECHNICAL_DEBT, 40.0)
+ // B is 5% --> goal is to reach 200*0.05=10 --> effort is 40-10=30
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 40.0 - (200.0 * 0.05));
+
+ // E to A
+ with(CoreMetrics.DEVELOPMENT_COST, 200.0)
+ .and(CoreMetrics.TECHNICAL_DEBT, 180.0)
+ // B is 5% --> goal is to reach 200*0.05=10 --> effort is 180-10=170
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 180.0 - (200.0 * 0.05));
+
+ // already A
+ with(CoreMetrics.DEVELOPMENT_COST, 200.0)
+ .and(CoreMetrics.TECHNICAL_DEBT, 8.0)
+ // B is 5% --> goal is to reach 200*0.05=10 --> debt is already at 8 --> effort to reach A is zero
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+
+ // exactly lower range of B
+ with(CoreMetrics.DEVELOPMENT_COST, 200.0)
+ .and(CoreMetrics.TECHNICAL_DEBT, 10.0)
+ // B is 5% --> goal is to reach 200*0.05=10 --> debt is 10 --> effort to reach A is zero
+ // FIXME need zero to reach A but effective rating is B !
+ .assertThatValueIs(CoreMetrics.EFFORT_TO_REACH_MAINTAINABILITY_RATING_A, 0.0);
+ }
+
+ @Test
+ public void test_reliability_rating() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.A);
+
+ with(
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(1),
+ newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setCount(5),
+ // excluded, not a bug
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(3))
+ // highest severity of bugs is CRITICAL --> D
+ .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.D);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(5))
+ // no bugs --> A
+ .assertThatValueIs(CoreMetrics.RELIABILITY_RATING, Rating.A);
+ }
+
+ @Test
+ public void test_security_rating() {
+ withNoIssues()
+ .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.A);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setCount(1),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setCount(5),
+ // excluded, not a vulnerability
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setCount(3))
+ // highest severity of vulnerabilities is CRITICAL --> D
+ .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.D);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setCount(5))
+ // no vulnerabilities --> A
+ .assertThatValueIs(CoreMetrics.SECURITY_RATING, Rating.A);
+ }
+
+ @Test
+ public void test_new_bugs() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_BUGS, 0.0);
+
+ with(
+ newGroup(RuleType.BUG).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.BUG).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
+ newGroup(RuleType.BUG).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
+ // not bugs
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(9),
+ newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(11))
+ .assertThatLeakValueIs(CoreMetrics.NEW_BUGS, 5 + 7);
+ }
+
+ @Test
+ public void test_new_code_smells() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_CODE_SMELLS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
+ // not code smells
+ newGroup(RuleType.BUG).setInLeak(true).setCount(9),
+ newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(11))
+ .assertThatLeakValueIs(CoreMetrics.NEW_CODE_SMELLS, 5 + 7);
+ }
+
+ @Test
+ public void test_new_vulnerabilities() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_VULNERABILITIES, 0.0);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setInLeak(false).setSeverity(Severity.MAJOR).setCount(3),
+ newGroup(RuleType.VULNERABILITY).setInLeak(true).setSeverity(Severity.CRITICAL).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setInLeak(true).setSeverity(Severity.MINOR).setCount(7),
+ // not vulnerabilities
+ newGroup(RuleType.BUG).setInLeak(true).setCount(9),
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(11))
+ .assertThatLeakValueIs(CoreMetrics.NEW_VULNERABILITIES, 5 + 7);
+ }
+
+ @Test
+ public void test_new_violations() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.BUG).setInLeak(true).setCount(5),
+ newGroup(RuleType.CODE_SMELL).setInLeak(true).setCount(7),
+ newGroup(RuleType.VULNERABILITY).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.BUG).setInLeak(false).setCount(11),
+ newGroup(RuleType.CODE_SMELL).setInLeak(false).setCount(13),
+ newGroup(RuleType.VULNERABILITY).setInLeak(false).setCount(17))
+ .assertThatLeakValueIs(CoreMetrics.NEW_VIOLATIONS, 5 + 7 + 9);
+ }
+
+ @Test
+ public void test_new_blocker_violations() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(true).setCount(7),
+ // not blocker
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(false).setCount(11),
+ newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(false).setCount(13))
+ .assertThatLeakValueIs(CoreMetrics.NEW_BLOCKER_VIOLATIONS, 3 + 5 + 7);
+ }
+
+ @Test
+ public void test_new_critical_violations() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(7),
+ // not CRITICAL
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(false).setCount(11),
+ newGroup(RuleType.BUG).setSeverity(Severity.CRITICAL).setInLeak(false).setCount(13))
+ .assertThatLeakValueIs(CoreMetrics.NEW_CRITICAL_VIOLATIONS, 3 + 5 + 7);
+ }
+
+ @Test
+ public void test_new_major_violations() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAJOR_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(true).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setInLeak(true).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MAJOR).setInLeak(true).setCount(7),
+ // not MAJOR
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MAJOR).setInLeak(false).setCount(11),
+ newGroup(RuleType.BUG).setSeverity(Severity.MAJOR).setInLeak(false).setCount(13))
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAJOR_VIOLATIONS, 3 + 5 + 7);
+ }
+
+ @Test
+ public void test_new_minor_violations() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_MINOR_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MINOR).setInLeak(true).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setInLeak(true).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setInLeak(true).setCount(7),
+ // not MINOR
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.MINOR).setInLeak(false).setCount(11),
+ newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setInLeak(false).setCount(13))
+ .assertThatLeakValueIs(CoreMetrics.NEW_MINOR_VIOLATIONS, 3 + 5 + 7);
+ }
+
+ @Test
+ public void test_new_info_violations() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.INFO).setInLeak(true).setCount(3),
+ newGroup(RuleType.BUG).setSeverity(Severity.INFO).setInLeak(true).setCount(5),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setInLeak(true).setCount(7),
+ // not INFO
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.CRITICAL).setInLeak(true).setCount(9),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.INFO).setInLeak(false).setCount(11),
+ newGroup(RuleType.BUG).setSeverity(Severity.INFO).setInLeak(false).setCount(13))
+ .assertThatLeakValueIs(CoreMetrics.NEW_INFO_VIOLATIONS, 3 + 5 + 7);
+ }
+
+ @Test
+ public void test_new_technical_debt() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 0.0);
+
+ with(
+ newGroup(RuleType.CODE_SMELL).setEffort(3.0).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.CODE_SMELL).setEffort(5.0).setInLeak(false),
+ // not code smells
+ newGroup(RuleType.BUG).setEffort(7.0).setInLeak(true),
+ // exclude resolved
+ newResolvedGroup(RuleType.CODE_SMELL).setEffort(17.0).setInLeak(true))
+ .assertThatLeakValueIs(CoreMetrics.NEW_TECHNICAL_DEBT, 3.0);
+ }
+
+ @Test
+ public void test_new_reliability_remediation_effort() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, 0.0);
+
+ with(
+ newGroup(RuleType.BUG).setEffort(3.0).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.BUG).setEffort(5.0).setInLeak(false),
+ // not bugs
+ newGroup(RuleType.CODE_SMELL).setEffort(7.0).setInLeak(true),
+ // exclude resolved
+ newResolvedGroup(RuleType.BUG).setEffort(17.0).setInLeak(true))
+ .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_REMEDIATION_EFFORT, 3.0);
+ }
+
+ @Test
+ public void test_new_security_remediation_effort() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, 0.0);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setEffort(3.0).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.VULNERABILITY).setEffort(5.0).setInLeak(false),
+ // not vulnerability
+ newGroup(RuleType.CODE_SMELL).setEffort(7.0).setInLeak(true),
+ // exclude resolved
+ newResolvedGroup(RuleType.VULNERABILITY).setEffort(17.0).setInLeak(true))
+ .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_REMEDIATION_EFFORT, 3.0);
+ }
+
+ @Test
+ public void test_new_reliability_rating() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_RATING, Rating.A);
+
+ with(
+ newGroup(RuleType.BUG).setSeverity(Severity.INFO).setCount(3).setInLeak(true),
+ newGroup(RuleType.BUG).setSeverity(Severity.MINOR).setCount(1).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(false),
+ // not bug
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true),
+ // exclude resolved
+ newResolvedGroup(RuleType.BUG).setSeverity(Severity.BLOCKER).setInLeak(true))
+ // highest severity of bugs on leak period is minor -> B
+ .assertThatLeakValueIs(CoreMetrics.NEW_RELIABILITY_RATING, Rating.B);
+ }
+
+ @Test
+ public void test_new_security_rating() {
+ withNoIssues().assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_RATING, Rating.A);
+
+ with(
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.INFO).setCount(3).setInLeak(true),
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.MINOR).setCount(1).setInLeak(true),
+ // not in leak
+ newGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(false),
+ // not vulnerability
+ newGroup(RuleType.CODE_SMELL).setSeverity(Severity.BLOCKER).setInLeak(true),
+ // exclude resolved
+ newResolvedGroup(RuleType.VULNERABILITY).setSeverity(Severity.BLOCKER).setInLeak(true))
+ // highest severity of bugs on leak period is minor -> B
+ .assertThatLeakValueIs(CoreMetrics.NEW_SECURITY_RATING, Rating.B);
+ }
+
+ @Test
+ public void test_new_sqale_debt_ratio_and_new_maintainability_rating() {
+ withNoIssues()
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ // technical_debt not computed
+ withLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+ withLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 20)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ // development_cost not computed
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+
+ // input measures are available
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 20.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, 20.0)
+ .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)
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 10.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 200.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.E);
+
+ // B is 5% --> min debt is exactly 200*0.05=10
+ 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.B);
+
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, 0.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)
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, 80.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0);
+
+ withLeak(CoreMetrics.NEW_TECHNICAL_DEBT, -20.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)
+ .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)
+ .andLeak(CoreMetrics.NEW_DEVELOPMENT_COST, -80.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_SQALE_DEBT_RATIO, 0.0)
+ .assertThatLeakValueIs(CoreMetrics.NEW_MAINTAINABILITY_RATING, Rating.A);
+ }
+
+ private Verifier with(IssueGroupDto... groups) {
+ return new Verifier(groups);
+ }
+
+ private Verifier withNoIssues() {
+ return new Verifier(new IssueGroupDto[0]);
+ }
+
+ private Verifier with(Metric metric, double value) {
+ return new Verifier(new IssueGroupDto[0]).and(metric, value);
+ }
+
+ private Verifier withLeak(Metric metric, double leakValue) {
+ return new Verifier(new IssueGroupDto[0]).andLeak(metric, leakValue);
+ }
+
+ private class Verifier {
+ private final IssueGroupDto[] groups;
+ private final Map<Metric, Double> values = new HashMap<>();
+ private final Map<Metric, Double> leakValues = new HashMap<>();
+
+ private Verifier(IssueGroupDto[] groups) {
+ this.groups = groups;
+ }
+
+ Verifier and(Metric metric, double value) {
+ this.values.put(metric, value);
+ return this;
+ }
+
+ Verifier andLeak(Metric metric, double value) {
+ this.leakValues.put(metric, value);
+ return this;
+ }
+
+ Verifier assertThatValueIs(Metric metric, double expectedValue) {
+ TestContext context = run(metric, false);
+ assertThat(context.doubleValue).isNotNull().isEqualTo(expectedValue);
+ return this;
+ }
+
+ Verifier assertThatLeakValueIs(Metric metric, double expectedValue) {
+ TestContext context = run(metric, true);
+ assertThat(context.doubleLeakValue).isNotNull().isEqualTo(expectedValue);
+ return this;
+ }
+
+ Verifier assertThatLeakValueIs(Metric metric, Rating expectedRating) {
+ TestContext context = run(metric, true);
+ assertThat(context.ratingLeakValue).isNotNull().isEqualTo(expectedRating);
+ return this;
+ }
+
+ Verifier assertThatValueIs(Metric metric, Rating expectedValue) {
+ TestContext context = run(metric, false);
+ assertThat(context.ratingValue).isNotNull().isEqualTo(expectedValue);
+ return this;
+ }
+
+ private TestContext run(Metric metric, boolean expectLeakFormula) {
+ IssueMetricFormula formula = underTest.getFormulas().stream()
+ .filter(f -> f.getMetric().getKey().equals(metric.getKey()))
+ .findFirst()
+ .get();
+ assertThat(formula.isOnLeak()).isEqualTo(expectLeakFormula);
+ TestContext context = new TestContext(formula.getDependentMetrics(), values, leakValues);
+ formula.compute(context, newIssueCounter(groups));
+ return context;
+ }
+ }
+
+ private static IssueCounter newIssueCounter(IssueGroupDto... issues) {
+ return new IssueCounter(asList(issues));
+ }
+
+ private static IssueGroupDto newGroup() {
+ return newGroup(RuleType.CODE_SMELL);
+ }
+
+ private static IssueGroupDto newGroup(RuleType ruleType) {
+ IssueGroupDto dto = new IssueGroupDto();
+ // set non-null fields
+ dto.setRuleType(ruleType.getDbConstant());
+ dto.setCount(1);
+ dto.setEffort(0.0);
+ dto.setSeverity(Severity.INFO);
+ dto.setStatus(Issue.STATUS_OPEN);
+ dto.setInLeak(false);
+ return dto;
+ }
+
+ private static IssueGroupDto newResolvedGroup(RuleType ruleType) {
+ return newGroup(ruleType).setResolution(Issue.RESOLUTION_FALSE_POSITIVE).setStatus(Issue.STATUS_CLOSED);
+ }
+
+ private static IssueGroupDto newResolvedGroup(String resolution, String status) {
+ return newGroup().setResolution(resolution).setStatus(status);
+ }
+
+ private static class TestContext implements IssueMetricFormula.Context {
+ private final Set<Metric> dependentMetrics;
+ private Double doubleValue;
+ private Rating ratingValue;
+ private Double doubleLeakValue;
+ private Rating ratingLeakValue;
+ private final Map<Metric, Double> values;
+ private final Map<Metric, Double> leakValues;
+
+ private TestContext(Collection<Metric> dependentMetrics, Map<Metric, Double> values, Map<Metric, Double> leakValues) {
+ this.dependentMetrics = new HashSet<>(dependentMetrics);
+ this.values = values;
+ this.leakValues = leakValues;
+ }
+
+ @Override
+ public ComponentDto getComponent() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public DebtRatingGrid getDebtRatingGrid() {
+ return new DebtRatingGrid(new double[] {0.05, 0.1, 0.2, 0.5});
+ }
+
+ @Override
+ public Optional<Double> getValue(Metric metric) {
+ if (!dependentMetrics.contains(metric)) {
+ throw new IllegalStateException("Metric " + metric.getKey() + " is not declared as a dependency");
+ }
+ if (values.containsKey(metric)) {
+ return Optional.of(values.get(metric));
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public Optional<Double> getLeakValue(Metric metric) {
+ if (!dependentMetrics.contains(metric)) {
+ throw new IllegalStateException("Metric " + metric.getKey() + " is not declared as a dependency");
+ }
+ if (leakValues.containsKey(metric)) {
+ return Optional.of(leakValues.get(metric));
+ }
+ return Optional.empty();
+ }
+
+ @Override
+ public void setValue(double value) {
+ this.doubleValue = value;
+ }
+
+ @Override
+ public void setValue(Rating value) {
+ this.ratingValue = value;
+ }
+
+ @Override
+ public void setLeakValue(double value) {
+ this.doubleLeakValue = value;
+ }
+
+ @Override
+ public void setLeakValue(Rating value) {
+ this.ratingLeakValue = value;
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureComputerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureComputerImplTest.java
new file mode 100644
index 00000000000..be76582d739
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureComputerImplTest.java
@@ -0,0 +1,377 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mockito;
+import org.sonar.api.config.PropertyDefinitions;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.core.config.CorePropertyDefinitions;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.server.computation.task.projectanalysis.qualitymodel.Rating;
+import org.sonar.server.es.ProjectIndexer;
+import org.sonar.server.es.TestProjectIndexers;
+import org.sonar.server.qualitygate.changeevent.QGChangeEvent;
+import org.sonar.server.settings.ProjectConfigurationLoader;
+import org.sonar.server.settings.TestProjectConfigurationLoader;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.sonar.api.resources.Qualifiers.ORDERED_BOTTOM_UP;
+
+public class LiveMeasureComputerImplTest {
+
+ @Rule
+ public DbTester db = DbTester.create();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private TestProjectIndexers projectIndexer = new TestProjectIndexers();
+ private MetricDto intMetric;
+ private MetricDto ratingMetric;
+ private ComponentDto project;
+ private ComponentDto dir;
+ private ComponentDto file1;
+ private ComponentDto file2;
+
+ @Before
+ public void setUp() throws Exception {
+ intMetric = db.measures().insertMetric(m -> m.setValueType(Metric.ValueType.INT.name()));
+ ratingMetric = db.measures().insertMetric(m -> m.setValueType(Metric.ValueType.RATING.name()));
+ project = db.components().insertMainBranch();
+ 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));
+ }
+
+ @Test
+ public void compute_and_insert_measures_if_they_dont_exist_yet() {
+ markProjectAsAnalyzed(project);
+
+ List<QGChangeEvent> result = run(asList(file1, file2), newQualifierBasedIntFormula(), newRatingConstantFormula(Rating.C));
+
+ // 2 measures per component have been created
+ // Numeric value depends on qualifier (see newQualifierBasedIntFormula())
+ assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(8);
+ assertThatIntMeasureHasValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
+ assertThatRatingMeasureHasValue(file1, Rating.C);
+ assertThatIntMeasureHasValue(file2, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
+ assertThatRatingMeasureHasValue(file2, Rating.C);
+ assertThatIntMeasureHasValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
+ assertThatRatingMeasureHasValue(dir, Rating.C);
+ assertThatIntMeasureHasValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
+ assertThatRatingMeasureHasValue(project, Rating.C);
+ assertThatProjectChanged(result, project);
+ }
+
+ @Test
+ public void compute_and_update_measures_if_they_already_exist() {
+ markProjectAsAnalyzed(project);
+ db.measures().insertLiveMeasure(project, intMetric, m -> m.setValue(42.0));
+ db.measures().insertLiveMeasure(dir, intMetric, m -> m.setValue(42.0));
+ db.measures().insertLiveMeasure(file1, intMetric, m -> m.setValue(42.0));
+ db.measures().insertLiveMeasure(file2, intMetric, m -> m.setValue(42.0));
+
+ // generates values 1, 2, 3
+ List<QGChangeEvent> result = run(file1, newQualifierBasedIntFormula());
+
+ assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(4);
+ assertThatProjectChanged(result, project);
+
+ // Numeric value depends on qualifier (see newQualifierBasedIntFormula())
+ assertThatIntMeasureHasValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
+ assertThatIntMeasureHasValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
+ assertThatIntMeasureHasValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
+ // untouched
+ assertThatIntMeasureHasValue(file2, 42.0);
+ }
+
+ @Test
+ public void variation_is_refreshed_when_int_value_is_changed() {
+ markProjectAsAnalyzed(project);
+ // value is:
+ // 42 on last analysis
+ // 42-12=30 on beginning of leak period
+ db.measures().insertLiveMeasure(project, intMetric, m -> m.setValue(42.0).setVariation(12.0));
+
+ // new value is 44, so variation on leak period is 44-30=14
+ List<QGChangeEvent> result = run(file1, newIntConstantFormula(44.0));
+
+ LiveMeasureDto measure = assertThatIntMeasureHasValue(project, 44.0);
+ assertThat(measure.getVariation()).isEqualTo(14.0);
+ assertThatProjectChanged(result, project);
+ }
+
+ @Test
+ public void variation_is_refreshed_when_rating_value_is_changed() {
+ markProjectAsAnalyzed(project);
+ // value is:
+ // B on last analysis
+ // D on beginning of leak period --> variation is -2
+ db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setValue((double) Rating.B.getIndex()).setData("B").setVariation(-2.0));
+
+ // new value is C, so variation on leak period is D to C = -1
+ List<QGChangeEvent> result = run(file1, newRatingConstantFormula(Rating.C));
+
+ LiveMeasureDto measure = assertThatRatingMeasureHasValue(project, Rating.C);
+ assertThat(measure.getVariation()).isEqualTo(-1.0);
+ assertThatProjectChanged(result, project);
+ }
+
+ @Test
+ public void variation_does_not_change_if_rating_value_does_not_change() {
+ markProjectAsAnalyzed(project);
+ // value is:
+ // B on last analysis
+ // D on beginning of leak period --> variation is -2
+ db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setValue((double) Rating.B.getIndex()).setData("B").setVariation(-2.0));
+
+ // new value is still B, so variation on leak period is still -2
+ List<QGChangeEvent> result = run(file1, newRatingConstantFormula(Rating.B));
+
+ LiveMeasureDto measure = assertThatRatingMeasureHasValue(project, Rating.B);
+ assertThat(measure.getVariation()).isEqualTo(-2.0);
+ assertThatProjectChanged(result, project);
+ }
+
+ @Test
+ public void refresh_leak_measures() {
+ markProjectAsAnalyzed(project);
+ db.measures().insertLiveMeasure(project, intMetric, m -> m.setVariation(42.0).setValue(null));
+ db.measures().insertLiveMeasure(project, ratingMetric, m -> m.setVariation((double)Rating.E.getIndex()));
+ db.measures().insertLiveMeasure(dir, intMetric, m -> m.setVariation(42.0).setValue(null));
+ db.measures().insertLiveMeasure(dir, ratingMetric, m -> m.setVariation((double)Rating.D.getIndex()));
+ db.measures().insertLiveMeasure(file1, intMetric, m -> m.setVariation(42.0).setValue(null));
+ db.measures().insertLiveMeasure(file1, ratingMetric, m -> m.setVariation((double)Rating.C.getIndex()));
+
+ // generates values 1, 2, 3 on leak measures
+ List<QGChangeEvent> result = run(file1, newQualifierBasedIntLeakFormula(), newRatingLeakFormula(Rating.B));
+
+ assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(6);
+
+ // Numeric value depends on qualifier (see newQualifierBasedIntLeakFormula())
+ assertThatIntMeasureHasLeakValue(file1, ORDERED_BOTTOM_UP.indexOf(Qualifiers.FILE));
+ assertThatRatingMeasureHasLeakValue(file1, Rating.B);
+ assertThatIntMeasureHasLeakValue(dir, ORDERED_BOTTOM_UP.indexOf(Qualifiers.DIRECTORY));
+ assertThatRatingMeasureHasLeakValue(dir, Rating.B);
+ assertThatIntMeasureHasLeakValue(project, ORDERED_BOTTOM_UP.indexOf(Qualifiers.PROJECT));
+ assertThatRatingMeasureHasLeakValue(project, Rating.B);
+ assertThatProjectChanged(result, project);
+ }
+
+ @Test
+ public void do_nothing_if_project_has_not_being_analyzed() {
+ // project has no snapshots
+ List<QGChangeEvent> result = run(file1, newIncrementalFormula());
+
+ assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(0);
+ assertThatProjectNotChanged(result, project);
+ }
+
+ @Test
+ public void do_nothing_if_input_components_are_empty() {
+ List<QGChangeEvent> result = run(emptyList(), newIncrementalFormula());
+
+ assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(0);
+ assertThatProjectNotChanged(result, project);
+ }
+
+ @Test
+ public void refresh_multiple_projects_at_the_same_time() {
+ markProjectAsAnalyzed(project);
+ ComponentDto project2 = db.components().insertMainBranch();
+ ComponentDto fileInProject2 = db.components().insertComponent(ComponentTesting.newFileDto(project2));
+ markProjectAsAnalyzed(project2);
+
+ List<QGChangeEvent> result = run(asList(file1, fileInProject2), newQualifierBasedIntFormula());
+
+ // generated values depend on position of qualifier in Qualifiers.ORDERED_BOTTOM_UP (see formula)
+ assertThatIntMeasureHasValue(file1, 0);
+ assertThatIntMeasureHasValue(dir, 2);
+ assertThatIntMeasureHasValue(project, 4);
+ assertThatIntMeasureHasValue(fileInProject2, 0);
+ assertThatIntMeasureHasValue(project2, 4);
+
+ // no other measures generated
+ assertThat(db.countRowsOfTable(db.getSession(), "live_measures")).isEqualTo(5);
+ assertThatProjectChanged(result, project, project2);
+ }
+
+ @Test
+ public void refresh_multiple_branches_at_the_same_time() {
+ // FIXME
+ }
+
+ @Test
+ public void compute_quality_gate_status() {
+ // FIXME
+ }
+
+ @Test
+ public void exception_describes_context_when_a_formula_fails() {
+ markProjectAsAnalyzed(project);
+ Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Fail to compute " + metric.getKey() + " on " + project.getDbKey());
+
+ run(project, new IssueMetricFormula(metric, false, (context, issueCounter) -> {
+ throw new NullPointerException("BOOM");
+ }));
+ }
+
+ private List<QGChangeEvent> run(ComponentDto component, IssueMetricFormula... formulas) {
+ return run(singleton(component), formulas);
+ }
+
+ private List<QGChangeEvent> run(Collection<ComponentDto> components, IssueMetricFormula... formulas) {
+ IssueMetricFormulaFactory formulaFactory = new TestIssueMetricFormulaFactory(asList(formulas));
+
+ LiveQualityGateComputer qGateComputer = mock(LiveQualityGateComputer.class, Mockito.RETURNS_DEEP_STUBS);
+ MapSettings settings = new MapSettings(new PropertyDefinitions(CorePropertyDefinitions.all()));
+ ProjectConfigurationLoader configurationLoader = new TestProjectConfigurationLoader(settings.asConfig());
+
+ LiveMeasureComputerImpl underTest = new LiveMeasureComputerImpl(db.getDbClient(), formulaFactory, qGateComputer, configurationLoader, projectIndexer);
+
+ return underTest.refresh(db.getSession(), components);
+ }
+
+ private void markProjectAsAnalyzed(ComponentDto p) {
+ assertThat(p.qualifier()).isEqualTo(Qualifiers.PROJECT);
+ db.components().insertSnapshot(p, s -> s.setPeriodDate(1_490_000_000L));
+ }
+
+ private LiveMeasureDto assertThatIntMeasureHasValue(ComponentDto component, double expectedValue) {
+ LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), intMetric.getKey()).get();
+ assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
+ assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
+ assertThat(measure.getMetricId()).isEqualTo(intMetric.getId());
+ assertThat(measure.getValue()).isEqualTo(expectedValue);
+ return measure;
+ }
+
+ private LiveMeasureDto assertThatRatingMeasureHasValue(ComponentDto component, Rating expectedRating) {
+ LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), ratingMetric.getKey()).get();
+ assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
+ assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
+ assertThat(measure.getMetricId()).isEqualTo(ratingMetric.getId());
+ assertThat(measure.getValue()).isEqualTo(expectedRating.getIndex());
+ assertThat(measure.getDataAsString()).isEqualTo(expectedRating.name());
+ return measure;
+ }
+
+ private void assertThatIntMeasureHasLeakValue(ComponentDto component, double expectedValue) {
+ LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), intMetric.getKey()).get();
+ assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
+ assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
+ assertThat(measure.getMetricId()).isEqualTo(intMetric.getId());
+ assertThat(measure.getValue()).isNull();
+ assertThat(measure.getVariation()).isEqualTo(expectedValue);
+ }
+
+ private void assertThatRatingMeasureHasLeakValue(ComponentDto component, Rating expectedValue) {
+ LiveMeasureDto measure = db.getDbClient().liveMeasureDao().selectMeasure(db.getSession(), component.uuid(), ratingMetric.getKey()).get();
+ assertThat(measure.getComponentUuid()).isEqualTo(component.uuid());
+ assertThat(measure.getProjectUuid()).isEqualTo(component.projectUuid());
+ assertThat(measure.getMetricId()).isEqualTo(ratingMetric.getId());
+ assertThat(measure.getVariation()).isEqualTo((double) expectedValue.getIndex());
+ }
+
+ private IssueMetricFormula newIncrementalFormula() {
+ Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
+ AtomicInteger counter = new AtomicInteger();
+ return new IssueMetricFormula(metric, false, (ctx, issues) -> {
+ ctx.setValue((double) counter.incrementAndGet());
+ });
+ }
+
+ private IssueMetricFormula newIntConstantFormula(double constant) {
+ Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
+ return new IssueMetricFormula(metric, false, (ctx, issues) -> {
+ ctx.setValue(constant);
+ });
+ }
+
+ private IssueMetricFormula newRatingConstantFormula(Rating constant) {
+ Metric metric = new Metric.Builder(ratingMetric.getKey(), ratingMetric.getShortName(), Metric.ValueType.valueOf(ratingMetric.getValueType())).create();
+ return new IssueMetricFormula(metric, false, (ctx, issues) -> {
+ ctx.setValue(constant);
+ });
+ }
+
+ private IssueMetricFormula newIncrementalLeakFormula() {
+ Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
+ AtomicInteger counter = new AtomicInteger();
+ return new IssueMetricFormula(metric, true, (ctx, issues) -> {
+ ctx.setLeakValue((double) counter.incrementAndGet());
+ });
+ }
+
+ private IssueMetricFormula newRatingLeakFormula(Rating rating) {
+ Metric metric = new Metric.Builder(ratingMetric.getKey(), ratingMetric.getShortName(), Metric.ValueType.valueOf(ratingMetric.getValueType())).create();
+ return new IssueMetricFormula(metric, true, (ctx, issues) -> {
+ ctx.setLeakValue(rating);
+ });
+ }
+
+ private IssueMetricFormula newQualifierBasedIntFormula() {
+ Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
+ return new IssueMetricFormula(metric, false, (ctx, issues) -> {
+ ctx.setValue(ORDERED_BOTTOM_UP.indexOf(ctx.getComponent().qualifier()));
+ });
+ }
+
+ private IssueMetricFormula newQualifierBasedIntLeakFormula() {
+ Metric metric = new Metric.Builder(intMetric.getKey(), intMetric.getShortName(), Metric.ValueType.valueOf(intMetric.getValueType())).create();
+ return new IssueMetricFormula(metric, true, (ctx, issues) -> {
+ ctx.setLeakValue(ORDERED_BOTTOM_UP.indexOf(ctx.getComponent().qualifier()));
+ });
+ }
+
+ private void assertThatProjectChanged(List<QGChangeEvent> events, ComponentDto... projects) {
+ for (ComponentDto p : projects) {
+ assertThat(projectIndexer.hasBeenCalled(p.uuid(), ProjectIndexer.Cause.MEASURE_CHANGE)).isTrue();
+ }
+
+ assertThat(events).extracting(e -> e.getProject().uuid())
+ .containsExactlyInAnyOrder(Arrays.stream(projects).map(ComponentDto::uuid).toArray(String[]::new));
+ }
+
+ private void assertThatProjectNotChanged(List<QGChangeEvent> events, ComponentDto project) {
+ assertThat(projectIndexer.hasBeenCalled(project.uuid(), ProjectIndexer.Cause.MEASURE_CHANGE)).isFalse();
+ assertThat(events).hasSize(0);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureModuleTest.java
new file mode 100644
index 00000000000..3ac75e013d1
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveMeasureModuleTest.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LiveMeasureModuleTest {
+
+ @Test
+ public void verify_count_of_added_components() {
+ ComponentContainer container = new ComponentContainer();
+ new LiveMeasureModule().configure(container);
+ assertThat(container.size()).isEqualTo(3 + 2);
+ }
+
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveQualityGateComputerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveQualityGateComputerImplTest.java
new file mode 100644
index 00000000000..945165d987a
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/live/LiveQualityGateComputerImplTest.java
@@ -0,0 +1,198 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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 com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.db.DbTester;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.BranchType;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.qualitygate.QGateWithOrgDto;
+import org.sonar.db.qualitygate.QualityGateConditionDto;
+import org.sonar.server.qualitygate.Condition;
+import org.sonar.server.qualitygate.EvaluatedCondition;
+import org.sonar.server.qualitygate.EvaluatedQualityGate;
+import org.sonar.server.qualitygate.QualityGate;
+import org.sonar.server.qualitygate.QualityGateEvaluator;
+import org.sonar.server.qualitygate.QualityGateFinder;
+import org.sonar.server.qualitygate.ShortLivingBranchQualityGate;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.groups.Tuple.tuple;
+import static org.sonar.db.component.ComponentTesting.newBranchDto;
+import static org.sonar.db.metric.MetricTesting.newMetricDto;
+import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
+
+public class LiveQualityGateComputerImplTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Rule
+ public DbTester db = DbTester.create();
+
+ private TestQualityGateEvaluator qualityGateEvaluator = new TestQualityGateEvaluator();
+ private LiveQualityGateComputerImpl underTest = new LiveQualityGateComputerImpl(db.getDbClient(), new QualityGateFinder(db.getDbClient()), qualityGateEvaluator);
+
+ @Test
+ public void loadQualityGate_returns_hardcoded_gate_for_short_living_branches() {
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto project = db.components().insertPublicProject(organization);
+ BranchDto branch = newBranchDto(project).setBranchType(BranchType.SHORT);
+ db.components().insertProjectBranch(project, branch);
+
+ QualityGate result = underTest.loadQualityGate(db.getSession(), organization, project, branch);
+
+ assertThat(result).isSameAs(ShortLivingBranchQualityGate.GATE);
+ }
+
+ @Test
+ public void loadQualityGate_on_long_branch_returns_organization_default_gate() {
+ OrganizationDto organization = db.organizations().insert();
+ ComponentDto project = db.components().insertPublicProject(organization);
+ BranchDto branch = newBranchDto(project).setBranchType(BranchType.LONG);
+ db.components().insertProjectBranch(project, branch);
+
+ MetricDto metric = db.measures().insertMetric();
+ QGateWithOrgDto gate = db.qualityGates().insertQualityGate(organization);
+ db.qualityGates().setDefaultQualityGate(organization, gate);
+ QualityGateConditionDto leakCondition = db.qualityGates().addCondition(gate, metric, c -> c.setPeriod(1));
+ QualityGateConditionDto absoluteCondition = db.qualityGates().addCondition(gate, metric, c -> c.setPeriod(null));
+
+ QualityGate result = underTest.loadQualityGate(db.getSession(), organization, project, branch);
+
+ assertThat(result.getId()).isEqualTo("" + gate.getId());
+ assertThat(result.getConditions())
+ .extracting(Condition::getMetricKey, Condition::getOperator, c -> c.getErrorThreshold().get(), c -> c.getWarningThreshold().get(), Condition::isOnLeakPeriod)
+ .containsExactlyInAnyOrder(
+ tuple(metric.getKey(), Condition.Operator.fromDbValue(leakCondition.getOperator()), leakCondition.getErrorThreshold(), leakCondition.getWarningThreshold(), true),
+ tuple(metric.getKey(), Condition.Operator.fromDbValue(absoluteCondition.getOperator()), absoluteCondition.getErrorThreshold(), absoluteCondition.getWarningThreshold(),
+ false));
+ }
+
+ @Test
+ public void getMetricsRelatedTo() {
+ Condition condition = new Condition("metric1", Condition.Operator.EQUALS, "10", null, false);
+ QualityGate gate = new QualityGate("1", "foo", ImmutableSet.of(condition));
+
+ Set<String> result = underTest.getMetricsRelatedTo(gate);
+
+ assertThat(result).containsExactlyInAnyOrder(
+ // the metrics needed to compute the status of gate
+ condition.getMetricKey(),
+ // generated metrics
+ CoreMetrics.ALERT_STATUS_KEY, CoreMetrics.QUALITY_GATE_DETAILS_KEY);
+ }
+
+ @Test
+ public void refreshGateStatus_generates_gate_related_measures() {
+ ComponentDto project = ComponentTesting.newPublicProjectDto(newOrganizationDto());
+ MetricDto conditionMetric = newMetricDto();
+ MetricDto statusMetric = newMetricDto().setKey(CoreMetrics.ALERT_STATUS_KEY);
+ MetricDto detailsMetric = newMetricDto().setKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY);
+ Condition condition = new Condition(conditionMetric.getKey(), Condition.Operator.GREATER_THAN, "10", null, false);
+ QualityGate gate = new QualityGate("1", "foo", ImmutableSet.of(condition));
+ MeasureMatrix matrix = new MeasureMatrix(singleton(project), asList(conditionMetric, statusMetric, detailsMetric), emptyList());
+
+ EvaluatedQualityGate result = underTest.refreshGateStatus(project, gate, matrix);
+
+ QualityGateEvaluator.Measures measures = qualityGateEvaluator.getCalledMeasures();
+ assertThat(measures.get(conditionMetric.getKey())).isEmpty();
+
+ assertThat(result.getStatus()).isEqualTo(Metric.Level.OK);
+ assertThat(result.getEvaluatedConditions())
+ .extracting(EvaluatedCondition::getStatus)
+ .containsExactly(EvaluatedCondition.EvaluationStatus.OK);
+ assertThat(matrix.getMeasure(project, CoreMetrics.ALERT_STATUS_KEY).get().getDataAsString()).isEqualTo(Metric.Level.OK.name());
+ assertThat(matrix.getMeasure(project, CoreMetrics.QUALITY_GATE_DETAILS_KEY).get().getDataAsString())
+ .isNotEmpty()
+ // json format
+ .startsWith("{").endsWith("}");
+ }
+
+ @Test
+ public void refreshGateStatus_provides_measures_to_evaluator() {
+ ComponentDto project = ComponentTesting.newPublicProjectDto(newOrganizationDto());
+ MetricDto numericMetric = newMetricDto().setValueType(Metric.ValueType.FLOAT.name());
+ MetricDto stringMetric = newMetricDto().setValueType(Metric.ValueType.STRING.name());
+ MetricDto statusMetric = newMetricDto().setKey(CoreMetrics.ALERT_STATUS_KEY);
+ MetricDto detailsMetric = newMetricDto().setKey(CoreMetrics.QUALITY_GATE_DETAILS_KEY);
+ QualityGate gate = new QualityGate("1", "foo", Collections.emptySet());
+ LiveMeasureDto numericMeasure = new LiveMeasureDto().setMetricId(numericMetric.getId()).setValue(1.23).setVariation(4.56).setComponentUuid(project.uuid());
+ LiveMeasureDto stringMeasure = new LiveMeasureDto().setMetricId(stringMetric.getId()).setData("bar").setComponentUuid(project.uuid());
+ MeasureMatrix matrix = new MeasureMatrix(singleton(project), asList(statusMetric, detailsMetric, numericMetric, stringMetric), asList(numericMeasure, stringMeasure));
+
+ underTest.refreshGateStatus(project, gate, matrix);
+
+ QualityGateEvaluator.Measures measures = qualityGateEvaluator.getCalledMeasures();
+
+ QualityGateEvaluator.Measure loadedStringMeasure = measures.get(stringMetric.getKey()).get();
+ assertThat(loadedStringMeasure.getStringValue()).hasValue("bar");
+ assertThat(loadedStringMeasure.getValue()).isEmpty();
+ assertThat(loadedStringMeasure.getLeakValue()).isEmpty();
+ assertThat(loadedStringMeasure.getType()).isEqualTo(Metric.ValueType.STRING);
+
+ QualityGateEvaluator.Measure loadedNumericMeasure = measures.get(numericMetric.getKey()).get();
+ assertThat(loadedNumericMeasure.getStringValue()).isEmpty();
+ assertThat(loadedNumericMeasure.getValue()).hasValue(1.23);
+ assertThat(loadedNumericMeasure.getLeakValue()).hasValue(4.56);
+ assertThat(loadedNumericMeasure.getType()).isEqualTo(Metric.ValueType.FLOAT);
+ }
+
+ private static class TestQualityGateEvaluator implements QualityGateEvaluator {
+ private Measures measures;
+
+ @Override
+ public EvaluatedQualityGate evaluate(QualityGate gate, Measures measures) {
+ checkState(this.measures == null);
+ this.measures = measures;
+ EvaluatedQualityGate.Builder builder = EvaluatedQualityGate.newBuilder().setQualityGate(gate).setStatus(Metric.Level.OK);
+ for (Condition condition : gate.getConditions()) {
+ builder.addCondition(condition, EvaluatedCondition.EvaluationStatus.OK, "bar");
+ }
+ return builder.build();
+ }
+
+ private Measures getCalledMeasures() {
+ return measures;
+ }
+
+ @Override
+ public Set<String> getMetricKeys(QualityGate gate) {
+ return gate.getConditions().stream().map(Condition::getMetricKey).collect(Collectors.toSet());
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/live/MeasureMatrixTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/live/MeasureMatrixTest.java
new file mode 100644
index 00000000000..a163dc2c64f
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/live/MeasureMatrixTest.java
@@ -0,0 +1,209 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.ArrayList;
+import java.util.Collection;
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.measure.LiveMeasureDto;
+import org.sonar.db.metric.MetricDto;
+import org.sonar.db.organization.OrganizationDto;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.metric.MetricTesting.newMetricDto;
+import static org.sonar.db.organization.OrganizationTesting.newOrganizationDto;
+
+public class MeasureMatrixTest {
+
+ private static final OrganizationDto ORGANIZATION = newOrganizationDto();
+ private static final ComponentDto PROJECT = ComponentTesting.newPublicProjectDto(ORGANIZATION);
+ private static final ComponentDto FILE = ComponentTesting.newFileDto(PROJECT);
+ private static final MetricDto METRIC_1 = newMetricDto().setId(100);
+ private static final MetricDto METRIC_2 = newMetricDto().setId(200);
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void getMetric() {
+ Collection<MetricDto> metrics = asList(METRIC_1, METRIC_2);
+
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT, FILE), metrics, new ArrayList<>());
+
+ assertThat(underTest.getMetric(METRIC_2.getId())).isSameAs(METRIC_2);
+ }
+
+ @Test
+ public void getMetric_fails_if_metric_is_not_registered() {
+ Collection<MetricDto> metrics = asList(METRIC_1);
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT, FILE), metrics, new ArrayList<>());
+
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Metric with id " + METRIC_2.getId() + " not found");
+
+ underTest.getMetric(METRIC_2.getId());
+ }
+
+ @Test
+ public void getValue_returns_empty_if_measure_is_absent() {
+ MetricDto metric = newMetricDto();
+ LiveMeasureDto measure = newMeasure(metric, PROJECT).setValue(null);
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT), asList(metric), asList(measure));
+
+ assertThat(underTest.getMeasure(FILE, metric.getKey())).isEmpty();
+ }
+
+ @Test
+ public void getMeasure_throws_IAE_if_metric_is_not_registered() {
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT), asList(METRIC_1), emptyList());
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Metric with key _missing_ is not registered");
+
+ underTest.getMeasure(PROJECT, "_missing_");
+ }
+
+ @Test
+ public void setValue_double_rounds_up_and_updates_value() {
+ MetricDto metric = newMetricDto().setDecimalScale(2);
+ LiveMeasureDto measure = newMeasure(metric, PROJECT).setValue(1.23);
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT), asList(metric), asList(measure));
+
+ underTest.setValue(PROJECT, metric.getKey(), 3.14159);
+
+ assertThat(underTest.getMeasure(PROJECT, metric.getKey()).get().getValue()).isEqualTo(3.14);
+ assertThat(underTest.getChanged()).hasSize(1);
+
+ underTest.setValue(PROJECT, metric.getKey(), 3.148);
+ verifyValue(underTest, PROJECT, metric, 3.15);
+ verifyVariation(underTest, PROJECT, metric, null);
+ }
+
+ private void verifyValue(MeasureMatrix underTest, ComponentDto component, MetricDto metric, @Nullable Double expectedValue) {
+ Optional<LiveMeasureDto> measure = underTest.getMeasure(component, metric.getKey());
+ assertThat(measure).isPresent();
+ assertThat(measure.get().getValue()).isEqualTo(expectedValue);
+ }
+
+ private void verifyVariation(MeasureMatrix underTest, ComponentDto component, MetricDto metric, @Nullable Double expectedVariation) {
+ assertThat(underTest.getMeasure(component, metric.getKey()).get().getVariation()).isEqualTo(expectedVariation);
+ }
+
+ @Test
+ public void setValue_double_does_nothing_if_value_is_unchanged() {
+ MetricDto metric = newMetricDto().setDecimalScale(2);
+ LiveMeasureDto measure = newMeasure(metric, PROJECT).setValue(3.14);
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT), asList(metric), asList(measure));
+
+ underTest.setValue(PROJECT, metric.getKey(), 3.14159);
+
+ assertThat(underTest.getChanged()).isEmpty();
+ verifyValue(underTest, PROJECT, metric, 3.14);
+ }
+
+ @Test
+ public void setValue_double_updates_variation() {
+ MetricDto metric = newMetricDto().setDecimalScale(2);
+ LiveMeasureDto measure = newMeasure(metric, PROJECT).setValue(3.14).setVariation(1.14);
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT), asList(metric), asList(measure));
+
+ underTest.setValue(PROJECT, metric.getKey(), 3.56);
+
+ assertThat(underTest.getChanged()).hasSize(1);
+ verifyValue(underTest, PROJECT, metric, 3.56);
+ verifyVariation(underTest, PROJECT, metric, 3.56 - (3.14 - 1.14));
+ }
+
+ @Test
+ public void setValue_double_rounds_up_variation() {
+ MetricDto metric = newMetricDto().setDecimalScale(2);
+ LiveMeasureDto measure = newMeasure(metric, PROJECT).setValue(3.14).setVariation(1.14);
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT), asList(metric), asList(measure));
+
+ underTest.setValue(PROJECT, metric.getKey(), 3.569);
+
+ assertThat(underTest.getChanged()).hasSize(1);
+ verifyValue(underTest, PROJECT, metric, 3.57);
+ verifyVariation(underTest, PROJECT, metric, 1.57);
+ }
+
+ @Test
+ public void setValue_String_does_nothing_if_value_is_not_changed() {
+ LiveMeasureDto measure = newMeasure(METRIC_1, PROJECT).setData("foo");
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT, FILE), asList(METRIC_1), asList(measure));
+
+ underTest.setValue(PROJECT, METRIC_1.getKey(), "foo");
+
+ assertThat(underTest.getMeasure(PROJECT, METRIC_1.getKey()).get().getDataAsString()).isEqualTo("foo");
+ assertThat(underTest.getChanged()).isEmpty();
+ }
+
+ @Test
+ public void setValue_String_updates_value() {
+ LiveMeasureDto measure = newMeasure(METRIC_1, PROJECT).setData("foo");
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT, FILE), asList(METRIC_1), asList(measure));
+
+ underTest.setValue(PROJECT, METRIC_1.getKey(), "bar");
+
+ assertThat(underTest.getMeasure(PROJECT, METRIC_1.getKey()).get().getDataAsString()).isEqualTo("bar");
+ assertThat(underTest.getChanged()).extracting(LiveMeasureDto::getDataAsString).containsExactly("bar");
+ }
+
+ @Test
+ public void setLeakValue_rounds_up_and_updates_value() {
+ MetricDto metric = newMetricDto().setDecimalScale(2);
+ LiveMeasureDto measure = newMeasure(metric, PROJECT).setValue(null);
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT), asList(metric), asList(measure));
+
+ underTest.setLeakValue(PROJECT, metric.getKey(), 3.14159);
+ verifyVariation(underTest, PROJECT, metric, 3.14);
+ // do not update value
+ verifyValue(underTest, PROJECT, metric, null);
+
+ underTest.setLeakValue(PROJECT, metric.getKey(), 3.148);
+ verifyVariation(underTest, PROJECT, metric, 3.15);
+ // do not update value
+ verifyValue(underTest, PROJECT, metric, null);
+ }
+
+ @Test
+ public void setLeakValue_double_does_nothing_if_value_is_unchanged() {
+ MetricDto metric = newMetricDto().setDecimalScale(2);
+ LiveMeasureDto measure = newMeasure(metric, PROJECT).setValue(null).setVariation(3.14);
+ MeasureMatrix underTest = new MeasureMatrix(asList(PROJECT), asList(metric), asList(measure));
+
+ underTest.setLeakValue(PROJECT, metric.getKey(), 3.14159);
+
+ assertThat(underTest.getChanged()).isEmpty();
+ verifyVariation(underTest, PROJECT, metric, 3.14);
+ }
+
+ private LiveMeasureDto newMeasure(MetricDto metric, ComponentDto component) {
+ return new LiveMeasureDto().setMetricId(metric.getId()).setData("foo").setComponentUuid(component.uuid());
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/live/TestIssueMetricFormulaFactory.java b/server/sonar-server/src/test/java/org/sonar/server/measure/live/TestIssueMetricFormulaFactory.java
new file mode 100644
index 00000000000..ee29a7ee327
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/measure/live/TestIssueMetricFormulaFactory.java
@@ -0,0 +1,43 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.List;
+import java.util.Set;
+import org.sonar.api.measures.Metric;
+
+class TestIssueMetricFormulaFactory implements IssueMetricFormulaFactory {
+
+ private final List<IssueMetricFormula> formulas;
+
+ TestIssueMetricFormulaFactory(List<IssueMetricFormula> formulas) {
+ this.formulas = formulas;
+ }
+
+ @Override
+ public List<IssueMetricFormula> getFormulas() {
+ return formulas;
+ }
+
+ @Override
+ public Set<Metric> getFormulaMetrics() {
+ return IssueMetricFormulaFactory.extractMetrics(formulas);
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionEvaluatorTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionEvaluatorTest.java
new file mode 100644
index 00000000000..f8b4234992c
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/ConditionEvaluatorTest.java
@@ -0,0 +1,436 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.Optional;
+import java.util.OptionalDouble;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.qualitygate.ConditionEvaluatorTest.FakeMeasure.newFakeMeasureOnLeak;
+
+public class ConditionEvaluatorTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void EQUALS_double() {
+ test(new FakeMeasure(10.1d), Condition.Operator.EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.1");
+ test(new FakeMeasure(10.2d), Condition.Operator.EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.ERROR, "10.2");
+ test(new FakeMeasure(10.3d), Condition.Operator.EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.3");
+ }
+
+ @Test
+ public void NOT_EQUALS_double() {
+ test(new FakeMeasure(10.1d), Condition.Operator.NOT_EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.ERROR, "10.1");
+ test(new FakeMeasure(10.2d), Condition.Operator.NOT_EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.2");
+ test(new FakeMeasure(10.3d), Condition.Operator.NOT_EQUALS, "10.2", EvaluatedCondition.EvaluationStatus.ERROR, "10.3");
+ }
+
+ @Test
+ public void GREATER_THAN_double() {
+ test(new FakeMeasure(10.1d), Condition.Operator.GREATER_THAN, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.1");
+ test(new FakeMeasure(10.2d), Condition.Operator.GREATER_THAN, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.2");
+ test(new FakeMeasure(10.3d), Condition.Operator.GREATER_THAN, "10.2", EvaluatedCondition.EvaluationStatus.ERROR, "10.3");
+ }
+
+ @Test
+ public void LESS_THAN_double() {
+ test(new FakeMeasure(10.1d), Condition.Operator.LESS_THAN, "10.2", EvaluatedCondition.EvaluationStatus.ERROR, "10.1");
+ test(new FakeMeasure(10.2d), Condition.Operator.LESS_THAN, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.2");
+ test(new FakeMeasure(10.3d), Condition.Operator.LESS_THAN, "10.2", EvaluatedCondition.EvaluationStatus.OK, "10.3");
+ }
+
+ @Test
+ public void EQUALS_int() {
+ test(new FakeMeasure(10), Condition.Operator.EQUALS, "9", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.EQUALS, "10", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ test(new FakeMeasure(10), Condition.Operator.EQUALS, "11", EvaluatedCondition.EvaluationStatus.OK, "10");
+
+ // badly stored thresholds are truncated
+ test(new FakeMeasure(10), Condition.Operator.EQUALS, "10.4", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ test(new FakeMeasure(10), Condition.Operator.EQUALS, "10.9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ test(new FakeMeasure(11), Condition.Operator.EQUALS, "10.9", EvaluatedCondition.EvaluationStatus.OK, "11");
+ }
+
+ @Test
+ public void NOT_EQUALS_int() {
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "11", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+
+ // badly stored thresholds are truncated
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "10.4", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "10.9", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.NOT_EQUALS, "9.9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ }
+
+ @Test
+ public void GREATER_THAN_int() {
+ test(new FakeMeasure(10), Condition.Operator.GREATER_THAN, "9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ test(new FakeMeasure(10), Condition.Operator.GREATER_THAN, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.GREATER_THAN, "11", EvaluatedCondition.EvaluationStatus.OK, "10");
+
+ testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.GREATER_THAN, "9", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.GREATER_THAN, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
+ testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.GREATER_THAN, "11", EvaluatedCondition.EvaluationStatus.OK, "10");
+ }
+
+ @Test
+ public void LESS_THAN_int() {
+ test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "9", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "11", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "11", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+
+ testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.LESS_THAN, "9", EvaluatedCondition.EvaluationStatus.OK, "10");
+ testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.LESS_THAN, "10", EvaluatedCondition.EvaluationStatus.OK, "10");
+ testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.LESS_THAN, "11", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ testOnLeak(newFakeMeasureOnLeak(10), Condition.Operator.LESS_THAN, "11", EvaluatedCondition.EvaluationStatus.ERROR, "10");
+ }
+
+ @Test
+ public void no_value_present() {
+ test(new FakeMeasure((Integer) null), Condition.Operator.LESS_THAN, "9", EvaluatedCondition.EvaluationStatus.OK, null);
+ test(null, Condition.Operator.LESS_THAN, "9", EvaluatedCondition.EvaluationStatus.OK, null);
+ }
+
+ @Test
+ public void empty_warning_condition() {
+ test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "9", null, EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(10), Condition.Operator.LESS_THAN, "9", "", EvaluatedCondition.EvaluationStatus.OK, "10");
+ test(new FakeMeasure(3), Condition.Operator.LESS_THAN, "9", "", EvaluatedCondition.EvaluationStatus.ERROR, "3");
+ }
+
+ private void test(@Nullable QualityGateEvaluator.Measure measure, Condition.Operator operator, String errorThreshold, EvaluatedCondition.EvaluationStatus expectedStatus,
+ @Nullable String expectedValue) {
+ test(measure, operator, errorThreshold, null, expectedStatus, expectedValue);
+ }
+
+ private void test(@Nullable QualityGateEvaluator.Measure measure, Condition.Operator operator, String errorThreshold, @Nullable String warningThreshold,
+ EvaluatedCondition.EvaluationStatus expectedStatus, @Nullable String expectedValue) {
+ Condition condition = new Condition("foo", operator, errorThreshold, warningThreshold, false);
+
+ EvaluatedCondition result = ConditionEvaluator.evaluate(condition, new FakeMeasures(measure));
+
+ assertThat(result.getStatus()).isEqualTo(expectedStatus);
+ if (expectedValue == null) {
+ assertThat(result.getValue()).isNotPresent();
+ } else {
+ assertThat(result.getValue()).hasValue(expectedValue);
+ }
+ }
+
+ private void testOnLeak(QualityGateEvaluator.Measure measure, Condition.Operator operator, String errorThreshold, EvaluatedCondition.EvaluationStatus expectedStatus,
+ @Nullable String expectedValue) {
+ Condition condition = new Condition("foo", operator, errorThreshold, null, true);
+
+ EvaluatedCondition result = ConditionEvaluator.evaluate(condition, new FakeMeasures(measure));
+
+ assertThat(result.getStatus()).isEqualTo(expectedStatus);
+ if (expectedValue == null) {
+ assertThat(result.getValue()).isNotPresent();
+ } else {
+ assertThat(result.getValue()).hasValue(expectedValue);
+ }
+ }
+
+ private static class FakeMeasures implements QualityGateEvaluator.Measures {
+ private final QualityGateEvaluator.Measure measure;
+
+ FakeMeasures(@Nullable QualityGateEvaluator.Measure measure) {
+ this.measure = measure;
+ }
+
+ @Override
+ public Optional<QualityGateEvaluator.Measure> get(String metricKey) {
+ return Optional.ofNullable(measure);
+ }
+ }
+
+ static class FakeMeasure implements QualityGateEvaluator.Measure {
+ private Double leakValue;
+ private Double value;
+ private Metric.ValueType valueType;
+
+ private FakeMeasure() {
+
+ }
+
+ FakeMeasure(@Nullable Double value) {
+ this.value = value;
+ this.valueType = Metric.ValueType.FLOAT;
+ }
+
+ FakeMeasure(@Nullable Integer value) {
+ this.value = value == null ? null : value.doubleValue();
+ this.valueType = Metric.ValueType.INT;
+ }
+
+ static FakeMeasure newFakeMeasureOnLeak(@Nullable Integer value) {
+ FakeMeasure that = new FakeMeasure();
+ that.leakValue = value == null ? null : value.doubleValue();
+ that.valueType = Metric.ValueType.INT;
+ return that;
+ }
+
+ @Override
+ public Metric.ValueType getType() {
+ return valueType;
+ }
+
+ @Override
+ public OptionalDouble getValue() {
+ return value == null ? OptionalDouble.empty() : OptionalDouble.of(value);
+ }
+
+ @Override
+ public Optional<String> getStringValue() {
+ return Optional.empty();
+ }
+
+ @Override
+ public OptionalDouble getLeakValue() {
+ return leakValue == null ? OptionalDouble.empty() : OptionalDouble.of(leakValue);
+ }
+ }
+
+ // @Test
+ // public void testEquals_for_String() {
+ // Metric metric = createMetric(STRING);
+ // Measure measure = newMeasureBuilder().create("TEST");
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "TEST"),
+ // measure)).hasLevel(ERROR).hasValue("TEST");
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "TEST2"),
+ // measure)).hasLevel(OK).hasValue("TEST");
+ // }
+ //
+ // @Test
+ //
+ // public void testNotEquals() {
+ // Metric metric = createMetric(STRING);
+ // Measure measure = newMeasureBuilder().create("TEST");
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, "TEST"),
+ // measure)).hasLevel(OK).hasValue("TEST");
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, "TEST2"),
+ // measure)).hasLevel(ERROR).hasValue("TEST");
+ // }
+ //
+ // @Test
+ // public void testEquals_Percent() {
+ // Metric metric = createMetric(PERCENT);
+ // Measure measure = newMeasureBuilder().create(10.2d, 1, null);
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.2"),
+ // measure)).hasLevel(ERROR).hasValue(10.2d);
+ // }
+ //
+ // @Test
+ // public void testEquals_Float() {
+ // Metric metric = createMetric(PERCENT);
+ // Measure measure = newMeasureBuilder().create(10.2d, 1, null);
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.2"),
+ // measure)).hasLevel(ERROR).hasValue(10.2d);
+ // }
+ //
+ // @Test
+ // public void testEquals_Int() {
+ // Metric metric = createMetric(INT);
+ // Measure measure = newMeasureBuilder().create(10, null);
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10"), measure)).hasLevel(ERROR).hasValue(10);
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.2"),
+ // measure)).hasLevel(ERROR).hasValue(10);
+ // }
+ //
+ // @Test
+ // public void testEquals_Level() {
+ // Metric metric = createMetric(LEVEL);
+ // Measure measure = newMeasureBuilder().create(ERROR);
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, ERROR.name()),
+ // measure)).hasLevel(ERROR).hasValue(ERROR.name());
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, OK.name()),
+ // measure)).hasLevel(OK).hasValue(ERROR.name());
+ // }
+ //
+ // @Test
+ // public void testNotEquals_Level() {
+ // Metric metric = createMetric(LEVEL);
+ // Measure measure = newMeasureBuilder().create(ERROR);
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, OK.name()),
+ // measure)).hasLevel(ERROR).hasValue(ERROR.name());
+ // }
+ //
+ // @Test
+ // public void testEquals_BOOL() {
+ // Metric metric = createMetric(BOOL);
+ // Measure measure = newMeasureBuilder().create(false, null);
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "1"), measure)).hasLevel(OK).hasValue(false);
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "0"),
+ // measure)).hasLevel(ERROR).hasValue(false);
+ // }
+ //
+ // @Test
+ // public void testNotEquals_BOOL() {
+ // Metric metric = createMetric(BOOL);
+ // Measure measure = newMeasureBuilder().create(false, null);
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, "1"),
+ // measure)).hasLevel(ERROR).hasValue(false);
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, NOT_EQUALS, "0"),
+ // measure)).hasLevel(OK).hasValue(false);
+ // }
+ //
+ // @Test
+ // public void getLevel_throws_IEA_if_error_threshold_is_not_parsable_boolean() {
+ // Metric metric = createMetric(BOOL);
+ // Measure measure = newMeasureBuilder().create(false, null);
+ //
+ // expectedException.expect(IllegalArgumentException.class);
+ // expectedException.expectMessage("Quality Gate: Unable to parse value 'polop' to compare against name");
+ //
+ // underTest.evaluate(createErrorCondition(metric, EQUALS, "polop"), measure);
+ // }
+ //
+ // @Test
+ // public void testEquals_work_duration() {
+ // Metric metric = createMetric(WORK_DUR);
+ // Measure measure = newMeasureBuilder().create(60l, null);
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "60"), measure)).hasLevel(ERROR);
+ // }
+ //
+ // @Test
+ // public void getLevel_throws_IEA_if_error_threshold_is_not_parsable_long() {
+ // Metric metric = createMetric(WORK_DUR);
+ // Measure measure = newMeasureBuilder().create(60l, null);
+ //
+ // expectedException.expect(IllegalArgumentException.class);
+ // expectedException.expectMessage("Quality Gate: Unable to parse value 'polop' to compare against name");
+ //
+ // underTest.evaluate(createErrorCondition(metric, EQUALS, "polop"), measure);
+ // }
+ //
+ // @Test
+ // public void testErrorAndWarningLevel() {
+ // Metric metric = createMetric(FLOAT);
+ // Measure measure = newMeasureBuilder().create(10.2d, 1, null);
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.2"), measure)).hasLevel(ERROR);
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.1"), measure)).hasLevel(OK);
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(new org.sonar.server.computation.task.projectanalysis.qualitygate.Condition(metric,
+ // EQUALS.getDbValue(), "10.3", "10.2", false), measure)).hasLevel(Measure.Level.WARN);
+ // EvaluationResultAssert.assertThat(underTest.evaluate(new org.sonar.server.computation.task.projectanalysis.qualitygate.Condition(metric,
+ // LESS_THAN.getDbValue(), "10.3", "10.2", false), measure)).hasLevel(Measure.Level.ERROR);
+ // }
+ //
+ // @Test
+ // public void condition_is_always_ok_when_measure_is_noValue() {
+ // for (Metric.MetricType metricType : from(asList(values())).filter(not(in(ImmutableSet.of(DATA, LEVEL))))) {
+ // Metric metric = createMetric(metricType);
+ // Measure measure = newMeasureBuilder().createNoValue();
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(createErrorCondition(metric, EQUALS, "10.2"), measure)).hasLevel(OK);
+ // }
+ // }
+ //
+ // @Test
+ // public void testUnsupportedType() {
+ // Metric metric = createMetric(DATA);
+ // Measure measure = newMeasureBuilder().create("3.14159265358");
+ //
+ // expectedException.expect(IllegalArgumentException.class);
+ // expectedException.expectMessage("Conditions on MetricType DATA are not supported");
+ //
+ // underTest.evaluate(createErrorCondition(metric, EQUALS, "1.60217657"), measure);
+ // }
+ //
+ // @Test
+ // public void condition_on_period() {
+ // for (Metric.MetricType metricType : ImmutableList.of(FLOAT, INT, WORK_DUR)) {
+ // Metric metric = createMetric(metricType);
+ // Measure measure = newMeasureBuilder().setVariation(3d).createNoValue();
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(new org.sonar.server.computation.task.projectanalysis.qualitygate.Condition(metric,
+ // GREATER_THAN.getDbValue(), "3", null, true), measure)).hasLevel(OK);
+ // }
+ // }
+ //
+ // @Test
+ // public void condition_on_period_without_value_is_OK() {
+ // Metric metric = createMetric(FLOAT);
+ // Measure measure = newMeasureBuilder().createNoValue();
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(new org.sonar.server.computation.task.projectanalysis.qualitygate.Condition(metric,
+ // GREATER_THAN.getDbValue(), "3", null, true), measure)).hasLevel(OK).hasValue(null);
+ // }
+ //
+ // @Test
+ // public void condition_on_rating() throws Exception {
+ // Metric metric = createMetric(RATING);
+ // Measure measure = newMeasureBuilder().create(4, "D");
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(new org.sonar.server.computation.task.projectanalysis.qualitygate.Condition(metric,
+ // GREATER_THAN.getDbValue(), "4", null, false), measure)).hasLevel(OK).hasValue(4);
+ // EvaluationResultAssert.assertThat(underTest.evaluate(new org.sonar.server.computation.task.projectanalysis.qualitygate.Condition(metric,
+ // GREATER_THAN.getDbValue(), "2", null, false), measure)).hasLevel(ERROR).hasValue(4);
+ // }
+ //
+ // @Test
+ // public void condition_on_rating_on_leak_period() throws Exception {
+ // Metric metric = createMetric(RATING);
+ // Measure measure = newMeasureBuilder().setVariation(4d).createNoValue();
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(new org.sonar.server.computation.task.projectanalysis.qualitygate.Condition(metric,
+ // GREATER_THAN.getDbValue(), "5", null, true), measure)).hasLevel(OK).hasValue(4);
+ // EvaluationResultAssert.assertThat(underTest.evaluate(new org.sonar.server.computation.task.projectanalysis.qualitygate.Condition(metric,
+ // GREATER_THAN.getDbValue(), "2", null, true), measure)).hasLevel(ERROR).hasValue(4);
+ // }
+ //
+ // @Test
+ // public void condition_on_rating_on_leak_period_when_variation_is_zero() throws Exception {
+ // Metric metric = createMetric(RATING);
+ // Measure measure = newMeasureBuilder().setVariation(0d).createNoValue();
+ //
+ // EvaluationResultAssert.assertThat(underTest.evaluate(new org.sonar.server.computation.task.projectanalysis.qualitygate.Condition(metric,
+ // GREATER_THAN.getDbValue(), "4", null, true), measure)).hasLevel(OK).hasValue(0);
+ // }
+ //
+ // private static org.sonar.server.computation.task.projectanalysis.qualitygate.Condition createErrorCondition(Metric metric,
+ // org.sonar.server.computation.task.projectanalysis.qualitygate.Condition.Operator operator, String errorThreshold) {
+ // return new Condition(metric, operator.getDbValue(), errorThreshold, null, false);
+ // }
+ //
+ // private static MetricImpl createMetric(Metric.MetricType metricType) {
+ // return new MetricImpl(1, "key", "name", metricType);
+ // }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java
index 9c55e05e952..b02d5dd2cd5 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/EvaluatedQualityGateTest.java
@@ -25,13 +25,12 @@ import org.apache.commons.lang.RandomStringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric.Level;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.server.qualitygate.EvaluatedQualityGate.newBuilder;
-import static org.sonar.server.qualitygate.EvaluatedQualityGate.Status.OK;
-import static org.sonar.server.qualitygate.EvaluatedQualityGate.Status.WARN;
public class EvaluatedQualityGateTest {
private static final String QUALITY_GATE_ID = "qg_id";
@@ -45,7 +44,7 @@ public class EvaluatedQualityGateTest {
public ExpectedException expectedException = ExpectedException.none();
private final Random random = new Random();
- private final EvaluatedQualityGate.Status randomStatus = EvaluatedQualityGate.Status.values()[random.nextInt(EvaluatedQualityGate.Status.values().length)];
+ private final Level randomStatus = Level.values()[random.nextInt(Level.values().length)];
private final EvaluatedCondition.EvaluationStatus randomEvaluationStatus = EvaluatedCondition.EvaluationStatus.values()[random
.nextInt(EvaluatedCondition.EvaluationStatus.values().length)];
private final String randomValue = random.nextBoolean() ? null : RandomStringUtils.randomAlphanumeric(3);
@@ -53,30 +52,6 @@ public class EvaluatedQualityGateTest {
private EvaluatedQualityGate.Builder builder = newBuilder();
@Test
- public void setQualityGate_fails_with_NPE_if_argument_is_null() {
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("qualityGate can't be null");
-
- builder.setQualityGate(null);
- }
-
- @Test
- public void setStatus_fails_with_NPE_if_argument_is_null() {
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("status can't be null");
-
- builder.setStatus(null);
- }
-
- @Test
- public void build_fails_with_NPE_if_qualityGate_not_set() {
- expectedException.expect(NullPointerException.class);
- expectedException.expectMessage("qualityGate can't be null");
-
- builder.build();
- }
-
- @Test
public void build_fails_with_NPE_if_status_not_set() {
builder.setQualityGate(NO_CONDITION_QUALITY_GATE);
@@ -185,7 +160,7 @@ public class EvaluatedQualityGateTest {
public void equals_is_based_on_all_fields() {
EvaluatedQualityGate.Builder builder = this.builder
.setQualityGate(ONE_CONDITION_QUALITY_GATE)
- .setStatus(WARN)
+ .setStatus(Level.WARN)
.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.WARN, "foo");
EvaluatedQualityGate underTest = builder.build();
@@ -194,10 +169,10 @@ public class EvaluatedQualityGateTest {
assertThat(underTest).isNotEqualTo(null);
assertThat(underTest).isNotEqualTo(new Object());
assertThat(underTest).isNotEqualTo(builder.setQualityGate(new QualityGate("other_id", QUALITY_GATE_NAME, singleton(CONDITION_1))).build());
- assertThat(underTest).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(OK).build());
+ assertThat(underTest).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(Level.OK).build());
assertThat(underTest).isNotEqualTo(newBuilder()
.setQualityGate(ONE_CONDITION_QUALITY_GATE)
- .setStatus(WARN)
+ .setStatus(Level.WARN)
.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo")
.build());
}
@@ -206,7 +181,7 @@ public class EvaluatedQualityGateTest {
public void hashcode_is_based_on_all_fields() {
EvaluatedQualityGate.Builder builder = this.builder
.setQualityGate(ONE_CONDITION_QUALITY_GATE)
- .setStatus(WARN)
+ .setStatus(Level.WARN)
.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.WARN, "foo");
EvaluatedQualityGate underTest = builder.build();
@@ -215,10 +190,10 @@ public class EvaluatedQualityGateTest {
assertThat(underTest.hashCode()).isNotEqualTo(null);
assertThat(underTest.hashCode()).isNotEqualTo(new Object().hashCode());
assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(new QualityGate("other_id", QUALITY_GATE_NAME, singleton(CONDITION_1))).build().hashCode());
- assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(OK).build().hashCode());
+ assertThat(underTest.hashCode()).isNotEqualTo(builder.setQualityGate(ONE_CONDITION_QUALITY_GATE).setStatus(Level.OK).build().hashCode());
assertThat(underTest.hashCode()).isNotEqualTo(newBuilder()
.setQualityGate(ONE_CONDITION_QUALITY_GATE)
- .setStatus(WARN)
+ .setStatus(Level.WARN)
.addCondition(CONDITION_1, EvaluatedCondition.EvaluationStatus.OK, "foo")
.build().hashCode());
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/LiveQualityGateFactoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/LiveQualityGateFactoryImplTest.java
deleted file mode 100644
index 955d56ba35e..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/LiveQualityGateFactoryImplTest.java
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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 com.google.common.collect.ImmutableList;
-import java.util.List;
-import java.util.Optional;
-import java.util.Random;
-import java.util.stream.IntStream;
-import javax.annotation.Nullable;
-import org.assertj.core.groups.Tuple;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.System2;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.rule.RuleTesting;
-import org.sonar.server.es.EsTester;
-import org.sonar.server.issue.index.IssueIndex;
-import org.sonar.server.issue.index.IssueIndexDefinition;
-import org.sonar.server.issue.index.IssueIndexer;
-import org.sonar.server.issue.index.IssueIteratorFactory;
-import org.sonar.server.permission.index.AuthorizationTypeSupport;
-import org.sonar.server.tester.UserSessionRule;
-
-import static java.lang.String.valueOf;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.groups.Tuple.tuple;
-import static org.sonar.api.measures.CoreMetrics.BUGS_KEY;
-import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS_KEY;
-import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY;
-import static org.sonar.db.component.ComponentTesting.newBranchDto;
-
-public class LiveQualityGateFactoryImplTest {
- private static final List<String> OPEN_STATUSES = ImmutableList.of(Issue.STATUS_OPEN, Issue.STATUS_CONFIRMED);
- private static final List<String> NON_OPEN_STATUSES = Issue.STATUSES.stream().filter(OPEN_STATUSES::contains).collect(MoreCollectors.toList());
-
- @Rule
- public DbTester dbTester = DbTester.create(System2.INSTANCE);
- @Rule
- public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig()));
- @Rule
- public UserSessionRule userSessionRule = UserSessionRule.standalone();
-
- private Random random = new Random();
- private String randomOpenStatus = OPEN_STATUSES.get(random.nextInt(OPEN_STATUSES.size()));
- private String randomNonOpenStatus = NON_OPEN_STATUSES.get(random.nextInt(NON_OPEN_STATUSES.size()));
- private String randomResolution = Issue.RESOLUTIONS.get(random.nextInt(Issue.RESOLUTIONS.size()));
- private IssueIndexer issueIndexer = new IssueIndexer(esTester.client(), dbTester.getDbClient(), new IssueIteratorFactory(dbTester.getDbClient()));
- private IssueIndex issueIndex = new IssueIndex(esTester.client(), System2.INSTANCE, userSessionRule, new AuthorizationTypeSupport(userSessionRule));
-
- private LiveQualityGateFactoryImpl underTest = new LiveQualityGateFactoryImpl(issueIndex, System2.INSTANCE);
-
- @Test
- public void compute_QG_ok_if_there_is_no_issue_in_index_ignoring_permissions() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = insertPrivateBranch(organization, BranchType.SHORT);
-
- EvaluatedQualityGate qualityGate = underTest.buildForShortLivedBranch(project);
-
- assertThat(qualityGate.getStatus()).isEqualTo(EvaluatedQualityGate.Status.OK);
- assertThat(qualityGate.getEvaluatedConditions())
- .extracting(EvaluatedCondition::getStatus, EvaluatedCondition::getValue)
- .containsOnly(tuple(EvaluatedCondition.EvaluationStatus.OK, Optional.of("0")));
- }
-
- @Test
- public void computes_QG_error_if_there_is_one_unresolved_bug_issue_in_index_ignoring_permissions() {
- int unresolvedIssues = 1 + random.nextInt(10);
-
- computesQGErrorIfThereIsAtLeastOneUnresolvedIssueInIndexOfTypeIgnoringPermissions(
- unresolvedIssues,
- RuleType.BUG,
- tuple(BUGS_KEY, EvaluatedCondition.EvaluationStatus.ERROR, Optional.of(valueOf(unresolvedIssues))),
- tuple(VULNERABILITIES_KEY, EvaluatedCondition.EvaluationStatus.OK, Optional.of("0")),
- tuple(CODE_SMELLS_KEY, EvaluatedCondition.EvaluationStatus.OK, Optional.of("0")));
- }
-
- @Test
- public void computes_QG_error_if_there_is_one_unresolved_vulnerability_issue_in_index_ignoring_permissions() {
- int unresolvedIssues = 1 + random.nextInt(10);
-
- computesQGErrorIfThereIsAtLeastOneUnresolvedIssueInIndexOfTypeIgnoringPermissions(
- unresolvedIssues,
- RuleType.VULNERABILITY,
- tuple(BUGS_KEY, EvaluatedCondition.EvaluationStatus.OK, Optional.of("0")),
- tuple(VULNERABILITIES_KEY, EvaluatedCondition.EvaluationStatus.ERROR, Optional.of(valueOf(unresolvedIssues))),
- tuple(CODE_SMELLS_KEY, EvaluatedCondition.EvaluationStatus.OK, Optional.of("0")));
- }
-
- @Test
- public void computes_QG_error_if_there_is_one_unresolved_codeSmell_issue_in_index_ignoring_permissions() {
- int unresolvedIssues = 1 + random.nextInt(10);
-
- computesQGErrorIfThereIsAtLeastOneUnresolvedIssueInIndexOfTypeIgnoringPermissions(
- unresolvedIssues,
- RuleType.CODE_SMELL,
- tuple(BUGS_KEY, EvaluatedCondition.EvaluationStatus.OK, Optional.of("0")),
- tuple(VULNERABILITIES_KEY, EvaluatedCondition.EvaluationStatus.OK, Optional.of("0")),
- tuple(CODE_SMELLS_KEY, EvaluatedCondition.EvaluationStatus.ERROR, Optional.of(valueOf(unresolvedIssues))));
- }
-
- private void computesQGErrorIfThereIsAtLeastOneUnresolvedIssueInIndexOfTypeIgnoringPermissions(
- int unresolvedIssues, RuleType ruleType, Tuple... expectedQGConditions) {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = insertPrivateBranch(organization, BranchType.SHORT);
- IntStream.range(0, unresolvedIssues).forEach(i -> insertIssue(project, ruleType, randomOpenStatus, null));
- IntStream.range(0, random.nextInt(10)).forEach(i -> insertIssue(project, ruleType, randomNonOpenStatus, randomResolution));
- indexIssues(project);
-
- EvaluatedQualityGate qualityGate = underTest.buildForShortLivedBranch(project);
-
- assertThat(qualityGate.getStatus()).isEqualTo(EvaluatedQualityGate.Status.ERROR);
- assertThat(qualityGate.getEvaluatedConditions())
- .extracting(s -> s.getCondition().getMetricKey(), EvaluatedCondition::getStatus, EvaluatedCondition::getValue)
- .containsOnly(expectedQGConditions);
- }
-
- @Test
- public void computes_QG_error_with_all_failing_conditions() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = insertPrivateBranch(organization, BranchType.SHORT);
- int unresolvedBugs = 1 + random.nextInt(10);
- int unresolvedVulnerabilities = 1 + random.nextInt(10);
- int unresolvedCodeSmells = 1 + random.nextInt(10);
- IntStream.range(0, unresolvedBugs).forEach(i -> insertIssue(project, RuleType.BUG, randomOpenStatus, null));
- IntStream.range(0, unresolvedVulnerabilities).forEach(i -> insertIssue(project, RuleType.VULNERABILITY, randomOpenStatus, null));
- IntStream.range(0, unresolvedCodeSmells).forEach(i -> insertIssue(project, RuleType.CODE_SMELL, randomOpenStatus, null));
- indexIssues(project);
-
- EvaluatedQualityGate qualityGate = underTest.buildForShortLivedBranch(project);
-
- assertThat(qualityGate.getStatus()).isEqualTo(EvaluatedQualityGate.Status.ERROR);
- assertThat(qualityGate.getEvaluatedConditions())
- .extracting(s -> s.getCondition().getMetricKey(), EvaluatedCondition::getStatus, EvaluatedCondition::getValue)
- .containsOnly(
- Tuple.tuple(BUGS_KEY, EvaluatedCondition.EvaluationStatus.ERROR, Optional.of(valueOf(unresolvedBugs))),
- Tuple.tuple(VULNERABILITIES_KEY, EvaluatedCondition.EvaluationStatus.ERROR, Optional.of(valueOf(unresolvedVulnerabilities))),
- Tuple.tuple(CODE_SMELLS_KEY, EvaluatedCondition.EvaluationStatus.ERROR, Optional.of(valueOf(unresolvedCodeSmells))));
- }
-
- private void indexIssues(ComponentDto project) {
- issueIndexer.indexOnAnalysis(project.uuid());
- }
-
- private void insertIssue(ComponentDto component, RuleType ruleType, String status, @Nullable String resolution) {
- RuleDefinitionDto rule = RuleTesting.newRule();
- dbTester.rules().insert(rule);
- dbTester.commit();
- dbTester.issues().insert(rule, component, component, i -> i.setType(ruleType).setStatus(status).setResolution(resolution));
- dbTester.commit();
- }
-
- private ComponentDto insertPrivateBranch(OrganizationDto organization, BranchType branchType) {
- ComponentDto project = dbTester.components().insertPrivateProject(organization);
- BranchDto branchDto = newBranchDto(project.projectUuid(), branchType)
- .setKey("foo");
- return dbTester.components().insertProjectBranch(project, branchDto);
- }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/LiveQualityGateFactory.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateConverterTest.java
index c0638c88a5d..c00312694b3 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/qualitygate/LiveQualityGateFactory.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateConverterTest.java
@@ -19,8 +19,13 @@
*/
package org.sonar.server.qualitygate;
-import org.sonar.db.component.ComponentDto;
+import org.junit.Test;
+
+public class QualityGateConverterTest {
+
+ @Test
+ public void test_ToJson() {
+ // FIXME
+ }
-public interface LiveQualityGateFactory {
- EvaluatedQualityGate buildForShortLivedBranch(ComponentDto componentDto);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java
index b037cedc155..fc05daf97d4 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateFinderTest.java
@@ -49,7 +49,7 @@ public class QualityGateFinderTest {
ComponentDto project = db.components().insertPrivateProject();
QualityGateDto dbQualityGate = db.qualityGates().createDefaultQualityGate(db.getDefaultOrganization(), qg -> qg.setName("Sonar way"));
- QualityGateFinder.QualityGateData result = underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project.getId());
+ QualityGateFinder.QualityGateData result = underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project);
assertThat(result).isNotNull();
assertThat(result.getQualityGate().getId()).isEqualTo(dbQualityGate.getId());
@@ -63,7 +63,7 @@ public class QualityGateFinderTest {
QualityGateDto dbQualityGate = db.qualityGates().insertQualityGate(db.getDefaultOrganization(), qg -> qg.setName("My team QG"));
db.qualityGates().associateProjectToQualityGate(project, dbQualityGate);
- QualityGateFinder.QualityGateData result = underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project.getId());
+ QualityGateFinder.QualityGateData result = underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project);
assertThat(result).isNotNull();
assertThat(result.getQualityGate().getId()).isEqualTo(dbQualityGate.getId());
@@ -80,7 +80,7 @@ public class QualityGateFinderTest {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage(format("Unable to find the quality gate [%s] for organization [%s]", dbQualityGate.getUuid(), db.getDefaultOrganization().getUuid()));
- underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project.getId());
+ underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project);
}
@Test
@@ -91,7 +91,7 @@ public class QualityGateFinderTest {
db.getDbClient().qualityGateDao().delete(dbQualityGate, dbSession);
expectedException.expect(NotFoundException.class);
- underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project.getId());
+ underTest.getQualityGate(dbSession, db.getDefaultOrganization(), project);
}
@Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java
index 686a0a543f1..d0dd0201970 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/QualityGateModuleTest.java
@@ -29,6 +29,6 @@ public class QualityGateModuleTest {
public void verify_count_of_added_components() {
ComponentContainer container = new ComponentContainer();
new QualityGateModule().configure(container);
- assertThat(container.size()).isEqualTo(20 + 2);
+ assertThat(container.size()).isEqualTo(21 + 2);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryImplTest.java
deleted file mode 100644
index ae01892df4f..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryImplTest.java
+++ /dev/null
@@ -1,594 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.changeevent;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Matchers;
-import org.mockito.Mockito;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.issue.DefaultTransitions;
-import org.sonar.api.issue.Issue;
-import org.sonar.api.rules.RuleType;
-import org.sonar.api.utils.System2;
-import org.sonar.core.issue.IssueChangeContext;
-import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.component.AnalysisPropertyDto;
-import org.sonar.db.component.BranchDao;
-import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.BranchType;
-import org.sonar.db.component.ComponentDao;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.component.ComponentTesting;
-import org.sonar.db.component.SnapshotDao;
-import org.sonar.db.component.SnapshotDto;
-import org.sonar.db.issue.IssueDto;
-import org.sonar.db.organization.OrganizationDto;
-import org.sonar.server.qualitygate.LiveQualityGateFactory;
-import org.sonar.server.qualitygate.changeevent.QGChangeEventFactory.IssueChange;
-import org.sonar.server.settings.ProjectConfigurationLoader;
-import org.sonar.server.tester.UserSessionRule;
-import org.sonar.server.webhook.WebhookPayloadFactory;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptyList;
-import static java.util.Collections.singleton;
-import static java.util.Collections.singletonList;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.mockito.Mockito.mock;
-import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
-import static org.sonar.db.component.ComponentTesting.newBranchDto;
-
-@RunWith(DataProviderRunner.class)
-public class QGChangeEventFactoryImplTest {
- @Rule
- public DbTester dbTester = DbTester.create(System2.INSTANCE);
- @Rule
- public UserSessionRule userSessionRule = UserSessionRule.standalone();
-
- private DbClient dbClient = dbTester.getDbClient();
-
- private Random random = new Random();
- private RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
-
- private IssueChangeContext scanChangeContext = IssueChangeContext.createScan(new Date());
- private IssueChangeContext userChangeContext = IssueChangeContext.createUser(new Date(), "userLogin");
- private WebhookPayloadFactory webhookPayloadFactory = mock(WebhookPayloadFactory.class);
- private DbClient spiedOnDbClient = Mockito.spy(dbClient);
- private ProjectConfigurationLoader projectConfigurationLoader = mock(ProjectConfigurationLoader.class);
- private LiveQualityGateFactory liveQualityGateFactory = mock(LiveQualityGateFactory.class);
- private QGChangeEventFactoryImpl underTest = new QGChangeEventFactoryImpl(spiedOnDbClient, projectConfigurationLoader, liveQualityGateFactory);
- private DbClient mockedDbClient = mock(DbClient.class);
- private QGChangeEventFactoryImpl mockedUnderTest = new QGChangeEventFactoryImpl(mockedDbClient, projectConfigurationLoader, liveQualityGateFactory);
-
- @Test
- public void on_type_change_has_no_effect_if_SearchResponseData_has_no_issue() {
- mockedUnderTest.from(issueChangeData(), new IssueChange(randomRuleType), userChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_type_change_has_no_effect_if_scan_IssueChangeContext() {
- mockedUnderTest.from(issueChangeData(), new IssueChange(randomRuleType), scanChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_transition_change_has_no_effect_if_SearchResponseData_has_no_issue() {
- mockedUnderTest.from(issueChangeData(), new IssueChange(randomAlphanumeric(12)), userChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void onTransition_has_no_effect_if_transition_key_is_empty() {
- on_transition_changeHasNoEffectForTransitionKey("");
- }
-
- @Test
- public void onTransition_has_no_effect_if_transition_key_is_random() {
- on_transition_changeHasNoEffectForTransitionKey(randomAlphanumeric(99));
- }
-
- @Test
- public void on_transition_change_has_no_effect_if_transition_key_is_ignored_default_transition_key() {
- Set<String> supportedDefaultTransitionKeys = ImmutableSet.of(
- DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
- DefaultTransitions.ALL.stream()
- .filter(s -> !supportedDefaultTransitionKeys.contains(s))
- .forEach(this::on_transition_changeHasNoEffectForTransitionKey);
- }
-
- private void on_transition_changeHasNoEffectForTransitionKey(@Nullable String transitionKey) {
- Mockito.reset(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
-
- mockedUnderTest.from(issueChangeData(newIssueDto()), new IssueChange(transitionKey), userChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_transition_change_has_no_effect_if_scan_IssueChangeContext() {
- mockedUnderTest.from(issueChangeData(newIssueDto()), new IssueChange(randomAlphanumeric(12)), scanChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_type_and_transition_change_has_no_effect_if_SearchResponseData_has_no_issue() {
- mockedUnderTest.from(issueChangeData(), new IssueChange(randomRuleType, randomAlphanumeric(3)), userChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_type_and_transition_change_has_no_effect_if_scan_IssueChangeContext() {
- mockedUnderTest.from(issueChangeData(), new IssueChange(randomRuleType, randomAlphanumeric(3)), scanChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- public void on_type_and_transition_has_no_effect_if_transition_key_is_empty() {
- on_type_and_transition_changeHasNoEffectForTransitionKey("");
- }
-
- @Test
- public void on_type_and_transition_has_no_effect_if_transition_key_is_random() {
- on_type_and_transition_changeHasNoEffectForTransitionKey(randomAlphanumeric(66));
- }
-
- @Test
- public void on_type_and_transition_has_no_effect_if_transition_key_is_ignored_default_transition_key() {
- Set<String> supportedDefaultTransitionKeys = ImmutableSet.of(
- DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
- DefaultTransitions.ALL.stream()
- .filter(s -> !supportedDefaultTransitionKeys.contains(s))
- .forEach(this::on_type_and_transition_changeHasNoEffectForTransitionKey);
- }
-
- private void on_type_and_transition_changeHasNoEffectForTransitionKey(@Nullable String transitionKey) {
- Mockito.reset(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
-
- mockedUnderTest.from(issueChangeData(newIssueDto()), new IssueChange(randomRuleType, transitionKey), userChangeContext);
-
- Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
- }
-
- @Test
- @UseDataProvider("validIssueChanges")
- public void broadcast_to_QGEventListeners_for_short_living_branch_of_issue(IssueChange issueChange) {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPublicProject(organization);
- ComponentAndBranch branch = insertProjectBranch(project, BranchType.SHORT, "foo");
- SnapshotDto analysis = insertAnalysisTask(branch);
- Configuration configuration = mockLoadProjectConfiguration(branch);
-
- Map<String, String> properties = new HashMap<>();
- properties.put("sonar.analysis.test1", randomAlphanumeric(50));
- properties.put("sonar.analysis.test2", randomAlphanumeric(5000));
- insertPropertiesFor(analysis.getUuid(), properties);
-
- Collection<QGChangeEvent> events = underTest.from(issueChangeData(newIssueDto(branch)), issueChange, userChangeContext);
-
- assertThat(events).hasSize(1);
- QGChangeEvent event = events.iterator().next();
- assertThat(event.getProject()).isEqualTo(branch.component);
- assertThat(event.getBranch()).isEqualTo(branch.branch);
- assertThat(event.getAnalysis()).isEqualTo(analysis);
- assertThat(event.getProjectConfiguration()).isSameAs(configuration);
- }
-
- @Test
- public void do_not_load_project_configuration_nor_analysis_nor_call_webhook_if_there_are_no_short_branch() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPublicProject(organization);
- ComponentAndBranch longBranch1 = insertProjectBranch(project, BranchType.LONG, "foo");
- ComponentAndBranch longBranch2 = insertProjectBranch(project, BranchType.LONG, "bar");
- ImmutableList<IssueDto> issueDtos = ImmutableList.of(newIssueDto(project), newIssueDto(longBranch1), newIssueDto(longBranch2));
-
- SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
- Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
- underTest.from(issueChangeData(issueDtos), new IssueChange(randomRuleType), userChangeContext);
-
- Mockito.verifyZeroInteractions(projectConfigurationLoader);
- Mockito.verify(snapshotDaoSpy, Mockito.times(0)).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.anyCollectionOf(String.class));
- }
-
- @Test
- public void creates_single_QGChangeEvent_per_short_branch_with_at_least_one_issue_in_SearchResponseData() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
- SnapshotDto analysis1 = insertAnalysisTask(branch1);
- SnapshotDto analysis2 = insertAnalysisTask(branch2);
- SnapshotDto analysis3 = insertAnalysisTask(branch3);
- int issuesBranch1 = 2 + random.nextInt(10);
- int issuesBranch2 = 2 + random.nextInt(10);
- int issuesBranch3 = 2 + random.nextInt(10);
- List<IssueDto> issueDtos = Stream.of(
- IntStream.range(0, issuesBranch1).mapToObj(i -> newIssueDto(branch1.component)),
- IntStream.range(0, issuesBranch2).mapToObj(i -> newIssueDto(branch2.component)),
- IntStream.range(0, issuesBranch3).mapToObj(i -> newIssueDto(branch3.component)))
- .flatMap(s -> s)
- .collect(MoreCollectors.toList());
- Configuration configuration1 = mock(Configuration.class);
- Configuration configuration2 = mock(Configuration.class);
- Configuration configuration3 = mock(Configuration.class);
- mockLoadProjectConfigurations(
- branch1.component, configuration1,
- branch2.component, configuration2,
- branch3.component, configuration3);
-
- ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
- BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
- SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
- Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
- Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
- Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
- Collection<QGChangeEvent> qgChangeEvents = underTest.from(issueChangeData(issueDtos),
- new IssueChange(randomRuleType), userChangeContext);
-
- assertThat(qgChangeEvents)
- .hasSize(3)
- .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
- .containsOnly(
- tuple(branch1.branch, configuration1, analysis1),
- tuple(branch2.branch, configuration2, analysis2),
- tuple(branch3.branch, configuration3, analysis3));
-
- // verifyWebhookCalled(branch1, configuration1, analysis1);
- // verifyWebhookCalled(branch2, configuration2, analysis2);
- // verifyWebhookCalled(branch3, configuration3, analysis3);
- // extractPayloadFactoryArguments(3);
-
- Set<String> uuids = ImmutableSet.of(branch1.uuid(), branch2.uuid(), branch3.uuid());
- Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
- }
-
- @Test
- public void create_QGChangeEvent_only_for_short_branches() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentAndBranch shortBranch = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch longBranch = insertPrivateBranch(organization, BranchType.LONG);
- SnapshotDto analysis1 = insertAnalysisTask(shortBranch);
- SnapshotDto analysis2 = insertAnalysisTask(longBranch);
- Configuration configuration = mockLoadProjectConfiguration(shortBranch);
-
- ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
- BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
- SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
- Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
- Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
- Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
- Collection<QGChangeEvent> qgChangeEvents = underTest.from(
- issueChangeData(asList(newIssueDto(shortBranch), newIssueDto(longBranch))),
- new IssueChange(randomRuleType),
- userChangeContext);
-
- assertThat(qgChangeEvents)
- .hasSize(1)
- .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
- .containsOnly(tuple(shortBranch.branch, configuration, analysis1));
-
- Set<String> uuids = ImmutableSet.of(shortBranch.uuid(), longBranch.uuid());
- Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid())));
- Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
- }
-
- @Test
- public void do_not_load_componentDto_from_DB_if_all_are_in_inputData() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
- SnapshotDto analysis1 = insertAnalysisTask(branch1);
- SnapshotDto analysis2 = insertAnalysisTask(branch2);
- SnapshotDto analysis3 = insertAnalysisTask(branch3);
- List<IssueDto> issueDtos = asList(newIssueDto(branch1), newIssueDto(branch2), newIssueDto(branch3));
- Configuration configuration1 = mock(Configuration.class);
- Configuration configuration2 = mock(Configuration.class);
- Configuration configuration3 = mock(Configuration.class);
- mockLoadProjectConfigurations(
- branch1.component, configuration1,
- branch2.component, configuration2,
- branch3.component, configuration3);
-
- ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
- Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
- Collection<QGChangeEvent> qgChangeEvents = underTest.from(
- issueChangeData(issueDtos, branch1, branch2, branch3),
- new IssueChange(randomRuleType),
- userChangeContext);
-
- assertThat(qgChangeEvents)
- .hasSize(3)
- .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
- .containsOnly(
- tuple(branch1.branch, configuration1, analysis1),
- tuple(branch2.branch, configuration2, analysis2),
- tuple(branch3.branch, configuration3, analysis3));
-
- Mockito.verify(componentDaoSpy, Mockito.times(0)).selectByUuids(Matchers.any(DbSession.class), Matchers.anyCollectionOf(String.class));
- Mockito.verifyNoMoreInteractions(componentDaoSpy);
- }
-
- @Test
- public void call_db_only_for_componentDto_not_in_inputData() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
- ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
- SnapshotDto analysis1 = insertAnalysisTask(branch1);
- SnapshotDto analysis2 = insertAnalysisTask(branch2);
- SnapshotDto analysis3 = insertAnalysisTask(branch3);
- List<IssueDto> issueDtos = asList(newIssueDto(branch1), newIssueDto(branch2), newIssueDto(branch3));
- Configuration configuration1 = mock(Configuration.class);
- Configuration configuration2 = mock(Configuration.class);
- Configuration configuration3 = mock(Configuration.class);
- mockLoadProjectConfigurations(
- branch1.component, configuration1,
- branch2.component, configuration2,
- branch3.component, configuration3);
-
- ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
- BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
- SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
- Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
- Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
- Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
- Collection<QGChangeEvent> qgChangeEvents = underTest.from(
- issueChangeData(issueDtos, branch1, branch3),
- new IssueChange(randomRuleType),
- userChangeContext);
-
- assertThat(qgChangeEvents).hasSize(3);
-
- Set<String> uuids = ImmutableSet.of(branch1.uuid(), branch2.uuid(), branch3.uuid());
- Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(branch2.uuid())));
- Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
- }
-
- @Test
- public void supports_issues_on_files_and_filter_on_short_branches_asap_when_calling_db() {
- OrganizationDto organization = dbTester.organizations().insert();
- ComponentDto project = dbTester.components().insertPrivateProject(organization);
- ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project));
- ComponentAndBranch shortBranch = insertProjectBranch(project, BranchType.SHORT, "foo");
- ComponentAndBranch longBranch = insertProjectBranch(project, BranchType.LONG, "bar");
- ComponentDto shortBranchFile = dbTester.components().insertComponent(ComponentTesting.newFileDto(shortBranch.component));
- ComponentDto longBranchFile = dbTester.components().insertComponent(ComponentTesting.newFileDto(longBranch.component));
- SnapshotDto analysis1 = insertAnalysisTask(project);
- SnapshotDto analysis2 = insertAnalysisTask(shortBranch);
- SnapshotDto analysis3 = insertAnalysisTask(longBranch);
- List<IssueDto> issueDtos = asList(
- newIssueDto(file, project),
- newIssueDto(shortBranchFile, shortBranch),
- newIssueDto(longBranchFile, longBranch));
- Configuration configuration = mockLoadProjectConfiguration(shortBranch);
-
- ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
- BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
- SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
- Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
- Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
- Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
- Collection<QGChangeEvent> qgChangeEvents = underTest.from(issueChangeData(issueDtos),
- new IssueChange(randomRuleType), userChangeContext);
-
- assertThat(qgChangeEvents)
- .hasSize(1)
- .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
- .containsOnly(tuple(shortBranch.branch, configuration, analysis2));
-
- Set<String> uuids = ImmutableSet.of(project.uuid(), shortBranch.uuid(), longBranch.uuid());
- Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
- Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid(), longBranch.uuid())));
- Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid())));
- Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
- }
-
- private Configuration mockLoadProjectConfiguration(ComponentAndBranch componentAndBranch) {
- Configuration configuration = mock(Configuration.class);
- Mockito.when(projectConfigurationLoader.loadProjectConfigurations(Matchers.any(DbSession.class), Matchers.eq(singleton(componentAndBranch.component))))
- .thenReturn(ImmutableMap.of(componentAndBranch.uuid(), configuration));
- return configuration;
- }
-
- private void mockLoadProjectConfigurations(Object... branchesAndConfiguration) {
- checkArgument(branchesAndConfiguration.length % 2 == 0);
- Set<ComponentDto> components = new HashSet<>();
- Map<String, Configuration> result = new HashMap<>();
- for (int i = 0; i < branchesAndConfiguration.length; i++) {
- ComponentDto component = (ComponentDto) branchesAndConfiguration[i++];
- Configuration configuration = (Configuration) branchesAndConfiguration[i];
- components.add(component);
- result.put(component.uuid(), configuration);
- }
- Mockito.when(projectConfigurationLoader.loadProjectConfigurations(Matchers.any(DbSession.class), Matchers.eq(components)))
- .thenReturn(result);
- }
-
- private ComponentAndBranch insertPrivateBranch(OrganizationDto organization, BranchType branchType) {
- ComponentDto project = dbTester.components().insertPrivateProject(organization);
- BranchDto branchDto = newBranchDto(project.projectUuid(), branchType)
- .setKey("foo");
- ComponentDto newComponent = dbTester.components().insertProjectBranch(project, branchDto);
- return new ComponentAndBranch(newComponent, branchDto);
- }
-
- public ComponentAndBranch insertProjectBranch(ComponentDto project, BranchType type, String branchKey) {
- BranchDto branchDto = newBranchDto(project.projectUuid(), type).setKey(branchKey);
- ComponentDto newComponent = dbTester.components().insertProjectBranch(project, branchDto);
- return new ComponentAndBranch(newComponent, branchDto);
- }
-
- private static class ComponentAndBranch {
- private final ComponentDto component;
-
- private final BranchDto branch;
-
- private ComponentAndBranch(ComponentDto component, BranchDto branch) {
- this.component = component;
- this.branch = branch;
- }
-
- public ComponentDto getComponent() {
- return component;
- }
-
- public BranchDto getBranch() {
- return branch;
- }
-
- public String uuid() {
- return component.uuid();
- }
-
- }
-
- private void insertPropertiesFor(String snapshotUuid, Map<String, String> properties) {
- List<AnalysisPropertyDto> analysisProperties = properties.entrySet().stream()
- .map(entry -> new AnalysisPropertyDto()
- .setUuid(UuidFactoryFast.getInstance().create())
- .setSnapshotUuid(snapshotUuid)
- .setKey(entry.getKey())
- .setValue(entry.getValue()))
- .collect(toArrayList(properties.size()));
- dbTester.getDbClient().analysisPropertiesDao().insert(dbTester.getSession(), analysisProperties);
- dbTester.getSession().commit();
- }
-
- private SnapshotDto insertAnalysisTask(ComponentAndBranch componentAndBranch) {
- return insertAnalysisTask(componentAndBranch.component);
- }
-
- private SnapshotDto insertAnalysisTask(ComponentDto component) {
- return dbTester.components().insertSnapshot(component);
- }
-
- @DataProvider
- public static Object[][] validIssueChanges() {
- return new Object[][] {
- {new IssueChange(RuleType.BUG)},
- {new IssueChange(RuleType.VULNERABILITY)},
- {new IssueChange(RuleType.CODE_SMELL)},
- {new IssueChange(DefaultTransitions.RESOLVE)},
- {new IssueChange(RuleType.BUG, DefaultTransitions.RESOLVE)},
- {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.RESOLVE)},
- {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.RESOLVE)},
- {new IssueChange(DefaultTransitions.FALSE_POSITIVE)},
- {new IssueChange(RuleType.BUG, DefaultTransitions.FALSE_POSITIVE)},
- {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.FALSE_POSITIVE)},
- {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.FALSE_POSITIVE)},
- {new IssueChange(DefaultTransitions.WONT_FIX)},
- {new IssueChange(RuleType.BUG, DefaultTransitions.WONT_FIX)},
- {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.WONT_FIX)},
- {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.WONT_FIX)},
- {new IssueChange(DefaultTransitions.REOPEN)},
- {new IssueChange(RuleType.BUG, DefaultTransitions.REOPEN)},
- {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.REOPEN)},
- {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.REOPEN)}
- };
- }
-
- private QGChangeEventFactory.IssueChangeData issueChangeData() {
- return new QGChangeEventFactory.IssueChangeData(emptyList(), emptyList());
- }
-
- private QGChangeEventFactory.IssueChangeData issueChangeData(IssueDto issueDto) {
- return new QGChangeEventFactory.IssueChangeData(singletonList(issueDto.toDefaultIssue()), emptyList());
- }
-
- private QGChangeEventFactory.IssueChangeData issueChangeData(Collection<IssueDto> issueDtos, ComponentAndBranch... components) {
- return new QGChangeEventFactory.IssueChangeData(
- issueDtos.stream().map(IssueDto::toDefaultIssue).collect(Collectors.toList()),
- Arrays.stream(components).map(ComponentAndBranch::getComponent).collect(Collectors.toList()));
- }
-
- private IssueDto newIssueDto(@Nullable ComponentAndBranch projectAndBranch) {
- return projectAndBranch == null ? newIssueDto() : newIssueDto(projectAndBranch.component, projectAndBranch.component);
- }
-
- private IssueDto newIssueDto(ComponentDto componentDto) {
- return newIssueDto(componentDto, componentDto);
- }
-
- private IssueDto newIssueDto() {
- return newIssueDto((ComponentDto) null, (ComponentDto) null);
- }
-
- private IssueDto newIssueDto(@Nullable ComponentDto component, @Nullable ComponentAndBranch componentAndBranch) {
- return newIssueDto(component, componentAndBranch == null ? null : componentAndBranch.component);
- }
-
- private IssueDto newIssueDto(@Nullable ComponentDto component, @Nullable ComponentDto project) {
- RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
- String randomStatus = Issue.STATUSES.get(random.nextInt(Issue.STATUSES.size()));
- IssueDto res = new IssueDto()
- .setType(randomRuleType)
- .setStatus(randomStatus)
- .setRuleKey(randomAlphanumeric(3), randomAlphanumeric(4));
- if (component != null) {
- res.setComponent(component);
- }
- if (project != null) {
- res.setProject(project);
- }
- return res;
- }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryTest.java
deleted file mode 100644
index 62d95d86768..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventFactoryTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.changeevent;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.rules.RuleType;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class QGChangeEventFactoryTest {
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Test
- public void IssueChange_constructor_throws_IAE_if_both_args_are_null() {
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("At least one of ruleType and transitionKey must be non null");
-
- new QGChangeEventFactory.IssueChange(null, null);
- }
-
- @Test
- public void verify_IssueChange_getters() {
- QGChangeEventFactory.IssueChange transitionKeyOnly = new QGChangeEventFactoryImpl.IssueChange("foo");
- assertThat(transitionKeyOnly.getTransitionKey()).contains("foo");
- assertThat(transitionKeyOnly.getRuleType()).isEmpty();
- QGChangeEventFactory.IssueChange ruleTypeOnly = new QGChangeEventFactoryImpl.IssueChange(RuleType.BUG);
- assertThat(ruleTypeOnly.getTransitionKey()).isEmpty();
- assertThat(ruleTypeOnly.getRuleType()).contains(RuleType.BUG);
- QGChangeEventFactory.IssueChange transitionKeyAndRuleType = new QGChangeEventFactoryImpl.IssueChange(RuleType.VULNERABILITY, "bar");
- assertThat(transitionKeyAndRuleType.getTransitionKey()).contains("bar");
- assertThat(transitionKeyAndRuleType.getRuleType()).contains(RuleType.VULNERABILITY);
- }
-
- @Test
- public void verify_IssueChange_equality() {
- QGChangeEventFactory.IssueChange underTest = new QGChangeEventFactory.IssueChange(RuleType.BUG);
-
- assertThat(underTest).isEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG));
- assertThat(underTest).isEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG, null));
-
- assertThat(underTest).isNotEqualTo(null);
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.CODE_SMELL));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.CODE_SMELL, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.VULNERABILITY));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.VULNERABILITY, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(randomAlphanumeric(10)));
-
- String transitionKey = randomAlphanumeric(10);
- underTest = new QGChangeEventFactory.IssueChange(transitionKey);
-
- assertThat(underTest).isEqualTo(new QGChangeEventFactory.IssueChange(transitionKey));
- assertThat(underTest).isEqualTo(new QGChangeEventFactory.IssueChange(null, transitionKey));
-
- assertThat(underTest).isNotEqualTo(null);
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG, transitionKey));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.BUG, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.CODE_SMELL));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.CODE_SMELL, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.CODE_SMELL, transitionKey));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.VULNERABILITY));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.VULNERABILITY, randomAlphanumeric(10)));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(RuleType.VULNERABILITY, transitionKey));
- assertThat(underTest).isNotEqualTo(new QGChangeEventFactory.IssueChange(randomAlphanumeric(9)));
- }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java
index 8e729ebfe8c..47cee4474b7 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/qualitygate/changeevent/QGChangeEventListenersImplTest.java
@@ -45,6 +45,7 @@ import org.sonar.db.component.ComponentDto;
import org.sonar.server.qualitygate.changeevent.QGChangeEventListener.ChangedIssue;
import static java.util.Collections.emptyList;
+import static java.util.Collections.emptySet;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
@@ -69,9 +70,7 @@ public class QGChangeEventListenersImplTest {
private String component1Uuid = RandomStringUtils.randomAlphabetic(6);
private ComponentDto component1 = newComponentDto(component1Uuid);
private DefaultIssue component1Issue = newDefaultIssue(component1Uuid);
- private QGChangeEventFactory.IssueChangeData oneIssueOnComponent1 = new QGChangeEventFactory.IssueChangeData(
- singletonList(component1Issue),
- singletonList(component1));
+ private List<DefaultIssue> oneIssueOnComponent1 = singletonList(component1Issue);
private QGChangeEvent component1QGChangeEvent = newQGChangeEvent(component1);
private InOrder inOrder = Mockito.inOrder(listener1, listener2, listener3);
@@ -79,37 +78,15 @@ public class QGChangeEventListenersImplTest {
private QGChangeEventListenersImpl underTest = new QGChangeEventListenersImpl(new QGChangeEventListener[] {listener1, listener2, listener3});
@Test
- public void broadcastOnIssueChange_has_no_effect_when_issueChangeData_is_empty() {
- QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(emptyList(), emptyList());
-
- underTest.broadcastOnIssueChange(issueChangeData, singletonList(component1QGChangeEvent));
-
- verifyZeroInteractions(listener1, listener2, listener3);
- }
-
- @Test
- public void broadcastOnIssueChange_has_no_effect_when_issueChangeData_has_no_issue() {
- QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(
- emptyList(), singletonList(newComponentDto(component1Uuid)));
-
- underTest.broadcastOnIssueChange(issueChangeData, singletonList(component1QGChangeEvent));
-
- verifyZeroInteractions(listener1, listener2, listener3);
- }
-
- @Test
- public void broadcastOnIssueChange_has_no_effect_when_issueChangeData_has_no_component() {
- QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(
- singletonList(newDefaultIssue(component1Uuid)), emptyList());
-
- underTest.broadcastOnIssueChange(issueChangeData, singletonList(component1QGChangeEvent));
+ public void broadcastOnIssueChange_has_no_effect_when_issues_are_empty() {
+ underTest.broadcastOnIssueChange(emptyList(), singletonList(component1QGChangeEvent));
verifyZeroInteractions(listener1, listener2, listener3);
}
@Test
public void broadcastOnIssueChange_has_no_effect_when_no_changeEvent() {
- underTest.broadcastOnIssueChange(oneIssueOnComponent1, Collections.emptySet());
+ underTest.broadcastOnIssueChange(oneIssueOnComponent1, emptySet());
verifyZeroInteractions(listener1, listener2, listener3);
}
@@ -225,9 +202,7 @@ public class QGChangeEventListenersImplTest {
.flatMap(s -> s)
.collect(Collectors.toList());
- QGChangeEventFactory.IssueChangeData issueChangeData = new QGChangeEventFactory.IssueChangeData(
- randomizedList(issues),
- randomizedList(Arrays.asList(component1, component2, component3, component4)));
+ List<DefaultIssue> changedIssues = randomizedList(issues);
List<QGChangeEvent> qgChangeEvents = Stream.of(
Stream.of(component1QGChangeEvent),
Stream.of(component2QGChangeEvent),
@@ -236,7 +211,7 @@ public class QGChangeEventListenersImplTest {
.flatMap(s -> s)
.collect(Collectors.toList());
- underTest.broadcastOnIssueChange(issueChangeData, randomizedList(qgChangeEvents));
+ underTest.broadcastOnIssueChange(changedIssues, randomizedList(qgChangeEvents));
listeners.forEach(listener -> {
verifyListenerCalled(listener, component1QGChangeEvent, component1Issue);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/settings/TestProjectConfigurationLoader.java b/server/sonar-server/src/test/java/org/sonar/server/settings/TestProjectConfigurationLoader.java
new file mode 100644
index 00000000000..f1a252795af
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/settings/TestProjectConfigurationLoader.java
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.settings;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.sonar.api.config.Configuration;
+import org.sonar.db.DbSession;
+import org.sonar.db.component.ComponentDto;
+
+public class TestProjectConfigurationLoader implements ProjectConfigurationLoader {
+
+ private final Configuration config;
+
+ public TestProjectConfigurationLoader(Configuration config) {
+ this.config = config;
+ }
+
+ @Override
+ public Map<String, Configuration> loadProjectConfigurations(DbSession dbSession, Set<ComponentDto> projects) {
+ Map<String, Configuration> map = new HashMap<>();
+ for (ComponentDto project : projects) {
+ map.put(project.uuid(), config);
+ }
+ return map;
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java
index 1fcc474f594..21c7cb87390 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/ProjectAnalysisTest.java
@@ -24,6 +24,7 @@ import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.sonar.api.measures.Metric;
import org.sonar.server.qualitygate.EvaluatedQualityGate;
import org.sonar.server.qualitygate.QualityGate;
@@ -41,7 +42,7 @@ public class ProjectAnalysisTest {
private final Branch branch = new Branch(true, "name", Branch.Type.SHORT);
private final EvaluatedQualityGate qualityGate = EvaluatedQualityGate.newBuilder()
.setQualityGate(new QualityGate("id", "name", emptySet()))
- .setStatus(EvaluatedQualityGate.Status.WARN)
+ .setStatus(Metric.Level.WARN)
.build();
private final Map<String, String> properties = ImmutableMap.of("a", "b");
private ProjectAnalysis underTest = new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, 1L, properties);
@@ -108,7 +109,7 @@ public class ProjectAnalysisTest {
assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, null, 1L, properties));
EvaluatedQualityGate otherQualityGate = EvaluatedQualityGate.newBuilder()
.setQualityGate(new QualityGate("A", "B", emptySet()))
- .setStatus(EvaluatedQualityGate.Status.WARN)
+ .setStatus(Metric.Level.WARN)
.build();
assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, otherQualityGate, 1L, properties));
assertThat(underTest).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, qualityGate, null, properties));
@@ -134,7 +135,7 @@ public class ProjectAnalysisTest {
assertThat(underTest.hashCode()).isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, null, 1L, properties).hashCode());
EvaluatedQualityGate otherQualityGate = EvaluatedQualityGate.newBuilder()
.setQualityGate(new QualityGate("A", "B", emptySet()))
- .setStatus(EvaluatedQualityGate.Status.WARN)
+ .setStatus(Metric.Level.WARN)
.build();
assertThat(underTest.hashCode())
.isNotEqualTo(new ProjectAnalysis(project, ceTask, analysis, branch, otherQualityGate, 1L, properties).hashCode());
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java
index 86dc92e961a..7415178a93d 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookPayloadFactoryImplTest.java
@@ -24,6 +24,7 @@ import java.util.Map;
import javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Test;
+import org.sonar.api.measures.Metric;
import org.sonar.api.platform.Server;
import org.sonar.api.utils.System2;
import org.sonar.server.qualitygate.Condition;
@@ -59,7 +60,7 @@ public class WebhookPayloadFactoryImplTest {
Condition condition = new Condition("coverage", Condition.Operator.GREATER_THAN, "70.0", "75.0", true);
EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder()
.setQualityGate(new QualityGate("G1", "Gate One", singleton(condition)))
- .setStatus(EvaluatedQualityGate.Status.WARN)
+ .setStatus(Metric.Level.WARN)
.addCondition(condition, EvaluatedCondition.EvaluationStatus.WARN, "74.0")
.build();
ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, emptyMap());
@@ -105,7 +106,7 @@ public class WebhookPayloadFactoryImplTest {
Condition condition = new Condition("coverage", Condition.Operator.GREATER_THAN, "70.0", "75.0", false);
EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder()
.setQualityGate(new QualityGate("G1", "Gate One", singleton(condition)))
- .setStatus(EvaluatedQualityGate.Status.WARN)
+ .setStatus(Metric.Level.WARN)
.addCondition(condition, EvaluatedCondition.EvaluationStatus.NO_VALUE, null)
.build();
ProjectAnalysis analysis = newAnalysis(task, gate, null, 1_500_000_000_000L, emptyMap());
@@ -146,7 +147,7 @@ public class WebhookPayloadFactoryImplTest {
CeTask task = new CeTask("#1", CeTask.Status.SUCCESS);
EvaluatedQualityGate gate = EvaluatedQualityGate.newBuilder()
.setQualityGate(new QualityGate("G1", "Gate One", emptySet()))
- .setStatus(EvaluatedQualityGate.Status.WARN)
+ .setStatus(Metric.Level.WARN)
.build();
Map<String, String> scannerProperties = ImmutableMap.of(
"sonar.analysis.revision", "ab45d24",
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java
index a8d82fc9770..1f0bb7d2e7f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java
@@ -32,6 +32,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Matchers;
import org.mockito.Mockito;
import org.sonar.api.config.Configuration;
+import org.sonar.api.measures.Metric;
import org.sonar.api.utils.System2;
import org.sonar.core.util.UuidFactoryFast;
import org.sonar.db.DbClient;
@@ -67,7 +68,7 @@ public class WebhookQGChangeEventListenerTest {
private static final EvaluatedQualityGate EVALUATED_QUALITY_GATE_1 = EvaluatedQualityGate.newBuilder()
.setQualityGate(new QualityGate(valueOf(ShortLivingBranchQualityGate.ID), ShortLivingBranchQualityGate.NAME, emptySet()))
- .setStatus(EvaluatedQualityGate.Status.OK)
+ .setStatus(Metric.Level.OK)
.build();
private static final Set<QGChangeEventListener.ChangedIssue> CHANGED_ISSUES_ARE_IGNORED = emptySet();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookDeliveriesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookDeliveriesActionTest.java
index a49233c995a..14d91c65782 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookDeliveriesActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookDeliveriesActionTest.java
@@ -82,7 +82,7 @@ public class WebhookDeliveriesActionTest {
}
@Test
- public void search_by_component_and_return_no_records() throws Exception {
+ public void search_by_component_and_return_no_records() {
userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
Webhooks.DeliveriesWsResponse response = ws.newRequest()
@@ -93,7 +93,7 @@ public class WebhookDeliveriesActionTest {
}
@Test
- public void search_by_task_and_return_no_records() throws Exception {
+ public void search_by_task_and_return_no_records() {
userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
Webhooks.DeliveriesWsResponse response = ws.newRequest()
@@ -104,7 +104,7 @@ public class WebhookDeliveriesActionTest {
}
@Test
- public void search_by_component_and_return_records_of_example() throws Exception {
+ public void search_by_component_and_return_records_of_example() {
WebhookDeliveryDto dto = newWebhookDeliveryDto()
.setUuid("d1")
.setComponentUuid(project.uuid())
@@ -128,7 +128,7 @@ public class WebhookDeliveriesActionTest {
}
@Test
- public void search_by_task_and_return_records() throws Exception {
+ public void search_by_task_and_return_records() {
WebhookDeliveryDto dto1 = newWebhookDeliveryDto().setComponentUuid(project.uuid()).setCeTaskUuid("t1");
WebhookDeliveryDto dto2 = newWebhookDeliveryDto().setComponentUuid(project.uuid()).setCeTaskUuid("t1");
WebhookDeliveryDto dto3 = newWebhookDeliveryDto().setComponentUuid(project.uuid()).setCeTaskUuid("t2");
@@ -146,7 +146,7 @@ public class WebhookDeliveriesActionTest {
}
@Test
- public void search_by_component_and_throw_ForbiddenException_if_not_admin_of_project() throws Exception {
+ public void search_by_component_and_throw_ForbiddenException_if_not_admin_of_project() {
WebhookDeliveryDto dto = newWebhookDeliveryDto()
.setComponentUuid(project.uuid());
dbClient.webhookDeliveryDao().insert(db.getSession(), dto);
@@ -162,7 +162,7 @@ public class WebhookDeliveriesActionTest {
}
@Test
- public void search_by_task_and_throw_ForbiddenException_if_not_admin_of_project() throws Exception {
+ public void search_by_task_and_throw_ForbiddenException_if_not_admin_of_project() {
WebhookDeliveryDto dto = newWebhookDeliveryDto()
.setComponentUuid(project.uuid());
dbClient.webhookDeliveryDao().insert(db.getSession(), dto);
@@ -178,7 +178,7 @@ public class WebhookDeliveriesActionTest {
}
@Test
- public void throw_IAE_if_both_component_and_task_parameters_are_set() throws Exception {
+ public void throw_IAE_if_both_component_and_task_parameters_are_set() {
userSession.logIn();
expectedException.expect(IllegalArgumentException.class);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookDeliveryActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookDeliveryActionTest.java
index e54e9d3f8d8..dbc6db0af65 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookDeliveryActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/webhook/ws/WebhookDeliveryActionTest.java
@@ -87,7 +87,7 @@ public class WebhookDeliveryActionTest {
}
@Test
- public void return_404_if_delivery_does_not_exist() throws Exception {
+ public void return_404_if_delivery_does_not_exist() {
userSession.logIn();
expectedException.expect(NotFoundException.class);
@@ -99,7 +99,7 @@ public class WebhookDeliveryActionTest {
}
@Test
- public void load_the_delivery_of_example() throws Exception {
+ public void load_the_delivery_of_example() {
WebhookDeliveryDto dto = newWebhookDeliveryDto()
.setUuid("d1")
.setComponentUuid(project.uuid())
@@ -124,7 +124,7 @@ public class WebhookDeliveryActionTest {
}
@Test
- public void return_delivery_that_failed_to_be_sent() throws Exception {
+ public void return_delivery_that_failed_to_be_sent() {
WebhookDeliveryDto dto = newWebhookDeliveryDto()
.setComponentUuid(project.uuid())
.setSuccess(false)
@@ -146,7 +146,31 @@ public class WebhookDeliveryActionTest {
}
@Test
- public void throw_ForbiddenException_if_not_admin_of_project() throws Exception {
+ public void return_delivery_with_none_of_optional_fields() {
+ WebhookDeliveryDto dto = newWebhookDeliveryDto()
+ .setComponentUuid(project.uuid())
+ .setCeTaskUuid(null)
+ .setHttpStatus(null)
+ .setDurationMs(null)
+ .setErrorStacktrace(null)
+ .setAnalysisUuid(null);
+ dbClient.webhookDeliveryDao().insert(db.getSession(), dto);
+ db.commit();
+ userSession.logIn().addProjectPermission(UserRole.ADMIN, project);
+
+ Webhooks.DeliveryWsResponse response = ws.newRequest()
+ .setParam("deliveryId", dto.getUuid())
+ .executeProtobuf(Webhooks.DeliveryWsResponse.class);
+
+ Webhooks.Delivery actual = response.getDelivery();
+ assertThat(actual.hasHttpStatus()).isFalse();
+ assertThat(actual.hasDurationMs()).isFalse();
+ assertThat(actual.hasErrorStacktrace()).isFalse();
+ assertThat(actual.hasCeTaskId()).isFalse();
+ }
+
+ @Test
+ public void throw_ForbiddenException_if_not_admin_of_project() {
WebhookDeliveryDto dto = newWebhookDeliveryDto()
.setComponentUuid(project.uuid());
dbClient.webhookDeliveryDao().insert(db.getSession(), dto);
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java
index 2d810712ff5..702462c9f8e 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueChangeContext.java
@@ -25,11 +25,6 @@ import java.util.Objects;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
-/**
- * PLUGINS MUST NOT BE USED THIS CLASS, EXCEPT FOR UNIT TESTING.
- *
- * @since 3.6
- */
public class IssueChangeContext implements Serializable {
private final String login;
diff --git a/sonar-core/src/main/java/org/sonar/core/util/stream/MoreCollectors.java b/sonar-core/src/main/java/org/sonar/core/util/stream/MoreCollectors.java
index 31a46f37660..d9d2e512908 100644
--- a/sonar-core/src/main/java/org/sonar/core/util/stream/MoreCollectors.java
+++ b/sonar-core/src/main/java/org/sonar/core/util/stream/MoreCollectors.java
@@ -25,6 +25,7 @@ import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -35,6 +36,7 @@ import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
+import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
@@ -111,6 +113,13 @@ public final class MoreCollectors {
}
/**
+ * A Collector into an {@link EnumSet} of specified enumeration.
+ */
+ public static <E extends Enum<E>> Collector<E, ?, EnumSet<E>> toEnumSet(Class<E> enumClass) {
+ return Collectors.toCollection(() -> EnumSet.noneOf(enumClass));
+ }
+
+ /**
* Delegates to {@link java.util.stream.Collectors#toCollection(Supplier)}.
*/
public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index f6a72667d62..89c5ededa57 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -1556,7 +1556,8 @@ metric.covered_lines.description=Covered lines
metric.covered_lines.name=Covered Lines
metric.critical_violations.description=Critical issues
metric.critical_violations.name=Critical Issues
-metric.development_cost.name=SQALE Development Cost
+metric.development_cost.description=Development cost
+metric.development_cost.name=Development Cost
metric.directories.description=Directories
metric.directories.name=Directories
metric.distance.description=Distance
@@ -1680,6 +1681,8 @@ metric.new_coverage.name=Coverage on New Code
metric.new_coverage.short_name=Coverage
metric.new_critical_violations.description=New Critical issues
metric.new_critical_violations.name=New Critical Issues
+metric.new_development_cost.description=Development cost on new code
+metric.new_development_cost.name=Development Cost on New Code
metric.new_duplicated_blocks.name=Duplicated Blocks on New Code
metric.new_duplicated_blocks.extra_short_name=Duplicated Blocks
metric.new_duplicated_blocks.description=Duplicated blocks on new code
diff --git a/sonar-core/src/test/java/org/sonar/core/util/stream/MoreCollectorsTest.java b/sonar-core/src/test/java/org/sonar/core/util/stream/MoreCollectorsTest.java
index 398d3f3756a..68c8ae6cec6 100644
--- a/sonar-core/src/test/java/org/sonar/core/util/stream/MoreCollectorsTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/util/stream/MoreCollectorsTest.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -101,7 +102,7 @@ public class MoreCollectorsTest {
@Test
public void toSet_with_size_builds_an_ImmutableSet() {
- Set<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(toSet(30));
+ Set<Integer> res = Stream.of(1, 2, 3, 4, 5).collect(toSet(30));
assertThat(res).isInstanceOf(ImmutableSet.class)
.containsExactly(1, 2, 3, 4, 5);
}
@@ -112,6 +113,20 @@ public class MoreCollectorsTest {
}
@Test
+ public void toEnumSet() {
+ Set<MyEnum> res = Stream.of(MyEnum.ONE, MyEnum.ONE, MyEnum.TWO).collect(MoreCollectors.toEnumSet(MyEnum.class));
+ assertThat(res).isInstanceOf(EnumSet.class)
+ .containsExactly(MyEnum.ONE, MyEnum.TWO);
+ }
+
+ @Test
+ public void toEnumSet_with_empty_stream() {
+ Set<MyEnum> res = Stream.<MyEnum>empty().collect(MoreCollectors.toEnumSet(MyEnum.class));
+ assertThat(res).isInstanceOf(EnumSet.class)
+ .isEmpty();
+ }
+
+ @Test
public void toArrayList_builds_an_ArrayList() {
List<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(toArrayList());
assertThat(res).isInstanceOf(ArrayList.class)
@@ -516,4 +531,8 @@ public class MoreCollectorsTest {
return text;
}
}
+
+ private enum MyEnum {
+ ONE, TWO, THREE
+ }
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
index d0eccf976e8..2c1545efbd1 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/CoreMetrics.java
@@ -2413,8 +2413,26 @@ public final class CoreMetrics {
/**
* @since 4.5
*/
- public static final Metric<String> DEVELOPMENT_COST = new Metric.Builder(DEVELOPMENT_COST_KEY, "SQALE Development Cost", Metric.ValueType.STRING)
- .setDescription("SQALE development cost")
+ public static final Metric<String> DEVELOPMENT_COST = new Metric.Builder(DEVELOPMENT_COST_KEY, "Development Cost", Metric.ValueType.STRING)
+ .setDescription("Development cost")
+ .setDomain(DOMAIN_MAINTAINABILITY)
+ .setDirection(Metric.DIRECTION_WORST)
+ .setOptimizedBestValue(true)
+ .setBestValue(0.0)
+ .setQualitative(true)
+ .setHidden(true)
+ .create();
+
+ /**
+ * @since 7.0
+ */
+ public static final String NEW_DEVELOPMENT_COST_KEY = "new_development_cost";
+
+ /**
+ * @since 7.0
+ */
+ public static final Metric<String> NEW_DEVELOPMENT_COST = new Metric.Builder(NEW_DEVELOPMENT_COST_KEY, "Development Cost on New Code", Metric.ValueType.STRING)
+ .setDescription("Development cost on new code")
.setDomain(DOMAIN_MAINTAINABILITY)
.setDirection(Metric.DIRECTION_WORST)
.setOptimizedBestValue(true)
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/Qualifiers.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/Qualifiers.java
index 96d02ed89cf..8605fd9798c 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/Qualifiers.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/Qualifiers.java
@@ -19,8 +19,12 @@
*/
package org.sonar.api.resources;
+import java.util.List;
import java.util.Objects;
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableList;
+
/**
* The qualifier determines the exact type of a resource.
* Plugins can define their own qualifiers.
@@ -70,6 +74,15 @@ public final class Qualifiers {
// ugly, should be replaced by "natures"
public static final String UNIT_TEST_FILE = "UTS";
+ /**
+ * List of qualifiers, ordered from bottom to up regarding position
+ * in tree of components
+ *
+ * @since 7.0
+ */
+ public static final List<String> ORDERED_BOTTOM_UP = unmodifiableList(asList(
+ FILE, UNIT_TEST_FILE, DIRECTORY, MODULE, PROJECT, APP, SUBVIEW, VIEW));
+
private Qualifiers() {
// only static methods
}
diff --git a/tests/projects/measure/LiveMeasuresTest/sonar-project.properties b/tests/projects/measure/LiveMeasuresTest/sonar-project.properties
new file mode 100644
index 00000000000..dbe1d71850c
--- /dev/null
+++ b/tests/projects/measure/LiveMeasuresTest/sonar-project.properties
@@ -0,0 +1,5 @@
+sonar.projectKey=LiveMeasuresTestExample
+sonar.projectName=LiveMeasuresTestExample
+sonar.projectVersion=1.0-SNAPSHOT
+sonar.sources=src
+sonar.language=xoo
diff --git a/tests/projects/measure/LiveMeasuresTest/src/file_with_one_line.xoo b/tests/projects/measure/LiveMeasuresTest/src/file_with_one_line.xoo
new file mode 100644
index 00000000000..89b24ecec50
--- /dev/null
+++ b/tests/projects/measure/LiveMeasuresTest/src/file_with_one_line.xoo
@@ -0,0 +1 @@
+line 1
diff --git a/tests/projects/measure/LiveMeasuresTest/src/file_with_three_lines.xoo b/tests/projects/measure/LiveMeasuresTest/src/file_with_three_lines.xoo
new file mode 100644
index 00000000000..a92d664bc20
--- /dev/null
+++ b/tests/projects/measure/LiveMeasuresTest/src/file_with_three_lines.xoo
@@ -0,0 +1,3 @@
+line 1
+line 2
+line 3
diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueMeasureTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueMeasureTest.java
index 125d4475cb8..57c0aca0d3e 100644
--- a/tests/src/test/java/org/sonarqube/tests/issue/IssueMeasureTest.java
+++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueMeasureTest.java
@@ -48,15 +48,15 @@ public class IssueMeasureTest extends AbstractIssueTest {
ORCHESTRATOR.getServer().associateProjectToQualityProfile(MULTI_MODULE_SAMPLE_PROJECT_KEY, "xoo", "with-many-rules");
runProjectAnalysis(ORCHESTRATOR, "shared/xoo-multi-modules-sample");
- assertThat(search(IssueQuery.create().componentRoots(MULTI_MODULE_SAMPLE_PROJECT_KEY)).paging().total()).isEqualTo(136);
+ assertThat(search(IssueQuery.create().componentRoots(MULTI_MODULE_SAMPLE_PROJECT_KEY)).paging().total()).isEqualTo(128);
Map<String, Double> measures = getMeasuresAsDoubleByMetricKey(ORCHESTRATOR, MULTI_MODULE_SAMPLE_PROJECT_KEY, "violations", "info_violations", "minor_violations",
"major_violations",
"blocker_violations", "critical_violations");
- assertThat(measures.get("violations")).isEqualTo(136);
+ assertThat(measures.get("violations")).isEqualTo(128);
assertThat(measures.get("info_violations")).isEqualTo(2);
assertThat(measures.get("minor_violations")).isEqualTo(61);
- assertThat(measures.get("major_violations")).isEqualTo(65);
+ assertThat(measures.get("major_violations")).isEqualTo(57);
assertThat(measures.get("blocker_violations")).isEqualTo(4);
assertThat(measures.get("critical_violations")).isEqualTo(4);
}
diff --git a/tests/src/test/java/org/sonarqube/tests/issue/IssueSearchTest.java b/tests/src/test/java/org/sonarqube/tests/issue/IssueSearchTest.java
index b8f96966eb1..0f2d03d7aac 100644
--- a/tests/src/test/java/org/sonarqube/tests/issue/IssueSearchTest.java
+++ b/tests/src/test/java/org/sonarqube/tests/issue/IssueSearchTest.java
@@ -55,7 +55,7 @@ public class IssueSearchTest extends AbstractIssueTest {
private static final String PROJECT_KEY2 = "com.sonarsource.it.samples:multi-modules-sample2";
private static int DEFAULT_PAGINATED_RESULTS = 100;
- private static int TOTAL_NB_ISSUES = 272;
+ private static int TOTAL_NB_ISSUES = 256;
@BeforeClass
public static void prepareData() {
@@ -92,15 +92,15 @@ public class IssueSearchTest extends AbstractIssueTest {
@Test
public void search_issues_by_component_roots() {
assertSearch(r -> r.setComponentRoots("com.sonarsource.it.samples:multi-modules-sample")).hasSize(DEFAULT_PAGINATED_RESULTS);
- assertSearch(r -> r.setComponentRoots("com.sonarsource.it.samples:multi-modules-sample:module_a")).hasSize(82);
- assertSearch(r -> r.setComponentRoots("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1")).hasSize(36);
+ assertSearch(r -> r.setComponentRoots("com.sonarsource.it.samples:multi-modules-sample:module_a")).hasSize(76);
+ assertSearch(r -> r.setComponentRoots("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1")).hasSize(35);
assertThat(search(IssueQuery.create().componentRoots("unknown")).list()).isEmpty();
}
@Test
public void search_issues_by_components() {
- assertSearch(r -> r.setComponents("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo")).hasSize(34);
+ assertSearch(r -> r.setComponents("com.sonarsource.it.samples:multi-modules-sample:module_a:module_a1:src/main/xoo/com/sonar/it/samples/modules/a1/HelloA1.xoo")).hasSize(33);
assertSearch(r -> r.setComponents("unknown")).isEmpty();
}
@@ -261,7 +261,7 @@ public class IssueSearchTest extends AbstractIssueTest {
@Test
public void search_issues_by_types() throws IOException {
assertThat(searchResponse(r -> r.setTypes(singletonList("CODE_SMELL"))).getPaging().getTotal()).isEqualTo(142);
- assertThat(searchResponse(r -> r.setTypes(singletonList("BUG"))).getPaging().getTotal()).isEqualTo(122);
+ assertThat(searchResponse(r -> r.setTypes(singletonList("BUG"))).getPaging().getTotal()).isEqualTo(106);
assertThat(searchResponse(r -> r.setTypes(singletonList("VULNERABILITY"))).getPaging().getTotal()).isEqualTo(8);
}
diff --git a/tests/src/test/java/org/sonarqube/tests/measure/LiveMeasuresTest.java b/tests/src/test/java/org/sonarqube/tests/measure/LiveMeasuresTest.java
new file mode 100644
index 00000000000..a47587aa4b3
--- /dev/null
+++ b/tests/src/test/java/org/sonarqube/tests/measure/LiveMeasuresTest.java
@@ -0,0 +1,198 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.sonarqube.tests.measure;
+
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.Table;
+import com.sonar.orchestrator.Orchestrator;
+import com.sonar.orchestrator.build.SonarScanner;
+import java.util.List;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonarqube.qa.util.Tester;
+import org.sonarqube.ws.Issues;
+import org.sonarqube.ws.Measures;
+import org.sonarqube.ws.Projects.CreateWsResponse.Project;
+import org.sonarqube.ws.Qualitygates;
+import org.sonarqube.ws.client.issues.DoTransitionRequest;
+import org.sonarqube.ws.client.issues.SearchRequest;
+import org.sonarqube.ws.client.issues.SetSeverityRequest;
+import org.sonarqube.ws.client.issues.SetTypeRequest;
+import org.sonarqube.ws.client.measures.ComponentRequest;
+import org.sonarqube.ws.client.qualitygates.CreateConditionRequest;
+import util.ItUtils;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class LiveMeasuresTest {
+
+ private static final String PROJECT_KEY = "LiveMeasuresTestExample";
+ private static final String PROJECT_DIR = "measure/LiveMeasuresTest";
+ private static final String FILE_WITH_ONE_LINE_KEY = PROJECT_KEY + ":src/file_with_one_line.xoo";
+ private static final String FILE_WITH_THREE_LINES_KEY = PROJECT_KEY + ":src/file_with_three_lines.xoo";
+ private static final String SRC_DIR_KEY = PROJECT_KEY + ":src";
+
+ @ClassRule
+ public static Orchestrator orchestrator = MeasureSuite.ORCHESTRATOR;
+
+ @Rule
+ public Tester tester = new Tester(orchestrator);
+
+ @Test
+ public void refresh_measures_when_touching_ws_from_web_services() {
+ ItUtils.restoreProfile(orchestrator, getClass().getResource("/measure/LiveMeasuresTest/one-bug-per-line-profile.xml"));
+ Project project = tester.projects().provision(r -> r.setProject(PROJECT_KEY).setName("LiveMeasuresTestExample"));
+ orchestrator.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "one-bug-per-line-profile");
+ // quality gate on Security Rating: Warning when greater than A and Error when greater than C
+ Qualitygates.CreateResponse simple = tester.qGates().generate();
+ tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(String.valueOf(simple.getId()))
+ .setMetric("security_rating").setOp("GT").setWarning("1").setError("3"));
+ tester.qGates().associateProject(simple, project);
+ scanSample();
+
+ expectMeasures("bugs", 1, 3, 4, 4);
+ expectMeasures("vulnerabilities", 0, 0, 0, 0);
+ expectMeasures("violations", 1, 3, 4, 4);
+ // highest severity is MAJOR --> rating is C (3)
+ expectMeasures("reliability_rating", 3, 3, 3, 3);
+ // zero vulnerabilities -> rating is A (1)
+ expectMeasures("security_rating", 1, 1, 1, 1);
+ assertQualityGate("OK");
+
+ // mark a bug as FP
+ Issues.Issue issue = getFirstIssue(FILE_WITH_ONE_LINE_KEY, "BUG");
+ markAsFalsePositive(issue);
+ expectMeasures("bugs", 0, 3, 3, 3);
+ expectMeasures("vulnerabilities", 0, 0, 0, 0);
+ expectMeasures("violations", 0, 3, 3, 3);
+ expectMeasures("reliability_rating", 1, 3, 3, 3);
+ expectMeasures("security_rating", 1, 1, 1, 1);
+
+ // convert a bug to a vulnerability
+ issue = getFirstIssue(FILE_WITH_THREE_LINES_KEY, "BUG");
+ convertToType(issue, "VULNERABILITY");
+ expectMeasures("bugs", 0, 2, 2, 2);
+ expectMeasures("vulnerabilities", 0, 1, 1, 1);
+ expectMeasures("violations", 0, 3, 3, 3);
+ // highest severity of bugs is still MAJOR --> C (3)
+ expectMeasures("reliability_rating", 1, 3, 3, 3);
+ // a file has now a MAJOR vulnerability --> C (3)
+ expectMeasures("security_rating", 1, 3, 3, 3);
+ assertQualityGate("WARN");
+
+ // increase severity of a vulnerability to BLOCKER
+ issue = getFirstIssue(FILE_WITH_THREE_LINES_KEY, "VULNERABILITY");
+ changeSeverity(issue, "BLOCKER");
+ expectMeasures("bugs", 0, 2, 2, 2);
+ expectMeasures("vulnerabilities", 0, 1, 1, 1);
+ expectMeasures("violations", 0, 3, 3, 3);
+ expectMeasures("reliability_rating", 1, 3, 3, 3);
+ // highest severity of vulnerabilities is now BLOCKER --> security rating goes E (5)
+ expectMeasures("security_rating", 1, 5, 5, 5);
+ assertQualityGate("ERROR");
+
+ // no changes after new analysis
+ MeasuresDump dumpBeforeAnalysis = dump();
+ scanSample();
+ MeasuresDump dumpAfterAnalysis = dump();
+ dumpAfterAnalysis.assertEquals(dumpBeforeAnalysis);
+ }
+
+ private void markAsFalsePositive(Issues.Issue issue) {
+ tester.wsClient().issues().doTransition(new DoTransitionRequest().setIssue(issue.getKey()).setTransition("falsepositive"));
+ }
+
+ private void convertToType(Issues.Issue issue, String type) {
+ tester.wsClient().issues().setType(new SetTypeRequest().setIssue(issue.getKey()).setType(type));
+ }
+
+ private void changeSeverity(Issues.Issue issue, String severity) {
+ tester.wsClient().issues().setSeverity(new SetSeverityRequest().setIssue(issue.getKey()).setSeverity(severity));
+ }
+
+ private Issues.Issue getFirstIssue(String componentKey, String type) {
+ return tester.wsClient().issues().search(new SearchRequest()
+ .setResolved("false")
+ .setTypes(singletonList(type))
+ .setComponentKeys(singletonList(componentKey))).getIssuesList().get(0);
+ }
+
+ private void scanSample() {
+ orchestrator.executeBuildQuietly(SonarScanner.create(ItUtils.projectDir(PROJECT_DIR)));
+ }
+
+ private void expectMeasures(String metric, double fileWithOneLineExpectedValue, double fileWithThreeLinesExpectedValue, double srcDirExpectedValue, double projectExpectedValue) {
+ assertThat(Double.parseDouble(loadMeasure(FILE_WITH_ONE_LINE_KEY, metric).getValue()))
+ .as("Value of measure " + metric)
+ .isEqualTo(fileWithOneLineExpectedValue);
+ assertThat(Double.parseDouble(loadMeasure(FILE_WITH_THREE_LINES_KEY, metric).getValue()))
+ .as("Value of measure " + metric)
+ .isEqualTo(fileWithThreeLinesExpectedValue);
+ assertThat(Double.parseDouble(loadMeasure(SRC_DIR_KEY, metric).getValue()))
+ .as("Value of measure " + metric)
+ .isEqualTo(srcDirExpectedValue);
+ assertThat(Double.parseDouble(loadMeasure(PROJECT_KEY, metric).getValue()))
+ .as("Value of measure " + metric)
+ .isEqualTo(projectExpectedValue);
+ }
+
+ private void assertQualityGate(String expectedQualityGate) {
+ assertThat(loadMeasure(PROJECT_KEY, "alert_status").getValue()).isEqualTo(expectedQualityGate);
+ }
+
+ private Measures.Measure loadMeasure(String componentKey, String metricKey) {
+ return tester.wsClient().measures().component(
+ new ComponentRequest()
+ .setMetricKeys(singletonList(metricKey))
+ .setComponent(componentKey))
+ .getComponent().getMeasuresList().get(0);
+ }
+
+ private MeasuresDump dump() {
+ MeasuresDump dump = new MeasuresDump();
+
+ asList(FILE_WITH_ONE_LINE_KEY, FILE_WITH_THREE_LINES_KEY, SRC_DIR_KEY, PROJECT_KEY).forEach(componentKey -> {
+ List<Measures.Measure> measures = tester.wsClient().measures().component(new ComponentRequest()
+ .setComponent(componentKey)
+ // TODO request all metrics
+ .setMetricKeys(asList("bugs", "vulnerabilities", "reliability_rating", "security_rating", "sqale_rating", "major_violations", "blocker_violations"))
+ ).getComponent().getMeasuresList();
+ measures.forEach(m -> dump.valuesByComponentAndMetric.put(m.getComponent(), m.getMetric(), m.getValue()));
+ });
+
+ return dump;
+ }
+
+ private static class MeasuresDump {
+ private final Table<String, String, String> valuesByComponentAndMetric = HashBasedTable.create();
+
+ void assertEquals(MeasuresDump dump) {
+ assertThat(valuesByComponentAndMetric.size()).isEqualTo(dump.valuesByComponentAndMetric.size());
+ for (Table.Cell<String, String, String> cell : valuesByComponentAndMetric.cellSet()) {
+ assertThat(dump.valuesByComponentAndMetric.get(cell.getRowKey(), cell.getColumnKey()))
+ .as("Measure " + cell.getColumnKey() + " on component " + cell.getRowKey())
+ .isEqualTo(cell.getValue());
+ }
+ }
+ }
+}
diff --git a/tests/src/test/java/org/sonarqube/tests/qualityModel/ReliabilityMeasureTest.java b/tests/src/test/java/org/sonarqube/tests/qualityModel/ReliabilityMeasureTest.java
index 3c033f0acc6..2997929905c 100644
--- a/tests/src/test/java/org/sonarqube/tests/qualityModel/ReliabilityMeasureTest.java
+++ b/tests/src/test/java/org/sonarqube/tests/qualityModel/ReliabilityMeasureTest.java
@@ -66,11 +66,11 @@ public class ReliabilityMeasureTest {
orchestrator.getServer().associateProjectToQualityProfile(PROJECT, "xoo", "with-many-rules");
orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample")));
- assertMeasures(PROJECT, 61, 305, 4);
- assertMeasures(MODULE, 37, 185, 4);
- assertMeasures(SUB_MODULE, 16, 80, 4);
- assertMeasures(DIRECTORY, 16, 80, 4);
- assertMeasures(FILE, 16, 80, 4);
+ assertMeasures(PROJECT, 53, 265, 4);
+ assertMeasures(MODULE, 31, 155, 4);
+ assertMeasures(SUB_MODULE, 15, 75, 4);
+ assertMeasures(DIRECTORY, 15, 75, 4);
+ assertMeasures(FILE, 15, 75, 4);
}
@Test
diff --git a/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java b/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java
index fcdcc89a98d..92c6bed436b 100644
--- a/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java
+++ b/tests/src/test/java/org/sonarqube/tests/webhook/WebhooksTest.java
@@ -30,18 +30,29 @@ import org.apache.commons.lang3.StringUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
+import org.sonarqube.qa.util.Tester;
import org.sonarqube.tests.Category3Suite;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.Organizations.Organization;
+import org.sonarqube.ws.Projects.CreateWsResponse.Project;
+import org.sonarqube.ws.Qualitygates;
+import org.sonarqube.ws.Qualityprofiles.CreateWsResponse.QualityProfile;
import org.sonarqube.ws.Webhooks;
import org.sonarqube.ws.client.HttpException;
import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.issues.BulkChangeRequest;
+import org.sonarqube.ws.client.issues.SearchRequest;
import org.sonarqube.ws.client.projects.DeleteRequest;
+import org.sonarqube.ws.client.qualitygates.CreateConditionRequest;
import org.sonarqube.ws.client.settings.ResetRequest;
import org.sonarqube.ws.client.settings.SetRequest;
import org.sonarqube.ws.client.webhooks.DeliveriesRequest;
import org.sonarqube.ws.client.webhooks.DeliveryRequest;
import util.ItUtils;
+import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static java.util.stream.IntStream.range;
import static org.assertj.core.api.Assertions.assertThat;
@@ -57,20 +68,22 @@ public class WebhooksTest {
@ClassRule
public static Orchestrator orchestrator = Category3Suite.ORCHESTRATOR;
-
@ClassRule
public static ExternalServer externalServer = new ExternalServer();
+ @Rule
+ public Tester tester = new Tester(orchestrator);
+
private WsClient adminWs = ItUtils.newAdminWsClient(orchestrator);
@Before
- public void setUp() throws Exception {
+ public void setUp() {
externalServer.clear();
}
@Before
@After
- public void reset() throws Exception {
+ public void reset() {
disableGlobalWebhooks();
try {
// delete project and related properties/webhook deliveries
@@ -215,6 +228,48 @@ public class WebhooksTest {
assertThat(getPersistedDeliveries()).isEmpty();
}
+ @Test
+ public void send_webhook_on_issue_change() throws InterruptedException {
+ Organization defaultOrganization = tester.organizations().getDefaultOrganization();
+ Project wsProject = tester.projects().provision(r -> r.setProject(PROJECT_KEY).setName(PROJECT_NAME));
+ enableProjectWebhooks(PROJECT_KEY, new Webhook("Burgr", externalServer.urlFor("/burgr")));
+ // quality profile with one issue per line
+ QualityProfile qualityProfile = tester.qProfiles().createXooProfile(defaultOrganization);
+ tester.qProfiles().activateRule(qualityProfile, "xoo:OneIssuePerLine");
+ tester.qProfiles().assignQProfileToProject(qualityProfile, wsProject);
+ // quality gate definition
+ Qualitygates.CreateResponse qGate = tester.qGates().generate();
+ tester.qGates().service().createCondition(new CreateConditionRequest().setGateId(String.valueOf(qGate.getId()))
+ .setMetric("reliability_rating").setOp("GT").setError("1"));
+ tester.qGates().associateProject(qGate, wsProject);
+ // analyze project and clear first webhook
+ analyseProject();
+ waitUntilAllWebHooksCalled(1);
+ externalServer.clear();
+
+ // change an issue to blocker bug
+ Issue firstIssue = tester.wsClient().issues().search(new SearchRequest()).getIssues(0);
+ tester.wsClient().issues().bulkChange(new BulkChangeRequest().setIssues(singletonList(firstIssue.getKey()))
+ .setSetSeverity(singletonList("BLOCKER"))
+ .setSetType(singletonList("BUG")));
+ waitUntilAllWebHooksCalled(1);
+
+ PayloadRequest request = externalServer.getPayloadRequests().get(0);
+ assertThat(request.getHttpHeaders().get("X-SonarQube-Project")).isEqualTo(PROJECT_KEY);
+ // verify content of payload
+ Map<String, Object> payload = jsonToMap(request.getJson());
+ assertThat(payload.get("status")).isEqualTo("SUCCESS");
+ assertThat(payload.get("analysedAt")).isNotNull();
+ Map<String, String> project = (Map<String, String>) payload.get("project");
+ assertThat(project.get("key")).isEqualTo(PROJECT_KEY);
+ assertThat(project.get("name")).isEqualTo(PROJECT_NAME);
+ assertThat(project.get("url")).isEqualTo(orchestrator.getServer().getUrl() + "/dashboard?id=" + PROJECT_KEY);
+ Map<String, Object> gate = (Map<String, Object>) payload.get("qualityGate");
+ assertThat(gate.get("name")).isEqualTo(qGate.getName());
+ assertThat(gate.get("status")).isEqualTo("ERROR");
+ assertThat(gate.get("conditions")).isNotNull();
+ }
+
private void analyseProject() {
runProjectAnalysis(orchestrator, "shared/xoo-sample",
"sonar.projectKey", PROJECT_KEY,
diff --git a/tests/src/test/resources/measure/LiveMeasuresTest/one-bug-per-line-profile.xml b/tests/src/test/resources/measure/LiveMeasuresTest/one-bug-per-line-profile.xml
new file mode 100644
index 00000000000..162413cacb9
--- /dev/null
+++ b/tests/src/test/resources/measure/LiveMeasuresTest/one-bug-per-line-profile.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0"?><!-- Generated by Sonar -->
+<profile>
+ <name>one-bug-per-line-profile</name>
+ <language>xoo</language>
+ <rules>
+ <rule>
+ <repositoryKey>xoo</repositoryKey>
+ <key>OneBugIssuePerLine</key>
+ <priority>MAJOR</priority>
+ </rule>
+ </rules>
+</profile>