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

SLI-1788 SLI-1784 SLI-1796 Create Walkthrough for SonarQube for IDE #1280

Merged
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* SonarLint for IntelliJ IDEA
* Copyright (C) 2015-2024 SonarSource
* [email protected]
*
* 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 02
*/
package org.sonarlint.intellij;

import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupActivity;
import com.intellij.openapi.wm.ToolWindowManager;
import org.jetbrains.annotations.NotNull;

public class OpenWelcomePageOnceOneProjectOpened implements StartupActivity {

public static final String HAS_WALKTHROUGH_RUN_ONCE = "hasWalkthroughRunOnce";

@Override
public void runActivity(@NotNull Project project) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
return;
}

var properties = PropertiesComponent.getInstance();

if (!properties.getBoolean(HAS_WALKTHROUGH_RUN_ONCE, false)) {
properties.setValue(HAS_WALKTHROUGH_RUN_ONCE, true);
openWelcomePage(project);
}
}

private static void openWelcomePage(Project project) {
var toolWindow = ToolWindowManager.getInstance(project).getToolWindow("Welcome to SonarQube for IDE");
eray-felek-sonarsource marked this conversation as resolved.
Show resolved Hide resolved

if (toolWindow == null) {
return;
}

toolWindow.show();
}
}
2 changes: 2 additions & 0 deletions src/main/java/org/sonarlint/intellij/SonarLintIcons.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ object SonarLintIcons {
val RESOLVED = getIcon("/images/resolved.svg")
@JvmField
val FOCUS = getIcon("/images/focus.svg")
@JvmField
val WALKTHROUGH_ICON = getIcon("/images/sonarqube-for-ide-mark.png")

private val BUG_ICONS = mapOf(
IssueSeverity.BLOCKER to getIcon("/images/bug/bugBlocker.svg"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ object SonarLintDocumentation {
const val TROUBLESHOOTING_CONNECTED_MODE_SETUP_LINK = "$BASE_DOCS_URL/troubleshooting/#troubleshooting-connected-mode-setup"
const val RULE_SECTION_LINK = "$BASE_DOCS_URL/using-sonarlint/rules/#rule-selection"
const val FILE_EXCLUSION_LINK = "$BASE_DOCS_URL/using-sonarlint/file-exclusions"
const val AI_FIX_SUGGESTIONS_LINK = "$BASE_DOCS_URL/using-sonarlint/investigating-issues/#ai-generated-fix-suggestions"
}

object Community {
const val COMMUNITY_LINK = "https://community.sonarsource.com/c/sl/fault/6"
eray-felek-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
}

object SonarQube {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ package org.sonarlint.intellij.telemetry
import com.intellij.ide.BrowserUtil
import org.sonarlint.intellij.common.util.SonarLintUtils
import org.sonarlint.intellij.documentation.SonarLintDocumentation
import org.sonarlint.intellij.documentation.SonarLintDocumentation.Community.COMMUNITY_LINK
import org.sonarlint.intellij.documentation.SonarLintDocumentation.Intellij.AI_FIX_SUGGESTIONS_LINK
import org.sonarlint.intellij.documentation.SonarLintDocumentation.Intellij.BASE_DOCS_URL
import org.sonarlint.intellij.documentation.SonarLintDocumentation.Intellij.RULE_SECTION_LINK

enum class LinkTelemetry(
Expand All @@ -32,7 +35,10 @@ enum class LinkTelemetry(
SONARCLOUD_FREE_SIGNUP_PAGE("sonarqubeCloudFreeSignUp", SonarLintDocumentation.Marketing.SONARCLOUD_PRODUCT_SIGNUP_LINK),
CONNECTED_MODE_DOCS("connectedModeDocs", SonarLintDocumentation.Intellij.CONNECTED_MODE_LINK),
SONARQUBE_EDITIONS_DOWNLOADS("sonarQubeEditionsDownloads", SonarLintDocumentation.Marketing.SONARQUBE_EDITIONS_DOWNLOADS_LINK),
RULE_SELECTION_PAGE("rulesSelectionDocs", RULE_SECTION_LINK);
RULE_SELECTION_PAGE("rulesSelectionDocs", RULE_SECTION_LINK),
AI_FIX_SUGGESTIONS_PAGE("aiFixSuggestionsDocs", AI_FIX_SUGGESTIONS_LINK),
COMMUNITY_PAGE("communityReportPage", COMMUNITY_LINK),
BASE_DOCS_PAGE("baseDocs", BASE_DOCS_URL);

fun browseWithTelemetry() {
SonarLintUtils.getService(SonarLintTelemetry::class.java).helpAndFeedbackLinkClicked(linkId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* SonarLint for IntelliJ IDEA
* Copyright (C) 2015-2024 SonarSource
* [email protected]
*
* 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 02
*/
package org.sonarlint.intellij.ui.walkthrough

import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.ui.HyperlinkAdapter
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBPanel
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.SwingHelper
import com.intellij.util.ui.UIUtil
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.FlowLayout
import java.awt.Font
import javax.swing.JButton
import javax.swing.JEditorPane
import javax.swing.JScrollPane
import javax.swing.SwingConstants
import javax.swing.event.HyperlinkEvent
import org.sonarlint.intellij.SonarLintIcons
import org.sonarlint.intellij.config.project.SonarLintProjectConfigurable
import org.sonarlint.intellij.telemetry.LinkTelemetry
import org.sonarlint.intellij.ui.walkthrough.SonarLintWalkthroughUtils.FONT
import org.sonarlint.intellij.ui.walkthrough.SonarLintWalkthroughUtils.PREVIOUS
import org.sonarlint.intellij.ui.walkthrough.SonarLintWalkthroughUtils.SONARQUBE_FOR_IDE
import org.sonarlint.intellij.ui.walkthrough.SonarLintWalkthroughUtils.addCenterPanel

private const val TAINT_VULNERABILITIES = "Taint Vulnerabilities"
private const val NEXT_BUTTON_TEXT = "Next: Reach Out to Us"

class ConnectWithYourTeamPanel(project: Project) :
JBPanel<JBPanel<*>>(BorderLayout()) {

val backButton = JButton(PREVIOUS)
val nextButton = JButton(NEXT_BUTTON_TEXT)

init {
val font = UIUtil.getLabelFont()

val connectWithYourTeamImageLabel = JBLabel(SonarLintIcons.WALKTHROUGH_ICON)

val connectWithYourTeamStepLabel = JBLabel("Step 3/4", SwingConstants.LEFT)
connectWithYourTeamStepLabel.font = Font(FONT, Font.PLAIN, 14)

val connectWithYourTeamLabel = JBLabel("Connect with your team")
connectWithYourTeamLabel.font = Font(FONT, Font.BOLD, 16)
val connectWithYourTeamText = createConnectWithYourTeamPageText(font, project)

val connectWithYourTeamScrollPane = JScrollPane(connectWithYourTeamText)
connectWithYourTeamScrollPane.border = null
connectWithYourTeamScrollPane.preferredSize = Dimension(
WIDTH,
HEIGHT
)

val connectWithYourTeamBackButtonPanel = JBPanel<JBPanel<*>>(FlowLayout(FlowLayout.LEFT))
connectWithYourTeamBackButtonPanel.add(backButton)

val connectWithYourTeamNextButtonPanel = JBPanel<JBPanel<*>>(FlowLayout(FlowLayout.RIGHT))
connectWithYourTeamNextButtonPanel.add(nextButton)

addCenterPanel(
connectWithYourTeamStepLabel, connectWithYourTeamLabel, connectWithYourTeamScrollPane,
connectWithYourTeamBackButtonPanel, connectWithYourTeamNextButtonPanel, this, connectWithYourTeamImageLabel
)
}

private fun createConnectWithYourTeamPageText(font: Font, project: Project): JEditorPane {
val descriptionPane = SwingHelper.createHtmlViewer(false, font, null, null)
descriptionPane.apply {
text = """
Apply the same set of rules as your team by using $SONARQUBE_FOR_IDE in Connected Mode with SonarQube Cloud or SonarQube Server.<br><br>
With connected mode, benefit from advanced analysis like <a href="#taintVulnerabilities">Taint Vulnerabilities</a> and open
issues and <a href="#aiFixSuggestions">AI fix suggestions</a> from SonarQube (Server, Cloud) in the IDE.<br><br>
Already using SonarQube (Server, Cloud)? <a href="#setupConnection">Set up a connection</a>
""".trimIndent()
border = JBUI.Borders.empty(8, 8)

addHyperlinkListener(object : HyperlinkAdapter() {
override fun hyperlinkActivated(e: HyperlinkEvent) {
if ("#setupConnection" == e.description) {
val configurable = SonarLintProjectConfigurable(project)
ShowSettingsUtil.getInstance().editConfigurable(project, configurable)
} else if ("#taintVulnerabilities" == e.description) {
val sonarqubeToolWindow = ToolWindowManager.getInstance(project).getToolWindow(SONARQUBE_FOR_IDE)
sonarqubeToolWindow?.let {
if (!sonarqubeToolWindow.isVisible) {
sonarqubeToolWindow.activate(null)
}
val taintVulnerabilitiesContent = sonarqubeToolWindow.contentManager.findContent(TAINT_VULNERABILITIES)

taintVulnerabilitiesContent?.let {
sonarqubeToolWindow.contentManager.setSelectedContent(taintVulnerabilitiesContent)
}
}
} else {
LinkTelemetry.AI_FIX_SUGGESTIONS_PAGE.browseWithTelemetry()
}
}
})
}

return descriptionPane
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* SonarLint for IntelliJ IDEA
* Copyright (C) 2015-2024 SonarSource
* [email protected]
*
* 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 02
*/
package org.sonarlint.intellij.ui.walkthrough

import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.ui.HyperlinkAdapter
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBPanel
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.SwingHelper
import com.intellij.util.ui.UIUtil
import java.awt.BorderLayout
import java.awt.Dimension
import java.awt.FlowLayout
import java.awt.Font
import javax.swing.BorderFactory
import javax.swing.JButton
import javax.swing.JEditorPane
import javax.swing.JScrollPane
import javax.swing.SwingConstants
import javax.swing.event.HyperlinkEvent
import org.sonarlint.intellij.SonarLintIcons
import org.sonarlint.intellij.config.global.SonarLintGlobalConfigurable
import org.sonarlint.intellij.ui.walkthrough.SonarLintWalkthroughUtils.FONT
import org.sonarlint.intellij.ui.walkthrough.SonarLintWalkthroughUtils.PREVIOUS
import org.sonarlint.intellij.ui.walkthrough.SonarLintWalkthroughUtils.SONARQUBE_FOR_IDE
import org.sonarlint.intellij.ui.walkthrough.SonarLintWalkthroughUtils.addCenterPanel

private const val CURRENT_FILE = "Current File"
private const val NEXT_BUTTON_TEXT = "Next: Connect with Your Team"

class LearnAsYouCodePanel(project: Project) :
JBPanel<JBPanel<*>>(BorderLayout()) {

val nextButton = JButton(NEXT_BUTTON_TEXT)
val backButton = JButton(PREVIOUS)

init {
val font = UIUtil.getLabelFont()

val learnAsYouCodeImageLabel =
JBLabel(SonarLintIcons.WALKTHROUGH_ICON)

val learnAsYouCodeStepLabel = JBLabel("Step 2/4", SwingConstants.LEFT)
learnAsYouCodeStepLabel.font = Font(FONT, Font.PLAIN, 14)

val learnAsYouCodePageLabel = JBLabel("Learn as You Code")
learnAsYouCodePageLabel.font = Font(FONT, Font.BOLD, 16)
val learnAsYouCodeText = createLearnAsYouCodePageText(font, project)

val learnAsYouCodeScrollPane = JScrollPane(learnAsYouCodeText)
learnAsYouCodeScrollPane.border = BorderFactory.createEmptyBorder()
learnAsYouCodeScrollPane.preferredSize = Dimension(WIDTH, HEIGHT)

val learnAsYouCodePageBackButtonPanel = JBPanel<JBPanel<*>>(FlowLayout(FlowLayout.LEFT))
learnAsYouCodePageBackButtonPanel.add(backButton)

val learnAsYouCodePageNextButtonPanel = JBPanel<JBPanel<*>>(FlowLayout(FlowLayout.RIGHT))
learnAsYouCodePageNextButtonPanel.add(nextButton)

addCenterPanel(
learnAsYouCodeStepLabel, learnAsYouCodePageLabel, learnAsYouCodeScrollPane,
learnAsYouCodePageBackButtonPanel, learnAsYouCodePageNextButtonPanel, this, learnAsYouCodeImageLabel
)
}

private fun createLearnAsYouCodePageText(font: Font, project: Project): JEditorPane {
val descriptionPane = SwingHelper.createHtmlViewer(false, font, null, null)
descriptionPane.apply {
text = """
Check the <a href="#currentFile">Current File</a> view: When $SONARQUBE_FOR_IDE found something, click on the issue to
get the rule description and an example of compliant code.<br><br>
Some rules offer quick fixes when you hover over the issue location.<br><br>
Finally you can disable rules in the <a href="#settings">settings</a>.
""".trimIndent()
border = JBUI.Borders.empty(8, 8)

addHyperlinkListener(object : HyperlinkAdapter() {
override fun hyperlinkActivated(e: HyperlinkEvent) {
if ("#currentFile" == e.description) {
val sonarqubeToolWindow = ToolWindowManager.getInstance(project).getToolWindow(SONARQUBE_FOR_IDE)
sonarqubeToolWindow?.let {
if (!sonarqubeToolWindow.isVisible) {
sonarqubeToolWindow.activate(null)
}
val currentFileContent = sonarqubeToolWindow.contentManager.findContent(CURRENT_FILE)
currentFileContent?.let {
sonarqubeToolWindow.contentManager.setSelectedContent(currentFileContent)
}
}

} else if ("#settings" == e.description) {
ShowSettingsUtil.getInstance().showSettingsDialog(project, SonarLintGlobalConfigurable::class.java)
}
}
})
}

return descriptionPane
}
}
Loading
Loading