Skip to content

Commit 4b5a95c

Browse files
SLCORE-1543 open dependency risk in browser
1 parent df3967f commit 4b5a95c

File tree

6 files changed

+202
-2
lines changed

6 files changed

+202
-2
lines changed

backend/core/src/main/java/org/sonarsource/sonarlint/core/sca/ScaService.java

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,43 @@
2828
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
2929
import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;
3030
import org.sonarsource.sonarlint.core.repository.config.ConfigurationRepository;
31+
import org.sonarsource.sonarlint.core.repository.connection.ConnectionConfigurationRepository;
32+
import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;
3133
import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcErrorCode;
3234
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.DependencyRiskTransition;
3335
import org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.AffectedPackageDto;
3436
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.GetDependencyRiskDetailsResponse;
3537
import org.sonarsource.sonarlint.core.rpc.protocol.backend.tracking.ScaIssueDto;
3638
import org.sonarsource.sonarlint.core.serverapi.sca.GetIssueReleaseResponse;
39+
import org.sonarsource.sonarlint.core.rpc.protocol.client.OpenUrlInBrowserParams;
40+
import org.sonarsource.sonarlint.core.serverapi.EndpointParams;
41+
import org.sonarsource.sonarlint.core.serverapi.ServerApiHelper;
42+
import org.sonarsource.sonarlint.core.serverapi.UrlUtils;
3743
import org.sonarsource.sonarlint.core.serverconnection.issues.ServerScaIssue;
3844
import org.sonarsource.sonarlint.core.storage.StorageService;
45+
import org.sonarsource.sonarlint.core.telemetry.TelemetryService;
3946

4047
public class ScaService {
4148
private static final SonarLintLogger LOG = SonarLintLogger.get();
4249

4350
private final ConfigurationRepository configurationRepository;
51+
private final ConnectionConfigurationRepository connectionRepository;
4452
private final StorageService storageService;
4553
private final SonarQubeClientManager sonarQubeClientManager;
4654
private final SonarProjectBranchTrackingService branchTrackingService;
55+
private final SonarLintRpcClient client;
56+
private final TelemetryService telemetryService;
4757

48-
public ScaService(ConfigurationRepository configurationRepository, StorageService storageService, SonarQubeClientManager sonarQubeClientManager,
49-
SonarProjectBranchTrackingService branchTrackingService) {
58+
public ScaService(ConfigurationRepository configurationRepository, ConnectionConfigurationRepository connectionRepository,
59+
StorageService storageService, SonarQubeClientManager sonarQubeClientManager,
60+
SonarProjectBranchTrackingService branchTrackingService, SonarLintRpcClient client, TelemetryService telemetryService) {
5061
this.configurationRepository = configurationRepository;
62+
this.connectionRepository = connectionRepository;
5163
this.storageService = storageService;
5264
this.sonarQubeClientManager = sonarQubeClientManager;
5365
this.branchTrackingService = branchTrackingService;
66+
this.client = client;
67+
this.telemetryService = telemetryService;
5468
}
5569

5670
public void changeStatus(String configurationScopeId, UUID issueReleaseKey, DependencyRiskTransition transition, @CheckForNull String comment,
@@ -139,6 +153,38 @@ private static GetDependencyRiskDetailsResponse convertToRpcResponse(GetIssueRel
139153
serverResponse.vulnerability().description(), affectedPackages);
140154
}
141155

156+
public void openDependencyRiskInBrowser(String configurationScopeId, String dependencyKey) {
157+
var effectiveBinding = configurationRepository.getEffectiveBinding(configurationScopeId);
158+
var endpointParams = effectiveBinding.flatMap(binding -> connectionRepository.getEndpointParams(binding.connectionId()));
159+
if (effectiveBinding.isEmpty() || endpointParams.isEmpty()) {
160+
LOG.warn("Configuration scope {} is not bound properly, unable to open dependency risk", configurationScopeId);
161+
return;
162+
}
163+
var branchName = branchTrackingService.awaitEffectiveSonarProjectBranch(configurationScopeId);
164+
if (branchName.isEmpty()) {
165+
LOG.warn("Configuration scope {} has no matching branch, unable to open dependency risk", configurationScopeId);
166+
return;
167+
}
168+
169+
var url = buildScaBrowseUrl(effectiveBinding.get().sonarProjectKey(), branchName.get(), dependencyKey, endpointParams.get());
170+
171+
client.openUrlInBrowser(new OpenUrlInBrowserParams(url));
172+
173+
// TODO: Add SCA-specific telemetry method when available
174+
// telemetryService.dependencyRiskOpenedInBrowser();
175+
}
176+
177+
static String buildScaBrowseUrl(String projectKey, String branch, String dependencyKey, EndpointParams endpointParams) {
178+
var relativePath = "/dependency-risks/"
179+
+ UrlUtils.urlEncode(dependencyKey)
180+
+ "/what?id="
181+
+ UrlUtils.urlEncode(projectKey)
182+
+ "&branch="
183+
+ UrlUtils.urlEncode(branch);
184+
185+
return ServerApiHelper.concat(endpointParams.getBaseUrl(), relativePath);
186+
}
187+
142188
public static class ScaIssueNotFoundException extends RuntimeException {
143189
private final String issueKey;
144190

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 org.junit.jupiter.api.Test;
23+
import org.sonarsource.sonarlint.core.serverapi.EndpointParams;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
27+
class ScaServiceTests {
28+
29+
@Test
30+
void testBuildSonarQubeServerScaUrl() {
31+
assertThat(ScaService.buildScaBrowseUrl("myProject", "myBranch", "dependencyKey", new EndpointParams("http://foo.com", "", false, null)))
32+
.isEqualTo("http://foo.com/dependency-risks/dependencyKey/what?id=myProject&branch=myBranch");
33+
}
34+
35+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.ChangeScaIssueStatusParams;
2727
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.GetDependencyRiskDetailsParams;
2828
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.GetDependencyRiskDetailsResponse;
29+
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.OpenDependencyRiskInBrowserParams;
2930
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.ScaRpcService;
3031
import org.sonarsource.sonarlint.core.sca.ScaService;
3132

@@ -61,4 +62,9 @@ public CompletableFuture<GetDependencyRiskDetailsResponse> getDependencyRiskDeta
6162
return requestAsync(cancelMonitor -> getBean(ScaService.class)
6263
.getDependencyRiskDetails(params.getConfigurationScopeId(), params.getDependencyRiskKey(), cancelMonitor), params.getConfigurationScopeId());
6364
}
65+
66+
@Override
67+
public void openDependencyRiskInBrowser(OpenDependencyRiskInBrowserParams params) {
68+
notify(() -> getBean(ScaService.class).openDependencyRiskInBrowser(params.getConfigScopeId(), params.getDependencyKey()), params.getConfigScopeId());
69+
}
6470
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* SonarLint Core - Medium Tests
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 mediumtest.sca;
21+
22+
import java.io.IOException;
23+
import java.net.URL;
24+
import java.util.UUID;
25+
import org.sonarsource.sonarlint.core.rpc.protocol.backend.sca.OpenDependencyRiskInBrowserParams;
26+
import org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTest;
27+
import org.sonarsource.sonarlint.core.test.utils.junit5.SonarLintTestHarness;
28+
29+
import static org.mockito.ArgumentMatchers.any;
30+
import static org.mockito.Mockito.timeout;
31+
import static org.mockito.Mockito.verify;
32+
import static org.sonarsource.sonarlint.core.serverapi.UrlUtils.urlEncode;
33+
34+
class OpenDependencyRiskInBrowserMediumTests {
35+
public static final String CONNECTION_ID = "connectionId";
36+
public static final String SCOPE_ID = "scopeId";
37+
public static final String PROJECT_KEY = "projectKey";
38+
public static final String DEPENDENCY_KEY = UUID.randomUUID().toString();
39+
public static final String BRANCH_NAME = "master";
40+
41+
@SonarLintTest
42+
void it_should_open_dependency_risk_in_sonarqube(SonarLintTestHarness harness) throws IOException {
43+
var fakeClient = harness.newFakeClient().build();
44+
var backend = harness.newBackend()
45+
.withSonarQubeConnection(CONNECTION_ID, "http://localhost:12345", storage -> storage.withProject(PROJECT_KEY, project -> project.withMainBranch(BRANCH_NAME)))
46+
.withBoundConfigScope(SCOPE_ID, CONNECTION_ID, PROJECT_KEY)
47+
.withTelemetryEnabled()
48+
.start(fakeClient);
49+
50+
backend.getScaService().openDependencyRiskInBrowser(new OpenDependencyRiskInBrowserParams(
51+
SCOPE_ID, DEPENDENCY_KEY));
52+
53+
var expectedUrl = String.format("http://localhost:12345/dependency-risks/%s/what?id=%s&branch=%s",
54+
urlEncode(DEPENDENCY_KEY), urlEncode(PROJECT_KEY), urlEncode(BRANCH_NAME));
55+
56+
verify(fakeClient, timeout(5000)).openUrlInBrowser(new URL(expectedUrl));
57+
}
58+
59+
@SonarLintTest
60+
void it_should_not_open_dependency_risk_if_unbound(SonarLintTestHarness harness) {
61+
var fakeClient = harness.newFakeClient().build();
62+
var backend = harness.newBackend()
63+
.withUnboundConfigScope(SCOPE_ID)
64+
.start(fakeClient);
65+
66+
backend.getScaService().openDependencyRiskInBrowser(new OpenDependencyRiskInBrowserParams(
67+
SCOPE_ID, DEPENDENCY_KEY));
68+
69+
verify(fakeClient, timeout(5000).times(0)).openUrlInBrowser(any(URL.class));
70+
}
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* SonarLint Core - RPC Protocol
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.protocol.backend.sca;
21+
22+
public class OpenDependencyRiskInBrowserParams {
23+
private final String configScopeId;
24+
private final String dependencyKey;
25+
26+
public OpenDependencyRiskInBrowserParams(String configScopeId, String dependencyKey) {
27+
this.configScopeId = configScopeId;
28+
this.dependencyKey = dependencyKey;
29+
}
30+
31+
public String getConfigScopeId() {
32+
return configScopeId;
33+
}
34+
35+
public String getDependencyKey() {
36+
return dependencyKey;
37+
}
38+
}

rpc-protocol/src/main/java/org/sonarsource/sonarlint/core/rpc/protocol/backend/sca/ScaRpcService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.sonarsource.sonarlint.core.rpc.protocol.backend.sca;
2121

2222
import java.util.concurrent.CompletableFuture;
23+
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
2324
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest;
2425
import org.eclipse.lsp4j.jsonrpc.services.JsonSegment;
2526

@@ -52,4 +53,7 @@ public interface ScaRpcService {
5253
*/
5354
@JsonRequest
5455
CompletableFuture<GetDependencyRiskDetailsResponse> getDependencyRiskDetails(GetDependencyRiskDetailsParams params);
56+
57+
@JsonNotification
58+
void openDependencyRiskInBrowser(OpenDependencyRiskInBrowserParams params);
5559
}

0 commit comments

Comments
 (0)