Skip to content

Commit

Permalink
SLCORE-818 Provide connection parameters for SC while opening an issu…
Browse files Browse the repository at this point in the history
…e in IDE
  • Loading branch information
serhat-yenican-sonarsource committed May 31, 2024
1 parent bc4e90b commit 34e5a83
Show file tree
Hide file tree
Showing 10 changed files with 426 additions and 76 deletions.
18 changes: 18 additions & 0 deletions API_CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
# 10.3

## Breaking changes

## New features

### Open Issue in IDE

* Add the `getConnectionParams` method to `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams`
* It allows clients to get parameters to create either SonarQube or SonarCloud connection
* This field type is `Either<AssistSonarQubeConnection, AssistSonarCloudConnection>`
* Common methods of both connection types are added to the `AssistCreatingConnectionParams` class to provide users simplicity

## Deprecation

* `org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams.getServerUrl` is only meaningful for SQ
connections. Use `getConnection().getLeft().getServerUrl()` instead to get the `serverUrl` of a SQ connection

# 10.2

## Breaking changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import javax.inject.Singleton;
import org.sonarsource.sonarlint.core.BindingCandidatesFinder;
import org.sonarsource.sonarlint.core.BindingSuggestionProvider;
import org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;
import org.sonarsource.sonarlint.core.commons.log.SonarLintLogger;
import org.sonarsource.sonarlint.core.commons.progress.ExecutorServiceShutdownWatchable;
import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;
Expand All @@ -56,10 +57,11 @@ public class RequestHandlerBindingAssistant {
private final ConfigurationRepository configurationRepository;
private final UserTokenService userTokenService;
private final ExecutorServiceShutdownWatchable<?> executorService;
private final String sonarCloudUrl;

public RequestHandlerBindingAssistant(BindingSuggestionProvider bindingSuggestionProvider, BindingCandidatesFinder bindingCandidatesFinder, SonarLintRpcClient client,
ConnectionConfigurationRepository connectionConfigurationRepository,
ConfigurationRepository configurationRepository, UserTokenService userTokenService) {
public RequestHandlerBindingAssistant(BindingSuggestionProvider bindingSuggestionProvider, BindingCandidatesFinder bindingCandidatesFinder,
SonarLintRpcClient client, ConnectionConfigurationRepository connectionConfigurationRepository, ConfigurationRepository configurationRepository,
UserTokenService userTokenService, SonarCloudActiveEnvironment sonarCloudActiveEnvironment) {
this.bindingSuggestionProvider = bindingSuggestionProvider;
this.bindingCandidatesFinder = bindingCandidatesFinder;
this.client = client;
Expand All @@ -68,27 +70,29 @@ public RequestHandlerBindingAssistant(BindingSuggestionProvider bindingSuggestio
this.userTokenService = userTokenService;
this.executorService = new ExecutorServiceShutdownWatchable<>(new ThreadPoolExecutor(0, 1, 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), r -> new Thread(r, "Show Issue or Hotspot Request Handler")));
this.sonarCloudUrl = sonarCloudActiveEnvironment.getUri().toString();
}

interface Callback {
void andThen(String connectionId, @Nullable String configurationScopeId, SonarLintCancelMonitor cancelMonitor);
}

void assistConnectionAndBindingIfNeededAsync(String serverUrl, @Nullable String tokenName, @Nullable String tokenValue, String projectKey, Callback callback) {
void assistConnectionAndBindingIfNeededAsync(AssistCreatingConnectionParams connectionParams, String projectKey, Callback callback) {
var cancelMonitor = new SonarLintCancelMonitor();
cancelMonitor.watchForShutdown(executorService);
executorService.submit(() -> assistConnectionAndBindingIfNeeded(serverUrl, tokenName, tokenValue, projectKey, callback, cancelMonitor));
executorService.submit(() -> assistConnectionAndBindingIfNeeded(connectionParams, projectKey, callback, cancelMonitor));
}

private void assistConnectionAndBindingIfNeeded(String serverUrl, @Nullable String tokenName, @Nullable String tokenValue, String projectKey,
private void assistConnectionAndBindingIfNeeded(AssistCreatingConnectionParams connectionParams, String projectKey,
Callback callback, SonarLintCancelMonitor cancelMonitor) {
String serverUrl = getServerUrl(connectionParams);
LOG.debug("Assist connection and binding if needed for project {} and server {}", projectKey, serverUrl);
try {
var connectionsMatchingOrigin = connectionConfigurationRepository.findByUrl(serverUrl);
if (connectionsMatchingOrigin.isEmpty()) {
startFullBindingProcess();
try {
var assistNewConnectionResult = assistCreatingConnectionAndWaitForRepositoryUpdate(serverUrl, tokenName, tokenValue, cancelMonitor);
var assistNewConnectionResult = assistCreatingConnectionAndWaitForRepositoryUpdate(connectionParams, cancelMonitor);
var assistNewBindingResult = assistBindingAndWaitForRepositoryUpdate(assistNewConnectionResult.getNewConnectionId(),
projectKey, cancelMonitor);
callback.andThen(assistNewConnectionResult.getNewConnectionId(), assistNewBindingResult.getConfigurationScopeId(), cancelMonitor);
Expand All @@ -105,9 +109,13 @@ private void assistConnectionAndBindingIfNeeded(String serverUrl, @Nullable Stri
}
}

private AssistCreatingConnectionResponse assistCreatingConnectionAndWaitForRepositoryUpdate(String serverUrl, @Nullable String tokenName, @Nullable String tokenValue,
SonarLintCancelMonitor cancelMonitor) {
var assistNewConnectionResult = assistCreatingConnection(serverUrl, tokenName, tokenValue, cancelMonitor);
private String getServerUrl(AssistCreatingConnectionParams connectionParams) {
return connectionParams.getConnectionParams().isLeft() ? connectionParams.getConnectionParams().getLeft().getServerUrl() : sonarCloudUrl;
}

private AssistCreatingConnectionResponse assistCreatingConnectionAndWaitForRepositoryUpdate(
AssistCreatingConnectionParams connectionParams, SonarLintCancelMonitor cancelMonitor) {
var assistNewConnectionResult = assistCreatingConnection(connectionParams, cancelMonitor);

// Wait 5s for the connection to be created in the repository. This is happening asynchronously by the
// ConnectionService::didUpdateConnections event
Expand Down Expand Up @@ -179,19 +187,26 @@ void endFullBindingProcess() {
bindingSuggestionProvider.enable();
}

AssistCreatingConnectionResponse assistCreatingConnection(String serverUrl, @Nullable String tokenName, @Nullable String tokenValue, SonarLintCancelMonitor cancelMonitor) {
AssistCreatingConnectionResponse assistCreatingConnection(AssistCreatingConnectionParams connectionParams, SonarLintCancelMonitor cancelMonitor) {
try {
var future = client.assistCreatingConnection(new AssistCreatingConnectionParams(serverUrl, tokenName, tokenValue));
var future = client.assistCreatingConnection(connectionParams);
cancelMonitor.onCancel(() -> future.cancel(true));
return future.join();
} catch (Exception e) {
if (tokenName != null && tokenValue != null) {
userTokenService.revokeToken(new RevokeTokenParams(serverUrl, tokenName, tokenValue), cancelMonitor);
}
revokeToken(connectionParams, cancelMonitor);
throw e;
}
}

private void revokeToken(AssistCreatingConnectionParams connectionParams, SonarLintCancelMonitor cancelMonitor) {
String tokenName = connectionParams.getTokenName();
String tokenValue = connectionParams.getTokenValue();
if (tokenName != null && tokenValue != null) {
var revokeTokenParams = new RevokeTokenParams(getServerUrl(connectionParams), tokenName, tokenValue);
userTokenService.revokeToken(revokeTokenParams, cancelMonitor);
}
}

NewBinding assistBinding(String connectionId, String projectKey, SonarLintCancelMonitor cancelMonitor) {
var configScopeCandidates = bindingCandidatesFinder.findConfigScopesToBind(connectionId, projectKey, cancelMonitor);
// For now, we decided to only support automatic binding if there is only one clear candidate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import org.sonarsource.sonarlint.core.file.FilePathTranslation;
import org.sonarsource.sonarlint.core.file.PathTranslationService;
import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;
import org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;
import org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarQubeConnectionParams;
import org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.HotspotDetailsDto;
import org.sonarsource.sonarlint.core.rpc.protocol.client.hotspot.ShowHotspotParams;
import org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;
Expand Down Expand Up @@ -77,8 +79,9 @@ public void handle(ClassicHttpRequest request, ClassicHttpResponse response, Htt
return;
}
telemetryService.showHotspotRequestReceived();

requestHandlerBindingAssistant.assistConnectionAndBindingIfNeededAsync(showHotspotQuery.serverUrl, null, null, showHotspotQuery.projectKey,
var sonarQubeConnectionParams = new SonarQubeConnectionParams(showHotspotQuery.serverUrl, null, null);
var connectionParams = new AssistCreatingConnectionParams(sonarQubeConnectionParams);
requestHandlerBindingAssistant.assistConnectionAndBindingIfNeededAsync(connectionParams, showHotspotQuery.projectKey,
(connectionId, configScopeId, cancelMonitor) -> {
if (configScopeId != null) {
showHotspotForScope(connectionId, configScopeId, showHotspotQuery.hotspotKey, cancelMonitor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,21 @@
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.Method;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.ProtocolException;
import org.apache.hc.core5.http.io.HttpRequestHandler;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.net.URIBuilder;
import org.sonarsource.sonarlint.core.ServerApiProvider;
import org.sonarsource.sonarlint.core.SonarCloudActiveEnvironment;
import org.sonarsource.sonarlint.core.commons.progress.SonarLintCancelMonitor;
import org.sonarsource.sonarlint.core.file.FilePathTranslation;
import org.sonarsource.sonarlint.core.file.PathTranslationService;
import org.sonarsource.sonarlint.core.rpc.protocol.SonarLintRpcClient;
import org.sonarsource.sonarlint.core.rpc.protocol.client.connection.AssistCreatingConnectionParams;
import org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarCloudConnectionParams;
import org.sonarsource.sonarlint.core.rpc.protocol.client.connection.SonarQubeConnectionParams;
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.IssueDetailsDto;
import org.sonarsource.sonarlint.core.rpc.protocol.client.issue.ShowIssueParams;
import org.sonarsource.sonarlint.core.rpc.protocol.client.message.MessageType;
Expand All @@ -69,14 +75,16 @@ public class ShowIssueRequestHandler implements HttpRequestHandler {
private final TelemetryService telemetryService;
private final RequestHandlerBindingAssistant requestHandlerBindingAssistant;
private final PathTranslationService pathTranslationService;
private final String sonarCloudUrl;

public ShowIssueRequestHandler(SonarLintRpcClient client, ServerApiProvider serverApiProvider, TelemetryService telemetryService,
RequestHandlerBindingAssistant requestHandlerBindingAssistant, PathTranslationService pathTranslationService) {
RequestHandlerBindingAssistant requestHandlerBindingAssistant, PathTranslationService pathTranslationService, SonarCloudActiveEnvironment sonarCloudActiveEnvironment) {
this.client = client;
this.serverApiProvider = serverApiProvider;
this.telemetryService = telemetryService;
this.requestHandlerBindingAssistant = requestHandlerBindingAssistant;
this.pathTranslationService = pathTranslationService;
this.sonarCloudUrl = sonarCloudActiveEnvironment.getUri().toString();
}

@Override
Expand All @@ -88,7 +96,11 @@ public void handle(ClassicHttpRequest request, ClassicHttpResponse response, Htt
}
telemetryService.showIssueRequestReceived();

requestHandlerBindingAssistant.assistConnectionAndBindingIfNeededAsync(showIssueQuery.serverUrl, showIssueQuery.tokenName, showIssueQuery.tokenValue, showIssueQuery.projectKey,
AssistCreatingConnectionParams serverConnectionParams = createAssistServerConnectionParams(showIssueQuery);

requestHandlerBindingAssistant.assistConnectionAndBindingIfNeededAsync(
serverConnectionParams,
showIssueQuery.projectKey,
(connectionId, configScopeId, cancelMonitor) -> {
if (configScopeId != null) {
showIssueForScope(connectionId, configScopeId, showIssueQuery.issueKey, showIssueQuery.projectKey, showIssueQuery.branch,
Expand All @@ -100,6 +112,21 @@ public void handle(ClassicHttpRequest request, ClassicHttpResponse response, Htt
response.setEntity(new StringEntity("OK"));
}

private static AssistCreatingConnectionParams createAssistServerConnectionParams(ShowIssueQuery query) {
String tokenName = query.getTokenName();
String tokenValue = query.getTokenValue();
return query.isSonarCloud ?
new AssistCreatingConnectionParams(new SonarCloudConnectionParams(query.getOrganizationKey(), tokenName, tokenValue))
: new AssistCreatingConnectionParams(new SonarQubeConnectionParams(query.getServerUrl(), tokenName, tokenValue));
}

private boolean isSonarCloud(ClassicHttpRequest request) throws ProtocolException {
return Optional.ofNullable(request.getHeader("Origin"))
.map(NameValuePair::getValue)
.map(sonarCloudUrl::equals)
.orElse(false);
}

private void showIssueForScope(String connectionId, String configScopeId, String issueKey, String projectKey,
String branch, @Nullable String pullRequest, SonarLintCancelMonitor cancelMonitor) {
var issueDetailsOpt = tryFetchIssue(connectionId, issueKey, projectKey, branch, pullRequest, cancelMonitor);
Expand Down Expand Up @@ -159,7 +186,7 @@ private Optional<String> tryFetchCodeSnippet(String connectionId, String fileKey
}

@VisibleForTesting
static ShowIssueQuery extractQuery(ClassicHttpRequest request) {
ShowIssueQuery extractQuery(ClassicHttpRequest request) throws ProtocolException {
var params = new HashMap<String, String>();
try {
new URIBuilder(request.getUri(), StandardCharsets.UTF_8)
Expand All @@ -168,8 +195,10 @@ static ShowIssueQuery extractQuery(ClassicHttpRequest request) {
} catch (URISyntaxException e) {
// Ignored
}
return new ShowIssueQuery(params.get("server"), params.get("project"), params.get("issue"), params.get("branch"),
params.get("pullRequest"), params.get("tokenName"), params.get("tokenValue"));
boolean isSonarCloud = isSonarCloud(request);
String serverUrl = isSonarCloud ? sonarCloudUrl : params.get("server");
return new ShowIssueQuery(serverUrl, params.get("project"), params.get("issue"), params.get("branch"),
params.get("pullRequest"), params.get("tokenName"), params.get("tokenValue"), params.get("organizationKey"), isSonarCloud);
}

@VisibleForTesting
Expand All @@ -184,20 +213,27 @@ public static class ShowIssueQuery {
private final String tokenName;
@Nullable
private final String tokenValue;
@Nullable
private final String organizationKey;
private final boolean isSonarCloud;

public ShowIssueQuery(String serverUrl, String projectKey, String issueKey, String branch,
@Nullable String pullRequest, @Nullable String tokenName, @Nullable String tokenValue) {
public ShowIssueQuery(String serverUrl, String projectKey, String issueKey, String branch, @Nullable String pullRequest,
@Nullable String tokenName, @Nullable String tokenValue, @Nullable String organizationKey, boolean isSonarCloud) {
this.serverUrl = serverUrl;
this.projectKey = projectKey;
this.issueKey = issueKey;
this.branch = branch;
this.pullRequest = pullRequest;
this.tokenName = tokenName;
this.tokenValue = tokenValue;
this.organizationKey = organizationKey;
this.isSonarCloud = isSonarCloud;
}

public boolean isValid() {
return isNotBlank(serverUrl) && isNotBlank(projectKey) && isNotBlank(issueKey) && isNotBlank(branch)
return isNotBlank(projectKey) && isNotBlank(issueKey) && isNotBlank(branch)
&& (isSonarCloud || isNotBlank(serverUrl))
&& (!isSonarCloud || isNotBlank(organizationKey))
&& isPullRequestParamValid() && isTokenValid();
}

Expand Down Expand Up @@ -227,6 +263,11 @@ public String getProjectKey() {
return projectKey;
}

@Nullable
public String getOrganizationKey() {
return organizationKey;
}

public String getIssueKey() {
return issueKey;
}
Expand Down
Loading

0 comments on commit 34e5a83

Please sign in to comment.