aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build.gradle30
-rw-r--r--gradle.properties2
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java7
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeExporter.java55
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeImporter.java50
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeImporterWithMessages.java47
-rw-r--r--server/sonar-db-core/src/testFixtures/java/org/sonar/db/AbstractDbTester.java26
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/property/PropertiesDaoIT.java41
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaDependenciesDaoIT.java4
-rw-r--r--server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDaoIT.java109
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java4
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java2
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java5
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml10
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaIssuesReleasesDetailsMapper.xml44
-rw-r--r--server/sonar-db-dao/src/schema/schema-sq.ddl1
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigratePortfoliosLiveMeasuresToMeasuresIT.java2
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/AddProductionScopeToScaDependenciesTableIT.java (renamed from server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/AddProductionScopeToScaDependenciesTableIT.java)2
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnArchitectureGraphsIT.java55
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnScaReleasesComponentUuidTest.java (renamed from server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/CreateIndexOnScaReleasesComponentUuidTest.java)10
-rw-r--r--server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/DropIndexOnScaReleasesComponentTest.java (renamed from server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/DropIndexOnScaReleasesComponentTest.java)2
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java2
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/AddDeclaredLicenseExpressionToScaReleasesTable.java (renamed from server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/AddDeclaredLicenseExpressionToScaReleasesTable.java)2
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/AddProductionScopeToScaDependenciesTable.java (renamed from server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/AddProductionScopeToScaDependenciesTable.java)2
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnScaReleasesComponentUuid.java (renamed from server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/CreateIndexOnScaReleasesComponentUuid.java)2
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateUniqueIndexOnArchitectureGraphs.java60
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DbVersion202502.java6
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DropIndexOnScaReleasesComponent.java (renamed from server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DropIndexOnScaReleasesComponent.java)2
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503.java44
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/package-info.java23
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202502/AddDeclaredLicenseExpressionToScaReleasesTableTest.java (renamed from server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/AddDeclaredLicenseExpressionToScaReleasesTableTest.java)2
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503Test.java40
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/setting/ThreadLocalSettings.java2
-rw-r--r--server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/Dimension.java3
-rw-r--r--server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/FixSuggestionMetric.java56
-rw-r--r--server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/DimensionTest.java2
-rw-r--r--server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/FixSuggestionMetricTest.java51
-rw-r--r--server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java34
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java14
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/startup/PropertiesDBCleaner.java62
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/startup/PropertiesDBCleanerTest.java78
-rw-r--r--server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java2
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/measure/ws/ComponentActionIT.java28
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/QProfileExportersIT.java278
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/CreateActionIT.java136
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ExportActionIT.java87
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/CurrentActionIT.java11
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/DismissNoticeActionIT.java47
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentAction.java15
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileExporters.java207
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResult.java84
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImpl.java1
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java77
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ExportAction.java43
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ExportersAction.java32
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ImportersAction.java28
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/CurrentAction.java4
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DismissNoticeAction.java88
-rw-r--r--server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/create-example.json8
-rw-r--r--server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/exporters-example.json29
-rw-r--r--server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/importers-example.json29
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ExportersActionTest.java25
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ImportersActionTest.java27
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java2
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java2
-rw-r--r--settings.gradle4
-rw-r--r--sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java5
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java16
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuePublisher.java4
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java115
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaProperties.java16
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java1
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scm/git/JGitUtils.java14
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuePublisherTest.java13
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java250
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/ScaPropertiesTest.java50
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scm/git/JGitUtilsTest.java67
-rw-r--r--sonar-ws/src/main/protobuf/ws-qualityprofiles.proto6
-rw-r--r--sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java8
79 files changed, 1244 insertions, 1610 deletions
diff --git a/build.gradle b/build.gradle
index 18b5aa25637..e1936ee2a03 100644
--- a/build.gradle
+++ b/build.gradle
@@ -283,15 +283,15 @@ subprojects {
// bundled plugin list -- keep it alphabetically ordered
dependency 'com.sonarsource.abap:sonar-abap-plugin:3.15.1.6010'
dependency 'com.sonarsource.cobol:sonar-cobol-plugin:5.8.1.8428'
- dependency 'com.sonarsource.cpp:sonar-cfamily-dependencies-plugin:6.64.1.81116'
- dependency 'com.sonarsource.cpp:sonar-cfamily-plugin:6.64.1.81116'
- dependency 'com.sonarsource.dart:sonar-dart-plugin:1.0.0.1952'
+ dependency 'com.sonarsource.cpp:sonar-cfamily-dependencies-plugin:6.65.0.81949'
+ dependency 'com.sonarsource.cpp:sonar-cfamily-plugin:6.65.0.81949'
+ dependency 'com.sonarsource.dart:sonar-dart-plugin:1.1.0.2133'
dependency 'com.sonarsource.dbd:sonar-dbd-plugin:1.36.1.13250'
dependency 'com.sonarsource.dbd:sonar-dbd-java-frontend-plugin:1.36.1.13250'
dependency 'com.sonarsource.dbd:sonar-dbd-python-frontend-plugin:1.36.1.13250'
dependency 'com.sonarsource.dotnet:sonar-csharp-enterprise-plugin:10.7.0.110445'
dependency 'com.sonarsource.dotnet:sonar-vbnet-enterprise-plugin:10.7.0.110445'
- dependency 'com.sonarsource.go:sonar-go-enterprise-plugin:1.20.0.1242'
+ dependency 'com.sonarsource.go:sonar-go-enterprise-plugin:1.21.0.1607'
dependency 'com.sonarsource.pli:sonar-pli-plugin:1.16.0.5325'
dependency 'com.sonarsource.plsql:sonar-plsql-plugin:3.15.0.7123'
dependency 'com.sonarsource.plugins.vb:sonar-vb-plugin:2.14.0.5475'
@@ -310,17 +310,17 @@ subprojects {
dependency 'org.sonarsource.dotnet:sonar-csharp-plugin:10.7.0.110445'
dependency 'org.sonarsource.dotnet:sonar-vbnet-plugin:10.7.0.110445'
dependency 'org.sonarsource.flex:sonar-flex-plugin:2.14.0.5032'
- dependency 'org.sonarsource.go:sonar-go-plugin:1.20.0.1242'
+ dependency 'org.sonarsource.go:sonar-go-plugin:1.21.0.1607'
dependency 'org.sonarsource.html:sonar-html-plugin:3.19.0.5695'
dependency 'org.sonarsource.jacoco:sonar-jacoco-plugin:1.3.0.1538'
- dependency 'org.sonarsource.java:sonar-java-plugin:8.10.0.38194'
- dependency 'org.sonarsource.java:sonar-java-symbolic-execution-plugin:8.10.0.38194'
+ dependency 'org.sonarsource.java:sonar-java-plugin:8.11.0.38440'
+ dependency 'org.sonarsource.java:sonar-java-symbolic-execution-plugin:8.11.0.38440'
dependency 'org.sonarsource.javascript:sonar-javascript-plugin:10.21.1.30825'
dependency 'org.sonarsource.php:sonar-php-plugin:3.45.0.12991'
dependency 'org.sonarsource.plugins.cayc:sonar-cayc-plugin:2.4.0.2018'
- dependency 'org.sonarsource.python:sonar-python-plugin:5.1.0.20567'
- dependency 'com.sonarsource.python:sonar-python-enterprise-plugin:5.1.0.20567'
- dependency 'org.sonarsource.kotlin:sonar-kotlin-plugin:2.22.0.5972'
+ dependency 'org.sonarsource.python:sonar-python-plugin:5.2.0.20808'
+ dependency 'com.sonarsource.python:sonar-python-enterprise-plugin:5.2.0.20808'
+ dependency 'org.sonarsource.kotlin:sonar-kotlin-plugin:3.0.1.6889'
dependency "org.sonarsource.api.plugin:sonar-plugin-api:$pluginApiVersion"
dependency "org.sonarsource.api.plugin:sonar-plugin-api-test-fixtures:$pluginApiVersion"
dependency 'org.sonarsource.xml:sonar-xml-plugin:2.12.0.5749'
@@ -330,9 +330,9 @@ subprojects {
dependency 'com.sonarsource.text:sonar-text-developer-plugin:2.21.1.5779'
dependency 'com.sonarsource.text:sonar-text-enterprise-plugin:2.21.1.5779'
dependency 'com.sonarsource.jcl:sonar-jcl-plugin:1.4.1.1493'
- dependency 'com.sonarsource.architecture:sonar-architecture-plugin:1.8.0.4005'
- dependency 'com.sonarsource.architecture:sonar-architecture-java-frontend-plugin:1.8.0.4005'
- dependency 'com.sonarsource.architecture:sonar-architecture-javascript-frontend-plugin:1.8.0.4005'
+ dependency 'com.sonarsource.architecture:sonar-architecture-plugin:1.9.0.4841'
+ dependency 'com.sonarsource.architecture:sonar-architecture-java-frontend-plugin:1.9.0.4841'
+ dependency 'com.sonarsource.architecture:sonar-architecture-javascript-frontend-plugin:1.9.0.4841'
// Webapp
dependency "org.sonarsource.sonarqube:webapp-assets:$webappVersion"
@@ -511,10 +511,10 @@ subprojects {
dependency 'org.reflections:reflections:0.10.2'
dependency 'org.simpleframework:simple:5.1.6'
dependency 'org.sonarsource.git.blame:git-files-blame:1.1.0.1835'
- dependency('org.sonarsource.orchestrator:sonar-orchestrator-junit4:5.2.0.2403') {
+ dependency('org.sonarsource.orchestrator:sonar-orchestrator-junit4:5.4.0.2489') {
exclude 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
}
- dependency('org.sonarsource.orchestrator:sonar-orchestrator-junit5:5.2.0.2403') {
+ dependency('org.sonarsource.orchestrator:sonar-orchestrator-junit5:5.4.0.2489') {
exclude 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml'
}
dependency 'com.sonarsource.pdfreport:security-report-pdf-generation:2.0.0.184'
diff --git a/gradle.properties b/gradle.properties
index b5075b26036..14e9302ede0 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -15,4 +15,4 @@ elasticSearchServerVersion=8.16.3
projectType=application
artifactoryUrl=https://repox.jfrog.io/repox
jre_release_name=jdk-17.0.13+11
-webappVersion=2025.2.0.14025
+webappVersion=2025.2.0.14519
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
index b32bca06bda..b4aad85bdb0 100644
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
@@ -78,9 +78,6 @@ import org.sonar.xoo.rule.Xoo2SonarWayProfile;
import org.sonar.xoo.rule.XooBasicProfile;
import org.sonar.xoo.rule.XooBuiltInQualityProfilesDefinition;
import org.sonar.xoo.rule.XooEmptyProfile;
-import org.sonar.xoo.rule.XooFakeExporter;
-import org.sonar.xoo.rule.XooFakeImporter;
-import org.sonar.xoo.rule.XooFakeImporterWithMessages;
import org.sonar.xoo.rule.XooRulesDefinition;
import org.sonar.xoo.rule.XooSonarWayProfile;
import org.sonar.xoo.rule.hotspot.HotspotWithContextsSensor;
@@ -138,10 +135,6 @@ public class XooPlugin implements Plugin {
Xoo2BasicProfile.class,
XooEmptyProfile.class,
- XooFakeExporter.class,
- XooFakeImporter.class,
- XooFakeImporterWithMessages.class,
-
// SCM
XooScmProvider.class,
XooBlameCommand.class,
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeExporter.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeExporter.java
deleted file mode 100644
index 69a6068731e..00000000000
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeExporter.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2025 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.xoo.rule;
-
-import org.sonar.api.profiles.ProfileExporter;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.xoo.Xoo;
-
-import java.io.IOException;
-import java.io.Writer;
-
-/**
- * Fake exporter just for test
- */
-public class XooFakeExporter extends ProfileExporter {
- public XooFakeExporter() {
- super("XooFakeExporter", "Xoo Fake Exporter");
- }
-
- @Override
- public String[] getSupportedLanguages() {
- return new String[]{Xoo.KEY};
- }
-
- @Override
- public String getMimeType() {
- return "plain/custom";
- }
-
- @Override
- public void exportProfile(RulesProfile profile, Writer writer) {
- try {
- writer.write("xoo -> " + profile.getName() + " -> " + profile.getActiveRules().size());
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- }
-}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeImporter.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeImporter.java
deleted file mode 100644
index a89374e504b..00000000000
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeImporter.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2025 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.xoo.rule;
-
-import org.sonar.api.profiles.ProfileImporter;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.xoo.Xoo;
-
-import java.io.Reader;
-
-/**
- * Fake importer just for test, it will NOT take into account the given file but will create some hard-coded rules
- */
-public class XooFakeImporter extends ProfileImporter {
- public XooFakeImporter() {
- super("XooProfileImporter", "Xoo Profile Importer");
- }
-
- @Override
- public String[] getSupportedLanguages() {
- return new String[] {Xoo.KEY};
- }
-
- @Override
- public RulesProfile importProfile(Reader reader, ValidationMessages messages) {
- RulesProfile rulesProfile = RulesProfile.create();
- rulesProfile.activateRule(Rule.create(XooRulesDefinition.XOO_REPOSITORY, "x1"), RulePriority.CRITICAL);
- return rulesProfile;
- }
-}
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeImporterWithMessages.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeImporterWithMessages.java
deleted file mode 100644
index e7f31ec0ec1..00000000000
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooFakeImporterWithMessages.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2025 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.xoo.rule;
-
-import org.sonar.api.profiles.ProfileImporter;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.utils.ValidationMessages;
-
-import java.io.Reader;
-
-/**
- * Fake importer just for test, it will NOT take into account the given file but will display some info and warning messages
- */
-public class XooFakeImporterWithMessages extends ProfileImporter {
- public XooFakeImporterWithMessages() {
- super("XooFakeImporterWithMessages", "Xoo Profile Importer With Messages");
- }
-
- @Override
- public String[] getSupportedLanguages() {
- return new String[] {};
- }
-
- @Override
- public RulesProfile importProfile(Reader reader, ValidationMessages messages) {
- messages.addWarningText("a warning");
- messages.addInfoText("an info");
- return RulesProfile.create();
- }
-}
diff --git a/server/sonar-db-core/src/testFixtures/java/org/sonar/db/AbstractDbTester.java b/server/sonar-db-core/src/testFixtures/java/org/sonar/db/AbstractDbTester.java
index 10ec76b6d49..737e076950e 100644
--- a/server/sonar-db-core/src/testFixtures/java/org/sonar/db/AbstractDbTester.java
+++ b/server/sonar-db-core/src/testFixtures/java/org/sonar/db/AbstractDbTester.java
@@ -83,7 +83,8 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource {
private static final Map<Integer, Integer> POSTGRES_TYPE_SUBSTITUTION = Map.of(
BOOLEAN, BIT,
DOUBLE, NUMERIC,
- CLOB, VARCHAR);
+ CLOB, VARCHAR,
+ DECIMAL, NUMERIC);
private static final Map<Integer, Integer> MSSQL_TYPE_SUBSTITUTION = Map.of(
BOOLEAN, BIT,
VARCHAR, NVARCHAR,
@@ -93,7 +94,8 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource {
BOOLEAN, NUMERIC,
BIGINT, NUMERIC,
INTEGER, NUMERIC,
- DOUBLE, NUMERIC);
+ DOUBLE, NUMERIC,
+ DECIMAL, NUMERIC);
protected final T db;
@@ -125,7 +127,7 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource {
public void executeDdl(String ddl) {
try (Connection connection = getConnection();
- Statement stmt = connection.createStatement()) {
+ Statement stmt = connection.createStatement()) {
stmt.execute(ddl);
} catch (SQLException e) {
throw new IllegalStateException("Failed to execute DDL: " + ddl, e);
@@ -164,10 +166,10 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource {
}
String sql = "insert into " + table.toLowerCase(Locale.ENGLISH) + " (" +
- COMMA_JOINER.join(valuesByColumn.keySet().stream().map(t -> t.toLowerCase(Locale.ENGLISH)).toArray(String[]::new)) +
- ") values (" +
- COMMA_JOINER.join(Collections.nCopies(valuesByColumn.size(), '?')) +
- ")";
+ COMMA_JOINER.join(valuesByColumn.keySet().stream().map(t -> t.toLowerCase(Locale.ENGLISH)).toArray(String[]::new)) +
+ ") values (" +
+ COMMA_JOINER.join(Collections.nCopies(valuesByColumn.size(), '?')) +
+ ")";
executeUpdateSql(sql, valuesByColumn.values().toArray(new Object[valuesByColumn.size()]));
}
@@ -290,7 +292,7 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource {
public void assertColumnDefinition(String table, String column, int expectedType, @Nullable Integer expectedSize,
@Nullable Boolean isNullable) {
try (Connection connection = getConnection();
- ResultSet rs = connection.getMetaData().getColumns(null, null, toVendorCase(table), toVendorCase(column))) {
+ ResultSet rs = connection.getMetaData().getColumns(null, null, toVendorCase(table), toVendorCase(column))) {
boolean exists = false;
while (rs.next()) {
@@ -326,8 +328,8 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource {
public void assertColumnDoesNotExist(String table, String column) throws SQLException {
try (Connection connection = getConnection();
- PreparedStatement stmt = connection.prepareStatement("select * from " + table);
- ResultSet res = stmt.executeQuery()) {
+ PreparedStatement stmt = connection.prepareStatement("select * from " + table);
+ ResultSet res = stmt.executeQuery()) {
assertThat(getColumnNames(res)).doesNotContain(column);
}
}
@@ -365,7 +367,7 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource {
private void assertIndexImpl(String tableName, String indexName, boolean expectedUnique, String expectedColumn, String... expectedSecondaryColumns) {
try (Connection connection = getConnection();
- ResultSet rs = connection.getMetaData().getIndexInfo(null, null, toVendorCase(tableName), false, false)) {
+ ResultSet rs = connection.getMetaData().getIndexInfo(null, null, toVendorCase(tableName), false, false)) {
List<String> onColumns = new ArrayList<>();
while (rs.next()) {
@@ -403,7 +405,7 @@ public class AbstractDbTester<T extends TestDb> extends ExternalResource {
*/
public void assertIndexDoesNotExist(String tableName, String indexName) {
try (Connection connection = getConnection();
- ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName.toUpperCase(Locale.ENGLISH), false, false)) {
+ ResultSet rs = connection.getMetaData().getIndexInfo(null, null, tableName.toUpperCase(Locale.ENGLISH), false, false)) {
List<String> indices = new ArrayList<>();
while (rs.next()) {
if (rs.getString("INDEX_NAME") != null) {
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/property/PropertiesDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/property/PropertiesDaoIT.java
index 8cc5c95e032..05283591dbe 100644
--- a/server/sonar-db-dao/src/it/java/org/sonar/db/property/PropertiesDaoIT.java
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/property/PropertiesDaoIT.java
@@ -30,6 +30,7 @@ import java.util.Set;
import java.util.function.Consumer;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
+import org.assertj.core.groups.Tuple;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -125,10 +126,10 @@ class PropertiesDaoIT {
// Global + Project subscribers
assertThat(underTest.hasProjectNotificationSubscribersForDispatchers(projectUuid, singletonList(
"DispatcherWithGlobalAndProjectSubscribers")))
- .isTrue();
+ .isTrue();
assertThat(underTest.hasProjectNotificationSubscribersForDispatchers("PROJECT_B", singletonList(
"DispatcherWithGlobalAndProjectSubscribers")))
- .isTrue();
+ .isTrue();
}
@Test
@@ -536,7 +537,7 @@ class PropertiesDaoIT {
}
private static Object[][] allValuesForSelect() {
- return new Object[][] {
+ return new Object[][]{
{null, ""},
{"", ""},
{"some value", "some value"},
@@ -561,6 +562,26 @@ class PropertiesDaoIT {
}
@Test
+ void selectUserProperty() {
+ final String propertyKey = "user.property.one";
+
+ UserDto userDto1 = db.users().insertUser();
+ UserDto userDto2 = db.users().insertUser();
+
+ insertProperty(propertyKey, "one", null, userDto1.getUuid(), null, null, null);
+ insertProperty(propertyKey, "two", null, userDto2.getUuid(), null, null, null);
+
+ List<PropertyDto> property = underTest.selectUserPropertiesByKey(db.getSession(), propertyKey);
+
+ assertThat(property)
+ .extracting(PropertyDto::getKey, PropertyDto::getEntityUuid, PropertyDto::getUserUuid, PropertyDto::getValue)
+ .containsExactlyInAnyOrderElementsOf(Set.of(
+ Tuple.tuple(propertyKey, null, userDto1.getUuid(), "one"),
+ Tuple.tuple(propertyKey, null, userDto2.getUuid(), "two")
+ ));
+ }
+
+ @Test
void select_by_query() {
// global
insertProperty("global.one", "one", null, null, null, null, null);
@@ -641,10 +662,10 @@ class PropertiesDaoIT {
tuple(key, project2.getUuid()));
assertThat(underTest.selectPropertiesByKeysAndEntityUuids(session, newHashSet(key, anotherKey), newHashSet(project.getUuid(),
project2.getUuid())))
- .extracting(PropertyDto::getKey, PropertyDto::getEntityUuid).containsOnly(
- tuple(key, project.getUuid()),
- tuple(key, project2.getUuid()),
- tuple(anotherKey, project2.getUuid()));
+ .extracting(PropertyDto::getKey, PropertyDto::getEntityUuid).containsOnly(
+ tuple(key, project.getUuid()),
+ tuple(key, project2.getUuid()),
+ tuple(anotherKey, project2.getUuid()));
assertThat(underTest.selectPropertiesByKeysAndEntityUuids(session, newHashSet("unknown"), newHashSet(project.getUuid()))).isEmpty();
assertThat(underTest.selectPropertiesByKeysAndEntityUuids(session, newHashSet("key"), newHashSet("uuid123456789"))).isEmpty();
@@ -849,7 +870,7 @@ class PropertiesDaoIT {
}
static Object[][] valueUpdatesDataProvider() {
- return new Object[][] {
+ return new Object[][]{
{null, null},
{null, ""},
{null, "some value"},
@@ -934,8 +955,8 @@ class PropertiesDaoIT {
assertThat(db.select("select prop_key as \"key\", text_value as \"value\", entity_uuid as \"projectUuid\", user_uuid as \"userUuid\" " +
"from properties"))
- .extracting((row) -> row.get("key"), (row) -> row.get("value"), (row) -> row.get("projectUuid"), (row) -> row.get("userUuid"))
- .containsOnly(tuple("KEY", "ANOTHER_VALUE", null, null), tuple("ANOTHER_KEY", "VALUE", project.uuid(), "100"));
+ .extracting((row) -> row.get("key"), (row) -> row.get("value"), (row) -> row.get("projectUuid"), (row) -> row.get("userUuid"))
+ .containsOnly(tuple("KEY", "ANOTHER_VALUE", null, null), tuple("ANOTHER_KEY", "VALUE", project.uuid(), "100"));
}
private static Map<String, String> mapOf(String... values) {
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaDependenciesDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaDependenciesDaoIT.java
index c5bd44b7414..99c028718ef 100644
--- a/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaDependenciesDaoIT.java
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaDependenciesDaoIT.java
@@ -41,7 +41,6 @@ class ScaDependenciesDaoIT {
@Test
void insert_shouldPersistScaDependencies() {
- ComponentDto componentDto = prepareComponentDto();
ScaDependencyDto scaDependencyDto = db.getScaDependenciesDbTester().insertScaDependency("scaReleaseUuid", "1");
List<Map<String, Object>> select = db.select(db.getSession(), "select * from sca_dependencies");
@@ -64,7 +63,6 @@ class ScaDependenciesDaoIT {
@Test
void deleteByUuid_shouldDeleteScaDependencies() {
- ComponentDto componentDto = prepareComponentDto();
ScaDependencyDto scaDependencyDto = db.getScaDependenciesDbTester().insertScaDependency("scaReleaseUuid", "1");
List<Map<String, Object>> select = db.select(db.getSession(), "select * from sca_dependencies");
@@ -78,7 +76,6 @@ class ScaDependenciesDaoIT {
@Test
void selectByUuid_shouldLoadScaDependency() {
- ComponentDto componentDto = prepareComponentDto();
ScaDependencyDto scaDependencyDto = db.getScaDependenciesDbTester().insertScaDependency("scaReleaseUuid", "1");
var loadedOptional = scaDependenciesDao.selectByUuid(db.getSession(), scaDependencyDto.uuid());
@@ -202,7 +199,6 @@ class ScaDependenciesDaoIT {
@Test
void update_shouldUpdateScaDependency() {
- ComponentDto componentDto = prepareComponentDto();
ScaDependencyDto scaDependencyDto = db.getScaDependenciesDbTester().insertScaDependency("scaReleaseUuid", "1", true);
ScaDependencyDto updatedScaDependency = scaDependencyDto.toBuilder()
.setUpdatedAt(scaDependencyDto.updatedAt() + 1)
diff --git a/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDaoIT.java b/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDaoIT.java
index c023fbce2f1..4db2590aed3 100644
--- a/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDaoIT.java
+++ b/server/sonar-db-dao/src/it/java/org/sonar/db/sca/ScaIssuesReleasesDetailsDaoIT.java
@@ -53,6 +53,35 @@ class ScaIssuesReleasesDetailsDaoIT {
.thenComparing(ScaIssueReleaseDetailsDto::issueReleaseUuid);
}
+ private static Comparator<ScaIssueReleaseDetailsDto> severityComparator() {
+ return Comparator.comparing(dto -> dto.severity().databaseSortKey());
+ }
+
+ private static Comparator<ScaIssueReleaseDetailsDto> cvssScoreComparator() {
+ return Comparator.comparing(ScaIssueReleaseDetailsDto::cvssScore,
+ // we treat null cvss as a score of 0.0
+ Comparator.nullsFirst(Comparator.naturalOrder()));
+ }
+
+ private static Comparator<ScaIssueReleaseDetailsDto> comparator(ScaIssuesReleasesDetailsQuery.Sort sort) {
+ return switch (sort) {
+ case IDENTITY_ASC -> identityComparator();
+ case IDENTITY_DESC -> identityComparator().reversed();
+ case SEVERITY_ASC -> severityComparator()
+ .thenComparing(cvssScoreComparator())
+ .thenComparing(identityComparator());
+ case SEVERITY_DESC -> severityComparator().reversed()
+ .thenComparing(cvssScoreComparator().reversed())
+ .thenComparing(identityComparator());
+ case CVSS_SCORE_ASC -> cvssScoreComparator()
+ .thenComparing(ScaIssueReleaseDetailsDto::severity)
+ .thenComparing(identityComparator());
+ case CVSS_SCORE_DESC -> cvssScoreComparator().reversed()
+ .thenComparing(Comparator.comparing(ScaIssueReleaseDetailsDto::severity).reversed())
+ .thenComparing(identityComparator());
+ };
+ }
+
@Test
void selectByBranchUuid_shouldReturnIssues() {
var projectData = db.components().insertPrivateProject();
@@ -64,7 +93,7 @@ class ScaIssuesReleasesDetailsDaoIT {
assertThat(foundPage).hasSize(1).isSubsetOf(issue1, issue2);
var foundAllIssues = scaIssuesReleasesDetailsDao.selectByBranchUuid(db.getSession(), componentDto.branchUuid(), Pagination.forPage(1).andSize(10));
- assertThat(foundAllIssues).hasSize(2).containsExactlyElementsOf(Stream.of(issue1, issue2).sorted(identityComparator()).toList());
+ assertThat(foundAllIssues).hasSize(2).containsExactlyElementsOf(Stream.of(issue1, issue2).sorted(comparator(ScaIssuesReleasesDetailsQuery.Sort.SEVERITY_DESC)).toList());
}
@Test
@@ -81,6 +110,34 @@ class ScaIssuesReleasesDetailsDaoIT {
}
@Test
+ void selectByReleaseUuid_shouldReturnIssues() {
+ var projectData = db.components().insertPrivateProject();
+ var componentDto = projectData.getMainBranchComponent();
+ var issue1 = db.getScaIssuesReleasesDetailsDbTester().insertIssue(ScaIssueType.VULNERABILITY, "1", componentDto.uuid());
+ var release1 = issue1.releaseDto();
+ // make these other issues use the same release and have a variety of CVSS
+ var issue2 = db.getScaIssuesReleasesDetailsDbTester().insertIssue(ScaIssueType.VULNERABILITY, "2", componentDto.uuid(),
+ null, vi -> vi.toBuilder().setCvssScore(new BigDecimal("1.1")).build(),
+ releaseDto -> release1,
+ issueReleaseDto -> issueReleaseDto.toBuilder().setScaReleaseUuid(release1.uuid()).build());
+ var issue3 = db.getScaIssuesReleasesDetailsDbTester().insertIssue(ScaIssueType.VULNERABILITY, "3", componentDto.uuid(),
+ null, vi -> vi.toBuilder().setCvssScore(new BigDecimal("9.9")).build(),
+ releaseDto -> release1,
+ issueReleaseDto -> issueReleaseDto.toBuilder().setScaReleaseUuid(release1.uuid()).build());
+ var issue4 = db.getScaIssuesReleasesDetailsDbTester().insertIssue(ScaIssueType.PROHIBITED_LICENSE, "4", componentDto.uuid(),
+ null, null,
+ releaseDto -> release1,
+ issueReleaseDto -> issueReleaseDto.toBuilder().setScaReleaseUuid(release1.uuid()).build());
+
+ var foundPage = scaIssuesReleasesDetailsDao.selectByBranchUuid(db.getSession(), componentDto.branchUuid(), Pagination.forPage(1).andSize(1));
+
+ assertThat(foundPage).hasSize(1).isSubsetOf(issue1, issue2, issue3, issue4);
+ var foundAllIssues = scaIssuesReleasesDetailsDao.selectByBranchUuid(db.getSession(), componentDto.branchUuid(), Pagination.forPage(1).andSize(10));
+ assertThat(foundAllIssues).hasSize(4)
+ .containsExactlyElementsOf(Stream.of(issue1, issue2, issue3, issue4).sorted(comparator(ScaIssuesReleasesDetailsQuery.Sort.SEVERITY_DESC)).toList());
+ }
+
+ @Test
void withNoQueryFilters_shouldReturnAllIssues() {
setupAndExecuteQueryTest(Function.identity(), QueryTestData::expectedIssuesSortedByIdentityAsc, "All issues should be returned");
}
@@ -531,61 +588,21 @@ class ScaIssuesReleasesDetailsDaoIT {
List<ScaIssueReleaseDetailsDto> transitiveIssues,
List<ScaIssueReleaseDetailsDto> productionIssues,
List<ScaIssueReleaseDetailsDto> notProductionIssues) {
- private static Comparator<ScaIssueReleaseDetailsDto> cvssScoreComparator() {
- return Comparator.comparing(ScaIssueReleaseDetailsDto::cvssScore,
- // we treat null cvss as a score of 0.0
- Comparator.nullsFirst(Comparator.naturalOrder()));
- }
-
- private static Comparator<ScaIssueReleaseDetailsDto> severityComparator() {
- return Comparator.comparing(dto -> dto.severity().databaseSortKey());
- }
public String branchUuid() {
return componentDto.branchUuid();
}
- public List<ScaIssueReleaseDetailsDto> expectedIssuesSortedByIdentityAsc() {
- return expectedIssues.stream().sorted(identityComparator()).toList();
- }
-
- public List<ScaIssueReleaseDetailsDto> expectedIssuesSortedByIdentityDesc() {
- return expectedIssues.stream().sorted(identityComparator().reversed()).toList();
- }
-
- public List<ScaIssueReleaseDetailsDto> expectedIssuesSortedBySeverityAsc() {
- return expectedIssues.stream().sorted(severityComparator()
- .thenComparing(cvssScoreComparator())
- .thenComparing(identityComparator())).toList();
- }
-
- public List<ScaIssueReleaseDetailsDto> expectedIssuesSortedBySeverityDesc() {
- return expectedIssues.stream().sorted(severityComparator().reversed()
- .thenComparing(cvssScoreComparator().reversed())
- .thenComparing(identityComparator())).toList();
+ public List<ScaIssueReleaseDetailsDto> expectedIssuesSorted(ScaIssuesReleasesDetailsQuery.Sort sort) {
+ return expectedIssues.stream().sorted(comparator(sort)).toList();
}
- public List<ScaIssueReleaseDetailsDto> expectedIssuesSortedByCvssAsc() {
- return expectedIssues.stream().sorted(cvssScoreComparator()
- .thenComparing(ScaIssueReleaseDetailsDto::severity)
- .thenComparing(identityComparator())).toList();
+ public List<ScaIssueReleaseDetailsDto> expectedIssuesSortedByIdentityAsc() {
+ return expectedIssuesSorted(ScaIssuesReleasesDetailsQuery.Sort.IDENTITY_ASC);
}
public List<ScaIssueReleaseDetailsDto> expectedIssuesSortedByCvssDesc() {
- return expectedIssues.stream().sorted(cvssScoreComparator().reversed()
- .thenComparing(Comparator.comparing(ScaIssueReleaseDetailsDto::severity).reversed())
- .thenComparing(identityComparator())).toList();
- }
-
- public List<ScaIssueReleaseDetailsDto> expectedIssuesSorted(ScaIssuesReleasesDetailsQuery.Sort sort) {
- return switch (sort) {
- case IDENTITY_ASC -> expectedIssuesSortedByIdentityAsc();
- case IDENTITY_DESC -> expectedIssuesSortedByIdentityDesc();
- case SEVERITY_ASC -> expectedIssuesSortedBySeverityAsc();
- case SEVERITY_DESC -> expectedIssuesSortedBySeverityDesc();
- case CVSS_SCORE_ASC -> expectedIssuesSortedByCvssAsc();
- case CVSS_SCORE_DESC -> expectedIssuesSortedByCvssDesc();
- };
+ return expectedIssuesSorted(ScaIssuesReleasesDetailsQuery.Sort.CVSS_SCORE_DESC);
}
public List<ScaIssueReleaseDetailsDto> expectedIssuesWithPackageManager(PackageManager packageManager) {
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java
index 97d5ee872e4..824ea54b14e 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesDao.java
@@ -179,6 +179,10 @@ public class PropertiesDao implements Dao {
return getMapper(session).selectProjectPropertyByKey(key);
}
+ public List<PropertyDto> selectUserPropertiesByKey(DbSession session, String key) {
+ return getMapper(session).selectUserPropertiesByKey(key);
+ }
+
/**
* Saves the specified property and its value.
* <p>
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java
index c1685ad9a59..8fb96f382a2 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/property/PropertiesMapper.java
@@ -42,6 +42,8 @@ public interface PropertiesMapper {
List<PropertyDto> selectProjectPropertyByKey(@Param("key") String key);
+ List<PropertyDto> selectUserPropertiesByKey(@Param("key") String key);
+
List<PropertyDto> selectByEntityUuids(@Param("entityUuids") List<String> entityUuids);
List<PropertyDto> selectByQuery(@Param("query") PropertyQuery query);
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java
index 7187f8d4c2c..0c4b7798934 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/qualityprofile/ActiveRuleDto.java
@@ -31,7 +31,6 @@ import org.apache.commons.lang3.builder.ToStringStyle;
import org.sonar.api.issue.impact.Severity;
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.ActiveRule;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.SeverityUtil;
@@ -39,8 +38,8 @@ import static java.util.Objects.requireNonNull;
public class ActiveRuleDto {
- public static final String INHERITED = ActiveRule.INHERITED;
- public static final String OVERRIDES = ActiveRule.OVERRIDES;
+ public static final String INHERITED = "INHERITED";
+ public static final String OVERRIDES = "OVERRIDES";
private static final Gson GSON = new Gson();
private static final Type TYPE = new TypeToken<Map<SoftwareQuality, Severity>>() {
}.getType();
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml
index b186e885986..a6c5ca9e2e0 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/property/PropertiesMapper.xml
@@ -163,6 +163,16 @@
and p.user_uuid is null
</select>
+ <select id="selectUserPropertiesByKey" parameterType="map" resultType="ScrapProperty">
+ select
+ <include refid="columnsToScrapPropertyDto"/>
+ from properties p
+ inner join users u on u.uuid=p.user_uuid
+ where
+ p.prop_key = #{key, jdbcType=VARCHAR}
+ and p.entity_uuid is null
+ </select>
+
<select id="selectByQuery" parameterType="map" resultType="ScrapProperty">
select
<include refid="columnsToScrapPropertyDto"/>
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaIssuesReleasesDetailsMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaIssuesReleasesDetailsMapper.xml
index 8f621db36b2..fc5b3468983 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaIssuesReleasesDetailsMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/sca/ScaIssuesReleasesDetailsMapper.xml
@@ -19,7 +19,7 @@
</constructor>
</resultMap>
- <sql id="issuesWithScaColumns">
+ <sql id="columns">
<!-- These have to match all of the properties in the other tables' mappers,
adding the columnPrefix given in our resultMap above -->
sir.uuid as issue_release_uuid,
@@ -57,6 +57,17 @@
svi.updated_at as svi_updated_at
</sql>
+ <sql id="columnsWithCvssSortKey">
+ <include refid="columns"/>,
+ <!-- It seems that the behavior of NULL in ORDER BY varies by database backend, with different
+ defaults and a lack of universal support for NULLS FIRST / NULLS LAST.
+ This poses an issue for nullable columns we want to sort by such as cvss_score.
+ On databases that support it, NULLS FIRST could probably use the index while this COALESCE
+ hack does not, so maybe someday we want to conditionalize on db backend somehow. -->
+ <!-- NULL score is treated as least severe -->
+ COALESCE(svi.cvss_score, 0.0) as cvss_sort_key
+ </sql>
+
<sql id="sqlBaseJoins">
from sca_issues_releases sir
inner join sca_issues si on sir.sca_issue_uuid = si.uuid
@@ -80,21 +91,22 @@
</sql>
<select id="selectByReleaseUuid" parameterType="map" resultMap="scaIssueReleaseDetailsResultMap">
- select <include refid="issuesWithScaColumns"/>
+ select <include refid="columnsWithCvssSortKey"/>
<include refid="sqlSelectByReleaseUuid"/>
- ORDER BY <include refid="sqlIdentityOrderColumns"/>
+ <include refid="sqlOrderBySeverityDesc"/>
</select>
<select id="selectByBranchUuid" parameterType="map" resultMap="scaIssueReleaseDetailsResultMap">
- select <include refid="issuesWithScaColumns"/>
+ select <include refid="columnsWithCvssSortKey"/>
<include refid="sqlSelectByBranchUuid"/>
- ORDER BY <include refid="sqlIdentityOrderColumns"/>
+ <include refid="sqlOrderBySeverityDesc"/>
<include refid="org.sonar.db.common.Common.pagination"/>
</select>
<select id="selectByScaIssueReleaseUuid" parameterType="map" resultMap="scaIssueReleaseDetailsResultMap">
- select <include refid="issuesWithScaColumns"/>
+ select <include refid="columns"/>
<include refid="sqlSelectByScaIssueReleaseUuid"/>
+ <!-- no ORDER BY here because it's always one result -->
</select>
<select id="countByBranchUuid" parameterType="string" resultType="int">
@@ -166,10 +178,17 @@
<sql id="sqlIdentityOrderColumns">
<!-- the unique index is ordered as: scaIssueType, vulnerabilityId, packageUrl, spdxLicenseId
so we're guessing (or hoping?) that is the most efficient sort order, and it should sort of make
- more sense to users than random -->
+ more sense to users than random. This sort is alphabetical first by issue type then
+ by CVE ID or license name. -->
si.sca_issue_type ASC, si.vulnerability_id ASC, si.package_url ASC, si.spdx_license_id ASC, sir.uuid ASC
</sql>
+ <!-- this is the default sort for the selects that don't have a sort parameter (i.e. not the query)
+ but is probably slower than the identity sort until/unless we create a matching index -->
+ <sql id="sqlOrderBySeverityDesc">
+ ORDER BY sir.severity_sort_key DESC, cvss_sort_key DESC, <include refid="sqlIdentityOrderColumns"/>
+ </sql>
+
<sql id="sqlOrderByQuery">
<choose>
<when test="query.sort == @org.sonar.db.sca.ScaIssuesReleasesDetailsQuery$Sort@IDENTITY_ASC">
@@ -185,7 +204,7 @@
</when>
<when test="query.sort == @org.sonar.db.sca.ScaIssuesReleasesDetailsQuery$Sort@SEVERITY_DESC">
<!-- because many severities are the same, we try to keep the user intent by ordering by cvss score secondarily -->
- ORDER BY sir.severity_sort_key DESC, cvss_sort_key DESC, <include refid="sqlIdentityOrderColumns"/>
+ <include refid="sqlOrderBySeverityDesc"/>
</when>
<when test="query.sort == @org.sonar.db.sca.ScaIssuesReleasesDetailsQuery$Sort@CVSS_SCORE_ASC">
<!-- because cvss score can be null, we try to keep the user intent by ordering by severity secondarily -->
@@ -203,14 +222,7 @@
</sql>
<select id="selectByQuery" parameterType="map" resultMap="scaIssueReleaseDetailsResultMap">
- select <include refid="issuesWithScaColumns"/>,
- <!-- It seems that the behavior of NULL in ORDER BY varies by database backend, with different
- defaults and a lack of universal support for NULLS FIRST / NULLS LAST.
- This poses an issue for nullable columns we want to sort by such as cvss_score.
- On databases that support it, NULLS FIRST could probably use the index while this COALESCE
- hack does not, so maybe someday we want to conditionalize on db backend somehow. -->
- <!-- NULL score is treated as least severe -->
- COALESCE(svi.cvss_score, 0.0) as cvss_sort_key
+ select <include refid="columnsWithCvssSortKey"/>
<include refid="sqlBaseJoins"/>
<include refid="sqlSelectByQueryWhereClause"/>
<include refid="sqlOrderByQuery"/>
diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl
index 78f221a55e4..d2d54a7a8f7 100644
--- a/server/sonar-db-dao/src/schema/schema-sq.ddl
+++ b/server/sonar-db-dao/src/schema/schema-sq.ddl
@@ -126,6 +126,7 @@ CREATE TABLE "ARCHITECTURE_GRAPHS"(
"GRAPH_DATA" CHARACTER LARGE OBJECT NOT NULL
);
ALTER TABLE "ARCHITECTURE_GRAPHS" ADD CONSTRAINT "PK_ARCHITECTURE_GRAPHS" PRIMARY KEY("UUID");
+CREATE UNIQUE NULLS NOT DISTINCT INDEX "UQ_IDX_AG_BRANCH_TYPE_SOURCE" ON "ARCHITECTURE_GRAPHS"("BRANCH_UUID" NULLS FIRST, "TYPE" NULLS FIRST, "SOURCE" NULLS FIRST);
CREATE TABLE "AUDITS"(
"UUID" CHARACTER VARYING(40) NOT NULL,
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigratePortfoliosLiveMeasuresToMeasuresIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigratePortfoliosLiveMeasuresToMeasuresIT.java
index 79dd5abe808..56acc2f70f9 100644
--- a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigratePortfoliosLiveMeasuresToMeasuresIT.java
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v108/MigratePortfoliosLiveMeasuresToMeasuresIT.java
@@ -282,7 +282,7 @@ class MigratePortfoliosLiveMeasuresToMeasuresIT {
"component_uuid", componentUuid,
"branch_uuid", branch,
"json_value", "{\"any\":\"thing\"}",
- "json_value_hash", "1234",
+ "json_value_hash", 1234,
"created_at", 12,
"updated_at", 12);
}
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/AddProductionScopeToScaDependenciesTableIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/AddProductionScopeToScaDependenciesTableIT.java
index b8ec749e66b..8ed2cb41454 100644
--- a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/AddProductionScopeToScaDependenciesTableIT.java
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/AddProductionScopeToScaDependenciesTableIT.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.platform.db.migration.version.v202503;
+package org.sonar.server.platform.db.migration.version.v202502;
import java.sql.SQLException;
import org.junit.jupiter.api.Test;
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnArchitectureGraphsIT.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnArchitectureGraphsIT.java
new file mode 100644
index 00000000000..cc1d462e536
--- /dev/null
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnArchitectureGraphsIT.java
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v202502;
+
+import java.sql.SQLException;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.sonar.db.MigrationDbTester;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+import static org.sonar.db.MigrationDbTester.createForMigrationStep;
+import static org.sonar.server.platform.db.migration.version.v202502.CreateUniqueIndexOnArchitectureGraphs.COLUMN_NAME_BRANCH_UUID;
+import static org.sonar.server.platform.db.migration.version.v202502.CreateUniqueIndexOnArchitectureGraphs.COLUMN_NAME_SOURCE;
+import static org.sonar.server.platform.db.migration.version.v202502.CreateUniqueIndexOnArchitectureGraphs.COLUMN_NAME_TYPE;
+import static org.sonar.server.platform.db.migration.version.v202502.CreateUniqueIndexOnArchitectureGraphs.INDEX_NAME;
+import static org.sonar.server.platform.db.migration.version.v202502.CreateUniqueIndexOnArchitectureGraphs.TABLE_NAME;
+
+class CreateIndexOnArchitectureGraphsIT {
+
+ @RegisterExtension
+ public final MigrationDbTester db = createForMigrationStep(CreateUniqueIndexOnArchitectureGraphs.class);
+ private final DdlChange underTest = new CreateUniqueIndexOnArchitectureGraphs(db.database());
+
+ @Test
+ void execute_shouldCreateIndex() throws SQLException {
+ db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME);
+ underTest.execute();
+ db.assertUniqueIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME_BRANCH_UUID, COLUMN_NAME_TYPE, COLUMN_NAME_SOURCE);
+ }
+
+ @Test
+ void execute_shouldBeReentrant() throws SQLException {
+ db.assertIndexDoesNotExist(TABLE_NAME, INDEX_NAME);
+ underTest.execute();
+ underTest.execute();
+ db.assertUniqueIndex(TABLE_NAME, INDEX_NAME, COLUMN_NAME_BRANCH_UUID, COLUMN_NAME_TYPE, COLUMN_NAME_SOURCE);
+ }
+}
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/CreateIndexOnScaReleasesComponentUuidTest.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnScaReleasesComponentUuidTest.java
index d7630686669..05857305b63 100644
--- a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/CreateIndexOnScaReleasesComponentUuidTest.java
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnScaReleasesComponentUuidTest.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.platform.db.migration.version.v202503;
+package org.sonar.server.platform.db.migration.version.v202502;
import java.sql.SQLException;
import org.junit.jupiter.api.Test;
@@ -26,10 +26,10 @@ import org.sonar.db.MigrationDbTester;
import org.sonar.server.platform.db.migration.step.DdlChange;
import static org.sonar.db.MigrationDbTester.createForMigrationStep;
-import static org.sonar.server.platform.db.migration.version.v202503.CreateIndexOnScaReleasesComponentUuid.COLUMN_NAME_COMPONENT_UUID;
-import static org.sonar.server.platform.db.migration.version.v202503.CreateIndexOnScaReleasesComponentUuid.COLUMN_NAME_UUID;
-import static org.sonar.server.platform.db.migration.version.v202503.CreateIndexOnScaReleasesComponentUuid.INDEX_NAME;
-import static org.sonar.server.platform.db.migration.version.v202503.CreateIndexOnScaReleasesComponentUuid.TABLE_NAME;
+import static org.sonar.server.platform.db.migration.version.v202502.CreateIndexOnScaReleasesComponentUuid.COLUMN_NAME_COMPONENT_UUID;
+import static org.sonar.server.platform.db.migration.version.v202502.CreateIndexOnScaReleasesComponentUuid.COLUMN_NAME_UUID;
+import static org.sonar.server.platform.db.migration.version.v202502.CreateIndexOnScaReleasesComponentUuid.INDEX_NAME;
+import static org.sonar.server.platform.db.migration.version.v202502.CreateIndexOnScaReleasesComponentUuid.TABLE_NAME;
class CreateIndexOnScaReleasesComponentUuidTest {
@RegisterExtension
diff --git a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/DropIndexOnScaReleasesComponentTest.java b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/DropIndexOnScaReleasesComponentTest.java
index 97f8a4d557d..ec377d5fb96 100644
--- a/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202503/DropIndexOnScaReleasesComponentTest.java
+++ b/server/sonar-db-migration/src/it/java/org/sonar/server/platform/db/migration/version/v202502/DropIndexOnScaReleasesComponentTest.java
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.platform.db.migration.version.v202503;
+package org.sonar.server.platform.db.migration.version.v202502;
import java.sql.SQLException;
import org.junit.jupiter.api.Test;
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java
index 5b5af55fd0b..f844369bf02 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/MigrationConfigurationModule.java
@@ -39,7 +39,6 @@ import org.sonar.server.platform.db.migration.version.v107.DbVersion107;
import org.sonar.server.platform.db.migration.version.v108.DbVersion108;
import org.sonar.server.platform.db.migration.version.v202501.DbVersion202501;
import org.sonar.server.platform.db.migration.version.v202502.DbVersion202502;
-import org.sonar.server.platform.db.migration.version.v202503.DbVersion202503;
public class MigrationConfigurationModule extends Module {
@Override
@@ -59,7 +58,6 @@ public class MigrationConfigurationModule extends Module {
DbVersion108.class,
DbVersion202501.class,
DbVersion202502.class,
- DbVersion202503.class,
// migration steps
MigrationStepRegistryImpl.class,
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/AddDeclaredLicenseExpressionToScaReleasesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/AddDeclaredLicenseExpressionToScaReleasesTable.java
index b8a0c26e52b..c44a3dc006c 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/AddDeclaredLicenseExpressionToScaReleasesTable.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/AddDeclaredLicenseExpressionToScaReleasesTable.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.platform.db.migration.version.v202503;
+package org.sonar.server.platform.db.migration.version.v202502;
import java.sql.SQLException;
import org.sonar.db.Database;
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/AddProductionScopeToScaDependenciesTable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/AddProductionScopeToScaDependenciesTable.java
index 4764e60d61b..bf2aab43019 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/AddProductionScopeToScaDependenciesTable.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/AddProductionScopeToScaDependenciesTable.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.platform.db.migration.version.v202503;
+package org.sonar.server.platform.db.migration.version.v202502;
import java.sql.SQLException;
import org.sonar.db.Database;
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/CreateIndexOnScaReleasesComponentUuid.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnScaReleasesComponentUuid.java
index 85dc6395842..848d5925a9e 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/CreateIndexOnScaReleasesComponentUuid.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateIndexOnScaReleasesComponentUuid.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.platform.db.migration.version.v202503;
+package org.sonar.server.platform.db.migration.version.v202502;
import java.sql.Connection;
import java.sql.SQLException;
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateUniqueIndexOnArchitectureGraphs.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateUniqueIndexOnArchitectureGraphs.java
new file mode 100644
index 00000000000..802396d18b1
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/CreateUniqueIndexOnArchitectureGraphs.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.db.migration.version.v202502;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.db.DatabaseUtils;
+import org.sonar.server.platform.db.migration.sql.CreateIndexBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class CreateUniqueIndexOnArchitectureGraphs extends DdlChange {
+
+ static final String TABLE_NAME = "architecture_graphs";
+ static final String INDEX_NAME = "uq_idx_ag_branch_type_source";
+ static final String COLUMN_NAME_BRANCH_UUID = "branch_uuid";
+ static final String COLUMN_NAME_TYPE = "type";
+ static final String COLUMN_NAME_SOURCE = "source";
+
+ public CreateUniqueIndexOnArchitectureGraphs(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ try (Connection connection = getDatabase().getDataSource().getConnection()) {
+ createIndex(context, connection);
+ }
+ }
+
+ private void createIndex(Context context, Connection connection) {
+ if (!DatabaseUtils.indexExistsIgnoreCase(TABLE_NAME, INDEX_NAME, connection)) {
+ context.execute(new CreateIndexBuilder(getDialect())
+ .setTable(TABLE_NAME)
+ .setName(INDEX_NAME)
+ .setUnique(true)
+ .addColumn(COLUMN_NAME_BRANCH_UUID, false)
+ .addColumn(COLUMN_NAME_TYPE, false)
+ .addColumn(COLUMN_NAME_SOURCE, false)
+ .build());
+ }
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DbVersion202502.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DbVersion202502.java
index 0db5c95b6f3..8bbbe17ee01 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DbVersion202502.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DbVersion202502.java
@@ -54,6 +54,10 @@ public class DbVersion202502 implements DbVersion {
.add(2025_02_015, "Add new_in_pull_request column to SCA dependencies", AddNewInPullRequestToScaDependenciesTable.class)
.add(2025_02_016, "Insert default AI Codefix provider key and modelKey properties", InsertDefaultAiSuggestionProviderKeyAndModelKeyProperties.class)
.add(2025_02_017, "Add table 'architecture_graphs'", CreateArchitectureGraphsTable.class)
- ;
+ .add(2025_02_018, "Drop 'sca_releases_comp_uuid' index", DropIndexOnScaReleasesComponent.class)
+ .add(2025_02_019, "Create 'sca_releases_comp_uuid_uuid' index", CreateIndexOnScaReleasesComponentUuid.class)
+ .add(2025_02_020, "Add 'sca_dependencies.production_scope' column", AddProductionScopeToScaDependenciesTable.class)
+ .add(2025_02_021, "Add declared_license_expression to SCA releases", AddDeclaredLicenseExpressionToScaReleasesTable.class)
+ .add(2025_02_022, "Create 'uq_idx_ag_branch_type_source' for architecture graphs", CreateUniqueIndexOnArchitectureGraphs.class);
}
}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DropIndexOnScaReleasesComponent.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DropIndexOnScaReleasesComponent.java
index 8001e821463..5cec4743767 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DropIndexOnScaReleasesComponent.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202502/DropIndexOnScaReleasesComponent.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.platform.db.migration.version.v202503;
+package org.sonar.server.platform.db.migration.version.v202502;
import org.sonar.db.Database;
import org.sonar.server.platform.db.migration.step.DropIndexChange;
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503.java
deleted file mode 100644
index c015f4a94ea..00000000000
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2025 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.platform.db.migration.version.v202503;
-
-import org.sonar.server.platform.db.migration.step.MigrationStepRegistry;
-import org.sonar.server.platform.db.migration.version.DbVersion;
-
-// ignoring bad number formatting, as it's indented that we align the migration numbers to SQ versions
-@SuppressWarnings("java:S3937")
-public class DbVersion202503 implements DbVersion {
-
- /**
- * We use the start of the 10.X cycle as an opportunity to align migration numbers with the SQ version number.
- * Please follow this pattern:
- * 2025_03_000
- * 2025_03_001
- * 2025_03_002
- */
- @Override
- public void addSteps(MigrationStepRegistry registry) {
- registry
- .add(2025_03_000, "Drop 'sca_releases_comp_uuid' index", DropIndexOnScaReleasesComponent.class)
- .add(2025_03_001, "Create 'sca_releases_comp_uuid_uuid' index", CreateIndexOnScaReleasesComponentUuid.class)
- .add(2025_03_002, "Add 'sca_dependencies.production_scope' column", AddProductionScopeToScaDependenciesTable.class)
- .add(2025_03_003, "Add declared_license_expression to SCA releases", AddDeclaredLicenseExpressionToScaReleasesTable.class);
- }
-}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/package-info.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/package-info.java
deleted file mode 100644
index cc36bc37f03..00000000000
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v202503/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2025 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.platform.db.migration.version.v202503;
-
-import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/AddDeclaredLicenseExpressionToScaReleasesTableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202502/AddDeclaredLicenseExpressionToScaReleasesTableTest.java
index f2ff0411c79..182fcf41ecb 100644
--- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/AddDeclaredLicenseExpressionToScaReleasesTableTest.java
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202502/AddDeclaredLicenseExpressionToScaReleasesTableTest.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.server.platform.db.migration.version.v202503;
+package org.sonar.server.platform.db.migration.version.v202502;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503Test.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503Test.java
deleted file mode 100644
index e1480dd9844..00000000000
--- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v202503/DbVersion202503Test.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2025 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.server.platform.db.migration.version.v202503;
-
-import org.junit.jupiter.api.Test;
-
-import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMigrationNotEmpty;
-import static org.sonar.server.platform.db.migration.version.DbVersionTestUtils.verifyMinimumMigrationNumber;
-
-class DbVersion202503Test {
-
- private final DbVersion202503 underTest = new DbVersion202503();
-
- @Test
- void migrationNumber_starts_at_2025_03_000() {
- verifyMinimumMigrationNumber(underTest, 2025_03_000);
- }
-
- @Test
- void verify_migration_is_not_empty() {
- verifyMigrationNotEmpty(underTest);
- }
-}
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/setting/ThreadLocalSettings.java b/server/sonar-server-common/src/main/java/org/sonar/server/setting/ThreadLocalSettings.java
index 92ffa498023..1786a62e90f 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/setting/ThreadLocalSettings.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/setting/ThreadLocalSettings.java
@@ -20,13 +20,13 @@
package org.sonar.server.setting;
import com.google.common.annotations.VisibleForTesting;
+import jakarta.inject.Inject;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
-import jakarta.inject.Inject;
import org.apache.ibatis.exceptions.PersistenceException;
import org.sonar.api.CoreProperties;
import org.sonar.api.ce.ComputeEngineSide;
diff --git a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/Dimension.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/Dimension.java
index f3479858bff..4b136272f6c 100644
--- a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/Dimension.java
+++ b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/Dimension.java
@@ -31,7 +31,8 @@ public enum Dimension {
USER("user"),
PROJECT("project"),
LANGUAGE("language"),
- ANALYSIS("analysis");
+ ANALYSIS("analysis"),
+ FIX_SUGGESTION("fixsuggestion");
private final String value;
diff --git a/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/FixSuggestionMetric.java b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/FixSuggestionMetric.java
new file mode 100644
index 00000000000..e7b079a0b6d
--- /dev/null
+++ b/server/sonar-telemetry-core/src/main/java/org/sonar/telemetry/core/schema/FixSuggestionMetric.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.telemetry.core.schema;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.sonar.telemetry.core.TelemetryDataType;
+
+import static org.sonar.telemetry.core.Granularity.ADHOC;
+
+public class FixSuggestionMetric extends InstallationMetric {
+
+ @JsonProperty("fix_suggestion_uuid")
+ private String fixSuggestionUuid;
+
+ @JsonProperty("project_uuid")
+ private String projectUuid;
+
+ public FixSuggestionMetric(String key, Object value, TelemetryDataType type, String projectUuid, String fixSuggestionUuid) {
+ super(key, value, type, ADHOC);
+ this.projectUuid = projectUuid;
+ this.fixSuggestionUuid = fixSuggestionUuid;
+ }
+
+ public String getProjectUuid() {
+ return projectUuid;
+ }
+
+ public void setProjectUuid(String projectUuid) {
+ this.projectUuid = projectUuid;
+ }
+
+ public String getFixSuggestionUuid() {
+ return fixSuggestionUuid;
+ }
+
+ public void setFixSuggestionUuid(String fixSuggestionUuid) {
+ this.fixSuggestionUuid = fixSuggestionUuid;
+ }
+}
diff --git a/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/DimensionTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/DimensionTest.java
index f80c2a971bb..69d77f3716f 100644
--- a/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/DimensionTest.java
+++ b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/DimensionTest.java
@@ -39,11 +39,13 @@ class DimensionTest {
assertEquals(Dimension.USER, Dimension.fromValue("user"));
assertEquals(Dimension.PROJECT, Dimension.fromValue("project"));
assertEquals(Dimension.LANGUAGE, Dimension.fromValue("language"));
+ assertEquals(Dimension.FIX_SUGGESTION, Dimension.fromValue("fixsuggestion"));
assertEquals(Dimension.INSTALLATION, Dimension.fromValue("INSTALLATION"));
assertEquals(Dimension.USER, Dimension.fromValue("USER"));
assertEquals(Dimension.PROJECT, Dimension.fromValue("PROJECT"));
assertEquals(Dimension.LANGUAGE, Dimension.fromValue("LANGUAGE"));
+ assertEquals(Dimension.FIX_SUGGESTION, Dimension.fromValue("FIXSUGGESTION"));
}
@Test
diff --git a/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/FixSuggestionMetricTest.java b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/FixSuggestionMetricTest.java
new file mode 100644
index 00000000000..4169ca95089
--- /dev/null
+++ b/server/sonar-telemetry-core/src/test/java/org/sonar/telemetry/core/schema/FixSuggestionMetricTest.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.telemetry.core.schema;
+
+import org.junit.jupiter.api.Test;
+import org.sonar.telemetry.core.Granularity;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.telemetry.core.TelemetryDataType.STRING;
+
+class FixSuggestionMetricTest {
+
+ @Test
+ void getters() {
+ FixSuggestionMetric metric = new FixSuggestionMetric("ai_codefix.suggestion_rule_key", "rule:key", STRING, "projectUuid", "fixSuggestionUuid");
+
+ assertThat(metric.getKey()).isEqualTo("ai_codefix.suggestion_rule_key");
+ assertThat(metric.getValue()).isEqualTo("rule:key");
+ assertThat(metric.getProjectUuid()).isEqualTo("projectUuid");
+ assertThat(metric.getGranularity()).isEqualTo(Granularity.ADHOC);
+ assertThat(metric.getType()).isEqualTo(STRING);
+ assertThat(metric.getFixSuggestionUuid()).isEqualTo("fixSuggestionUuid");
+ }
+
+ @Test
+ void setters() {
+ FixSuggestionMetric metric = new FixSuggestionMetric("ai_codefix.suggestion_rule_key", "rule:key", STRING, "projectUuid", "fixSuggestionUuid");
+ metric.setProjectUuid("newProjectUuid");
+ metric.setFixSuggestionUuid("newFixSuggestionUuid");
+
+ assertThat(metric.getProjectUuid()).isEqualTo("newProjectUuid");
+ assertThat(metric.getFixSuggestionUuid()).isEqualTo("newFixSuggestionUuid");
+ }
+}
diff --git a/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java b/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java
index 174537211d5..51745d15781 100644
--- a/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java
+++ b/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java
@@ -19,6 +19,8 @@
*/
package org.sonar.server.plugins;
+import java.util.List;
+import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.Plugin;
@@ -96,6 +98,35 @@ public class DetectPluginChangeIT {
}
@Test
+ public void detect_changes_when_forced_refresh() {
+ addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+ addPluginToFs("plugin1", "hash1", PluginType.BUNDLED);
+ addPluginToDb("plugin2", "hash2", PluginDto.Type.EXTERNAL);
+ addPluginToFs("plugin2", "hash2", PluginType.EXTERNAL);
+
+ dbTester.executeDdl("insert into internal_properties (kee, is_empty, text_value, created_at) values ('plugin.refresh.forced', false, 'true', 12345)");
+
+ detectPluginChange.start();
+ assertThat(detectPluginChange.anyPluginChanged()).isTrue();
+
+ // Ensure the force refresh flag has been deleted
+ assertThat(getInternalProperty("plugin.refresh.forced")).isEmpty();
+ }
+
+ @Test
+ public void detect_changes_when_internal_propertiy_has_false_value_should_not_refresh() {
+ addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED);
+ addPluginToFs("plugin1", "hash1", PluginType.BUNDLED);
+ addPluginToDb("plugin2", "hash2", PluginDto.Type.EXTERNAL);
+ addPluginToFs("plugin2", "hash2", PluginType.EXTERNAL);
+
+ dbTester.executeDdl("insert into internal_properties (kee, is_empty, text_value, created_at) values ('plugin.refresh.forced', false, 'false', 12345)");
+
+ detectPluginChange.start();
+ assertThat(detectPluginChange.anyPluginChanged()).isFalse();
+ }
+
+ @Test
public void fail_if_start_twice() {
detectPluginChange.start();
assertThrows(IllegalStateException.class, detectPluginChange::start);
@@ -123,4 +154,7 @@ public class DetectPluginChangeIT {
pluginRepository.addPlugin(serverPlugin);
}
+ private List<Map<String, Object>> getInternalProperty(String key) {
+ return dbTester.select("select * from internal_properties where kee='" + key + "'");
+ }
}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java
index 40f8f8036d7..af1972430f9 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/DetectPluginChange.java
@@ -34,6 +34,7 @@ import org.sonar.db.plugin.PluginDto;
import static java.util.function.Function.identity;
public class DetectPluginChange implements Startable {
+ public static final String FORCE_PLUGIN_RELOAD_PROPERTY = "plugin.refresh.forced";
private static final Logger LOG = Loggers.get(DetectPluginChange.class);
private final ServerPluginRepository serverPluginRepository;
@@ -49,7 +50,7 @@ public class DetectPluginChange implements Startable {
public void start() {
Preconditions.checkState(changesDetected == null, "Can only call #start() once");
Profiler profiler = Profiler.create(LOG).startInfo("Detect plugin changes");
- changesDetected = anyChange();
+ changesDetected = isForcedReload() || anyChange();
if (changesDetected) {
LOG.debug("Plugin changes detected");
} else {
@@ -65,6 +66,17 @@ public class DetectPluginChange implements Startable {
return changesDetected;
}
+ private boolean isForcedReload() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ boolean forceRefresh = Boolean.parseBoolean(dbClient.internalPropertiesDao().selectByKey(dbSession, FORCE_PLUGIN_RELOAD_PROPERTY).orElse("false"));
+ if (forceRefresh) {
+ dbClient.internalPropertiesDao().delete(dbSession, FORCE_PLUGIN_RELOAD_PROPERTY);
+ dbSession.commit();
+ }
+ return forceRefresh;
+ }
+ }
+
private boolean anyChange() {
try (DbSession dbSession = dbClient.openSession(false)) {
Map<String, PluginDto> dbPluginsByKey = dbClient.pluginDao().selectAll(dbSession).stream()
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/PropertiesDBCleaner.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/PropertiesDBCleaner.java
new file mode 100644
index 00000000000..d4f87c46ce4
--- /dev/null
+++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/PropertiesDBCleaner.java
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.startup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.SonarEdition;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.Startable;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+
+import static java.util.Arrays.asList;
+
+public class PropertiesDBCleaner implements Startable {
+ private static final Logger LOG = LoggerFactory.getLogger(PropertiesDBCleaner.class);
+ private final SonarRuntime runtime;
+ private final DbClient dbClient;
+
+ public PropertiesDBCleaner(DbClient dbClient, SonarRuntime runtime) {
+ this.dbClient = dbClient;
+ this.runtime = runtime;
+ }
+
+ @Override
+ public void start() {
+ LOG.info("Clean up properties from db");
+ deleteMisraPropertyIfRequired();
+ }
+
+ private void deleteMisraPropertyIfRequired() {
+ String misraProperty = "sonar.earlyAccess.misra.enabled";
+ SonarEdition edition = runtime.getEdition();
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ if (asList(SonarEdition.COMMUNITY, SonarEdition.DEVELOPER).contains(edition)) {
+ dbClient.propertiesDao().deleteGlobalProperty(misraProperty, dbSession);
+ dbSession.commit();
+ }
+ }
+ }
+
+ @Override
+ public void stop() {
+ // Nothing to do
+ }
+}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/PropertiesDBCleanerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/PropertiesDBCleanerTest.java
new file mode 100644
index 00000000000..15c9955f853
--- /dev/null
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/PropertiesDBCleanerTest.java
@@ -0,0 +1,78 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.startup;
+
+import java.util.Objects;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.sonar.api.SonarEdition;
+import org.sonar.api.SonarRuntime;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.DbTester;
+import org.sonar.db.property.PropertyDto;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+
+class PropertiesDBCleanerTest {
+ @RegisterExtension
+ public DbTester db = DbTester.create();
+ private final DbClient dbClient = db.getDbClient();
+ private final DbSession dbSession = db.getSession();
+ private final SonarRuntime sonarRuntime = mock(SonarRuntime.class);
+ private static final String MISRA_SETTING = "sonar.earlyAccess.misra.enabled";
+
+ @ParameterizedTest
+ @ValueSource(strings = { "COMMUNITY", "DEVELOPER" })
+ void should_clean_up_misra_prop_when_dev_or_community_edition(String edition) {
+ when(sonarRuntime.getEdition()).thenReturn(SonarEdition.valueOf(edition));
+
+ dbClient
+ .propertiesDao()
+ .saveProperty(dbSession, new PropertyDto()
+ .setKey(MISRA_SETTING)
+ .setValue("true"), null, null, null, null);
+ dbSession.commit();
+
+ new PropertiesDBCleaner(dbClient, sonarRuntime).start();
+ assertThat(dbClient.propertiesDao().selectGlobalProperty(MISRA_SETTING)).isNull();
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "ENTERPRISE", "DATACENTER" })
+ void should_not_clean_up_misra_prop_when_enterprise_or_above(String edition) {
+ when(sonarRuntime.getEdition()).thenReturn(SonarEdition.valueOf(edition));
+
+ PropertyDto prop = new PropertyDto()
+ .setKey(MISRA_SETTING)
+ .setValue("true");
+ dbClient
+ .propertiesDao()
+ .saveProperty(dbSession, prop, null, null, null, null);
+ dbSession.commit();
+
+ new PropertiesDBCleaner(dbClient, sonarRuntime).start();
+ assertThat(Objects.requireNonNull(dbClient.propertiesDao().selectGlobalProperty(MISRA_SETTING)).getValue()).isEqualTo(prop.getValue());
+ }
+}
diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
index e9162a0c1f3..f8c11a99d6e 100644
--- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
+++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/WebApiEndpoints.java
@@ -21,7 +21,7 @@ package org.sonar.server.v2;
public class WebApiEndpoints {
public static final String JSON_MERGE_PATCH_CONTENT_TYPE = "application/merge-patch+json";
- public static final String INTERNAL = "internal";
+ public static final String INTERNAL = "x-sonar-internal";
public static final String SYSTEM_DOMAIN = "/system";
public static final String LIVENESS_ENDPOINT = SYSTEM_DOMAIN + "/liveness";
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/measure/ws/ComponentActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/measure/ws/ComponentActionIT.java
index 95a2e80093a..0c1e7a36271 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/measure/ws/ComponentActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/measure/ws/ComponentActionIT.java
@@ -33,6 +33,7 @@ import org.sonar.db.component.ProjectData;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.measure.MeasureDto;
import org.sonar.db.metric.MetricDto;
+import org.sonar.db.permission.GlobalPermission;
import org.sonar.server.component.TestComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
@@ -50,6 +51,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.tuple;
import static org.sonar.api.measures.CoreMetrics.RELIABILITY_ISSUES;
import static org.sonar.api.utils.DateUtils.parseDateTime;
+import static org.sonar.api.web.UserRole.SCAN;
import static org.sonar.api.web.UserRole.USER;
import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME;
import static org.sonar.db.component.BranchType.PULL_REQUEST;
@@ -107,6 +109,32 @@ public class ComponentActionIT {
}
@Test
+ public void user_with_project_scan_permission_is_allowed_to_get_project_measures() {
+ ProjectData projectData = db.components().insertPrivateProject();
+ ComponentDto mainBranch = projectData.getMainBranchComponent();
+ userSession.addProjectPermission(SCAN, projectData.getProjectDto())
+ .registerBranches(projectData.getMainBranchDto());
+ MetricDto metric = db.measures().insertMetric(m -> m.setValueType("INT"));
+
+ ComponentWsResponse response = newRequest(mainBranch.getKey(), metric.getKey());
+
+ assertThat(response.getMetrics().getMetricsCount()).isOne();
+ }
+
+ @Test
+ public void user_with_global_scan_permission_is_allowed_to_get_project_status() {
+ ProjectData projectData = db.components().insertPrivateProject();
+ ComponentDto mainBranch = projectData.getMainBranchComponent();
+ userSession.addPermission(GlobalPermission.SCAN);
+
+ MetricDto metric = db.measures().insertMetric(m -> m.setValueType("INT"));
+
+ ComponentWsResponse response = newRequest(mainBranch.getKey(), metric.getKey());
+
+ assertThat(response.getMetrics().getMetricsCount()).isOne();
+ }
+
+ @Test
public void without_additional_fields() {
ProjectData projectData = db.components().insertPrivateProject();
ComponentDto mainBranch = projectData.getMainBranchComponent();
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/QProfileExportersIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/QProfileExportersIT.java
deleted file mode 100644
index 9e06d87c0d6..00000000000
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/QProfileExportersIT.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2025 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.qualityprofile;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.io.StringWriter;
-import java.io.Writer;
-import java.util.Collection;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.ArgumentCaptor;
-import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
-import org.sonar.api.profiles.ProfileExporter;
-import org.sonar.api.profiles.ProfileImporter;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.utils.System2;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.db.DbSession;
-import org.sonar.db.DbTester;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.rule.DefaultRuleFinder;
-import org.sonar.server.rule.RuleDescriptionFormatter;
-import org.sonar.server.tester.UserSessionRule;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.apache.commons.io.IOUtils.toInputStream;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.sonar.db.qualityprofile.QualityProfileTesting.newQualityProfileDto;
-
-public class QProfileExportersIT {
-
- @org.junit.Rule
- public UserSessionRule userSessionRule = UserSessionRule.standalone();
-
- private final System2 system2 = new AlwaysIncreasingSystem2();
-
- @org.junit.Rule
- public DbTester db = DbTester.create(system2);
-
- private final RuleFinder ruleFinder = new DefaultRuleFinder(db.getDbClient(), mock(RuleDescriptionFormatter.class));
- private final ProfileExporter[] exporters = new ProfileExporter[] {
- new StandardExporter(), new XooExporter()};
- private final ProfileImporter[] importers = new ProfileImporter[] {
- new XooProfileImporter(), new XooProfileImporterWithMessages(), new XooProfileImporterWithError()};
- private RuleDto rule;
- private final QProfileRules qProfileRules = mock(QProfileRules.class);
- private final QProfileExporters underTest = new QProfileExporters(db.getDbClient(), ruleFinder, qProfileRules, exporters, importers);
-
- @Before
- public void setUp() {
- rule = db.rules().insert(r -> r.setLanguage("xoo").setRepositoryKey("SonarXoo").setRuleKey("R1"));
- }
-
- @Test
- public void exportersForLanguage() {
- assertThat(underTest.exportersForLanguage("xoo")).hasSize(2);
- assertThat(underTest.exportersForLanguage("java")).hasSize(1);
- assertThat(underTest.exportersForLanguage("java").get(0)).isInstanceOf(StandardExporter.class);
- }
-
- @Test
- public void mimeType() {
- assertThat(underTest.mimeType("xootool")).isEqualTo("plain/custom");
-
- // default mime type
- assertThat(underTest.mimeType("standard")).isEqualTo("text/plain");
- }
-
- @Test
- public void import_xml() {
- QProfileDto profile = createProfile();
-
- underTest.importXml(profile, "XooProfileImporter", toInputStream("<xml/>", UTF_8), db.getSession());
-
- ArgumentCaptor<QProfileDto> profileCapture = ArgumentCaptor.forClass(QProfileDto.class);
- Class<Collection<RuleActivation>> collectionClass = (Class<Collection<RuleActivation>>) (Class) Collection.class;
- ArgumentCaptor<Collection<RuleActivation>> activationCapture = ArgumentCaptor.forClass(collectionClass);
- verify(qProfileRules).activateAndCommit(any(DbSession.class), profileCapture.capture(), activationCapture.capture());
-
- assertThat(profileCapture.getValue().getKee()).isEqualTo(profile.getKee());
- Collection<RuleActivation> activations = activationCapture.getValue();
- assertThat(activations).hasSize(1);
- RuleActivation activation = activations.iterator().next();
- assertThat(activation.getRuleUuid()).isEqualTo(rule.getUuid());
- assertThat(activation.getSeverity()).isEqualTo("CRITICAL");
- }
-
- @Test
- public void import_xml_return_messages() {
- QProfileDto profile = createProfile();
-
- QProfileResult result = underTest.importXml(profile, "XooProfileImporterWithMessages", toInputStream("<xml/>", UTF_8), db.getSession());
-
- assertThat(result.infos()).containsOnly("an info");
- assertThat(result.warnings()).containsOnly("a warning");
- }
-
- @Test
- public void fail_to_import_xml_when_error_in_importer() {
- QProfileDto qProfileDto = newQualityProfileDto();
- InputStream inputStream = toInputStream("<xml/>", UTF_8);
- DbSession dbSession = db.getSession();
- assertThatThrownBy(() -> underTest.importXml(
- qProfileDto, "XooProfileImporterWithError", inputStream, dbSession))
- .isInstanceOf(BadRequestException.class)
- .hasMessage("error!");
- }
-
- @Test
- public void fail_to_import_xml_on_unknown_importer() {
- QProfileDto qProfileDto = newQualityProfileDto();
- InputStream inputStream = toInputStream("<xml/>", UTF_8);
- DbSession dbSession = db.getSession();
- assertThatThrownBy(() -> underTest.importXml(qProfileDto, "Unknown", inputStream, dbSession))
- .isInstanceOf(BadRequestException.class)
- .hasMessage("No such importer : Unknown");
- }
-
- @Test
- public void export_empty_profile() {
- QProfileDto profile = createProfile();
-
- StringWriter writer = new StringWriter();
- underTest.export(db.getSession(), profile, "standard", writer);
- assertThat(writer).hasToString("standard -> " + profile.getName() + " -> 0");
-
- writer = new StringWriter();
- underTest.export(db.getSession(), profile, "xootool", writer);
- assertThat(writer).hasToString("xoo -> " + profile.getName() + " -> 0");
- }
-
- @Test
- public void export_profile() {
- QProfileDto profile = createProfile();
- db.qualityProfiles().activateRule(profile, rule);
-
- StringWriter writer = new StringWriter();
- underTest.export(db.getSession(), profile, "standard", writer);
- assertThat(writer).hasToString("standard -> " + profile.getName() + " -> 1");
-
- writer = new StringWriter();
- underTest.export(db.getSession(), profile, "xootool", writer);
- assertThat(writer).hasToString("xoo -> " + profile.getName() + " -> 1");
- }
-
- @Test
- public void export_throws_NotFoundException_if_exporter_does_not_exist() {
- QProfileDto profile = createProfile();
-
- assertThatThrownBy(() -> {
- underTest.export(db.getSession(), profile, "does_not_exist", new StringWriter());
- })
- .isInstanceOf(NotFoundException.class)
- .hasMessage("Unknown quality profile exporter: does_not_exist");
- }
-
- private QProfileDto createProfile() {
- return db.qualityProfiles().insert(p -> p.setLanguage(rule.getLanguage()));
- }
-
- public static class XooExporter extends ProfileExporter {
- public XooExporter() {
- super("xootool", "Xoo Tool");
- }
-
- @Override
- public String[] getSupportedLanguages() {
- return new String[] {"xoo"};
- }
-
- @Override
- public String getMimeType() {
- return "plain/custom";
- }
-
- @Override
- public void exportProfile(RulesProfile profile, Writer writer) {
- try {
- writer.write("xoo -> " + profile.getName() + " -> " + profile.getActiveRules().size());
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- }
- }
-
- public static class StandardExporter extends ProfileExporter {
- public StandardExporter() {
- super("standard", "Standard");
- }
-
- @Override
- public void exportProfile(RulesProfile profile, Writer writer) {
- try {
- writer.write("standard -> " + profile.getName() + " -> " + profile.getActiveRules().size());
- } catch (IOException e) {
- throw new IllegalStateException(e);
- }
- }
- }
-
- public class XooProfileImporter extends ProfileImporter {
- public XooProfileImporter() {
- super("XooProfileImporter", "Xoo Profile Importer");
- }
-
- @Override
- public String[] getSupportedLanguages() {
- return new String[] {"xoo"};
- }
-
- @Override
- public RulesProfile importProfile(Reader reader, ValidationMessages messages) {
- RulesProfile rulesProfile = RulesProfile.create();
- rulesProfile.activateRule(Rule.create(rule.getRepositoryKey(), rule.getRuleKey()), RulePriority.CRITICAL);
- return rulesProfile;
- }
- }
-
- public static class XooProfileImporterWithMessages extends ProfileImporter {
- public XooProfileImporterWithMessages() {
- super("XooProfileImporterWithMessages", "Xoo Profile Importer With Message");
- }
-
- @Override
- public String[] getSupportedLanguages() {
- return new String[] {};
- }
-
- @Override
- public RulesProfile importProfile(Reader reader, ValidationMessages messages) {
- messages.addWarningText("a warning");
- messages.addInfoText("an info");
- return RulesProfile.create();
- }
- }
-
- public static class XooProfileImporterWithError extends ProfileImporter {
- public XooProfileImporterWithError() {
- super("XooProfileImporterWithError", "Xoo Profile Importer With Error");
- }
-
- @Override
- public RulesProfile importProfile(Reader reader, ValidationMessages messages) {
- messages.addErrorText("error!");
- return RulesProfile.create();
- }
- }
-
-}
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/CreateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/CreateActionIT.java
index 395b4147c18..af594642a2e 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/CreateActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/CreateActionIT.java
@@ -19,43 +19,20 @@
*/
package org.sonar.server.qualityprofile.ws;
-import com.google.common.collect.ImmutableMap;
-import java.io.Reader;
-import java.util.Collections;
-import java.util.Map;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.profiles.ProfileImporter;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.rules.RulePriority;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.System2;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.api.utils.Version;
-import org.sonar.core.platform.SonarQubeVersion;
import org.sonar.core.util.UuidFactoryFast;
-import org.sonar.core.util.UuidFactoryImpl;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleTesting;
import org.sonar.server.es.EsTester;
-import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.pushapi.qualityprofile.QualityProfileChangeEventService;
-import org.sonar.server.qualityprofile.QProfileExporters;
import org.sonar.server.qualityprofile.QProfileFactoryImpl;
-import org.sonar.server.qualityprofile.QProfileRules;
-import org.sonar.server.qualityprofile.QProfileRulesImpl;
-import org.sonar.server.qualityprofile.builtin.RuleActivator;
import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
-import org.sonar.server.rule.index.RuleIndex;
-import org.sonar.server.rule.index.RuleIndexer;
-import org.sonar.server.rule.index.RuleQuery;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
@@ -67,7 +44,6 @@ import org.sonarqube.ws.Qualityprofiles.CreateWsResponse.QualityProfile;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
import static org.sonar.db.permission.GlobalPermission.SCAN;
import static org.sonar.server.language.LanguageTesting.newLanguages;
@@ -75,9 +51,6 @@ import static org.sonar.server.language.LanguageTesting.newLanguages;
class CreateActionIT {
private static final String XOO_LANGUAGE = "xoo";
- private static final RuleDto RULE = RuleTesting.newXooX1()
- .setSeverity("MINOR")
- .setLanguage(XOO_LANGUAGE);
@RegisterExtension
private final DbTester db = DbTester.create();
@@ -86,21 +59,12 @@ class CreateActionIT {
@RegisterExtension
private final UserSessionRule userSession = UserSessionRule.standalone();
- private final Configuration config = mock(Configuration.class);
private final DbClient dbClient = db.getDbClient();
private final DbSession dbSession = db.getSession();
- private final RuleIndex ruleIndex = new RuleIndex(es.client(), System2.INSTANCE, config);
- private final RuleIndexer ruleIndexer = new RuleIndexer(es.client(), dbClient);
private final ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(dbClient, es.client());
- private final ProfileImporter[] profileImporters = createImporters();
- private final QualityProfileChangeEventService qualityProfileChangeEventService = mock(QualityProfileChangeEventService.class);
- private final SonarQubeVersion sonarQubeVersion = new SonarQubeVersion(Version.create(10, 3));
- private final RuleActivator ruleActivator = new RuleActivator(System2.INSTANCE, dbClient, UuidFactoryImpl.INSTANCE, null, userSession, mock(Configuration.class), sonarQubeVersion);
- private final QProfileRules qProfileRules = new QProfileRulesImpl(dbClient, ruleActivator, ruleIndex, activeRuleIndexer, qualityProfileChangeEventService);
- private final QProfileExporters qProfileExporters = new QProfileExporters(dbClient, null, qProfileRules, profileImporters);
private final CreateAction underTest = new CreateAction(dbClient, new QProfileFactoryImpl(dbClient, UuidFactoryFast.getInstance(), System2.INSTANCE, activeRuleIndexer),
- qProfileExporters, newLanguages(XOO_LANGUAGE), userSession, activeRuleIndexer, profileImporters);
+ newLanguages(XOO_LANGUAGE), userSession);
private WsActionTester ws = new WsActionTester(underTest);
@@ -111,7 +75,7 @@ class CreateActionIT {
assertThat(definition.responseExampleAsString()).isNotEmpty();
assertThat(definition.isPost()).isTrue();
assertThat(definition.params()).extracting(Param::key)
- .containsExactlyInAnyOrder("language", "name", "backup_with_messages", "backup_with_errors", "backup_xoo_lint");
+ .containsExactlyInAnyOrder("language", "name");
}
@Test
@@ -136,30 +100,6 @@ class CreateActionIT {
}
@Test
- void create_profile_from_backup_xml() {
- logInAsQProfileAdministrator();
- insertRule(RULE);
-
- executeRequest("New Profile", XOO_LANGUAGE, ImmutableMap.of("xoo_lint", "<xml/>"));
-
- QProfileDto dto = dbClient.qualityProfileDao().selectByNameAndLanguage(dbSession, "New Profile", XOO_LANGUAGE);
- assertThat(dto.getKee()).isNotNull();
- assertThat(dbClient.activeRuleDao().selectByProfileUuid(dbSession, dto.getKee())).hasSize(1);
- assertThat(ruleIndex.searchAll(new RuleQuery().setQProfile(dto).setActivation(true))).toIterable().hasSize(1);
- }
-
- @Test
- void create_profile_with_messages() {
- logInAsQProfileAdministrator();
-
- CreateWsResponse response = executeRequest("Profile with messages", XOO_LANGUAGE, ImmutableMap.of("with_messages", "<xml/>"));
-
- QualityProfile profile = response.getProfile();
- assertThat(profile.getInfos().getInfosList()).containsOnly("an info");
- assertThat(profile.getWarnings().getWarningsList()).containsOnly("a warning");
- }
-
- @Test
void fail_if_unsufficient_privileges() {
userSession
.logIn()
@@ -175,16 +115,6 @@ class CreateActionIT {
}
@Test
- void fail_if_import_generate_error() {
- logInAsQProfileAdministrator();
-
- assertThatThrownBy(() -> {
- executeRequest("Profile with errors", XOO_LANGUAGE, ImmutableMap.of("with_errors", "<xml/>"));
- })
- .isInstanceOf(BadRequestException.class);
- }
-
- @Test
void test_json() {
logInAsQProfileAdministrator();
@@ -199,23 +129,10 @@ class CreateActionIT {
assertThat(response.getMediaType()).isEqualTo(MediaTypes.JSON);
}
- private void insertRule(RuleDto ruleDto) {
- dbClient.ruleDao().insert(dbSession, ruleDto);
- dbSession.commit();
- ruleIndexer.commitAndIndex(dbSession, ruleDto.getUuid());
- }
-
private CreateWsResponse executeRequest(String name, String language) {
- return executeRequest(name, language, Collections.emptyMap());
- }
-
- private CreateWsResponse executeRequest(String name, String language, Map<String, String> xmls) {
TestRequest request = ws.newRequest()
.setParam("name", name)
.setParam("language", language);
- for (Map.Entry<String, String> entry : xmls.entrySet()) {
- request.setParam("backup_" + entry.getKey(), entry.getValue());
- }
return executeRequest(request);
}
@@ -223,55 +140,6 @@ class CreateActionIT {
return request.executeProtobuf(CreateWsResponse.class);
}
- private ProfileImporter[] createImporters() {
- class DefaultProfileImporter extends ProfileImporter {
- private DefaultProfileImporter() {
- super("xoo_lint", "Xoo Lint");
- setSupportedLanguages(XOO_LANGUAGE);
- }
-
- @Override
- public RulesProfile importProfile(Reader reader, ValidationMessages messages) {
- RulesProfile rulesProfile = RulesProfile.create();
- rulesProfile.activateRule(org.sonar.api.rules.Rule.create(RULE.getRepositoryKey(), RULE.getRuleKey()), RulePriority.BLOCKER);
- return rulesProfile;
- }
- }
-
- class ProfileImporterGeneratingMessages extends ProfileImporter {
- private ProfileImporterGeneratingMessages() {
- super("with_messages", "With messages");
- setSupportedLanguages(XOO_LANGUAGE);
- }
-
- @Override
- public RulesProfile importProfile(Reader reader, ValidationMessages messages) {
- RulesProfile rulesProfile = RulesProfile.create();
- messages.addWarningText("a warning");
- messages.addInfoText("an info");
- return rulesProfile;
- }
- }
-
- class ProfileImporterGeneratingErrors extends ProfileImporter {
- private ProfileImporterGeneratingErrors() {
- super("with_errors", "With errors");
- setSupportedLanguages(XOO_LANGUAGE);
- }
-
- @Override
- public RulesProfile importProfile(Reader reader, ValidationMessages messages) {
- RulesProfile rulesProfile = RulesProfile.create();
- messages.addErrorText("error!");
- return rulesProfile;
- }
- }
-
- return new ProfileImporter[] {
- new DefaultProfileImporter(), new ProfileImporterGeneratingMessages(), new ProfileImporterGeneratingErrors()
- };
- }
-
private void logInAsQProfileAdministrator() {
userSession
.logIn()
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ExportActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ExportActionIT.java
index 8b33646673d..c22c3ddd306 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ExportActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/qualityprofile/ws/ExportActionIT.java
@@ -23,11 +23,8 @@ import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import javax.annotation.Nullable;
-import org.apache.commons.lang3.StringUtils;
import org.junit.Rule;
import org.junit.Test;
-import org.sonar.api.profiles.ProfileExporter;
-import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
@@ -37,7 +34,6 @@ import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.language.LanguageTesting;
import org.sonar.server.qualityprofile.QProfileBackuper;
-import org.sonar.server.qualityprofile.QProfileExporters;
import org.sonar.server.qualityprofile.QProfileRestoreSummary;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsActionTester;
@@ -60,39 +56,10 @@ public class ExportActionIT {
private final QProfileBackuper backuper = new TestBackuper();
@Test
- public void export_profile() {
+ public void return_backup() {
QProfileDto profile = createProfile(false);
- WsActionTester tester = newWsActionTester(newExporter("polop"), newExporter("palap"));
- String result = tester.newRequest()
- .setParam("language", profile.getLanguage())
- .setParam("qualityProfile", profile.getName())
- .setParam("exporterKey", "polop").execute()
- .getInput();
-
- assertThat(result).isEqualTo("Profile " + profile.getLanguage() + "/" + profile.getName() + " exported by polop");
- }
-
- @Test
- public void export_default_profile() {
- QProfileDto nonDefaultProfile = createProfile(false);
- QProfileDto defaultProfile = createProfile(true);
-
- WsActionTester tester = newWsActionTester(newExporter("polop"), newExporter("palap"));
- String result = tester.newRequest()
- .setParam("language", XOO_LANGUAGE)
- .setParam("exporterKey", "polop")
- .execute()
- .getInput();
-
- assertThat(result).isEqualTo("Profile " + defaultProfile.getLanguage() + "/" + defaultProfile.getName() + " exported by polop");
- }
-
- @Test
- public void return_backup_when_exporter_is_not_specified() {
- QProfileDto profile = createProfile(false);
-
- String result = newWsActionTester(newExporter("polop")).newRequest()
+ String result = newWsActionTester().newRequest()
.setParam("language", profile.getLanguage())
.setParam("qualityProfile", profile.getName())
.execute()
@@ -112,20 +79,7 @@ public class ExportActionIT {
}
@Test
- public void throw_IAE_if_export_with_specified_key_does_not_exist() {
- QProfileDto profile = createProfile(true);
-
- assertThatThrownBy(() -> {
- newWsActionTester(newExporter("polop"), newExporter("palap")).newRequest()
- .setParam("language", XOO_LANGUAGE)
- .setParam("exporterKey", "unknown").execute();
- })
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Value of parameter 'exporterKey' (unknown) must be one of: [polop, palap]");
- }
-
- @Test
- public void definition_without_exporters() {
+ public void definition() {
WebService.Action definition = newWsActionTester().getDef();
assertThat(definition.isPost()).isFalse();
@@ -139,18 +93,6 @@ public class ExportActionIT {
assertThat(language.deprecatedSince()).isNullOrEmpty();
}
- @Test
- public void definition_with_exporters() {
- WebService.Action definition = newWsActionTester(newExporter("polop"), newExporter("palap")).getDef();
-
- assertThat(definition.isPost()).isFalse();
- assertThat(definition.isInternal()).isFalse();
- assertThat(definition.params()).extracting("key").containsExactlyInAnyOrder("language", "qualityProfile", "exporterKey");
- WebService.Param exportersParam = definition.param("exporterKey");
- assertThat(exportersParam.possibleValues()).containsOnly("polop", "palap");
- assertThat(exportersParam.isInternal()).isFalse();
- }
-
private QProfileDto createProfile(boolean isDefault) {
QProfileDto profile = db.qualityProfiles().insert(p -> p.setLanguage(XOO_LANGUAGE));
if (isDefault) {
@@ -159,27 +101,8 @@ public class ExportActionIT {
return profile;
}
- private WsActionTester newWsActionTester(ProfileExporter... profileExporters) {
- QProfileExporters exporters = new QProfileExporters(dbClient, null, null, profileExporters, null);
- return new WsActionTester(new ExportAction(dbClient, backuper, exporters, LanguageTesting.newLanguages(XOO_LANGUAGE, JAVA_LANGUAGE)));
- }
-
- private static ProfileExporter newExporter(String key) {
- return new ProfileExporter(key, StringUtils.capitalize(key)) {
- @Override
- public String getMimeType() {
- return "text/plain+" + key;
- }
-
- @Override
- public void exportProfile(RulesProfile profile, Writer writer) {
- try {
- writer.write(format("Profile %s/%s exported by %s", profile.getLanguage(), profile.getName(), key));
- } catch (IOException ioe) {
- throw new RuntimeException(ioe);
- }
- }
- };
+ private WsActionTester newWsActionTester() {
+ return new WsActionTester(new ExportAction(dbClient, backuper, LanguageTesting.newLanguages(XOO_LANGUAGE, JAVA_LANGUAGE)));
}
private static class TestBackuper implements QProfileBackuper {
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/CurrentActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/CurrentActionIT.java
index b2a716df087..f343d99f2b2 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/CurrentActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/CurrentActionIT.java
@@ -27,18 +27,18 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Suite;
-import org.sonar.db.component.ComponentQualifiers;
-import org.sonar.server.component.ComponentType;
-import org.sonar.server.component.ComponentTypeTree;
-import org.sonar.server.component.ComponentTypes;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
import org.sonar.core.platform.PlatformEditionProvider;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentQualifiers;
import org.sonar.db.property.PropertyDto;
import org.sonar.db.user.UserDto;
import org.sonar.server.common.avatar.AvatarResolverImpl;
+import org.sonar.server.component.ComponentType;
+import org.sonar.server.component.ComponentTypeTree;
+import org.sonar.server.component.ComponentTypes;
import org.sonar.server.permission.PermissionService;
import org.sonar.server.permission.PermissionServiceImpl;
import org.sonar.server.tester.UserSessionRule;
@@ -55,7 +55,6 @@ import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFIL
import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS;
import static org.sonar.db.permission.GlobalPermission.SCAN;
import static org.sonar.db.user.GroupTesting.newGroupDto;
-import static org.sonar.server.user.ws.DismissNoticeAction.AVAILABLE_NOTICE_KEYS;
import static org.sonar.test.JsonAssert.assertJson;
@RunWith(Suite.class)
@@ -262,7 +261,7 @@ public class CurrentActionIT {
@Parameterized.Parameters
public static Collection<String> parameterCombination() {
- return AVAILABLE_NOTICE_KEYS;
+ return DismissNoticeAction.DismissNotices.getAvailableKeys();
}
private final String notice;
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/DismissNoticeActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/DismissNoticeActionIT.java
index a3c6748a49a..4d0c6acf8b5 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/DismissNoticeActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/user/ws/DismissNoticeActionIT.java
@@ -20,12 +20,12 @@
package org.sonar.server.user.ws;
import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Optional;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
import org.sonar.db.property.PropertyDto;
@@ -37,20 +37,18 @@ import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.sonar.server.user.ws.DismissNoticeAction.AVAILABLE_NOTICE_KEYS;
-@RunWith(DataProviderRunner.class)
-public class DismissNoticeActionIT {
+class DismissNoticeActionIT {
- @Rule
- public DbTester db = DbTester.create(System2.INSTANCE);
- @Rule
- public UserSessionRule userSessionRule = UserSessionRule.standalone();
+ @RegisterExtension
+ private DbTester db = DbTester.create(System2.INSTANCE);
+ @RegisterExtension
+ private UserSessionRule userSessionRule = UserSessionRule.standalone();
private final WsActionTester tester = new WsActionTester(new DismissNoticeAction(userSessionRule, db.getDbClient()));
@Test
- public void authentication_is_required() {
+ void authentication_is_required() {
TestRequest testRequest = tester.newRequest()
.setParam("notice", "anyValue");
@@ -60,7 +58,7 @@ public class DismissNoticeActionIT {
}
@Test
- public void notice_parameter_is_mandatory() {
+ void notice_parameter_is_mandatory() {
userSessionRule.logIn();
TestRequest testRequest = tester.newRequest();
@@ -70,20 +68,19 @@ public class DismissNoticeActionIT {
}
@Test
- public void notice_not_supported() {
+ void notice_not_supported() {
userSessionRule.logIn();
TestRequest testRequest = tester.newRequest()
.setParam("notice", "not_supported_value");
assertThatThrownBy(testRequest::execute)
.isInstanceOf(IllegalArgumentException.class)
- .hasMessage(
- "Value of parameter 'notice' (not_supported_value) must be one of: [educationPrinciples, sonarlintAd, issueCleanCodeGuide, qualityGateCaYCConditionsSimplification, " +
- "overviewZeroNewIssuesSimplification, issueNewIssueStatusAndTransitionGuide, onboardingDismissCaycBranchSummaryGuide, showNewModesTour, showNewModesBanner]");
+ .hasMessageStartingWith(
+ "Value of parameter 'notice' (not_supported_value) must be one of: [");
}
@Test
- public void notice_already_exist_dont_fail() {
+ void notice_already_exist_dont_fail() {
userSessionRule.logIn();
PropertyDto property = new PropertyDto().setKey("user.dismissedNotices.educationPrinciples").setUserUuid(userSessionRule.getUuid());
db.properties().insertProperties(userSessionRule.getLogin(), null, null, null, property);
@@ -97,9 +94,9 @@ public class DismissNoticeActionIT {
assertThat(db.properties().findFirstUserProperty(userSessionRule.getUuid(), "user.dismissedNotices.educationPrinciples")).isPresent();
}
- @Test
- @UseDataProvider("noticeKeys")
- public void dismiss_notice(String noticeKey) {
+ @ParameterizedTest
+ @MethodSource("noticeKeys")
+ void dismiss_notice(String noticeKey) {
userSessionRule.logIn();
TestResponse testResponse = tester.newRequest()
@@ -113,9 +110,7 @@ public class DismissNoticeActionIT {
}
@DataProvider
- public static Object[][] noticeKeys() {
- return AVAILABLE_NOTICE_KEYS.stream()
- .map(noticeKey -> new Object[] {noticeKey})
- .toArray(Object[][]::new);
+ static Set<String> noticeKeys() {
+ return DismissNoticeAction.DismissNotices.getAvailableKeys();
}
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentAction.java
index 0f52c689e69..f57d384d416 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/measure/ws/ComponentAction.java
@@ -42,6 +42,7 @@ import org.sonar.db.component.SnapshotDto;
import org.sonar.db.measure.MeasureDto;
import org.sonar.db.metric.MetricDto;
import org.sonar.db.metric.MetricDtoFunctions;
+import org.sonar.db.permission.GlobalPermission;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.user.UserSession;
@@ -66,6 +67,7 @@ import static org.sonar.server.measure.ws.ComponentResponseCommon.addMetricToRes
import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createAdditionalFieldsParameter;
import static org.sonar.server.measure.ws.MeasuresWsParametersBuilder.createMetricKeysParameter;
import static org.sonar.server.measure.ws.SnapshotDtoToWsPeriod.snapshotToWsPeriods;
+import static org.sonar.server.user.AbstractUserSession.insufficientPrivilegesException;
import static org.sonar.server.ws.KeyExamples.KEY_BRANCH_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PROJECT_EXAMPLE_001;
import static org.sonar.server.ws.KeyExamples.KEY_PULL_REQUEST_EXAMPLE_001;
@@ -88,10 +90,15 @@ public class ComponentAction implements MeasuresWsAction {
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction(ACTION_COMPONENT)
.setDescription("Return component with specified measures.<br>" +
- "Requires the following permission: 'Browse' on the project of specified component.")
+ "Requires one of the following permissions:" +
+ "<ul>" +
+ "<li>'Browse' on the project of the specified component</li>" +
+ "<li>'Execute Analysis' on the project of the specified component</li>" +
+ "</ul>")
.setResponseExample(getClass().getResource("component-example.json"))
.setSince("5.4")
.setChangelog(
+ new Change("2025.2", "The 'Execute Analysis' permission also allows to access the endpoint"),
new Change("10.8", format("The following metrics are not deprecated anymore: %s",
MeasuresWsModule.getUndeprecatedMetricsinSonarQube108())),
new Change("10.8", String.format("Added new accepted values for the 'metricKeys' param: %s",
@@ -282,7 +289,11 @@ public class ComponentAction implements MeasuresWsAction {
}
private void checkPermissions(ComponentDto baseComponent) {
- userSession.checkComponentPermission(UserRole.USER, baseComponent);
+ if (!userSession.hasComponentPermission(UserRole.USER, baseComponent) &&
+ !userSession.hasComponentPermission(UserRole.SCAN, baseComponent) &&
+ !userSession.hasPermission(GlobalPermission.SCAN)) {
+ throw insufficientPrivilegesException();
+ }
}
private static class ComponentRequest {
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileExporters.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileExporters.java
deleted file mode 100644
index a6f2fc4bcc0..00000000000
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileExporters.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2025 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.qualityprofile;
-
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Lists;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.Writer;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import javax.annotation.CheckForNull;
-import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.sonar.api.profiles.ProfileExporter;
-import org.sonar.api.profiles.ProfileImporter;
-import org.sonar.api.profiles.RulesProfile;
-import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rules.ActiveRule;
-import org.sonar.api.rules.ActiveRuleParam;
-import org.sonar.api.rules.Rule;
-import org.sonar.api.rules.RuleFinder;
-import org.sonar.api.rules.RulePriority;
-import org.sonar.api.server.ServerSide;
-import org.sonar.api.utils.ValidationMessages;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.qualityprofile.ActiveRuleDto;
-import org.sonar.db.qualityprofile.ActiveRuleParamDto;
-import org.sonar.db.qualityprofile.OrgActiveRuleDto;
-import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.db.rule.RuleDto;
-import org.sonar.server.exceptions.BadRequestException;
-import org.sonar.server.exceptions.NotFoundException;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import static org.sonar.server.exceptions.BadRequestException.checkRequest;
-
-@ServerSide
-public class QProfileExporters {
-
- private final DbClient dbClient;
- private final RuleFinder ruleFinder;
- private final QProfileRules qProfileRules;
- private final ProfileExporter[] exporters;
- private final ProfileImporter[] importers;
-
- @Autowired(required = false)
- public QProfileExporters(DbClient dbClient, RuleFinder ruleFinder, QProfileRules qProfileRules, ProfileExporter[] exporters, ProfileImporter[] importers) {
- this.dbClient = dbClient;
- this.ruleFinder = ruleFinder;
- this.qProfileRules = qProfileRules;
- this.exporters = exporters;
- this.importers = importers;
- }
-
- /**
- * Used by the ioc container if no {@link ProfileImporter} is found
- */
- @Autowired(required = false)
- public QProfileExporters(DbClient dbClient, RuleFinder ruleFinder, QProfileRules qProfileRules, ProfileExporter[] exporters) {
- this(dbClient, ruleFinder, qProfileRules, exporters, new ProfileImporter[0]);
- }
-
- /**
- * Used by the ioc container if no {@link ProfileExporter} is found
- */
- @Autowired(required = false)
- public QProfileExporters(DbClient dbClient, RuleFinder ruleFinder, QProfileRules qProfileRules, ProfileImporter[] importers) {
- this(dbClient, ruleFinder, qProfileRules, new ProfileExporter[0], importers);
- }
-
- /**
- * Used by the ioc container if no {@link ProfileImporter} nor {@link ProfileExporter} is found
- */
- @Autowired(required = false)
- public QProfileExporters(DbClient dbClient, RuleFinder ruleFinder, QProfileRules qProfileRules) {
- this(dbClient, ruleFinder, qProfileRules, new ProfileExporter[0], new ProfileImporter[0]);
- }
-
- public List<ProfileExporter> exportersForLanguage(String language) {
- List<ProfileExporter> result = new ArrayList<>();
- for (ProfileExporter exporter : exporters) {
- if (exporter.getSupportedLanguages() == null || exporter.getSupportedLanguages().length == 0 || ArrayUtils.contains(exporter.getSupportedLanguages(), language)) {
- result.add(exporter);
- }
- }
- return result;
- }
-
- public String mimeType(String exporterKey) {
- ProfileExporter exporter = findExporter(exporterKey);
- return exporter.getMimeType();
- }
-
- public void export(DbSession dbSession, QProfileDto profile, String exporterKey, Writer writer) {
- ProfileExporter exporter = findExporter(exporterKey);
- exporter.exportProfile(wrap(dbSession, profile), writer);
- }
-
- private RulesProfile wrap(DbSession dbSession, QProfileDto profile) {
- RulesProfile target = new RulesProfile(profile.getName(), profile.getLanguage());
- List<OrgActiveRuleDto> activeRuleDtos = dbClient.activeRuleDao().selectByProfile(dbSession, profile);
- List<ActiveRuleParamDto> activeRuleParamDtos = dbClient.activeRuleDao().selectParamsByActiveRuleUuids(dbSession, Lists.transform(activeRuleDtos, ActiveRuleDto::getUuid));
- ListMultimap<String, ActiveRuleParamDto> activeRuleParamsByActiveRuleUuid = FluentIterable.from(activeRuleParamDtos).index(ActiveRuleParamDto::getActiveRuleUuid);
-
- for (ActiveRuleDto activeRule : activeRuleDtos) {
- // TODO all rules should be loaded by using one query with all active rule keys as parameter
- Rule rule = ruleFinder.findByKey(activeRule.getRuleKey());
- org.sonar.api.rules.ActiveRule wrappedActiveRule = target.activateRule(rule, RulePriority.valueOf(activeRule.getSeverityString()));
- List<ActiveRuleParamDto> paramDtos = activeRuleParamsByActiveRuleUuid.get(activeRule.getUuid());
- for (ActiveRuleParamDto activeRuleParamDto : paramDtos) {
- wrappedActiveRule.setParameter(activeRuleParamDto.getKey(), activeRuleParamDto.getValue());
- }
- }
- return target;
- }
-
- private ProfileExporter findExporter(String exporterKey) {
- for (ProfileExporter e : exporters) {
- if (exporterKey.equals(e.getKey())) {
- return e;
- }
- }
- throw new NotFoundException("Unknown quality profile exporter: " + exporterKey);
- }
-
- public QProfileResult importXml(QProfileDto profile, String importerKey, InputStream xml, DbSession dbSession) {
- return importXml(profile, importerKey, new InputStreamReader(xml, StandardCharsets.UTF_8), dbSession);
- }
-
- private QProfileResult importXml(QProfileDto profile, String importerKey, Reader xml, DbSession dbSession) {
- QProfileResult result = new QProfileResult();
- ValidationMessages messages = ValidationMessages.create();
- ProfileImporter importer = getProfileImporter(importerKey);
- RulesProfile definition = importer.importProfile(xml, messages);
- List<ActiveRuleChange> changes = importProfile(profile, definition, dbSession);
- result.addChanges(changes);
- processValidationMessages(messages, result);
- return result;
- }
-
- private List<ActiveRuleChange> importProfile(QProfileDto profile, RulesProfile definition, DbSession dbSession) {
- Map<RuleKey, RuleDto> rulesByRuleKey = dbClient.ruleDao().selectAll(dbSession)
- .stream()
- .collect(Collectors.toMap(RuleDto::getKey, Function.identity()));
- List<ActiveRule> activeRules = definition.getActiveRules();
- List<RuleActivation> activations = activeRules.stream()
- .map(activeRule -> toRuleActivation(activeRule, rulesByRuleKey))
- .filter(Objects::nonNull)
- .toList();
- return qProfileRules.activateAndCommit(dbSession, profile, activations);
- }
-
- private ProfileImporter getProfileImporter(String importerKey) {
- for (ProfileImporter importer : importers) {
- if (StringUtils.equals(importerKey, importer.getKey())) {
- return importer;
- }
- }
- throw BadRequestException.create("No such importer : " + importerKey);
- }
-
- private static void processValidationMessages(ValidationMessages messages, QProfileResult result) {
- checkRequest(messages.getErrors().isEmpty(), messages.getErrors());
- result.addWarnings(messages.getWarnings());
- result.addInfos(messages.getInfos());
- }
-
- @CheckForNull
- private static RuleActivation toRuleActivation(ActiveRule activeRule, Map<RuleKey, RuleDto> rulesByRuleKey) {
- RuleKey ruleKey = activeRule.getRule().ruleKey();
- RuleDto ruleDto = rulesByRuleKey.get(ruleKey);
- if (ruleDto == null) {
- return null;
- }
- String severity = activeRule.getSeverity().name();
- Map<String, String> params = activeRule.getActiveRuleParams().stream()
- .collect(Collectors.toMap(ActiveRuleParam::getKey, ActiveRuleParam::getValue));
- return RuleActivation.create(ruleDto.getUuid(), severity, params);
- }
-
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResult.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResult.java
deleted file mode 100644
index 2dadd2d8015..00000000000
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/QProfileResult.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2025 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.qualityprofile;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.sonar.db.qualityprofile.QProfileDto;
-
-public class QProfileResult {
-
- private List<String> warnings;
- private List<String> infos;
-
- private QProfileDto profile;
-
- private List<ActiveRuleChange> changes;
-
- public QProfileResult() {
- warnings = new ArrayList<>();
- infos = new ArrayList<>();
- changes = new ArrayList<>();
- }
-
- public List<String> warnings() {
- return warnings;
- }
-
- public QProfileResult addWarnings(List<String> warnings) {
- this.warnings.addAll(warnings);
- return this;
- }
-
- public List<String> infos() {
- return infos;
- }
-
- public QProfileResult addInfos(List<String> infos) {
- this.infos.addAll(infos);
- return this;
- }
-
- public QProfileDto profile() {
- return profile;
- }
-
- public QProfileResult setProfile(QProfileDto profile) {
- this.profile = profile;
- return this;
- }
-
- public List<ActiveRuleChange> getChanges() {
- return changes;
- }
-
- public QProfileResult addChanges(List<ActiveRuleChange> changes) {
- this.changes.addAll(changes);
- return this;
- }
-
- public QProfileResult add(QProfileResult result) {
- warnings.addAll(result.warnings());
- infos.addAll(result.infos());
- changes.addAll(result.getChanges());
- return this;
- }
-
-}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImpl.java
index 5a5b0d07f77..dd20b9c78c8 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImpl.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/builtin/BuiltInQProfileRepositoryImpl.java
@@ -32,7 +32,6 @@ import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
-import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java
index bd38a0f3d2e..2c391027a21 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java
@@ -19,13 +19,8 @@
*/
package org.sonar.server.qualityprofile.ws;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
import javax.annotation.Nullable;
-import org.sonar.api.profiles.ProfileImporter;
import org.sonar.api.resources.Languages;
-import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
@@ -33,11 +28,8 @@ import org.sonar.api.server.ws.WebService.NewAction;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.qualityprofile.QProfileDto;
-import org.sonar.server.qualityprofile.QProfileExporters;
import org.sonar.server.qualityprofile.QProfileFactory;
import org.sonar.server.qualityprofile.builtin.QProfileName;
-import org.sonar.server.qualityprofile.QProfileResult;
-import org.sonar.server.qualityprofile.index.ActiveRuleIndexer;
import org.sonar.server.user.UserSession;
import org.sonarqube.ws.Qualityprofiles.CreateWsResponse;
@@ -48,37 +40,22 @@ import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.ACTION_CREATE;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_LANGUAGE;
import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.PARAM_NAME;
-import org.springframework.beans.factory.annotation.Autowired;
public class CreateAction implements QProfileWsAction {
- private static final String PARAM_BACKUP_FORMAT = "backup_%s";
static final int NAME_MAXIMUM_LENGTH = 100;
private final DbClient dbClient;
private final QProfileFactory profileFactory;
- private final QProfileExporters exporters;
private final Languages languages;
- private final ProfileImporter[] importers;
private final UserSession userSession;
- private final ActiveRuleIndexer activeRuleIndexer;
- @Autowired(required = false)
- public CreateAction(DbClient dbClient, QProfileFactory profileFactory, QProfileExporters exporters, Languages languages,
- UserSession userSession, ActiveRuleIndexer activeRuleIndexer, ProfileImporter... importers) {
+ public CreateAction(DbClient dbClient, QProfileFactory profileFactory, Languages languages,
+ UserSession userSession) {
this.dbClient = dbClient;
this.profileFactory = profileFactory;
- this.exporters = exporters;
this.languages = languages;
this.userSession = userSession;
- this.activeRuleIndexer = activeRuleIndexer;
- this.importers = importers;
- }
-
- @Autowired(required = false)
- public CreateAction(DbClient dbClient, QProfileFactory profileFactory, QProfileExporters exporters, Languages languages,
- UserSession userSession, ActiveRuleIndexer activeRuleIndexer) {
- this(dbClient, profileFactory, exporters, languages, userSession, activeRuleIndexer, new ProfileImporter[0]);
}
@Override
@@ -90,7 +67,6 @@ public class CreateAction implements QProfileWsAction {
.setResponseExample(getClass().getResource("create-example.json"))
.setSince("5.2")
.setHandler(this);
- List<Change> changelog = new ArrayList<>();
create.createParam(PARAM_NAME)
.setRequired(true)
@@ -103,41 +79,22 @@ public class CreateAction implements QProfileWsAction {
.setDescription("Quality profile language")
.setExampleValue("js")
.setPossibleValues(getOrderedLanguageKeys(languages));
-
- for (ProfileImporter importer : importers) {
- String backupParamName = getBackupParamName(importer.getKey());
- create.createParam(backupParamName)
- .setDescription(String.format("A configuration file for %s.", importer.getName()))
- .setDeprecatedSince("9.8");
- changelog.add(new Change("9.8", String.format("'%s' parameter is deprecated", backupParamName)));
- }
-
- create.setChangelog(changelog.toArray(new Change[0]));
}
@Override
public void handle(Request request, Response response) throws Exception {
userSession.checkLoggedIn();
+ userSession.checkPermission(ADMINISTER_QUALITY_PROFILES);
try (DbSession dbSession = dbClient.openSession(false)) {
- userSession.checkPermission(ADMINISTER_QUALITY_PROFILES);
CreateRequest createRequest = toRequest(request);
- writeProtobuf(doHandle(dbSession, createRequest, request), request, response);
+ writeProtobuf(doHandle(dbSession, createRequest), request, response);
}
}
- private CreateWsResponse doHandle(DbSession dbSession, CreateRequest createRequest, Request request) {
- QProfileResult result = new QProfileResult();
+ private CreateWsResponse doHandle(DbSession dbSession, CreateRequest createRequest) {
QProfileDto profile = profileFactory.checkAndCreateCustom(dbSession, QProfileName.createFor(createRequest.getLanguage(), createRequest.getName()));
- result.setProfile(profile);
- for (ProfileImporter importer : importers) {
- String importerKey = importer.getKey();
- InputStream contentToImport = request.paramAsInputStream(getBackupParamName(importerKey));
- if (contentToImport != null) {
- result.add(exporters.importXml(profile, importerKey, contentToImport, dbSession));
- }
- }
- activeRuleIndexer.commitAndIndex(dbSession, result.getChanges());
- return buildResponse(result);
+ dbSession.commit();
+ return buildResponse(profile);
}
private static CreateRequest toRequest(Request request) {
@@ -147,28 +104,18 @@ public class CreateAction implements QProfileWsAction {
return builder.build();
}
- private CreateWsResponse buildResponse(QProfileResult result) {
- String language = result.profile().getLanguage();
+ private CreateWsResponse buildResponse(QProfileDto profile) {
+ String language = profile.getLanguage();
CreateWsResponse.QualityProfile.Builder builder = CreateWsResponse.QualityProfile.newBuilder()
- .setKey(result.profile().getKee())
- .setName(result.profile().getName())
+ .setKey(profile.getKee())
+ .setName(profile.getName())
.setLanguage(language)
- .setLanguageName(languages.get(result.profile().getLanguage()).getName())
+ .setLanguageName(languages.get(profile.getLanguage()).getName())
.setIsDefault(false)
.setIsInherited(false);
- if (!result.infos().isEmpty()) {
- builder.getInfosBuilder().addAllInfos(result.infos());
- }
- if (!result.warnings().isEmpty()) {
- builder.getWarningsBuilder().addAllWarnings(result.warnings());
- }
return CreateWsResponse.newBuilder().setProfile(builder.build()).build();
}
- private static String getBackupParamName(String importerKey) {
- return String.format(PARAM_BACKUP_FORMAT, importerKey);
- }
-
private static class CreateRequest {
private final String name;
private final String language;
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ExportAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ExportAction.java
index ce8e5c5bdc0..bd39c10a605 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ExportAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ExportAction.java
@@ -24,14 +24,10 @@ import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Set;
-import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
-import org.sonar.api.profiles.ProfileExporter;
import org.sonar.api.resources.Languages;
+import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.Response.Stream;
@@ -42,7 +38,6 @@ import org.sonar.db.DbSession;
import org.sonar.db.qualityprofile.QProfileDto;
import org.sonar.server.language.LanguageParamUtils;
import org.sonar.server.qualityprofile.QProfileBackuper;
-import org.sonar.server.qualityprofile.QProfileExporters;
import org.sonarqube.ws.MediaTypes;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -52,17 +47,13 @@ import static org.sonarqube.ws.client.qualityprofile.QualityProfileWsParameters.
public class ExportAction implements QProfileWsAction {
- private static final String PARAM_EXPORTER_KEY = "exporterKey";
-
private final DbClient dbClient;
private final QProfileBackuper backuper;
- private final QProfileExporters exporters;
private final Languages languages;
- public ExportAction(DbClient dbClient, QProfileBackuper backuper, QProfileExporters exporters, Languages languages) {
+ public ExportAction(DbClient dbClient, QProfileBackuper backuper, Languages languages) {
this.dbClient = dbClient;
this.backuper = backuper;
- this.exporters = exporters;
this.languages = languages;
}
@@ -72,7 +63,10 @@ public class ExportAction implements QProfileWsAction {
.setSince("5.2")
.setDescription("Export a quality profile.")
.setResponseExample(getClass().getResource("export-example.xml"))
- .setHandler(this);
+ .setHandler(this)
+ .setDeprecatedSince("25.4")
+ .setChangelog(
+ new Change("25.4", "Deprecated. Use GET /api/qualityprofiles/backup instead"));
action.createParam(PARAM_QUALITY_PROFILE)
.setDescription("Quality profile name to export. If left empty, the default profile for the language is exported.")
@@ -84,17 +78,6 @@ public class ExportAction implements QProfileWsAction {
.setExampleValue(LanguageParamUtils.getExampleValue(languages))
.setPossibleValues(LanguageParamUtils.getOrderedLanguageKeys(languages));
- Set<String> exporterKeys = Arrays.stream(languages.all())
- .map(language -> exporters.exportersForLanguage(language.getKey()))
- .flatMap(Collection::stream)
- .map(ProfileExporter::getKey)
- .collect(Collectors.toSet());
- if (!exporterKeys.isEmpty()) {
- action.createParam(PARAM_EXPORTER_KEY)
- .setDescription("Output format. If left empty, the same format as api/qualityprofiles/backup is used. " +
- "Possible values are described by api/qualityprofiles/exporters.")
- .setPossibleValues(exporterKeys);
- }
}
@Override
@@ -104,22 +87,16 @@ public class ExportAction implements QProfileWsAction {
try (DbSession dbSession = dbClient.openSession(false)) {
QProfileDto profile = loadProfile(dbSession, language, name);
- String exporterKey = exporters.exportersForLanguage(profile.getLanguage()).isEmpty() ? null : request.param(PARAM_EXPORTER_KEY);
- writeResponse(dbSession, profile, exporterKey, response);
+ writeResponse(dbSession, profile, response);
}
}
- private void writeResponse(DbSession dbSession, QProfileDto profile, @Nullable String exporterKey, Response response) throws IOException {
+ private void writeResponse(DbSession dbSession, QProfileDto profile, Response response) throws IOException {
Stream stream = response.stream();
ByteArrayOutputStream bufferStream = new ByteArrayOutputStream();
try (Writer writer = new OutputStreamWriter(bufferStream, UTF_8)) {
- if (exporterKey == null) {
- stream.setMediaType(MediaTypes.XML);
- backuper.backup(dbSession, profile, writer);
- } else {
- stream.setMediaType(exporters.mimeType(exporterKey));
- exporters.export(dbSession, profile, exporterKey, writer);
- }
+ stream.setMediaType(MediaTypes.XML);
+ backuper.backup(dbSession, profile, writer);
}
OutputStream output = response.stream().output();
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ExportersAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ExportersAction.java
index de3190bcf3f..57a318e4761 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ExportersAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ExportersAction.java
@@ -19,53 +19,27 @@
*/
package org.sonar.server.qualityprofile.ws;
-import org.sonar.api.profiles.ProfileExporter;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService.NewController;
import org.sonar.api.utils.text.JsonWriter;
-import org.springframework.beans.factory.annotation.Autowired;
public class ExportersAction implements QProfileWsAction {
- private final ProfileExporter[] exporters;
-
- @Autowired(required = false)
- public ExportersAction(ProfileExporter[] exporters) {
- this.exporters = exporters;
- }
-
- /**
- * Used by the container if no {@link ProfileExporter} is found
- */
- @Autowired(required = false)
- public ExportersAction() {
- this(new ProfileExporter[0]);
- }
-
@Override
public void define(NewController context) {
context.createAction("exporters")
- .setDescription("Lists available profile export formats.")
+ .setDescription("Deprecated. No more custom profile exporters.")
.setHandler(this)
.setResponseExample(getClass().getResource("exporters-example.json"))
- .setSince("5.2");
+ .setSince("5.2")
+ .setDeprecatedSince("25.4");
}
@Override
public void handle(Request request, Response response) throws Exception {
try (JsonWriter json = response.newJsonWriter()) {
json.beginObject().name("exporters").beginArray();
- for (ProfileExporter exporter : exporters) {
- json.beginObject()
- .prop("key", exporter.getKey())
- .prop("name", exporter.getName());
- json.name("languages").beginArray();
- for (String language : exporter.getSupportedLanguages()) {
- json.value(language);
- }
- json.endArray().endObject();
- }
json.endArray().endObject();
}
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ImportersAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ImportersAction.java
index 078434db363..3d93cc675bb 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ImportersAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/qualityprofile/ws/ImportersAction.java
@@ -19,50 +19,28 @@
*/
package org.sonar.server.qualityprofile.ws;
-import org.sonar.api.profiles.ProfileImporter;
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.text.JsonWriter;
-import org.springframework.beans.factory.annotation.Autowired;
public class ImportersAction implements QProfileWsAction {
- private final ProfileImporter[] importers;
-
- @Autowired(required = false)
- public ImportersAction(ProfileImporter[] importers) {
- this.importers = importers;
- }
-
- @Autowired(required = false)
- public ImportersAction() {
- this(new ProfileImporter[0]);
- }
-
@Override
public void define(WebService.NewController controller) {
controller.createAction("importers")
+ .setDescription("Deprecated. No more custom profile importers.")
.setSince("5.2")
.setDescription("List supported importers.")
.setResponseExample(getClass().getResource("importers-example.json"))
- .setHandler(this);
+ .setHandler(this)
+ .setDeprecatedSince("25.4");
}
@Override
public void handle(Request request, Response response) throws Exception {
try (JsonWriter json = response.newJsonWriter()) {
json.beginObject().name("importers").beginArray();
- for (ProfileImporter importer : importers) {
- json.beginObject()
- .prop("key", importer.getKey())
- .prop("name", importer.getName())
- .name("languages").beginArray();
- for (String languageKey : importer.getSupportedLanguages()) {
- json.value(languageKey);
- }
- json.endArray().endObject();
- }
json.endArray().endObject();
}
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/CurrentAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/CurrentAction.java
index 36a5c376f48..aabb5ff63a4 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/CurrentAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/CurrentAction.java
@@ -49,7 +49,6 @@ import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.sonar.api.web.UserRole.USER;
-import static org.sonar.server.user.ws.DismissNoticeAction.AVAILABLE_NOTICE_KEYS;
import static org.sonar.server.ws.WsUtils.writeProtobuf;
import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.APPLICATION;
import static org.sonarqube.ws.Users.CurrentWsResponse.HomepageType.PORTFOLIO;
@@ -124,7 +123,8 @@ public class CurrentAction implements UsersWsAction {
.setHomepage(buildHomepage(dbSession, user))
.setUsingSonarLintConnectedMode(user.getLastSonarlintConnectionDate() != null);
- AVAILABLE_NOTICE_KEYS.forEach(key -> builder.putDismissedNotices(key, isNoticeDismissed(user, key)));
+ DismissNoticeAction.DismissNotices.getAvailableKeys()
+ .forEach(key -> builder.putDismissedNotices(key, isNoticeDismissed(user, key)));
ofNullable(emptyToNull(user.getEmail())).ifPresent(builder::setEmail);
ofNullable(emptyToNull(user.getEmail())).ifPresent(u -> builder.setAvatar(avatarResolver.create(user)));
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DismissNoticeAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DismissNoticeAction.java
index 7a779e06308..407ffcf2f36 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DismissNoticeAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/user/ws/DismissNoticeAction.java
@@ -19,7 +19,9 @@
*/
package org.sonar.server.user.ws;
-import java.util.List;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
@@ -31,23 +33,52 @@ import org.sonar.db.property.PropertyQuery;
import org.sonar.server.user.UserSession;
import static com.google.common.base.Preconditions.checkState;
+import static org.sonar.server.user.ws.DismissNoticeAction.DismissNotices.EDUCATION_PRINCIPLES;
+import static org.sonar.server.user.ws.DismissNoticeAction.DismissNotices.ISSUE_CLEAN_CODE_GUIDE;
+import static org.sonar.server.user.ws.DismissNoticeAction.DismissNotices.ISSUE_NEW_ISSUE_STATUS_AND_TRANSITION_GUIDE;
+import static org.sonar.server.user.ws.DismissNoticeAction.DismissNotices.ONBOARDING_CAYC_BRANCH_SUMMARY_GUIDE;
+import static org.sonar.server.user.ws.DismissNoticeAction.DismissNotices.QUALITY_GATE_CAYC_CONDITIONS_SIMPLIFICATION;
+import static org.sonar.server.user.ws.DismissNoticeAction.DismissNotices.SHOW_DNA_BANNER;
+import static org.sonar.server.user.ws.DismissNoticeAction.DismissNotices.SHOW_DNA_OPTIN_BANNER;
+import static org.sonar.server.user.ws.DismissNoticeAction.DismissNotices.SHOW_DNA_TOUR;
+import static org.sonar.server.user.ws.DismissNoticeAction.DismissNotices.SHOW_NEW_MODES_BANNER;
+import static org.sonar.server.user.ws.DismissNoticeAction.DismissNotices.SHOW_NEW_MODES_TOUR;
public class DismissNoticeAction implements UsersWsAction {
- private static final String EDUCATION_PRINCIPLES = "educationPrinciples";
- private static final String SONARLINT_AD = "sonarlintAd";
- private static final String ISSUE_CLEAN_CODE_GUIDE = "issueCleanCodeGuide";
- private static final String QUALITY_GATE_CAYC_CONDITIONS_SIMPLIFICATION = "qualityGateCaYCConditionsSimplification";
- private static final String OVERVIEW_ZERO_NEW_ISSUES_SIMPLIFICATION = "overviewZeroNewIssuesSimplification";
- private static final String ISSUE_NEW_ISSUE_STATUS_AND_TRANSITION_GUIDE = "issueNewIssueStatusAndTransitionGuide";
- private static final String ONBOARDING_CAYC_BRANCH_SUMMARY_GUIDE = "onboardingDismissCaycBranchSummaryGuide";
- private static final String SHOW_NEW_MODES_TOUR = "showNewModesTour";
- private static final String SHOW_NEW_MODES_BANNER = "showNewModesBanner";
-
- protected static final List<String> AVAILABLE_NOTICE_KEYS = List.of(EDUCATION_PRINCIPLES, SONARLINT_AD, ISSUE_CLEAN_CODE_GUIDE, QUALITY_GATE_CAYC_CONDITIONS_SIMPLIFICATION,
- OVERVIEW_ZERO_NEW_ISSUES_SIMPLIFICATION, ISSUE_NEW_ISSUE_STATUS_AND_TRANSITION_GUIDE, ONBOARDING_CAYC_BRANCH_SUMMARY_GUIDE, SHOW_NEW_MODES_TOUR, SHOW_NEW_MODES_BANNER);
+ public enum DismissNotices {
+ EDUCATION_PRINCIPLES("educationPrinciples"),
+ SONARLINT_AD("sonarlintAd"),
+ ISSUE_CLEAN_CODE_GUIDE("issueCleanCodeGuide"),
+ QUALITY_GATE_CAYC_CONDITIONS_SIMPLIFICATION("qualityGateCaYCConditionsSimplification"),
+ OVERVIEW_ZERO_NEW_ISSUES_SIMPLIFICATION("overviewZeroNewIssuesSimplification"),
+ ISSUE_NEW_ISSUE_STATUS_AND_TRANSITION_GUIDE("issueNewIssueStatusAndTransitionGuide"),
+ ONBOARDING_CAYC_BRANCH_SUMMARY_GUIDE("onboardingDismissCaycBranchSummaryGuide"),
+ SHOW_NEW_MODES_TOUR("showNewModesTour"),
+ SHOW_NEW_MODES_BANNER("showNewModesBanner"),
+ SHOW_DNA_OPTIN_BANNER("showDesignAndArchitectureOptInBanner"),
+ SHOW_DNA_BANNER("showDesignAndArchitectureBanner"),
+ SHOW_DNA_TOUR("showDesignAndArchitectureTour"),
+ ;
+
+ private final String key;
+
+ DismissNotices(String key) {
+ this.key = key;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public static Set<String> getAvailableKeys() {
+ return Arrays.stream(values())
+ .map(DismissNotices::getKey)
+ .collect(Collectors.toSet());
+ }
+ }
public static final String USER_DISMISS_CONSTANT = "user.dismissedNotices.";
- public static final String SUPPORT_FOR_NEW_NOTICE_MESSAGE = "Support for new notice '%s' was added.";
+ private static final String SUPPORT_FOR_NEW_NOTICE_MESSAGE = "Support for new notice '%s' was added.";
private final UserSession userSession;
private final DbClient dbClient;
@@ -57,16 +88,29 @@ public class DismissNoticeAction implements UsersWsAction {
this.dbClient = dbClient;
}
+ private static String printNewNotice(DismissNotices notice) {
+ return SUPPORT_FOR_NEW_NOTICE_MESSAGE.formatted(notice.getKey());
+ }
+
+ private static String printNewNotice(DismissNotices... notices) {
+ String noticesList = Arrays.stream(notices)
+ .map(DismissNotices::getKey)
+ .collect(Collectors.joining(", "));
+ return SUPPORT_FOR_NEW_NOTICE_MESSAGE.formatted(noticesList);
+ }
+
+
@Override
public void define(WebService.NewController context) {
WebService.NewAction action = context.createAction("dismiss_notice")
.setDescription("Dismiss a notice for the current user. Silently ignore if the notice is already dismissed.")
- .setChangelog(new Change("10.8", SUPPORT_FOR_NEW_NOTICE_MESSAGE.formatted(SHOW_NEW_MODES_TOUR)))
- .setChangelog(new Change("10.8", SUPPORT_FOR_NEW_NOTICE_MESSAGE.formatted(SHOW_NEW_MODES_BANNER)))
- .setChangelog(new Change("10.6", SUPPORT_FOR_NEW_NOTICE_MESSAGE.formatted(ONBOARDING_CAYC_BRANCH_SUMMARY_GUIDE)))
- .setChangelog(new Change("10.4", SUPPORT_FOR_NEW_NOTICE_MESSAGE.formatted(ISSUE_NEW_ISSUE_STATUS_AND_TRANSITION_GUIDE)))
- .setChangelog(new Change("10.3", SUPPORT_FOR_NEW_NOTICE_MESSAGE.formatted(QUALITY_GATE_CAYC_CONDITIONS_SIMPLIFICATION)))
- .setChangelog(new Change("10.2", SUPPORT_FOR_NEW_NOTICE_MESSAGE.formatted(ISSUE_CLEAN_CODE_GUIDE)))
+ .setChangelog(new Change("25.4", printNewNotice(SHOW_DNA_OPTIN_BANNER, SHOW_DNA_BANNER, SHOW_DNA_TOUR)))
+ .setChangelog(new Change("10.8", printNewNotice(SHOW_NEW_MODES_TOUR)))
+ .setChangelog(new Change("10.8", printNewNotice(SHOW_NEW_MODES_BANNER)))
+ .setChangelog(new Change("10.6", printNewNotice(ONBOARDING_CAYC_BRANCH_SUMMARY_GUIDE)))
+ .setChangelog(new Change("10.4", printNewNotice(ISSUE_NEW_ISSUE_STATUS_AND_TRANSITION_GUIDE)))
+ .setChangelog(new Change("10.3", printNewNotice(QUALITY_GATE_CAYC_CONDITIONS_SIMPLIFICATION)))
+ .setChangelog(new Change("10.2", printNewNotice(ISSUE_CLEAN_CODE_GUIDE)))
.setSince("9.6")
.setInternal(true)
.setHandler(this)
@@ -75,7 +119,7 @@ public class DismissNoticeAction implements UsersWsAction {
action.createParam("notice")
.setDescription("notice key to dismiss")
.setExampleValue(EDUCATION_PRINCIPLES)
- .setPossibleValues(AVAILABLE_NOTICE_KEYS);
+ .setPossibleValues(DismissNotices.getAvailableKeys());
}
@Override
@@ -89,7 +133,7 @@ public class DismissNoticeAction implements UsersWsAction {
dismissNotice(response, currentUserUuid, noticeKeyParam);
}
- public void dismissNotice(Response response, String currentUserUuid, String noticeKeyParam) {
+ private void dismissNotice(Response response, String currentUserUuid, String noticeKeyParam) {
try (DbSession dbSession = dbClient.openSession(false)) {
String paramKey = USER_DISMISS_CONSTANT + noticeKeyParam;
PropertyQuery query = new PropertyQuery.Builder()
diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/create-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/create-example.json
index b154d6233ae..a1f54c5aa82 100644
--- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/create-example.json
+++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/create-example.json
@@ -6,11 +6,5 @@
"languageName" : "Java",
"name" : "My New Profile",
"key" : "AU-TpxcA-iU5OvuD2FL1"
- },
- "warnings" : [
- "Unable to import unknown PMD rule 'rulesets/java/strings.xml'",
- "Unable to import unknown PMD rule 'rulesets/java/basic.xml/UnnecessaryConversionTemporary'",
- "Unable to import unknown PMD rule 'rulesets/java/basic.xml/EmptyCatchBlock'",
- "Unable to import unknown PMD rule 'rulesets/java/braces.xml'"
- ]
+ }
}
diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/exporters-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/exporters-example.json
index 642a2bde09d..2d1cd2df442 100644
--- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/exporters-example.json
+++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/exporters-example.json
@@ -1,33 +1,4 @@
{
"exporters": [
- {
- "key": "pmd",
- "name": "PMD",
- "languages": [
- "java"
- ]
- },
- {
- "key": "checkstyle",
- "name": "Checkstyle",
- "languages": [
- "java"
- ]
- },
- {
- "key": "js-lint",
- "name": "JS Lint",
- "languages": [
- "js"
- ]
- },
- {
- "key": "android-lint",
- "name": "Android Lint",
- "languages": [
- "xml",
- "java"
- ]
- }
]
}
diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/importers-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/importers-example.json
index 8750609887a..a6d01b82776 100644
--- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/importers-example.json
+++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/qualityprofile/ws/importers-example.json
@@ -1,33 +1,4 @@
{
"importers": [
- {
- "key": "pmd",
- "name": "PMD",
- "languages": [
- "java"
- ]
- },
- {
- "key": "checkstyle",
- "name": "Checkstyle",
- "languages": [
- "java"
- ]
- },
- {
- "key": "js-lint",
- "name": "JS Lint",
- "languages": [
- "js"
- ]
- },
- {
- "key": "android-lint",
- "name": "Android Lint",
- "languages": [
- "xml",
- "java"
- ]
- }
]
}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ExportersActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ExportersActionTest.java
index 4ab5db1b9ba..d2298c7f0d5 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ExportersActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ExportersActionTest.java
@@ -19,10 +19,7 @@
*/
package org.sonar.server.qualityprofile.ws;
-import java.io.Writer;
import org.junit.Test;
-import org.sonar.api.profiles.ProfileExporter;
-import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.ws.WsActionTester;
@@ -30,7 +27,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.test.JsonAssert.assertJson;
public class ExportersActionTest {
- private WsActionTester ws = new WsActionTester(new ExportersAction(createExporters()));
+ private final WsActionTester ws = new WsActionTester(new ExportersAction());
@Test
public void importers_nominal() {
@@ -48,24 +45,4 @@ public class ExportersActionTest {
assertThat(exporters.responseExampleAsString()).isNotEmpty();
}
- private ProfileExporter[] createExporters() {
- class NoopImporter extends ProfileExporter {
- private NoopImporter(String key, String name, String... languages) {
- super(key, name);
- setSupportedLanguages(languages);
- }
-
- @Override
- public void exportProfile(RulesProfile profile, Writer writer) {
- // Nothing
- }
-
- }
- return new ProfileExporter[] {
- new NoopImporter("pmd", "PMD", "java"),
- new NoopImporter("checkstyle", "Checkstyle", "java"),
- new NoopImporter("js-lint", "JS Lint", "js"),
- new NoopImporter("android-lint", "Android Lint", "xml", "java")
- };
- }
}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ImportersActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ImportersActionTest.java
index 067a394af5a..63918caa13f 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ImportersActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/ws/ImportersActionTest.java
@@ -19,12 +19,8 @@
*/
package org.sonar.server.qualityprofile.ws;
-import java.io.Reader;
import org.junit.Test;
-import org.sonar.api.profiles.ProfileImporter;
-import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.server.ws.WebService;
-import org.sonar.api.utils.ValidationMessages;
import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
@@ -32,7 +28,7 @@ import static org.sonar.test.JsonAssert.assertJson;
public class ImportersActionTest {
- private WsActionTester ws = new WsActionTester(new ImportersAction(createImporters()));
+ private WsActionTester ws = new WsActionTester(new ImportersAction());
@Test
public void empty_importers() {
@@ -59,25 +55,4 @@ public class ImportersActionTest {
assertThat(importers.responseExampleAsString()).isNotEmpty();
}
- private ProfileImporter[] createImporters() {
- class NoopImporter extends ProfileImporter {
- private NoopImporter(String key, String name, String... languages) {
- super(key, name);
- setSupportedLanguages(languages);
- }
-
- @Override
- public RulesProfile importProfile(Reader reader, ValidationMessages messages) {
- return RulesProfile.create();
- }
-
- }
-
- return new ProfileImporter[] {
- new NoopImporter("pmd", "PMD", "java"),
- new NoopImporter("checkstyle", "Checkstyle", "java"),
- new NoopImporter("js-lint", "JS Lint", "js"),
- new NoopImporter("android-lint", "Android Lint", "xml", "java")
- };
- }
}
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
index 6d29800e273..8d6253b5e58 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel3.java
@@ -31,6 +31,7 @@ import org.sonar.server.platform.serverid.ServerIdModule;
import org.sonar.server.plugins.DetectPluginChange;
import org.sonar.server.setting.DatabaseSettingLoader;
import org.sonar.server.setting.DatabaseSettingsEnabler;
+import org.sonar.server.startup.PropertiesDBCleaner;
import static org.sonar.core.extension.CoreExtensionsInstaller.noAdditionalSideFilter;
import static org.sonar.core.extension.PlatformLevelPredicates.hasPlatformLevel;
@@ -49,6 +50,7 @@ public class PlatformLevel3 extends PlatformLevel {
NoopDatabaseMigrationImpl.class,
new ServerIdModule(),
ServerImpl.class,
+ PropertiesDBCleaner.class,
DatabaseSettingLoader.class,
DatabaseSettingsEnabler.class,
UriReader.class,
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index a9885cbf8d6..cb5cb05bc90 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -248,7 +248,6 @@ import org.sonar.server.qualitygate.ws.QualityGateWsModule;
import org.sonar.server.qualityprofile.QProfileBackuperImpl;
import org.sonar.server.qualityprofile.QProfileComparison;
import org.sonar.server.qualityprofile.QProfileCopier;
-import org.sonar.server.qualityprofile.QProfileExporters;
import org.sonar.server.qualityprofile.QProfileFactoryImpl;
import org.sonar.server.qualityprofile.QProfileParser;
import org.sonar.server.qualityprofile.QProfileResetImpl;
@@ -372,7 +371,6 @@ public class PlatformLevel4 extends PlatformLevel {
QProfileRulesImpl.class,
RuleActivator.class,
QualityProfileChangeEventServiceImpl.class,
- QProfileExporters.class,
QProfileFactoryImpl.class,
QProfileCopier.class,
QProfileBackuperImpl.class,
diff --git a/settings.gradle b/settings.gradle
index e8b44803d22..bcdc1176596 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -17,10 +17,6 @@ pluginManagement {
}
}
}
- plugins {
- id 'com.bmuschko.docker-remote-api' version '9.4.0'
- id 'org.ajoberstar.grgit' version '4.1.1'
- }
}
rootProject.name = 'sonarqube'
diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
index b4fd5512335..e84f2d3d5c8 100644
--- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
+++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
@@ -19,8 +19,6 @@
*/
package org.sonar.api.batch.sensor.internal;
-import static java.util.Collections.unmodifiableMap;
-
import java.io.File;
import java.io.InputStream;
import java.io.Serializable;
@@ -90,6 +88,8 @@ import org.sonar.api.scanner.fs.InputProject;
import org.sonar.api.utils.System2;
import org.sonar.api.utils.Version;
+import static java.util.Collections.unmodifiableMap;
+
/**
* Utility class to help testing {@link Sensor}. This is not an API and method signature may evolve.
* <p>
@@ -462,4 +462,5 @@ public class SensorContextTester implements SensorContext {
public NewSignificantCode newSignificantCode() {
return new DefaultSignificantCode(sensorStorage);
}
+
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java
index 6f67c7afa98..9c41891dbab 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java
@@ -26,7 +26,6 @@ import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.internal.FileMetadata;
import org.sonar.api.batch.rule.CheckFactory;
import org.sonar.api.batch.sensor.issue.internal.DefaultNoSonarFilter;
-import org.sonar.api.config.PropertyDefinition;
import org.sonar.api.scan.filesystem.PathResolver;
import org.sonar.core.extension.CoreExtensionsInstaller;
import org.sonar.core.metric.ScannerMetrics;
@@ -154,25 +153,10 @@ public class SpringScannerContainer extends SpringComponentContainer {
@Override
protected void doBeforeStart() {
- addSuffixesDeprecatedProperties();
addScannerExtensions();
addComponents();
}
- private void addSuffixesDeprecatedProperties() {
- add(
- /*
- * This is needed to support properly the deprecated sonar.rpg.suffixes property when the download optimization feature is enabled.
- * The value of the property is needed at the preprocessing stage, but being defined by an optional analyzer means that at preprocessing
- * it won't be properly available. This will be removed in SQ 11.0 together with the drop of the property from the rpg analyzer.
- * See SONAR-21514
- */
- PropertyDefinition.builder("sonar.rpg.file.suffixes")
- .deprecatedKey("sonar.rpg.suffixes")
- .multiValues(true)
- .build());
- }
-
private void addScannerExtensions() {
getParentComponentByType(CoreExtensionsInstaller.class)
.install(this, noExtensionFilter(), extension -> getScannerProjectExtensionsFilter().accept(extension));
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuePublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuePublisher.java
index 1e37a715066..df9bfd00b48 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuePublisher.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuePublisher.java
@@ -24,6 +24,7 @@ import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
@@ -54,6 +55,7 @@ import org.sonar.scanner.report.ReportPublisher;
@ThreadSafe
public class IssuePublisher {
+ private static final Set<String> noSonarKeyContains = Set.of("nosonar", "S1291");
private final ActiveRules activeRules;
private final IssueFilters filters;
private final ReportPublisher reportPublisher;
@@ -91,7 +93,7 @@ public class IssuePublisher {
return inputComponent.isFile()
&& textRange != null
&& ((DefaultInputFile) inputComponent).hasNoSonarAt(textRange.start().line())
- && !StringUtils.containsIgnoreCase(issue.ruleKey().rule(), "nosonar");
+ && noSonarKeyContains.stream().noneMatch(k -> StringUtils.containsIgnoreCase(issue.ruleKey().rule(), k));
}
public void initAndAddExternalIssue(ExternalIssue issue) {
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java
index 7ee00d8e08f..8a8e90cce25 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java
@@ -21,12 +21,19 @@ package org.sonar.scanner.sca;
import java.io.File;
import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Consumer;
+import javax.annotation.Nullable;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
@@ -36,6 +43,8 @@ import org.sonar.api.utils.System2;
import org.sonar.core.util.ProcessWrapperFactory;
import org.sonar.scanner.config.DefaultConfiguration;
import org.sonar.scanner.repository.TelemetryCache;
+import org.sonar.scanner.scm.ScmConfiguration;
+import org.sonar.scm.git.JGitUtils;
/**
* The CliService class is meant to serve as the main entrypoint for any commands
@@ -45,24 +54,26 @@ import org.sonar.scanner.repository.TelemetryCache;
*/
public class CliService {
private static final Logger LOG = LoggerFactory.getLogger(CliService.class);
+ public static final String EXCLUDED_MANIFESTS_PROP_KEY = "sonar.sca.excludedManifests";
+
private final ProcessWrapperFactory processWrapperFactory;
private final TelemetryCache telemetryCache;
private final System2 system2;
private final Server server;
+ private final ScmConfiguration scmConfiguration;
- public CliService(ProcessWrapperFactory processWrapperFactory, TelemetryCache telemetryCache, System2 system2, Server server) {
+ public CliService(ProcessWrapperFactory processWrapperFactory, TelemetryCache telemetryCache, System2 system2, Server server, ScmConfiguration scmConfiguration) {
this.processWrapperFactory = processWrapperFactory;
this.telemetryCache = telemetryCache;
this.system2 = system2;
this.server = server;
+ this.scmConfiguration = scmConfiguration;
}
public File generateManifestsZip(DefaultInputModule module, File cliExecutable, DefaultConfiguration configuration) throws IOException, IllegalStateException {
long startTime = system2.now();
boolean success = false;
try {
- var debugLevel = Level.DEBUG;
-
String zipName = "dependency-files.zip";
Path zipPath = module.getWorkDir().resolve(zipName);
List<String> args = new ArrayList<>();
@@ -75,29 +86,32 @@ public class CliService {
args.add("--directory");
args.add(module.getBaseDir().toString());
+ String excludeFlag = getExcludeFlag(module, configuration);
+ if (excludeFlag != null) {
+ args.add("--exclude");
+ args.add(excludeFlag);
+ }
+
boolean scaDebug = configuration.getBoolean("sonar.sca.debug").orElse(false);
if (LOG.isDebugEnabled() || scaDebug) {
LOG.info("Setting CLI to debug mode");
args.add("--debug");
- if (scaDebug) {
- // output --debug logs from stderr to the info level logger
- debugLevel = Level.INFO;
- }
}
- LOG.atLevel(debugLevel).log("Calling ProcessBuilder with args: {}", args);
-
Map<String, String> envProperties = new HashMap<>();
// sending this will tell the CLI to skip checking for the latest available version on startup
envProperties.put("TIDELIFT_SKIP_UPDATE_CHECK", "1");
envProperties.put("TIDELIFT_ALLOW_MANIFEST_FAILURES", "1");
envProperties.put("TIDELIFT_CLI_INSIDE_SCANNER_ENGINE", "1");
envProperties.put("TIDELIFT_CLI_SQ_SERVER_VERSION", server.getVersion());
- envProperties.putAll(ScaProperties.buildFromScannerProperties(configuration));
+ // EXCLUDED_MANIFESTS_PROP_KEY is a special case which we handle via --args, not environment variables
+ Set<String> ignoredProperties = Set.of(EXCLUDED_MANIFESTS_PROP_KEY);
+ envProperties.putAll(ScaProperties.buildFromScannerProperties(configuration, ignoredProperties));
- LOG.atLevel(debugLevel).log("Environment properties: {}", envProperties);
+ LOG.info("Running command: {}", args);
+ LOG.info("Environment properties: {}", envProperties);
- Consumer<String> logConsumer = LOG.atLevel(debugLevel)::log;
+ Consumer<String> logConsumer = LOG.atLevel(Level.INFO)::log;
processWrapperFactory.create(module.getWorkDir(), logConsumer, logConsumer, envProperties, args.toArray(new String[0])).execute();
LOG.info("Generated manifests zip file: {}", zipName);
success = true;
@@ -107,4 +121,81 @@ public class CliService {
telemetryCache.put("scanner.sca.execution.cli.success", String.valueOf(success));
}
}
+
+ private @Nullable String getExcludeFlag(DefaultInputModule module, DefaultConfiguration configuration) throws IOException {
+ List<String> configExcludedPaths = getConfigExcludedPaths(configuration);
+ List<String> scmIgnoredPaths = getScmIgnoredPaths(module);
+
+ ArrayList<String> mergedExclusionPaths = new ArrayList<>();
+ mergedExclusionPaths.addAll(configExcludedPaths);
+ mergedExclusionPaths.addAll(scmIgnoredPaths);
+
+ String workDirExcludedPath = getWorkDirExcludedPath(module);
+ if (workDirExcludedPath != null) {
+ mergedExclusionPaths.add(workDirExcludedPath);
+ }
+
+ if (mergedExclusionPaths.isEmpty()) {
+ return null;
+ }
+
+ // wrap each exclusion path in quotes to handle commas in file paths
+ return toCsvString(mergedExclusionPaths);
+ }
+
+ private static List<String> getConfigExcludedPaths(DefaultConfiguration configuration) {
+ String[] excludedPaths = configuration.getStringArray(EXCLUDED_MANIFESTS_PROP_KEY);
+ if (excludedPaths == null) {
+ return List.of();
+ }
+ return Arrays.stream(excludedPaths).toList();
+ }
+
+ private List<String> getScmIgnoredPaths(DefaultInputModule module) {
+ var scmProvider = scmConfiguration.provider();
+ // Only Git is supported at this time
+ if (scmProvider == null || scmProvider.key() == null || !scmProvider.key().equals("git")) {
+ return List.of();
+ }
+
+ if (scmConfiguration.isExclusionDisabled()) {
+ // The user has opted out of using the SCM exclusion rules
+ return List.of();
+ }
+
+ Path baseDirPath = module.getBaseDir();
+ List<String> scmIgnoredPaths = JGitUtils.getAllIgnoredPaths(baseDirPath);
+ if (scmIgnoredPaths.isEmpty()) {
+ return List.of();
+ }
+ return scmIgnoredPaths.stream()
+ .map(ignoredPathRel -> {
+ boolean isDirectory = Files.isDirectory(baseDirPath.resolve(ignoredPathRel));
+ // Directories need to get turned into a glob for the Tidelift CLI
+ return isDirectory ? (ignoredPathRel + "/**") : ignoredPathRel;
+ })
+ .toList();
+ }
+
+ private static String getWorkDirExcludedPath(DefaultInputModule module) {
+ Path baseDir = module.getBaseDir().toAbsolutePath().normalize();
+ Path workDir = module.getWorkDir().toAbsolutePath().normalize();
+
+ if (workDir.startsWith(baseDir)) {
+ // workDir is inside baseDir, so return the relative path as a glob
+ Path relativeWorkDir = baseDir.relativize(workDir);
+ return relativeWorkDir + "/**";
+ }
+
+ return null;
+ }
+
+ private static String toCsvString(List<String> values) throws IOException {
+ StringWriter sw = new StringWriter();
+ try (CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT)) {
+ printer.printRecord(values);
+ }
+ // trim to remove the trailing newline
+ return sw.toString().trim();
+ }
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaProperties.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaProperties.java
index 26a98e65fdc..5c848b4ddbc 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaProperties.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaProperties.java
@@ -20,7 +20,9 @@
package org.sonar.scanner.sca;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
+import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.sonar.scanner.config.DefaultConfiguration;
@@ -44,14 +46,22 @@ public class ScaProperties {
* { "sonar.someOtherProperty" : "value" } returns an empty map
*
* @param configuration the scanner configuration possibly containing sonar.sca.* properties
+ * @param ignoredPropertyNames property names that should not be processed as a property
* @return a map of Tidelift CLI compatible environment variable names to their configuration values
*/
- public static Map<String, String> buildFromScannerProperties(DefaultConfiguration configuration) {
- return configuration
- .getProperties()
+ public static Map<String, String> buildFromScannerProperties(DefaultConfiguration configuration, Set<String> ignoredPropertyNames) {
+ HashMap<String, String> props = new HashMap<>(configuration.getProperties());
+
+ // recursive mode defaults to true
+ if (!props.containsKey("sonar.sca.recursiveManifestSearch")) {
+ props.put("sonar.sca.recursiveManifestSearch", "true");
+ }
+
+ return props
.entrySet()
.stream()
.filter(entry -> entry.getKey().startsWith(SONAR_SCA_PREFIX))
+ .filter(entry -> !ignoredPropertyNames.contains(entry.getKey()))
.collect(Collectors.toMap(entry -> convertPropToEnvVariable(entry.getKey()), Map.Entry::getValue));
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java
index 855fd945109..6fb38fa4563 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java
@@ -260,4 +260,5 @@ public class ProjectSensorContext implements SensorContext {
}
return false;
}
+
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/JGitUtils.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/JGitUtils.java
index 972a8ce8da3..5b3b3257142 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/JGitUtils.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/JGitUtils.java
@@ -21,6 +21,9 @@ package org.sonar.scm.git;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.List;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
@@ -41,4 +44,15 @@ public class JGitUtils {
throw new IllegalStateException("Unable to open Git repository", e);
}
}
+
+ // Return a list of scm ignored paths relative to the baseDir.
+ public static List<String> getAllIgnoredPaths(Path baseDir) {
+ try (Repository repo = buildRepository(baseDir)) {
+ try (Git git = new Git(repo)) {
+ return git.status().call().getIgnoredNotInIndex().stream().sorted().toList();
+ } catch (GitAPIException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuePublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuePublisherTest.java
index 5a124b74214..de4a41c519b 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuePublisherTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuePublisherTest.java
@@ -26,6 +26,8 @@ import java.util.List;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -67,7 +69,6 @@ import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY;
@RunWith(MockitoJUnitRunner.class)
public class IssuePublisherTest {
private static final RuleKey JAVA_RULE_KEY = RuleKey.of("java", "AvoidCycle");
- private static final RuleKey NOSONAR_RULE_KEY = RuleKey.of("java", "NoSonarCheck");
private DefaultInputProject project;
@@ -279,11 +280,13 @@ public class IssuePublisherTest {
verifyNoInteractions(reportPublisher);
}
- @Test
- public void should_accept_issues_on_no_sonar_rules() {
+ @ParameterizedTest
+ @ValueSource(strings = {"NoSonarCheck", "S1291", "S1291Check"})
+ public void should_accept_issues_on_no_sonar_rules(String noSonarRule) {
+ RuleKey noSonarRuleKey = RuleKey.of("java", noSonarRule);
// The "No Sonar" rule logs violations on the lines that are flagged with "NOSONAR" !!
activeRulesBuilder.addRule(new NewActiveRule.Builder()
- .setRuleKey(NOSONAR_RULE_KEY)
+ .setRuleKey(noSonarRuleKey)
.setSeverity(Severity.INFO)
.setQProfileKey("qp-1")
.build());
@@ -293,7 +296,7 @@ public class IssuePublisherTest {
DefaultIssue issue = new DefaultIssue(project)
.at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message(""))
- .forRule(NOSONAR_RULE_KEY);
+ .forRule(noSonarRuleKey);
when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class), anyString())).thenReturn(true);
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java
index b8bb1a26961..597fafa833c 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/CliServiceTest.java
@@ -23,25 +23,34 @@ import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.SystemUtils;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
+import org.mockito.MockedStatic;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.scm.ScmProvider;
import org.sonar.api.platform.Server;
import org.sonar.api.testfixtures.log.LogTesterJUnit5;
import org.sonar.api.utils.System2;
import org.sonar.core.util.ProcessWrapperFactory;
import org.sonar.scanner.config.DefaultConfiguration;
import org.sonar.scanner.repository.TelemetryCache;
+import org.sonar.scanner.scm.ScmConfiguration;
+import org.sonar.scm.git.GitScmProvider;
+import org.sonar.scm.git.JGitUtils;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.slf4j.event.Level.DEBUG;
@@ -55,25 +64,53 @@ class CliServiceTest {
private final LogTesterJUnit5 logTester = new LogTesterJUnit5();
@TempDir
Path rootModuleDir;
+ private final ScmConfiguration scmConfiguration = mock(ScmConfiguration.class);
+ private final ScmProvider scmProvider = mock(GitScmProvider.class);
+ ProcessWrapperFactory processWrapperFactory = mock(ProcessWrapperFactory.class, CALLS_REAL_METHODS);
+ private MockedStatic<JGitUtils> jGitUtilsMock;
+ DefaultConfiguration configuration = mock(DefaultConfiguration.class);
private CliService underTest;
@BeforeEach
- void setup() {
+ void setup() throws IOException {
telemetryCache = new TelemetryCache();
+ Path workDir = rootModuleDir.resolve(".scannerwork");
+ Files.createDirectories(workDir);
rootInputModule = new DefaultInputModule(
- ProjectDefinition.create().setBaseDir(rootModuleDir.toFile()).setWorkDir(rootModuleDir.toFile()));
- underTest = new CliService(new ProcessWrapperFactory(), telemetryCache, System2.INSTANCE, server);
+ ProjectDefinition.create().setBaseDir(rootModuleDir.toFile()).setWorkDir(workDir.toFile()));
+ when(scmConfiguration.provider()).thenReturn(scmProvider);
+ when(scmProvider.key()).thenReturn("git");
+ when(scmConfiguration.isExclusionDisabled()).thenReturn(false);
+ jGitUtilsMock = org.mockito.Mockito.mockStatic(JGitUtils.class);
+ jGitUtilsMock.when(() -> JGitUtils.getAllIgnoredPaths(any(Path.class))).thenReturn(List.of("ignored.txt"));
+ when(server.getVersion()).thenReturn("1.0.0");
+ logTester.setLevel(INFO);
+ when(configuration.getBoolean("sonar.sca.debug")).thenReturn(Optional.of(true));
+
+ underTest = new CliService(processWrapperFactory, telemetryCache, System2.INSTANCE, server, scmConfiguration);
+ }
+
+ @AfterEach
+ void teardown() {
+ if (jGitUtilsMock != null) {
+ jGitUtilsMock.close();
+ }
}
@Test
void generateZip_shouldCallProcessCorrectly_andRegisterTelemetry() throws IOException, URISyntaxException {
assertThat(rootModuleDir.resolve("test_file").toFile().createNewFile()).isTrue();
- // We need to set the logging level to debug in order to be able to view the shell script's output
- logTester.setLevel(DEBUG);
+ when(configuration.getProperties()).thenReturn(Map.of("sonar.sca.recursiveManifestSearch", "true", CliService.EXCLUDED_MANIFESTS_PROP_KEY, "foo,bar,baz/**"));
+ when(configuration.get("sonar.sca.recursiveManifestSearch")).thenReturn(Optional.of("true"));
+ when(configuration.getStringArray(CliService.EXCLUDED_MANIFESTS_PROP_KEY)).thenReturn(new String[] {"foo", "bar", "baz/**"});
- List<String> args = List.of(
+ File producedZip = underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
+
+ assertThat(producedZip).exists();
+
+ var expectedArguments = List.of(
"projects",
"save-lockfiles",
"--zip",
@@ -81,35 +118,31 @@ class CliServiceTest {
rootInputModule.getWorkDir().resolve("dependency-files.zip").toString(),
"--directory",
rootInputModule.getBaseDir().toString(),
+ "--exclude",
+ "foo,bar,baz/**,ignored.txt,.scannerwork/**",
"--debug");
- String argumentOutput = "Arguments Passed In: " + String.join(" ", args);
- DefaultConfiguration configuration = mock(DefaultConfiguration.class);
- when(configuration.getProperties()).thenReturn(Map.of("sonar.sca.recursiveManifestSearch", "true"));
- when(configuration.get("sonar.sca.recursiveManifestSearch")).thenReturn(Optional.of("true"));
-
- File producedZip = underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
- assertThat(producedZip).exists();
-
- assertThat(logTester.logs(DEBUG))
- .contains(argumentOutput)
+ assertThat(logTester.logs(INFO))
+ .contains("Arguments Passed In: " + String.join(" ", expectedArguments))
.contains("TIDELIFT_SKIP_UPDATE_CHECK=1")
.contains("TIDELIFT_ALLOW_MANIFEST_FAILURES=1")
- .contains("TIDELIFT_RECURSIVE_MANIFEST_SEARCH=true");
- assertThat(logTester.logs(INFO)).contains("Generated manifests zip file: " + producedZip.getName());
+ .contains("TIDELIFT_RECURSIVE_MANIFEST_SEARCH=true")
+ .contains("Generated manifests zip file: " + producedZip.getName());
assertThat(telemetryCache.getAll()).containsKey("scanner.sca.execution.cli.duration").isNotNull();
assertThat(telemetryCache.getAll()).containsEntry("scanner.sca.execution.cli.success", "true");
}
@Test
- void generateZip_whenDebugLogLevel_shouldCallProcessCorrectly() throws IOException, URISyntaxException {
+ void generateZip_whenDebugLogLevelAndScaDebugNotEnabled_shouldWriteDebugLogsToDebugStream() throws IOException, URISyntaxException {
+ logTester.setLevel(DEBUG);
+ when(configuration.getBoolean("sonar.sca.debug")).thenReturn(Optional.of(false));
+
assertThat(rootModuleDir.resolve("test_file").toFile().createNewFile()).isTrue();
- // We need to set the logging level to debug in order to be able to view the shell script's output
- logTester.setLevel(DEBUG);
+ underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
- List<String> args = List.of(
+ var expectedArguments = List.of(
"projects",
"save-lockfiles",
"--zip",
@@ -117,27 +150,52 @@ class CliServiceTest {
rootInputModule.getWorkDir().resolve("dependency-files.zip").toString(),
"--directory",
rootInputModule.getBaseDir().toString(),
+ "--exclude",
+ "ignored.txt,.scannerwork/**",
"--debug");
- String argumentOutput = "Arguments Passed In: " + String.join(" ", args);
- DefaultConfiguration configuration = mock(DefaultConfiguration.class);
- when(configuration.getProperties()).thenReturn(Map.of("sonar.sca.recursiveManifestSearch", "true"));
- when(configuration.get("sonar.sca.recursiveManifestSearch")).thenReturn(Optional.of("true"));
+ assertThat(logTester.logs(INFO))
+ .contains("Arguments Passed In: " + String.join(" ", expectedArguments));
+ }
+
+ @Test
+ void generateZip_whenScaDebugEnabled_shouldWriteDebugLogsToInfoStream() throws IOException, URISyntaxException {
+ when(configuration.getBoolean("sonar.sca.debug")).thenReturn(Optional.of(true));
+
+ assertThat(rootModuleDir.resolve("test_file").toFile().createNewFile()).isTrue();
underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
- assertThat(logTester.logs(DEBUG))
- .contains(argumentOutput);
+ var expectedArguments = List.of(
+ "projects",
+ "save-lockfiles",
+ "--zip",
+ "--zip-filename",
+ rootInputModule.getWorkDir().resolve("dependency-files.zip").toString(),
+ "--directory",
+ rootInputModule.getBaseDir().toString(),
+ "--exclude",
+ "ignored.txt,.scannerwork/**",
+ "--debug");
+
+ assertThat(logTester.logs(INFO))
+ .contains("Arguments Passed In: " + String.join(" ", expectedArguments));
}
@Test
- void generateZip_whenScaDebugEnabled_shouldCallProcessCorrectly() throws IOException, URISyntaxException {
- assertThat(rootModuleDir.resolve("test_file").toFile().createNewFile()).isTrue();
+ void generateZip_shouldSendSQEnvVars() throws IOException, URISyntaxException {
+ underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
- // Set the logging level to info so that we don't automatically set --debug flag
- logTester.setLevel(INFO);
+ assertThat(logTester.logs(INFO))
+ .contains("TIDELIFT_CLI_INSIDE_SCANNER_ENGINE=1")
+ .contains("TIDELIFT_CLI_SQ_SERVER_VERSION=1.0.0");
+ }
- List<String> args = List.of(
+ @Test
+ void generateZip_includesIgnoredPathsFromGitProvider() throws Exception {
+ underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
+
+ var expectedArguments = List.of(
"projects",
"save-lockfiles",
"--zip",
@@ -145,34 +203,128 @@ class CliServiceTest {
rootInputModule.getWorkDir().resolve("dependency-files.zip").toString(),
"--directory",
rootInputModule.getBaseDir().toString(),
+ "--exclude",
+ "ignored.txt,.scannerwork/**",
"--debug");
- String argumentOutput = "Arguments Passed In: " + String.join(" ", args);
- DefaultConfiguration configuration = mock(DefaultConfiguration.class);
- when(configuration.getProperties()).thenReturn(Map.of("sonar.sca.recursiveManifestSearch", "true"));
- when(configuration.get("sonar.sca.recursiveManifestSearch")).thenReturn(Optional.of("true"));
- when(configuration.getBoolean("sonar.sca.debug")).thenReturn(Optional.of(true));
+ assertThat(logTester.logs(INFO))
+ .contains("Arguments Passed In: " + String.join(" ", expectedArguments))
+ .contains("TIDELIFT_SKIP_UPDATE_CHECK=1")
+ .contains("TIDELIFT_ALLOW_MANIFEST_FAILURES=1")
+ .contains("TIDELIFT_CLI_INSIDE_SCANNER_ENGINE=1")
+ .contains("TIDELIFT_CLI_SQ_SERVER_VERSION=1.0.0");
+
+ }
+
+ @Test
+ void generateZip_withNoScm_doesNotIncludeScmIgnoredPaths() throws Exception {
+ when(scmConfiguration.provider()).thenReturn(null);
underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
- assertThat(logTester.logs(INFO))
- .contains(argumentOutput);
+ String capturedArgs = logTester.logs().stream().filter(log -> log.contains("Arguments Passed In:")).findFirst().get();
+ assertThat(capturedArgs).contains("--exclude .scannerwork/** --debug");
}
@Test
- void generateZip_shouldSendSQEnvVars() throws IOException, URISyntaxException {
- // We need to set the logging level to debug in order to be able to view the shell script's output
- logTester.setLevel(DEBUG);
+ void generateZip_withNonGit_doesNotIncludeScmIgnoredPaths() throws Exception {
+ when(scmProvider.key()).thenReturn("notgit");
+
+ underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
+
+ String capturedArgs = logTester.logs().stream().filter(log -> log.contains("Arguments Passed In:")).findFirst().get();
+ assertThat(capturedArgs).contains("--exclude .scannerwork/** --debug");
+ }
- var version = "1.0.0";
- when(server.getVersion()).thenReturn(version);
+ @Test
+ void generateZip_withExclusionDisabled_doesNotIncludeScmIgnoredPaths() throws Exception {
+ when(scmConfiguration.isExclusionDisabled()).thenReturn(true);
- DefaultConfiguration configuration = mock(DefaultConfiguration.class);
underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
- assertThat(logTester.logs(DEBUG))
- .contains("TIDELIFT_CLI_INSIDE_SCANNER_ENGINE=1")
- .contains("TIDELIFT_CLI_SQ_SERVER_VERSION=" + version);
+ String capturedArgs = logTester.logs().stream().filter(log -> log.contains("Arguments Passed In:")).findFirst().get();
+ assertThat(capturedArgs).contains("--exclude .scannerwork/** --debug");
+ }
+
+ @Test
+ void generateZip_withNoScmIgnores_doesNotIncludeScmIgnoredPaths() throws Exception {
+ jGitUtilsMock.when(() -> JGitUtils.getAllIgnoredPaths(any(Path.class))).thenReturn(List.of());
+
+ underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
+
+ String capturedArgs = logTester.logs().stream().filter(log -> log.contains("Arguments Passed In:")).findFirst().get();
+ assertThat(capturedArgs).contains("--exclude .scannerwork/** --debug");
+ }
+
+ @Test
+ void generateZip_withExistingExcludedManifests_appendsScmIgnoredPaths() throws Exception {
+ when(configuration.getStringArray(CliService.EXCLUDED_MANIFESTS_PROP_KEY)).thenReturn(new String[] {"**/test/**"});
+
+ underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
+
+ String capturedArgs = logTester.logs().stream().filter(log -> log.contains("Arguments Passed In:")).findFirst().get();
+ assertThat(capturedArgs).contains("--exclude **/test/**,ignored.txt,.scannerwork/**");
+ }
+
+ @Test
+ void generateZip_withExcludedManifestsSettingContainingBadCharacters_handlesTheBadCharacters() throws Exception {
+ when(configuration.getStringArray(CliService.EXCLUDED_MANIFESTS_PROP_KEY)).thenReturn(new String[] {
+ "**/test/**", "**/path with spaces/**", "**/path,with,commas/**", "**/path'with'quotes/**", "**/path\"with\"double\"quotes/**"});
+
+ underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
+
+ String capturedArgs = logTester.logs().stream().filter(log -> log.contains("Arguments Passed In:")).findFirst().get();
+
+ String expectedExcludeFlag = """
+ --exclude **/test/**,**/path with spaces/**,"**/path,with,commas/**",**/path'with'quotes/**,"**/path""with""double""quotes/**",ignored.txt
+ """.strip();
+ assertThat(capturedArgs).contains(expectedExcludeFlag);
+ }
+
+ @Test
+ void generateZip_withScmIgnoresContainingBadCharacters_handlesTheBadCharacters() throws Exception {
+ jGitUtilsMock.when(() -> JGitUtils.getAllIgnoredPaths(any(Path.class)))
+ .thenReturn(List.of("**/test/**", "**/path with spaces/**", "**/path,with,commas/**", "**/path'with'quotes/**", "**/path\"with\"double\"quotes/**"));
+
+ underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
+
+ String capturedArgs = logTester.logs().stream().filter(log -> log.contains("Arguments Passed In:")).findFirst().get();
+
+ String expectedExcludeFlag = """
+ --exclude **/test/**,**/path with spaces/**,"**/path,with,commas/**",**/path'with'quotes/**,"**/path""with""double""quotes/**"
+ """.strip();
+ assertThat(capturedArgs).contains(expectedExcludeFlag);
+ }
+
+ @Test
+ void generateZip_withIgnoredDirectories_GlobifiesDirectories() throws Exception {
+ String ignoredDirectory = "directory1";
+ Files.createDirectories(rootModuleDir.resolve(ignoredDirectory));
+ String ignoredFile = "directory2/file.txt";
+ Path ignoredFilePath = rootModuleDir.resolve(ignoredFile);
+ Files.createDirectories(ignoredFilePath.getParent());
+ Files.createFile(ignoredFilePath);
+
+ jGitUtilsMock.when(() -> JGitUtils.getAllIgnoredPaths(any(Path.class))).thenReturn(List.of(ignoredDirectory, ignoredFile));
+ underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
+
+ String capturedArgs = logTester.logs().stream().filter(log -> log.contains("Arguments Passed In:")).findFirst().get();
+ assertThat(capturedArgs).contains("--exclude directory1/**,directory2/file.txt");
+ }
+
+ @Test
+ void generateZip_withExternalWorkDir_DoesNotExcludeWorkingDir() throws URISyntaxException, IOException {
+ Path externalWorkDir = Files.createTempDirectory("externalWorkDir");
+ try {
+ rootInputModule = new DefaultInputModule(ProjectDefinition.create().setBaseDir(rootModuleDir.toFile()).setWorkDir(externalWorkDir.toFile()));
+ underTest.generateManifestsZip(rootInputModule, scriptDir(), configuration);
+ String capturedArgs = logTester.logs().stream().filter(log -> log.contains("Arguments Passed In:")).findFirst().get();
+
+ // externalWorkDir is not present in the exclude flag
+ assertThat(capturedArgs).contains("--exclude ignored.txt --debug");
+ } finally {
+ externalWorkDir.toFile().delete();
+ }
}
private URL scriptUrl() {
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/ScaPropertiesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/ScaPropertiesTest.java
index c19600dcf3c..e598a225b9c 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/ScaPropertiesTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sca/ScaPropertiesTest.java
@@ -19,9 +19,11 @@
*/
package org.sonar.scanner.sca;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import org.junit.jupiter.api.Test;
import org.sonar.scanner.config.DefaultConfiguration;
@@ -34,34 +36,36 @@ class ScaPropertiesTest {
private final DefaultConfiguration configuration = mock(DefaultConfiguration.class);
@Test
- void buildFromScannerProperties_shouldReturnEmptyMap_whenNoPropertiesExist() {
+ void buildFromScannerProperties_withNoProperties_returnsDefaultMap() {
when(configuration.get(anyString())).thenReturn(Optional.empty());
- var result = ScaProperties.buildFromScannerProperties(configuration);
+ var result = ScaProperties.buildFromScannerProperties(configuration, Collections.emptySet());
- assertThat(result).isEmpty();
+ assertThat(result).containsExactly(
+ Map.entry("TIDELIFT_RECURSIVE_MANIFEST_SEARCH", "true"));
}
@Test
- void buildFromScannerProperties_shouldIgnoresUnmappedProperties() {
+ void buildFromScannerProperties_withUnmappedProperties_ignoresProperties() {
var inputProperties = new HashMap<String, String>();
inputProperties.put("sonar.sca.pythonBinary", "/usr/bin/python3");
inputProperties.put("sonar.sca.unknownProperty", "value");
- inputProperties.put("sonar.somethingElse", "ignoreMe");
+ inputProperties.put("sonar.somethingElse", "dont-include-non-sca");
+ inputProperties.put("sonar.sca.ignoredProperty", "ignore-me");
when(configuration.getProperties()).thenReturn(inputProperties);
when(configuration.get(anyString())).thenAnswer(i -> Optional.ofNullable(inputProperties.get(i.getArgument(0, String.class))));
- var result = ScaProperties.buildFromScannerProperties(configuration);
+ var result = ScaProperties.buildFromScannerProperties(configuration, Set.of("sonar.sca.ignoredProperty"));
assertThat(result).containsExactly(
+ Map.entry("TIDELIFT_RECURSIVE_MANIFEST_SEARCH", "true"),
Map.entry("TIDELIFT_PYTHON_BINARY", "/usr/bin/python3"),
Map.entry("TIDELIFT_UNKNOWN_PROPERTY", "value"));
}
@Test
- void buildFromScannerProperties_shouldMapAllKnownProperties() {
+ void buildFromScannerProperties_withLotsOfProperties_mapsAllProperties() {
var inputProperties = new HashMap<String, String>();
- inputProperties.put("sonar.sca.excludedManifests", "exclude/*");
inputProperties.put("sonar.sca.goNoResolve", "true");
inputProperties.put("sonar.sca.gradleConfigurationPattern", "pattern");
inputProperties.put("sonar.sca.gradleNoResolve", "false");
@@ -80,7 +84,6 @@ class ScaPropertiesTest {
when(configuration.get(anyString())).thenAnswer(i -> Optional.ofNullable(inputProperties.get(i.getArgument(0, String.class))));
var expectedProperties = new HashMap<String, String>();
- expectedProperties.put("TIDELIFT_EXCLUDED_MANIFESTS", "exclude/*");
expectedProperties.put("TIDELIFT_GO_NO_RESOLVE", "true");
expectedProperties.put("TIDELIFT_GRADLE_CONFIGURATION_PATTERN", "pattern");
expectedProperties.put("TIDELIFT_GRADLE_NO_RESOLVE", "false");
@@ -96,8 +99,35 @@ class ScaPropertiesTest {
expectedProperties.put("TIDELIFT_PYTHON_RESOLVE_LOCAL", "false");
expectedProperties.put("TIDELIFT_RECURSIVE_MANIFEST_SEARCH", "true");
- var result = ScaProperties.buildFromScannerProperties(configuration);
+ var result = ScaProperties.buildFromScannerProperties(configuration, Collections.emptySet());
assertThat(result).containsExactlyInAnyOrderEntriesOf(expectedProperties);
}
+
+
+ @Test
+ void buildFromScannerProperties_withoutRecursiveModeProp_defaultsRecursiveModeTrue() {
+ var inputProperties = new HashMap<String, String>();
+ when(configuration.getProperties()).thenReturn(inputProperties);
+ when(configuration.get(anyString())).thenAnswer(i -> Optional.ofNullable(inputProperties.get(i.getArgument(0, String.class))));
+
+ var result = ScaProperties.buildFromScannerProperties(configuration, Collections.emptySet());
+
+ assertThat(result).containsExactly(
+ Map.entry("TIDELIFT_RECURSIVE_MANIFEST_SEARCH", "true"));
+ }
+
+ @Test
+ void buildFromScannerProperties_withRecursiveModeProp_usesPropAsOverride() {
+ var inputProperties = new HashMap<String, String>();
+ inputProperties.put("sonar.sca.recursiveManifestSearch", "false");
+ when(configuration.getProperties()).thenReturn(inputProperties);
+ when(configuration.get(anyString())).thenAnswer(i -> Optional.ofNullable(inputProperties.get(i.getArgument(0, String.class))));
+
+ var result = ScaProperties.buildFromScannerProperties(configuration, Collections.emptySet());
+
+ assertThat(result).containsExactly(
+ Map.entry("TIDELIFT_RECURSIVE_MANIFEST_SEARCH", "false"));
+ }
+
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scm/git/JGitUtilsTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/JGitUtilsTest.java
new file mode 100644
index 00000000000..3578a3c60f7
--- /dev/null
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scm/git/JGitUtilsTest.java
@@ -0,0 +1,67 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2025 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.scm.git;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import org.eclipse.jgit.api.Git;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.sonar.api.utils.MessageException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class JGitUtilsTest {
+
+ @TempDir
+ Path rootModuleDir;
+
+ @Test
+ void getAllIgnoredPaths_ReturnsIgnoredFiles() throws Exception {
+ Git.init().setDirectory(rootModuleDir.toFile()).call();
+ Files.createDirectories(rootModuleDir.resolve("directory1"));
+ Files.createDirectories(rootModuleDir.resolve("directory2"));
+ Files.createDirectories(rootModuleDir.resolve("directory3"));
+ Files.write(rootModuleDir.resolve("directory1/file_a.txt"), "content".getBytes());
+ Files.write(rootModuleDir.resolve("directory1/file_b.txt"), "content".getBytes());
+ Files.write(rootModuleDir.resolve("directory2/file_a.txt"), "content".getBytes());
+ Files.write(rootModuleDir.resolve("directory2/file_b.txt"), "content".getBytes());
+ Files.write(rootModuleDir.resolve("directory3/file_a.txt"), "content".getBytes());
+ Files.write(rootModuleDir.resolve("directory3/file_b.txt"), "content".getBytes());
+ Files.write(rootModuleDir.resolve(".gitignore"), "ignored.txt\ndirectory1\ndirectory2/file_a.txt".getBytes());
+ Files.write(rootModuleDir.resolve("directory3/.gitignore"), "file_b.txt".getBytes());
+
+ List<String> result = JGitUtils.getAllIgnoredPaths(rootModuleDir);
+
+ // in directory1, the entire directory is ignored without listing each file
+ // in directory2, specific files are ignored, so those files are listed
+ // in directory3, specific files are ignored via a separate .gitignore file
+ assertThat(result).isEqualTo(List.of("directory1", "directory2/file_a.txt", "directory3/file_b.txt"));
+ }
+
+ @Test
+ void getIgnoredPaths_WithNonGitDirectory_ThrowsException() {
+ assertThatThrownBy(() -> JGitUtils.getAllIgnoredPaths(rootModuleDir))
+ .isInstanceOf(MessageException.class)
+ .hasMessageStartingWith("Not inside a Git work tree: ");
+ }
+}
diff --git a/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto b/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto
index 2cc8bba8afb..ce41bae08ad 100644
--- a/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto
+++ b/sonar-ws/src/main/protobuf/ws-qualityprofiles.proto
@@ -74,14 +74,16 @@ message CreateWsResponse {
optional string languageName = 4;
optional bool isInherited = 5;
optional bool isDefault = 6;
- optional Infos infos = 7;
- optional Warnings warnings = 8;
+ optional Infos infos = 7 [deprecated=true];
+ optional Warnings warnings = 8 [deprecated=true];
message Infos {
+ option deprecated = true;
repeated string infos = 1;
}
message Warnings {
+ option deprecated = true;
repeated string warnings = 1;
}
}
diff --git a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java
index e0566bc23cb..0585322e48e 100644
--- a/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java
+++ b/sonar-ws/src/testFixtures/java/org/sonarqube/ws/tester/UserTester.java
@@ -129,6 +129,7 @@ public class UserTester {
.setLogin(u.getLogin())
.setPermission("admin"));
dismissModesTour(u);
+ dimissDnaTour(u);
return u;
}
@@ -141,6 +142,7 @@ public class UserTester {
session.wsClient().permissions().addUser(new org.sonarqube.ws.client.permissions.AddUserRequest().setLogin(user.getLogin()).setPermission("admin"));
session.wsClient().userGroups().addUser(new AddUserRequest().setLogin(user.getLogin()).setName("sonar-administrators"));
dismissModesTour(user);
+ dimissDnaTour(user);
return user;
}
@@ -150,6 +152,12 @@ public class UserTester {
.credentials(user.getLogin(), user.getLogin())
.build()).users().dismissNotice("showNewModesTour");
}
+ private void dimissDnaTour(User user) {
+ WsClientFactories.getDefault().newClient(HttpConnector.newBuilder()
+ .url(session.wsClient().wsConnector().baseUrl())
+ .credentials(user.getLogin(), user.getLogin())
+ .build()).users().dismissNotice("showDesignAndArchitectureTour");
+ }
public UsersService service() {
return session.wsClient().users();