Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SLCORE-818 Provide connection parameters for SC while opening an issue in IDE #995

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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