diff --git a/its/src/org/sonarlint/eclipse/its/AbstractSonarQubeConnectedModeTest.java b/its/src/org/sonarlint/eclipse/its/AbstractSonarQubeConnectedModeTest.java index b8b486594..4168e6b5f 100644 --- a/its/src/org/sonarlint/eclipse/its/AbstractSonarQubeConnectedModeTest.java +++ b/its/src/org/sonarlint/eclipse/its/AbstractSonarQubeConnectedModeTest.java @@ -37,9 +37,9 @@ import org.eclipse.swt.widgets.Label; import org.junit.Before; import org.osgi.framework.FrameworkUtil; +import org.sonarlint.eclipse.its.reddeer.dialogs.ProjectSelectionDialog; import org.sonarlint.eclipse.its.reddeer.views.BindingsView; import org.sonarlint.eclipse.its.reddeer.wizards.ProjectBindingWizard; -import org.sonarlint.eclipse.its.reddeer.wizards.ProjectSelectionDialog; import org.sonarlint.eclipse.its.reddeer.wizards.ServerConnectionWizard; import org.sonarqube.ws.client.WsClient; import org.sonarqube.ws.client.project.CreateRequest; @@ -144,7 +144,7 @@ protected static void createConnectionAndBindProject(OrchestratorRule orchestrat projectsToBindPage.clickAdd(); var projectSelectionDialog = new ProjectSelectionDialog(); - projectSelectionDialog.setProjectName(projectKey); + projectSelectionDialog.filterProjectName(projectKey); projectSelectionDialog.ok(); projectBindingWizard.next(); diff --git a/its/src/org/sonarlint/eclipse/its/OpenInIdeTest.java b/its/src/org/sonarlint/eclipse/its/OpenInIdeTest.java index a8dfe54be..8fccb0da4 100644 --- a/its/src/org/sonarlint/eclipse/its/OpenInIdeTest.java +++ b/its/src/org/sonarlint/eclipse/its/OpenInIdeTest.java @@ -20,6 +20,7 @@ package org.sonarlint.eclipse.its; import com.sonar.orchestrator.container.Edition; +import com.sonar.orchestrator.container.Server; import com.sonar.orchestrator.junit4.OrchestratorRule; import java.io.IOException; import java.net.URI; @@ -30,22 +31,30 @@ import java.util.Map; import org.eclipse.reddeer.common.wait.TimePeriod; import org.eclipse.reddeer.common.wait.WaitUntil; +import org.eclipse.reddeer.core.condition.WidgetIsFound; +import org.eclipse.reddeer.core.exception.CoreLayerException; +import org.eclipse.reddeer.core.matcher.WithTextMatcher; import org.eclipse.reddeer.eclipse.ui.perspectives.JavaPerspective; import org.eclipse.reddeer.swt.impl.shell.DefaultShell; import org.eclipse.reddeer.workbench.impl.editor.TextEditor; +import org.eclipse.swt.widgets.Label; import org.junit.After; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Test; +import org.sonarlint.eclipse.its.reddeer.conditions.ConfirmConnectionCreationDialogOpened; import org.sonarlint.eclipse.its.reddeer.conditions.OpenInIdeDialogOpened; import org.sonarlint.eclipse.its.reddeer.conditions.RuleDescriptionViewOpenedWithContent; import org.sonarlint.eclipse.its.reddeer.conditions.SonarLintTaintVulnerabilitiesViewOpened; +import org.sonarlint.eclipse.its.reddeer.dialogs.ConfirmConnectionCreationDialog; +import org.sonarlint.eclipse.its.reddeer.dialogs.OpenInIdeDialog; +import org.sonarlint.eclipse.its.reddeer.dialogs.ProjectSelectionDialog; import org.sonarlint.eclipse.its.reddeer.preferences.SonarLintPreferences.IssueFilter; import org.sonarlint.eclipse.its.reddeer.preferences.SonarLintPreferences.IssuePeriod; import org.sonarlint.eclipse.its.reddeer.views.RuleDescriptionView; import org.sonarlint.eclipse.its.reddeer.views.SonarLintTaintVulnerabilitiesView; -import org.sonarlint.eclipse.its.reddeer.wizards.OpenInIdeDialog; +import org.sonarlint.eclipse.its.reddeer.wizards.ServerConnectionWizard; import org.sonarqube.ws.Issues.Issue; import org.sonarqube.ws.WsBranches.Branch; import org.sonarqube.ws.client.issue.SearchWsRequest; @@ -62,7 +71,7 @@ public class OpenInIdeTest extends AbstractSonarQubeConnectedModeTest { private static final String S106 = "java:S106"; private static final String S1481 = "java:S1481"; private static final String S2083 = "javasecurity:S2083"; - + /** Orchestrator to not be re-used in order for ITs to not fail -> always use latest release locally (not LTS) */ @ClassRule public static final OrchestratorRule orchestrator = OrchestratorRule.builderEnv() @@ -77,12 +86,12 @@ public class OpenInIdeTest extends AbstractSonarQubeConnectedModeTest { .setServerProperty("sonar.pushevents.polling.period", "1") .setServerProperty("sonar.pushevents.polling.last.timestamp", "1") .build(); - + @BeforeClass public static void prepare() { prepare(orchestrator); } - + /** as we re-use the same project we have to delete it on SonarQube after every test */ @After public void deleteSonarQubeProjects() { @@ -90,43 +99,119 @@ public void deleteSonarQubeProjects() { adminWsClient.projects().delete(DeleteRequest.builder().setKey(MAVEN_TAINT_PROJECT_KEY).build()); } } - - /** integration test for when the feature fails due to the local file not being found */ + @Test - public void test_open_in_ide_without_corner_cases() throws IOException, InterruptedException { + public void test_open_in_ide_assist_binding() throws IOException, InterruptedException { // Only available since SonarQube 10.2+ (LATEST_RELEASE / locally) Assume.assumeTrue(orchestrator.getServer().version().isGreaterThanOrEquals(10, 2)); - + // 1) create project on server / run first analysis createProjectOnSonarQube(orchestrator, MAVEN_TAINT_PROJECT_KEY, "SonarLint IT New Code"); runMavenBuild(orchestrator, MAVEN_TAINT_PROJECT_KEY, "projects", "java/maven-taint/pom.xml", Map.of("sonar.branch.name", "main")); - + + // 2) import project + new JavaPerspective().open(); + var rootProject = importExistingProjectIntoWorkspace("java/maven-taint", MAVEN_TAINT_PROJECT_KEY); + + // 3) get S1481 issue key / branch name from SonarQube + var s101 = getFirstIssue(S101); + assertThat(s101).isNotNull(); + + var branch = getFirstBranch(); + assertThat(branch).isNotNull(); + + // 4) trigger "Open in IDE" feature, but cancel + triggerOpenInIDE(orchestrator.getServer().getUrl(), branch.getName(), s101.getKey()); + new WaitUntil(new ConfirmConnectionCreationDialogOpened(), TimePeriod.DEFAULT); + new ConfirmConnectionCreationDialog().donottrust(); + + // 5) trigger "Open in IDE" feature, but accept this time + triggerOpenInIDE(orchestrator.getServer().getUrl(), branch.getName(), s101.getKey()); + new WaitUntil(new ConfirmConnectionCreationDialogOpened(), TimePeriod.DEFAULT); + new ConfirmConnectionCreationDialog().trust(); + + var wizard = new ServerConnectionWizard(); + assertThat(new ServerConnectionWizard.ServerTypePage(wizard).isSonarQubeSelected()).isTrue(); + wizard.next(); + + var serverUrlPage = new ServerConnectionWizard.ServerUrlPage(wizard); + assertThat(serverUrlPage.getUrl()).isEqualTo(orchestrator.getServer().getUrl()); + wizard.next(); + + var authenticationModePage = new ServerConnectionWizard.AuthenticationModePage(wizard); + authenticationModePage.selectUsernamePasswordMode(); + wizard.next(); + + var authenticationPage = new ServerConnectionWizard.AuthenticationPage(wizard); + authenticationPage.setUsername(Server.ADMIN_LOGIN); + authenticationPage.setPassword(Server.ADMIN_PASSWORD); + wizard.next(); + + // as login can take time, wait for the next page to appear + new WaitUntil(new WidgetIsFound(Label.class, new WithTextMatcher("SonarQube Connection Identifier"))); + var connectionNamePage = new ServerConnectionWizard.ConnectionNamePage(wizard); + + connectionNamePage.setConnectionName("from open IDE"); + wizard.next(); + + wizard.next(); + wizard.finish(); + + var projectSelectionDialog = new ProjectSelectionDialog(); + assertThat(projectSelectionDialog.getMessage()).contains("This Eclipse project will be bound to the Sonar project 'maven-taint' using connection 'from open IDE'"); + projectSelectionDialog.filterProjectName(MAVEN_TAINT_PROJECT_KEY); + projectSelectionDialog.ok(); + + var ruleDescriptionView = new RuleDescriptionView(); + new WaitUntil(new RuleDescriptionViewOpenedWithContent(ruleDescriptionView, S101), TimePeriod.DEFAULT); + + closeTaintPopupIfAny(); + } + + private void closeTaintPopupIfAny() { + try { + new DefaultShell("SonarLint - Taint vulnerability found").close(); + } catch (CoreLayerException notFound) { + + } + } + + @Test + public void test_open_in_ide_when_project_already_bound() throws IOException, InterruptedException { + // Only available since SonarQube 10.2+ (LATEST_RELEASE / locally) + Assume.assumeTrue(orchestrator.getServer().version().isGreaterThanOrEquals(10, 2)); + + // 1) create project on server / run first analysis + createProjectOnSonarQube(orchestrator, MAVEN_TAINT_PROJECT_KEY, "SonarLint IT New Code"); + runMavenBuild(orchestrator, MAVEN_TAINT_PROJECT_KEY, "projects", "java/maven-taint/pom.xml", + Map.of("sonar.branch.name", "main")); + // 2) import project and bind to SonarQube new JavaPerspective().open(); var rootProject = importExistingProjectIntoWorkspace("java/maven-taint", MAVEN_TAINT_PROJECT_KEY); createConnectionAndBindProject(orchestrator, MAVEN_TAINT_PROJECT_KEY); try { new DefaultShell("SonarLint Binding Suggestion").close(); - } catch (Exception ignored) { } - - + } catch (Exception ignored) { + } + // 3) delete file rootProject.getResource("src/main/java", "taint", "Variables.java").delete(); - + // 4) get S1481 issue key / branch name from SonarQube - var s101 = getFirstIssue(S1481); - assertThat(s101).isNotNull(); - + var s1481 = getFirstIssue(S1481); + assertThat(s1481).isNotNull(); + var branch = getFirstBranch(); assertThat(branch).isNotNull(); - + // 5) trigger "Open in IDE" feature: issue not found because file not found - // -> because the API is quite slow we have to await the opening of the error dialog - triggerOpenInIDE(orchestrator.getServer().getUrl(), branch.getName(), s101.getKey()); + // -> because the API is quite slow we have to await the opening of the error dialog + triggerOpenInIDE(orchestrator.getServer().getUrl(), branch.getName(), s1481.getKey()); new WaitUntil(new OpenInIdeDialogOpened(), TimePeriod.DEFAULT); new OpenInIdeDialog().ok(); - + // 6) change preferences regarding new code / issue filter + change file content setNewCodePreference(IssuePeriod.NEW_CODE); openFileAndWaitForAnalysisCompletion(rootProject.getResource("src/main/java", "taint", "SysOut.java")); @@ -140,59 +225,60 @@ public void test_open_in_ide_without_corner_cases() throws IOException, Interrup + ""); textEditor.save(); Thread.sleep(1000); // we have to wait for the analysis to finish - + // 7) get S106 issue key from SonarQube var s106 = getFirstIssue(S106); assertThat(s106).isNotNull(); - + // 8) trigger "Open in IDE" feature: issue not found because workspace preferences - // -> after we acknowledge SL to change preferences the dialog will open again + // -> after we acknowledge SL to change preferences the dialog will open again triggerOpenInIDE(orchestrator.getServer().getUrl(), branch.getName(), s106.getKey()); new WaitUntil(new OpenInIdeDialogOpened(), TimePeriod.DEFAULT); new OpenInIdeDialog().yes(); - + Thread.sleep(500); // we have to wait for the preference page to open and to get the job data setIssueFilterPreference(IssueFilter.ALL_ISSUES); - + new WaitUntil(new OpenInIdeDialogOpened(), TimePeriod.DEFAULT); new OpenInIdeDialog().ok(); - + // 9) get S101 issue key from SonarQube - var s1481 = getFirstIssue(S101); - assertThat(s1481).isNotNull(); - + var s101 = getFirstIssue(S101); + assertThat(s101).isNotNull(); + // 10) trigger "Open in IDE" feature: issue opens correctly including On-The-Fly / Rule Description view - triggerOpenInIDE(orchestrator.getServer().getUrl(), branch.getName(), s1481.getKey()); + triggerOpenInIDE(orchestrator.getServer().getUrl(), branch.getName(), s101.getKey()); var ruleDescriptionView = new RuleDescriptionView(); new WaitUntil(new RuleDescriptionViewOpenedWithContent(ruleDescriptionView, S101), TimePeriod.DEFAULT); ruleDescriptionView.open(); - + // 11) get S2083 issue key from SonarQube var s2083 = getFirstIssue(S2083); assertThat(s2083).isNotNull(); - + // 12) trigger "Open in IDE" feature: taint vulnerability opens correctly including Taint Vulnerabilities view triggerOpenInIDE(orchestrator.getServer().getUrl(), branch.getName(), s2083.getKey()); var sonarLintTaintVulnerabilitiesView = new SonarLintTaintVulnerabilitiesView(); new WaitUntil(new SonarLintTaintVulnerabilitiesViewOpened(sonarLintTaintVulnerabilitiesView), TimePeriod.DEFAULT); sonarLintTaintVulnerabilitiesView.open(); assertThat(sonarLintTaintVulnerabilitiesView.getItems()).isNotEmpty(); - new DefaultShell("SonarLint - Taint vulnerability found").close(); + + closeTaintPopupIfAny(); } - + /** * To emulate a user clicking on "Open in IDE" on their SonarQube on an issue - * + * * @param serverURL the server URL to be passed to the web request ??? * @param branch issue branch * @param projectKey project containing the issue * @param issueKey specific issue * @throws IOException when connection fails - * @throws InterruptedException + * @throws InterruptedException */ private void triggerOpenInIDE(String serverURL, String branch, String issueKey) throws InterruptedException, IOException { assertThat(hotspotServerPort).isNotEqualTo(-1); - + var request = HttpRequest.newBuilder() .uri(URI.create("http://localhost:" + hotspotServerPort + "/sonarlint/api/issues/show?server=" + URLEncoder.encode(serverURL) + "&project=" + MAVEN_TAINT_PROJECT_KEY @@ -201,26 +287,26 @@ private void triggerOpenInIDE(String serverURL, String branch, String issueKey) .header("Origin", serverURL) .header("Referer", serverURL) .GET().build(); - + var response = HttpClient.newHttpClient().send(request, java.net.http.HttpResponse.BodyHandlers.ofString()); assertThat(response.statusCode()).isEqualTo(200); } - + /** Get first issue from project matching the rule key provided (does not contain branch information???) */ private static Issue getFirstIssue(String ruleKey) { var response = adminWsClient.issues().search(new SearchWsRequest() .setRules(List.of(ruleKey)) .setProjects(List.of(MAVEN_TAINT_PROJECT_KEY))); - + assertThat(response.getIssuesCount()).isPositive(); return response.getIssues(0); } - + /** Get first branch from project */ private static Branch getFirstBranch() { var response = adminWsClient.projectBranches().list(MAVEN_TAINT_PROJECT_KEY); assertThat(response.getBranchesCount()).isPositive(); - + return response.getBranches(0); } } diff --git a/its/src/org/sonarlint/eclipse/its/SonarCloudConnectedModeTest.java b/its/src/org/sonarlint/eclipse/its/SonarCloudConnectedModeTest.java index 241c3eb74..c2876bf3f 100644 --- a/its/src/org/sonarlint/eclipse/its/SonarCloudConnectedModeTest.java +++ b/its/src/org/sonarlint/eclipse/its/SonarCloudConnectedModeTest.java @@ -29,10 +29,10 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.sonarlint.eclipse.its.reddeer.conditions.DialogMessageIsExpected; +import org.sonarlint.eclipse.its.reddeer.dialogs.ProjectSelectionDialog; import org.sonarlint.eclipse.its.reddeer.views.BindingsView; import org.sonarlint.eclipse.its.reddeer.views.BindingsView.Binding; import org.sonarlint.eclipse.its.reddeer.wizards.ProjectBindingWizard; -import org.sonarlint.eclipse.its.reddeer.wizards.ProjectSelectionDialog; import org.sonarlint.eclipse.its.reddeer.wizards.ServerConnectionWizard; import org.sonarqube.ws.client.HttpConnector; import org.sonarqube.ws.client.WsClient; @@ -141,7 +141,7 @@ public void configureServerWithTokenAndOrganization() { projectsToBindPage.clickAdd(); var projectSelectionDialog = new ProjectSelectionDialog(); - projectSelectionDialog.setProjectName(IMPORTED_PROJECT_NAME); + projectSelectionDialog.filterProjectName(IMPORTED_PROJECT_NAME); projectSelectionDialog.ok(); projectBindingWizard.next(); diff --git a/its/src/org/sonarlint/eclipse/its/SonarQubeConnectedModeTest.java b/its/src/org/sonarlint/eclipse/its/SonarQubeConnectedModeTest.java index 8175eaf92..dda58b218 100644 --- a/its/src/org/sonarlint/eclipse/its/SonarQubeConnectedModeTest.java +++ b/its/src/org/sonarlint/eclipse/its/SonarQubeConnectedModeTest.java @@ -55,6 +55,7 @@ import org.junit.Test; import org.sonarlint.eclipse.its.reddeer.conditions.DialogMessageIsExpected; import org.sonarlint.eclipse.its.reddeer.conditions.RuleDescriptionViewIsLoaded; +import org.sonarlint.eclipse.its.reddeer.dialogs.MarkIssueAsDialog; import org.sonarlint.eclipse.its.reddeer.preferences.SonarLintPreferences.IssuePeriod; import org.sonarlint.eclipse.its.reddeer.views.BindingsView; import org.sonarlint.eclipse.its.reddeer.views.BindingsView.Binding; @@ -62,7 +63,6 @@ import org.sonarlint.eclipse.its.reddeer.views.RuleDescriptionView; import org.sonarlint.eclipse.its.reddeer.views.SonarLintIssueMarker; import org.sonarlint.eclipse.its.reddeer.views.SonarLintTaintVulnerabilitiesView; -import org.sonarlint.eclipse.its.reddeer.wizards.MarkIssueAsDialog; import org.sonarlint.eclipse.its.reddeer.wizards.ProjectBindingWizard; import org.sonarlint.eclipse.its.reddeer.wizards.ServerConnectionWizard; import org.sonarlint.eclipse.its.reddeer.wizards.ServerConnectionWizard.AuthenticationPage; diff --git a/its/src/org/sonarlint/eclipse/its/StandaloneAnalysisTest.java b/its/src/org/sonarlint/eclipse/its/StandaloneAnalysisTest.java index 7120966ef..ce8398cd0 100644 --- a/its/src/org/sonarlint/eclipse/its/StandaloneAnalysisTest.java +++ b/its/src/org/sonarlint/eclipse/its/StandaloneAnalysisTest.java @@ -56,6 +56,7 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.sonarlint.eclipse.its.reddeer.conditions.OnTheFlyViewIsEmpty; +import org.sonarlint.eclipse.its.reddeer.dialogs.EnhancedWithConnectedModeInformationDialog; import org.sonarlint.eclipse.its.reddeer.perspectives.PhpPerspective; import org.sonarlint.eclipse.its.reddeer.perspectives.PydevPerspective; import org.sonarlint.eclipse.its.reddeer.preferences.SonarLintPreferences; @@ -64,7 +65,6 @@ import org.sonarlint.eclipse.its.reddeer.views.PydevPackageExplorer; import org.sonarlint.eclipse.its.reddeer.views.ReportView; import org.sonarlint.eclipse.its.reddeer.views.SonarLintIssueMarker; -import org.sonarlint.eclipse.its.reddeer.wizards.EnhancedWithConnectedModeInformationDialog; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; diff --git a/its/src/org/sonarlint/eclipse/its/reddeer/conditions/ConfirmConnectionCreationDialogOpened.java b/its/src/org/sonarlint/eclipse/its/reddeer/conditions/ConfirmConnectionCreationDialogOpened.java new file mode 100644 index 000000000..ceb63d4ea --- /dev/null +++ b/its/src/org/sonarlint/eclipse/its/reddeer/conditions/ConfirmConnectionCreationDialogOpened.java @@ -0,0 +1,55 @@ +/* + * SonarLint for Eclipse ITs + * Copyright (C) 2009-2023 SonarSource SA + * sonarlint@sonarsource.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.sonarlint.eclipse.its.reddeer.conditions; + +import org.eclipse.reddeer.common.condition.WaitCondition; +import org.sonarlint.eclipse.its.reddeer.dialogs.ConfirmConnectionCreationDialog; + +public class ConfirmConnectionCreationDialogOpened implements WaitCondition { + @Override + public boolean test() { + try { + new ConfirmConnectionCreationDialog().isEnabled(); + return true; + } catch (Exception ignored) { + return false; + } + } + + @Override + public ConfirmConnectionCreationDialog getResult() { + return new ConfirmConnectionCreationDialog(); + } + + @Override + public String description() { + return "'Confirm Connection Creation' dialog is opened"; + } + + @Override + public String errorMessageWhile() { + return "'Confirm Connection Creation' dialog is still opened"; + } + + @Override + public String errorMessageUntil() { + return "'Confirm Connection Creation' dialog is not yet opened"; + } +} diff --git a/its/src/org/sonarlint/eclipse/its/reddeer/conditions/OpenInIdeDialogOpened.java b/its/src/org/sonarlint/eclipse/its/reddeer/conditions/OpenInIdeDialogOpened.java index c357d3779..0743c0ea1 100644 --- a/its/src/org/sonarlint/eclipse/its/reddeer/conditions/OpenInIdeDialogOpened.java +++ b/its/src/org/sonarlint/eclipse/its/reddeer/conditions/OpenInIdeDialogOpened.java @@ -20,7 +20,7 @@ package org.sonarlint.eclipse.its.reddeer.conditions; import org.eclipse.reddeer.common.condition.WaitCondition; -import org.sonarlint.eclipse.its.reddeer.wizards.OpenInIdeDialog; +import org.sonarlint.eclipse.its.reddeer.dialogs.OpenInIdeDialog; /** Await the "Open in IDE" dialog is opened (no matter what the content is) */ public class OpenInIdeDialogOpened implements WaitCondition { diff --git a/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/ConfirmConnectionCreationDialog.java b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/ConfirmConnectionCreationDialog.java new file mode 100644 index 000000000..17aee9012 --- /dev/null +++ b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/ConfirmConnectionCreationDialog.java @@ -0,0 +1,37 @@ +/* + * SonarLint for Eclipse ITs + * Copyright (C) 2009-2023 SonarSource SA + * sonarlint@sonarsource.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.sonarlint.eclipse.its.reddeer.dialogs; + +import org.eclipse.reddeer.swt.impl.shell.DefaultShell; + +public class ConfirmConnectionCreationDialog extends DefaultShell { + public ConfirmConnectionCreationDialog() { + super("Do you trust this SonarQube server?"); + } + + public void trust() { + new TrustButton(this).click(); + } + + public void donottrust() { + new DoNotTrustButton(this).click(); + } + +} diff --git a/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/DoNotTrustButton.java b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/DoNotTrustButton.java new file mode 100644 index 000000000..bc785846a --- /dev/null +++ b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/DoNotTrustButton.java @@ -0,0 +1,37 @@ +/* + * SonarLint for Eclipse ITs + * Copyright (C) 2009-2023 SonarSource SA + * sonarlint@sonarsource.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.sonarlint.eclipse.its.reddeer.dialogs; + +import org.eclipse.reddeer.core.reference.ReferencedComposite; +import org.eclipse.reddeer.swt.impl.button.PredefinedButton; +import org.eclipse.swt.SWT; + +public class DoNotTrustButton extends PredefinedButton { + + public DoNotTrustButton(ReferencedComposite referencedComposite) { + this(referencedComposite, 0); + + } + + public DoNotTrustButton(ReferencedComposite referencedComposite, int index) { + super(referencedComposite, index, "I don't trust this server", SWT.PUSH); + } + +} diff --git a/its/src/org/sonarlint/eclipse/its/reddeer/wizards/EnhancedWithConnectedModeInformationDialog.java b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/EnhancedWithConnectedModeInformationDialog.java similarity index 97% rename from its/src/org/sonarlint/eclipse/its/reddeer/wizards/EnhancedWithConnectedModeInformationDialog.java rename to its/src/org/sonarlint/eclipse/its/reddeer/dialogs/EnhancedWithConnectedModeInformationDialog.java index c987d64e8..4bb08a458 100644 --- a/its/src/org/sonarlint/eclipse/its/reddeer/wizards/EnhancedWithConnectedModeInformationDialog.java +++ b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/EnhancedWithConnectedModeInformationDialog.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.sonarlint.eclipse.its.reddeer.wizards; +package org.sonarlint.eclipse.its.reddeer.dialogs; import org.eclipse.reddeer.core.reference.ReferencedComposite; import org.eclipse.reddeer.swt.impl.button.PredefinedButton; diff --git a/its/src/org/sonarlint/eclipse/its/reddeer/wizards/MarkIssueAsDialog.java b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/MarkIssueAsDialog.java similarity index 97% rename from its/src/org/sonarlint/eclipse/its/reddeer/wizards/MarkIssueAsDialog.java rename to its/src/org/sonarlint/eclipse/its/reddeer/dialogs/MarkIssueAsDialog.java index f6eb6bfc0..93494bf1c 100644 --- a/its/src/org/sonarlint/eclipse/its/reddeer/wizards/MarkIssueAsDialog.java +++ b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/MarkIssueAsDialog.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.sonarlint.eclipse.its.reddeer.wizards; +package org.sonarlint.eclipse.its.reddeer.dialogs; import org.eclipse.reddeer.core.reference.ReferencedComposite; import org.eclipse.reddeer.swt.impl.button.PredefinedButton; diff --git a/its/src/org/sonarlint/eclipse/its/reddeer/wizards/OpenInIdeDialog.java b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/OpenInIdeDialog.java similarity index 96% rename from its/src/org/sonarlint/eclipse/its/reddeer/wizards/OpenInIdeDialog.java rename to its/src/org/sonarlint/eclipse/its/reddeer/dialogs/OpenInIdeDialog.java index 33e69554b..812156ae0 100644 --- a/its/src/org/sonarlint/eclipse/its/reddeer/wizards/OpenInIdeDialog.java +++ b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/OpenInIdeDialog.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.sonarlint.eclipse.its.reddeer.wizards; +package org.sonarlint.eclipse.its.reddeer.dialogs; import org.eclipse.reddeer.swt.impl.button.NoButton; import org.eclipse.reddeer.swt.impl.button.OkButton; diff --git a/its/src/org/sonarlint/eclipse/its/reddeer/wizards/ProjectSelectionDialog.java b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/ProjectSelectionDialog.java similarity index 77% rename from its/src/org/sonarlint/eclipse/its/reddeer/wizards/ProjectSelectionDialog.java rename to its/src/org/sonarlint/eclipse/its/reddeer/dialogs/ProjectSelectionDialog.java index 69df722b2..cced37867 100644 --- a/its/src/org/sonarlint/eclipse/its/reddeer/wizards/ProjectSelectionDialog.java +++ b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/ProjectSelectionDialog.java @@ -17,23 +17,27 @@ * 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.sonarlint.eclipse.its.reddeer.wizards; +package org.sonarlint.eclipse.its.reddeer.dialogs; -import org.eclipse.reddeer.jface.dialogs.TitleAreaDialog; import org.eclipse.reddeer.swt.impl.button.OkButton; +import org.eclipse.reddeer.swt.impl.label.DefaultLabel; import org.eclipse.reddeer.swt.impl.shell.DefaultShell; import org.eclipse.reddeer.swt.impl.text.DefaultText; -public class ProjectSelectionDialog extends TitleAreaDialog { +public class ProjectSelectionDialog extends DefaultShell { public ProjectSelectionDialog() { - super(new DefaultShell()); + super("SonarLint - Project Selection"); } - public void setProjectName(String projectName) { + public void filterProjectName(String projectName) { new DefaultText(this).setText(projectName); } public void ok() { new OkButton(this).click(); } + + public String getMessage() { + return new DefaultLabel().getText(); + } } diff --git a/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/TrustButton.java b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/TrustButton.java new file mode 100644 index 000000000..30fc1fcdd --- /dev/null +++ b/its/src/org/sonarlint/eclipse/its/reddeer/dialogs/TrustButton.java @@ -0,0 +1,37 @@ +/* + * SonarLint for Eclipse ITs + * Copyright (C) 2009-2023 SonarSource SA + * sonarlint@sonarsource.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.sonarlint.eclipse.its.reddeer.dialogs; + +import org.eclipse.reddeer.core.reference.ReferencedComposite; +import org.eclipse.reddeer.swt.impl.button.PredefinedButton; +import org.eclipse.swt.SWT; + +public class TrustButton extends PredefinedButton { + + public TrustButton(ReferencedComposite referencedComposite) { + this(referencedComposite, 0); + + } + + public TrustButton(ReferencedComposite referencedComposite, int index) { + super(referencedComposite, index, "Connect to this SonarQube server", SWT.PUSH); + } + +} diff --git a/its/src/org/sonarlint/eclipse/its/reddeer/wizards/ServerConnectionWizard.java b/its/src/org/sonarlint/eclipse/its/reddeer/wizards/ServerConnectionWizard.java index eecd5745d..d285c9b61 100644 --- a/its/src/org/sonarlint/eclipse/its/reddeer/wizards/ServerConnectionWizard.java +++ b/its/src/org/sonarlint/eclipse/its/reddeer/wizards/ServerConnectionWizard.java @@ -48,7 +48,15 @@ public void selectSonarCloud() { } public void selectSonarQube() { - new RadioButton(this, 1).click(); + getSonarQubeRB().click(); + } + + public boolean isSonarQubeSelected() { + return getSonarQubeRB().isSelected(); + } + + private RadioButton getSonarQubeRB() { + return new RadioButton(this, 1); } } @@ -61,6 +69,10 @@ public ServerUrlPage(ReferencedComposite referencedComposite) { public void setUrl(String url) { new DefaultText(this).setText(url); } + + public String getUrl() { + return new DefaultText(this).getText(); + } } public static class AuthenticationModePage extends WizardPage { @@ -77,7 +89,7 @@ public void selectUsernamePasswordMode() { public static class AuthenticationPage extends WizardPage { public final String DEPRECATION_MESSAGE = "Authentication via username and password is deprecated and will " + "be removed in the future. Please use a token instead."; - + public AuthenticationPage(ReferencedComposite referencedComposite) { super(referencedComposite); } @@ -93,7 +105,7 @@ public void setUsername(String adminLogin) { public void setPassword(String password) { new DefaultText(this, 1).setText(password); } - + public String getDeprecationMessage() { return new DefaultLabel(this, 2).getText(); } diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/backend/SonarLintEclipseClient.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/backend/SonarLintEclipseClient.java index 656329402..32f2a0556 100644 --- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/backend/SonarLintEclipseClient.java +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/backend/SonarLintEclipseClient.java @@ -24,11 +24,13 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; import java.util.stream.Collectors; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.text.IDocument; import org.eclipse.swt.widgets.Display; @@ -40,19 +42,15 @@ import org.sonarlint.eclipse.core.internal.backend.ConfigScopeSynchronizer; import org.sonarlint.eclipse.core.internal.backend.SonarLintEclipseHeadlessClient; import org.sonarlint.eclipse.core.internal.engine.connected.ConnectedEngineFacade; -import org.sonarlint.eclipse.core.internal.engine.connected.IConnectedEngineFacade; import org.sonarlint.eclipse.core.internal.jobs.TaintIssuesUpdateAfterSyncJob; import org.sonarlint.eclipse.core.internal.markers.MarkerUtils; import org.sonarlint.eclipse.core.internal.preferences.SonarLintGlobalConfiguration; import org.sonarlint.eclipse.core.internal.utils.SonarLintUtils; import org.sonarlint.eclipse.core.resource.ISonarLintFile; import org.sonarlint.eclipse.core.resource.ISonarLintProject; -import org.sonarlint.eclipse.ui.internal.binding.ProjectSelectionDialog; import org.sonarlint.eclipse.ui.internal.binding.actions.AnalysisJobsScheduler; -import org.sonarlint.eclipse.ui.internal.binding.wizard.connection.ServerConnectionModel; -import org.sonarlint.eclipse.ui.internal.binding.wizard.connection.ServerConnectionModel.ConnectionType; -import org.sonarlint.eclipse.ui.internal.binding.wizard.connection.ServerConnectionWizard; -import org.sonarlint.eclipse.ui.internal.binding.wizard.project.ProjectBindingProcess; +import org.sonarlint.eclipse.ui.internal.binding.assist.AssistBindingJob; +import org.sonarlint.eclipse.ui.internal.binding.assist.AssistCreatingConnectionJob; import org.sonarlint.eclipse.ui.internal.hotspots.HotspotsView; import org.sonarlint.eclipse.ui.internal.job.BackendProgressJobScheduler; import org.sonarlint.eclipse.ui.internal.job.OpenIssueInEclipseJob; @@ -162,18 +160,45 @@ public void showHotspot(ShowHotspotParams params) { @Override public CompletableFuture assistCreatingConnection(AssistCreatingConnectionParams params) { - return DisplayUtils.bringToFrontAsync() - .thenComposeAsync(unused -> createConnection(params.getServerUrl())) - .thenApplyAsync(connection -> new AssistCreatingConnectionResponse(connection.getId())); + return CompletableFuture.supplyAsync(() -> { + try { + SonarLintLogger.get().debug("Assist creating a new connection..."); + var job = new AssistCreatingConnectionJob(params.getServerUrl()); + job.schedule(); + job.join(); + if (job.getResult().isOK()) { + SonarLintLogger.get().debug("Successfully created connection '" + job.getConnectionId() + "'"); + return new AssistCreatingConnectionResponse(job.getConnectionId()); + } else if (job.getResult().getCode() == IStatus.CANCEL) { + SonarLintLogger.get().debug("Assist creating connection was cancelled."); + return new AssistCreatingConnectionResponse(null); + } + throw new IllegalStateException(job.getResult().getMessage(), job.getResult().getException()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new CancellationException("Interrupted!"); + } + }); } @Override public CompletableFuture assistBinding(AssistBindingParams params) { - var connectionId = params.getConnectionId(); - var projectKey = params.getProjectKey(); - return DisplayUtils.bringToFrontAsync() - .thenComposeAsync(unused -> bindProjectTo(connectionId, projectKey)) - .thenApplyAsync(project -> new AssistBindingResponse(ConfigScopeSynchronizer.getConfigScopeId(project))); + return CompletableFuture.supplyAsync(() -> { + try { + SonarLintLogger.get().debug("Assist creating a new binding..."); + var job = new AssistBindingJob(params.getConnectionId(), params.getProjectKey()); + job.schedule(); + job.join(); + if (job.getResult().isOK()) { + SonarLintLogger.get().debug("Successfully created binding"); + return new AssistBindingResponse(ConfigScopeSynchronizer.getConfigScopeId(job.getProject())); + } + throw new IllegalStateException(job.getResult().getMessage(), job.getResult().getException()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new CancellationException("Interrupted!"); + } + }); } private static Optional findHotspotFile(String hotspotFilePath, ISonarLintProject project) { @@ -234,35 +259,6 @@ private static void showPopup(String type, String message) { }); } - @Nullable - private static CompletableFuture createConnection(String serverUrl) { - var model = new ServerConnectionModel(); - model.setConnectionType(ConnectionType.ONPREMISE); - model.setServerUrl(serverUrl); - var wizard = new ServerConnectionWizard(model); - wizard.setSkipBindingWizard(true); - return DisplayUtils.asyncExec(() -> { - var dialog = ServerConnectionWizard.createDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), wizard); - dialog.setBlockOnOpen(true); - dialog.open(); - return wizard.getResultServer(); - }); - } - - private static CompletableFuture bindProjectTo(String connectionId, String projectKey) { - return DisplayUtils.asyncExec(() -> ProjectSelectionDialog.pickProject(projectKey, connectionId)) - .thenComposeAsync(pickedProject -> { - var bindingJob = ProjectBindingProcess.scheduleProjectBinding(connectionId, List.of(pickedProject), projectKey); - try { - bindingJob.join(); - } catch (InterruptedException e) { - SonarLintLogger.get().error("Cannot bind project", e); - Thread.currentThread().interrupt(); - } - return CompletableFuture.completedFuture(pickedProject); - }); - } - @Override public void showSmartNotification(ShowSmartNotificationParams params) { var connectionOpt = SonarLintCorePlugin.getServersManager().findById(params.getConnectionId()); @@ -328,7 +324,7 @@ public void showSoonUnsupportedMessage(ShowSoonUnsupportedMessageParams params) if (SonarLintGlobalConfiguration.alreadySoonUnsupportedConnection(connectionVersionCombination)) { return; } - + Display.getDefault().syncExec(() -> { var popup = new SoonUnsupportedPopup(params.getDoNotShowAgainId(), params.getText()); popup.setFadingEnabled(false); @@ -355,7 +351,7 @@ public void showIssue(ShowIssueParams params) { return; } var project = projectOpt.get(); - + // We were just asked to connect and create a binding, this cannot happen -> Only log the information var bindingOpt = SonarLintCorePlugin.getServersManager().resolveBinding(project); if (bindingOpt.isEmpty()) { @@ -363,7 +359,7 @@ public void showIssue(ShowIssueParams params) { + "' removed its binding in the middle of running the action on '" + params + "'"); return; } - + // Handle expensive checks and actual logic in separate job to not block the thread new OpenIssueInEclipseJob(new OpenIssueContext("Open in IDE", params, project, bindingOpt.get())) .schedule(); diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/ProjectSelectionDialog.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/ProjectToBindSelectionDialog.java similarity index 51% rename from org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/ProjectSelectionDialog.java rename to org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/ProjectToBindSelectionDialog.java index f9f3a0fb0..db853e8ca 100644 --- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/ProjectSelectionDialog.java +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/ProjectToBindSelectionDialog.java @@ -20,50 +20,56 @@ package org.sonarlint.eclipse.ui.internal.binding; import java.util.Comparator; +import java.util.List; import java.util.stream.Collectors; -import org.eclipse.jface.viewers.LabelProvider; +import java.util.stream.Stream; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.window.Window; -import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.ElementListSelectionDialog; -import org.eclipse.ui.ide.IDE.SharedImages; import org.sonarlint.eclipse.core.internal.resources.ProjectsProviderUtils; import org.sonarlint.eclipse.core.resource.ISonarLintProject; -public class ProjectSelectionDialog { +import static java.util.Comparator.comparing; +import static java.util.stream.Collectors.toList; +public class ProjectToBindSelectionDialog extends ElementListSelectionDialog { + + public ProjectToBindSelectionDialog(Shell parent, String message, List projects) { + super(parent, new SonarLintProjectLabelProvider()); + setElements(projects.toArray()); + setTitle("SonarLint - Project Selection"); + setMessage(message); + setHelpAvailable(false); + } + + @Nullable public static ISonarLintProject pickProject(String projectKey, String connectionId) { var projects = ProjectsProviderUtils.allProjects() .stream() .sorted(Comparator.comparing(ISonarLintProject::getName)) .collect(Collectors.toList()); - var dialog = new ElementListSelectionDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), - new SonarLintProjectLabelProvider()); - dialog.setElements(projects.toArray()); - dialog.setMessage("Select a project.\nThis Eclipse project will be bound to the project '" + projectKey + "' using connection '" + connectionId + "'"); - dialog.setTitle("SonarLint - Project binding"); - dialog.setHelpAvailable(false); + var dialog = new ProjectToBindSelectionDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), + "Select a project.\nThis Eclipse project will be bound to the Sonar project '" + projectKey + "' using connection '" + connectionId + "'", projects); if (dialog.open() == Window.OK) { return (ISonarLintProject) dialog.getResult()[0]; } return null; } - private static final class SonarLintProjectLabelProvider extends LabelProvider { - @Override - public String getText(Object element) { - var current = (ISonarLintProject) element; - return current.getName(); - } - - @Override - public Image getImage(Object element) { - return PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT); + public static List selectProjectsToAdd(Shell parent, List alreadySelected) { + var projects = ProjectsProviderUtils.allProjects() + .stream() + .filter(p -> !alreadySelected.contains(p)) + .sorted(comparing(ISonarLintProject::getName)) + .collect(toList()); + var dialog = new ProjectToBindSelectionDialog(parent, "Select projects to add:", projects); + dialog.setMultipleSelection(true); + if (dialog.open() == Window.OK) { + return Stream.of(dialog.getResult()).map(ISonarLintProject.class::cast).collect(Collectors.toList()); } - } - - private ProjectSelectionDialog() { - // utility class + return List.of(); } } diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/SonarLintProjectLabelProvider.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/SonarLintProjectLabelProvider.java new file mode 100644 index 000000000..b069a95d1 --- /dev/null +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/SonarLintProjectLabelProvider.java @@ -0,0 +1,39 @@ +/* + * SonarLint for Eclipse + * Copyright (C) 2015-2023 SonarSource SA + * sonarlint@sonarsource.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.sonarlint.eclipse.ui.internal.binding; + +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE.SharedImages; +import org.sonarlint.eclipse.core.resource.ISonarLintProject; + +public class SonarLintProjectLabelProvider extends LabelProvider { + @Override + public String getText(Object element) { + var current = (ISonarLintProject) element; + return current.getName(); + } + + @Override + public Image getImage(Object element) { + return PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT); + } +} diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/AssistBindingJob.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/AssistBindingJob.java new file mode 100644 index 000000000..8df16f973 --- /dev/null +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/AssistBindingJob.java @@ -0,0 +1,78 @@ +/* + * SonarLint for Eclipse + * Copyright (C) 2015-2023 SonarSource SA + * sonarlint@sonarsource.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.sonarlint.eclipse.ui.internal.binding.assist; + +import java.util.List; +import java.util.concurrent.CancellationException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.ui.progress.UIJob; +import org.sonarlint.eclipse.core.SonarLintLogger; +import org.sonarlint.eclipse.core.resource.ISonarLintProject; +import org.sonarlint.eclipse.ui.internal.binding.ProjectToBindSelectionDialog; +import org.sonarlint.eclipse.ui.internal.binding.wizard.project.ProjectBindingProcess; +import org.sonarlint.eclipse.ui.internal.util.DisplayUtils; + +public class AssistBindingJob extends UIJob { + + private final String connectionId; + private final String projectKey; + @Nullable + private ISonarLintProject project; + + public AssistBindingJob(String connectionId, String projectKey) { + super("Assist creating SonarLint binding"); + // We don't want to have this job visible to the user, as there should be a dialog anyway + setSystem(true); + this.connectionId = connectionId; + this.projectKey = projectKey; + } + + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + DisplayUtils.bringToFront(); + this.project = bindProjectTo(connectionId, projectKey); + return Status.OK_STATUS; + } + + private static ISonarLintProject bindProjectTo(String connectionId, String projectKey) { + var pickedProject = ProjectToBindSelectionDialog.pickProject(projectKey, connectionId); + if (pickedProject == null) { + throw new CancellationException(); + } + var bindingJob = ProjectBindingProcess.scheduleProjectBinding(connectionId, List.of(pickedProject), projectKey); + try { + bindingJob.join(); + } catch (InterruptedException e) { + SonarLintLogger.get().error("Cannot bind project", e); + Thread.currentThread().interrupt(); + throw new CancellationException("Interrupted!"); + } + return pickedProject; + } + + @Nullable + public ISonarLintProject getProject() { + return project; + } + +} diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/AssistCreatingConnectionJob.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/AssistCreatingConnectionJob.java new file mode 100644 index 000000000..b0c4a5ea7 --- /dev/null +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/AssistCreatingConnectionJob.java @@ -0,0 +1,82 @@ +/* + * SonarLint for Eclipse + * Copyright (C) 2015-2023 SonarSource SA + * sonarlint@sonarsource.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.sonarlint.eclipse.ui.internal.binding.assist; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.progress.UIJob; +import org.sonarlint.eclipse.core.internal.engine.connected.IConnectedEngineFacade; +import org.sonarlint.eclipse.ui.internal.binding.wizard.connection.ServerConnectionModel; +import org.sonarlint.eclipse.ui.internal.binding.wizard.connection.ServerConnectionModel.ConnectionType; +import org.sonarlint.eclipse.ui.internal.binding.wizard.connection.ServerConnectionWizard; +import org.sonarlint.eclipse.ui.internal.util.DisplayUtils; + +public class AssistCreatingConnectionJob extends UIJob { + + private final String serverUrl; + @Nullable + private String connectionId; + + public AssistCreatingConnectionJob(String serverUrl) { + super("Assist creating SonarLint connection"); + // We don't want to have this job visible to the user, as there should be a dialog anyway + setSystem(true); + this.serverUrl = serverUrl; + } + + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + var shell = DisplayUtils.bringToFront(); + var dialog = new ConfirmConnectionCreationDialog(shell, serverUrl); + var result = dialog.open(); + if (result != 0) { + return Status.CANCEL_STATUS; + } + var connection = createConnection(serverUrl); + if (connection != null) { + this.connectionId = connection.getId(); + return Status.OK_STATUS; + } + + return Status.CANCEL_STATUS; + } + + @Nullable + private static IConnectedEngineFacade createConnection(String serverUrl) { + var model = new ServerConnectionModel(); + model.setConnectionType(ConnectionType.ONPREMISE); + model.setServerUrl(serverUrl); + var wizard = new ServerConnectionWizard(model); + wizard.setSkipBindingWizard(true); + var dialog = ServerConnectionWizard.createDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), wizard); + dialog.setBlockOnOpen(true); + dialog.open(); + return wizard.getResultServer(); + } + + @Nullable + public String getConnectionId() { + return connectionId; + } + +} diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/ConfirmConnectionCreationDialog.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/ConfirmConnectionCreationDialog.java new file mode 100644 index 000000000..89bf1e5dd --- /dev/null +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/ConfirmConnectionCreationDialog.java @@ -0,0 +1,48 @@ +/* + * SonarLint for Eclipse + * Copyright (C) 2015-2023 SonarSource SA + * sonarlint@sonarsource.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.sonarlint.eclipse.ui.internal.binding.assist; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Shell; +import org.sonarlint.eclipse.core.internal.telemetry.LinkTelemetry; +import org.sonarlint.eclipse.ui.internal.SonarLintImages; +import org.sonarlint.eclipse.ui.internal.util.BrowserUtils; + +class ConfirmConnectionCreationDialog extends MessageDialog { + + public ConfirmConnectionCreationDialog(Shell parentShell, String serverUrl) { + super(parentShell, "Do you trust this SonarQube server?", SonarLintImages.BALLOON_IMG, "The \"" + serverUrl + + "\" server is attempting to set up a connection with SonarLint. Letting SonarLint connect to an untrusted server is potentially dangerous.", MessageDialog.WARNING, + new String[] {"Connect to this SonarQube server", "I don't trust this server"}, 0); + } + + @Override + protected Control createCustomArea(Composite parent) { + var link = new Link(parent, SWT.WRAP); + link.setText("If you don't trust this server, we recommend canceling this action and manually setting up Connected Mode."); + link.addListener(SWT.Selection, e -> BrowserUtils.openExternalBrowserWithTelemetry(LinkTelemetry.CONNECTED_MODE_DOCS, e.display)); + return link; + } + +} diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/package-info.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/package-info.java new file mode 100644 index 000000000..27fd089e1 --- /dev/null +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/assist/package-info.java @@ -0,0 +1,20 @@ +/* + * SonarLint for Eclipse + * Copyright (C) 2015-2023 SonarSource SA + * sonarlint@sonarsource.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.sonarlint.eclipse.ui.internal.binding.assist; diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/wizard/project/ProjectsSelectionWizardPage.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/wizard/project/ProjectsSelectionWizardPage.java index 5a016e108..c0d094c59 100644 --- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/wizard/project/ProjectsSelectionWizardPage.java +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/binding/wizard/project/ProjectsSelectionWizardPage.java @@ -29,44 +29,23 @@ import org.eclipse.jface.databinding.wizard.WizardPageSupport; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.dialogs.ElementListSelectionDialog; -import org.eclipse.ui.ide.IDE.SharedImages; -import org.sonarlint.eclipse.core.internal.resources.ProjectsProviderUtils; import org.sonarlint.eclipse.core.resource.ISonarLintProject; +import org.sonarlint.eclipse.ui.internal.binding.ProjectToBindSelectionDialog; +import org.sonarlint.eclipse.ui.internal.binding.SonarLintProjectLabelProvider; import org.sonarlint.eclipse.ui.internal.util.wizard.BeanPropertiesCompat; import org.sonarlint.eclipse.ui.internal.util.wizard.ViewersObservablesCompat; -import static java.util.Comparator.comparing; -import static java.util.stream.Collectors.toList; - public class ProjectsSelectionWizardPage extends AbstractProjectBindingWizardPage { - private static final class SonarLintProjectLabelProvider extends LabelProvider { - @Override - public String getText(Object element) { - var current = (ISonarLintProject) element; - return current.getName(); - } - - @Override - public Image getImage(Object element) { - return PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OBJ_PROJECT); - } - } - private TableViewer projectsViewer; private Button btnRemove; @@ -151,20 +130,10 @@ private List getSelectedElements() { } protected void addSonarLintProjectsAction() { - var projects = ProjectsProviderUtils.allProjects() - .stream() - .filter(p -> !((List) projectsViewer.getInput()).contains(p)) - .sorted(comparing(ISonarLintProject::getName)) - .collect(toList()); - var dialog = new ElementListSelectionDialog(getShell(), new SonarLintProjectLabelProvider()); - dialog.setElements(projects.toArray()); - dialog.setMessage("Select projects to add:"); - dialog.setTitle("Project selection"); - dialog.setHelpAvailable(false); - dialog.setMultipleSelection(true); - if (dialog.open() == Window.OK) { + var selected = ProjectToBindSelectionDialog.selectProjectsToAdd(getShell(), (List) projectsViewer.getInput()); + if (!selected.isEmpty()) { var newInput = new ArrayList((List) projectsViewer.getInput()); - List.of(dialog.getResult()).forEach(o -> newInput.add((ISonarLintProject) o)); + newInput.addAll(selected); observableInput.setValue(newInput); } } diff --git a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/util/DisplayUtils.java b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/util/DisplayUtils.java index 4bcbfb397..b7d148cca 100644 --- a/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/util/DisplayUtils.java +++ b/org.sonarlint.eclipse.ui/src/org/sonarlint/eclipse/ui/internal/util/DisplayUtils.java @@ -23,6 +23,7 @@ import java.util.function.Supplier; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PlatformUI; public class DisplayUtils { @@ -46,11 +47,7 @@ public static CompletableFuture asyncExec(Runnable runnable) { return r.getFuture(); } - public static CompletableFuture bringToFrontAsync() { - return asyncExec(DisplayUtils::bringToFront); - } - - public static void bringToFront() { + public static Shell bringToFront() { var window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); var shell = window.getShell(); if (shell != null) { @@ -58,7 +55,9 @@ public static void bringToFront() { shell.setMinimized(false); } shell.forceActive(); + return shell; } + throw new IllegalStateException("No shell available"); } private static class RunnableWithResult implements Runnable {