Skip to content

Commit 7dd8e36

Browse files
SLCORE-1525 allow changing status of SCA issues
1 parent 3a7b84e commit 7dd8e36

File tree

15 files changed

+1097
-13
lines changed

15 files changed

+1097
-13
lines changed

API_CHANGES.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# 10.27
2+
3+
## New features
4+
5+
* Allow changing status of SCA issues via `org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.ScaRpcService.changeStatus`.
6+
* Required parameters are `configScopeId`, `issueId` and `transition`.
7+
* If transition is `ACCEPT`, `FIXED`, or `SAFE`, a `comment` field is mandatory
8+
19
# 10.26
210

311
## New features
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* SonarLint Core - Implementation
3+
* Copyright (C) 2016-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonarsource.sonarlint.core.sca;
21+
22+
import java.util.UUID;
23+
import javax.annotation.CheckForNull;
24+
import org.sonarsource.sonarlint.core.SonarQubeClientManager;
25+
import org.sonarsource.sonarlint.core.branch.SonarProjectBranchTrackingService;
26+
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
27+
import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;
28+
import org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;
29+
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskTransition;
30+
import org.sonarsource.sonarlint.core.serverconnection.issues.ServerScaIssue;
31+
import org.sonarsource.sonarlint.core.storage.StorageService;
32+
33+
public class ScaService {
34+
private static final SonarLintLogger LOG = SonarLintLogger.get();
35+
36+
private final ConfigurationRepository configurationRepository;
37+
private final StorageService storageService;
38+
private final SonarQubeClientManager sonarQubeClientManager;
39+
private final SonarProjectBranchTrackingService branchTrackingService;
40+
41+
public ScaService(ConfigurationRepository configurationRepository, StorageService storageService, SonarQubeClientManager sonarQubeClientManager,
42+
SonarProjectBranchTrackingService branchTrackingService) {
43+
this.configurationRepository = configurationRepository;
44+
this.storageService = storageService;
45+
this.sonarQubeClientManager = sonarQubeClientManager;
46+
this.branchTrackingService = branchTrackingService;
47+
}
48+
49+
public void changeStatus(String configurationScopeId, UUID issueReleaseKey, DependencyRiskTransition transition, @CheckForNull String comment,
50+
SonarLintCancelMonitor cancelMonitor) {
51+
var binding = configurationRepository.getEffectiveBindingOrThrow(configurationScopeId);
52+
var serverConnection = sonarQubeClientManager.getClientOrThrow(binding.connectionId());
53+
var projectServerIssueStore = storageService.binding(binding).findings();
54+
var branchName = branchTrackingService.awaitEffectiveSonarProjectBranch(configurationScopeId);
55+
56+
if (branchName.isEmpty()) {
57+
throw new IllegalArgumentException("Could not determine matched branch for configuration scope " + configurationScopeId);
58+
}
59+
60+
var scaIssues = projectServerIssueStore.loadScaIssues(branchName.get());
61+
var dependencyRiskOpt = scaIssues.stream().filter(issue -> issue.key().equals(issueReleaseKey)).findFirst();
62+
63+
if (dependencyRiskOpt.isEmpty()) {
64+
throw new ScaIssueNotFoundException("Dependency Risk with key " + issueReleaseKey.toString() + " was not found", issueReleaseKey.toString());
65+
}
66+
67+
var dependencyRisk = dependencyRiskOpt.get();
68+
69+
if (!dependencyRisk.transitions().contains(adaptTransition(transition))) {
70+
throw new IllegalArgumentException("Transition " + transition + " is not allowed for this SCA issue");
71+
}
72+
73+
if ((transition == DependencyRiskTransition.ACCEPT || transition == DependencyRiskTransition.SAFE || transition == DependencyRiskTransition.FIXED)
74+
&& (comment == null || comment.isBlank())) {
75+
throw new IllegalArgumentException("Comment is required for ACCEPT, FIXED, and SAFE transitions");
76+
}
77+
78+
LOG.info("Changing SCA issue status for issue {} to {} with comment: {}", issueReleaseKey, transition, comment);
79+
80+
serverConnection.withClientApi(serverApi -> serverApi.sca().changeStatus(issueReleaseKey, transition.name(), comment, cancelMonitor));
81+
}
82+
83+
private static ServerScaIssue.Transition adaptTransition(DependencyRiskTransition transition) {
84+
return switch (transition) {
85+
case REOPEN -> ServerScaIssue.Transition.REOPEN;
86+
case CONFIRM -> ServerScaIssue.Transition.CONFIRM;
87+
case ACCEPT -> ServerScaIssue.Transition.ACCEPT;
88+
case SAFE -> ServerScaIssue.Transition.SAFE;
89+
case FIXED -> ServerScaIssue.Transition.FIXED;
90+
};
91+
}
92+
93+
public static class ScaIssueNotFoundException extends RuntimeException {
94+
private final String issueKey;
95+
96+
public ScaIssueNotFoundException(String message, String issueKey) {
97+
super(message);
98+
this.issueKey = issueKey;
99+
}
100+
101+
public String getIssueKey() {
102+
return issueKey;
103+
}
104+
}
105+
}

backend/core/src/main/java/org/sonarsource/sonarlint/core/spring/SonarLintSpringAppConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
import org.sonarsource.sonarlint.core.sync.HotspotSynchronizationService;
100100
import org.sonarsource.sonarlint.core.sync.IssueSynchronizationService;
101101
import org.sonarsource.sonarlint.core.sync.ScaSynchronizationService;
102+
import org.sonarsource.sonarlint.core.sca.ScaService;
102103
import org.sonarsource.sonarlint.core.sync.SonarProjectBranchesSynchronizationService;
103104
import org.sonarsource.sonarlint.core.sync.SynchronizationService;
104105
import org.sonarsource.sonarlint.core.sync.TaintSynchronizationService;
@@ -195,6 +196,7 @@
195196
AiCodeFixService.class,
196197
ClientAwareTaskManager.class,
197198
ScaSynchronizationService.class,
199+
ScaService.class,
198200
})
199201
public class SonarLintSpringAppConfig {
200202

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* SonarLint Core - RPC Implementation
3+
* Copyright (C) 2016-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonarsource.sonarlint.core.rpc.impl;
21+
22+
import java.util.concurrent.CompletableFuture;
23+
import org.eclipse.lsp4j.jsonrpc.ResponseErrorException;
24+
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
25+
import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;
26+
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.ChangeScaIssueStatusParams;
27+
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.ScaRpcService;
28+
import org.sonarsource.sonarlint.core.sca.ScaService;
29+
30+
public class ScaRpcServiceDelegate extends AbstractRpcServiceDelegate implements ScaRpcService {
31+
32+
public ScaRpcServiceDelegate(SonarLintRpcServerImpl server) {
33+
super(server);
34+
}
35+
36+
@Override
37+
public CompletableFuture<Void> changeStatus(ChangeScaIssueStatusParams params) {
38+
return runAsync(cancelMonitor -> {
39+
try {
40+
getBean(ScaService.class).changeStatus(
41+
params.getConfigurationScopeId(),
42+
params.getIssueReleaseKey(),
43+
params.getTransition(),
44+
params.getComment(),
45+
cancelMonitor);
46+
} catch (ScaService.ScaIssueNotFoundException e) {
47+
var error = new ResponseError(SonarLintRpcErrorCode.ISSUE_NOT_FOUND,
48+
"Dependency Risk with key " + e.getIssueKey() + " was not found", e.getIssueKey());
49+
throw new ResponseErrorException(error);
50+
} catch (IllegalArgumentException e) {
51+
var error = new ResponseError(SonarLintRpcErrorCode.INVALID_ARGUMENT, e.getMessage(), null);
52+
throw new ResponseErrorException(error);
53+
}
54+
}, params.getConfigurationScopeId());
55+
}
56+
}

backend/rpc-impl/src/main/java/org/sonarsource/sonarlint/core/rpc/impl/SonarLintRpcServerImpl.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import org.sonarsource.sonarlint.core.rpc.protocol.backend.progress.TaskProgressRpcService;
6464
import org.sonarsource.sonarlint.core.rpc.protocol.backend.remediation.aicodefix.AiCodeFixRpcService;
6565
import org.sonarsource.sonarlint.core.rpc.protocol.backend.rules.RulesRpcService;
66+
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.ScaRpcService;
6667
import org.sonarsource.sonarlint.core.rpc.protocol.backend.telemetry.TelemetryRpcService;
6768
import org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ScaIssueTrackingRpcService;
6869
import org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.TaintVulnerabilityTrackingRpcService;
@@ -242,6 +243,11 @@ public TaskProgressRpcService getTaskProgressRpcService() {
242243
return new TaskProgressRpcServiceDelegate(this);
243244
}
244245

246+
@Override
247+
public ScaRpcService getScaService() {
248+
return new ScaRpcServiceDelegate(this);
249+
}
250+
245251
@Override
246252
public CompletableFuture<Void> shutdown() {
247253
LOG.info("SonarLint backend shutting down, instance={}", this);

backend/server-api/src/main/java/org/sonarsource/sonarlint/core/serverapi/sca/ScaApi.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323
import java.io.InputStreamReader;
2424
import java.nio.charset.StandardCharsets;
2525
import java.util.ArrayList;
26+
import java.util.UUID;
2627
import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;
2728
import org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;
2829
import org.sonarsource.sonarlint.core.serverapi.UrlUtils;
2930

31+
import static org.sonarsource.sonarlint.core.http.HttpClient.JSON_CONTENT_TYPE;
32+
3033
public class ScaApi {
3134

3235
private final ServerApiHelper serverApiHelper;
@@ -56,4 +59,17 @@ public GetIssuesReleasesResponse getIssuesReleases(String projectKey, String bra
5659
return new GetIssuesReleasesResponse(allIssuesReleases, new GetIssuesReleasesResponse.Page(allIssuesReleases.size()));
5760
}
5861

62+
public void changeStatus(UUID issueReleaseKey, String transitionKey, String comment, SonarLintCancelMonitor cancelMonitor) {
63+
var body = new ChangeStatusRequestBody(issueReleaseKey.toString(), transitionKey, comment);
64+
var url = "/api/v2/sca/issues-releases/change-status";
65+
66+
serverApiHelper.post(url, JSON_CONTENT_TYPE, body.toJson(), cancelMonitor);
67+
}
68+
69+
private record ChangeStatusRequestBody(String issueReleaseKey, String transitionKey, String comment) {
70+
public String toJson() {
71+
return new Gson().toJson(this);
72+
}
73+
}
74+
5975
}

0 commit comments

Comments
 (0)