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

feat: inject custom styling [IDE-240] #535

Merged
merged 6 commits into from
Jun 3, 2024
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
build/
out/
/bin/
src/main/resources/stylesheets/*.css
src/main/resources/stylesheets/*.css.map

#################################
# Gradle files #
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Snyk Security Changelog

## [2.8.1]
### Added
- Injects custom styling for the HTML panel used by Snyk Code for consistent ignores.

## [2.8.0]

### Added
- Serve Snyk Code functionality via language server. This enables auto-scanning on startup / save, code actions for Snyk Learn and, if enabled, Snyk Auto-Fix. The number of uploaded files is not shown anymore.
- Serve Snyk Code functionality via language server. This enables auto-scanning on startup / save, code actions for Snyk Learn and, if enabled, Snyk Auto-Fix. The number of uploaded files is not shown anymore.

## [2.7.21]

Expand Down
14 changes: 14 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ plugins {
id("org.jetbrains.kotlin.jvm") version "1.9.23"
id("io.gitlab.arturbosch.detekt") version ("1.23.6")
id("pl.allegro.tech.build.axion-release") version "1.17.0"
id("io.miret.etienne.sass") version "1.5.0"
}

version = scmVersion.version
Expand Down Expand Up @@ -88,6 +89,19 @@ detekt {
}

tasks {
// plugin from https://github.com/EtienneMiret/sass-gradle-plugin
compileSass {
sourceDir = project.file("${projectDir}/src/main/resources/stylesheets")
outputDir = project.file("${projectDir}/src/main/resources/stylesheets")
}

processResources{
dependsOn(compileSass)
}
compileKotlin{
dependsOn(processResources)
}

withType<KotlinCompile> {
kotlinOptions.jvmTarget = jdk
kotlinOptions.languageVersion = "1.9"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package io.snyk.plugin.ui.toolwindow.panels

import com.intellij.uiDesigner.core.GridLayoutManager
import com.intellij.util.ui.JBUI
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.SnykFile
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.ui.DescriptionHeaderPanel
import io.snyk.plugin.ui.SnykBalloonNotificationHelper
import io.snyk.plugin.ui.baseGridConstraintsAnchorWest
Expand All @@ -15,6 +15,7 @@ import io.snyk.plugin.ui.toolwindow.SnykToolWindowPanel
import io.snyk.plugin.ui.wrapWithScrollPane
import snyk.common.ProductType
import snyk.common.lsp.ScanIssue
import stylesheets.SnykStylesheets
import java.awt.BorderLayout
import java.awt.Font
import javax.swing.JLabel
Expand All @@ -37,7 +38,8 @@ class SuggestionDescriptionPanelFromLS(
issue.canLoadSuggestionPanelFromHTML()
) {
val openFileLoadHandlerGenerator = OpenFileLoadHandlerGenerator(snykFile)
val jbCefBrowserComponent = JCEFUtils.getJBCefBrowserComponentIfSupported(issue.details()) {
val html = this.getStyledHTML()
val jbCefBrowserComponent = JCEFUtils.getJBCefBrowserComponentIfSupported(html) {
openFileLoadHandlerGenerator.generate(it)
}
if (jbCefBrowserComponent == null) {
Expand Down Expand Up @@ -111,6 +113,29 @@ class SuggestionDescriptionPanelFromLS(
}
return Pair(panel, lastRowToAddSpacer)
}

fun getStyledHTML(): String {
var html = issue.details()
var ideStyle = ""
if (issue.additionalData.getProductType() == ProductType.CODE_SECURITY || issue.additionalData.getProductType() == ProductType.CODE_QUALITY) {
ideStyle = SnykStylesheets.SnykCodeSuggestion

}
html = html.replace("\${ideStyle}", "<style nonce=\${nonce}>$ideStyle</style>")

val nonce = getNonce()
html = html.replace("\${nonce}", nonce)

return html
}

private fun getNonce(): String {
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9')
return (1..32)
.map { allowedChars.random() }
.joinToString("")
}

}

fun defaultFontLabel(labelText: String, bold: Boolean = false): JLabel {
Expand Down
11 changes: 11 additions & 0 deletions src/main/kotlin/stylesheets/SnykStylesheets.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package stylesheets

object SnykStylesheets {
private fun getStylesheet(name: String): String {
return this::class.java.getResource(name)?.readText()
?: ""
}

val SnykCodeSuggestion = getStylesheet("/stylesheets/snyk_code_suggestion.css")
}

77 changes: 77 additions & 0 deletions src/main/resources/stylesheets/snyk_code_suggestion.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb-color);
}

::-webkit-scrollbar-thumb:hover {
background: #595a5c;
}

body {
color: var(--text-color);
}

a,
.link {
color: var(--link-color);
}

.delimiter {
border: 1px solid #505254;
}


.ignore-warning {
background: #FFF4ED;
color: #B6540B;
border: 1px solid #E27122;
}


.ignore-badge {
background: #FFF4ED;
color: #B6540B;
border: 1px solid #E27122;
}


.data-flow-body {
background-color: var(--data-flow-body-color);
}


.data-flow-clickable-row {
color: var(--link-color);
}


.data-flow-delimiter {
color: #BBBBBB;
}

.tabs-nav {
border-bottom: 1px solid var(--tabs-bottom-color);
}

.tab-item-icon path {
fill: var(--tab-item-github-icon-color);
}

.tab-item:hover {
background-color: var(--tab-item-hover-color);
}

.tab-item.is-selected {
border-bottom: 3px solid #3474f0;
}

.example-line.added {
background-color: var(--example-line-added-color);
}

.example-line.removed {
background-color: var(--example-line-removed-color);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we need to use a SASS file? I thought SASS is needed when we need things like nesting or mixins or other SASS magic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly because we use SASS in VSCode so I wanted to be as consistent as possible across the two, but also because there are more features available in SASS (like you said) and in the future we will have more than Snyk Code styling but still want to share stylesheets or colours across Snyk OSS, Snyk IaC, and Snyk Container


.ignore-action-container {
display: none;
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,18 @@ class SuggestionDescriptionPanelFromLSCodeTest : BasePlatformTestCase() {
val actualBrowser = getJLabelByText(cut, "<html>HTML message</html>")
assertNotNull(actualBrowser)
}

@Test
fun `test getStyledHTML should inject CSS into the HTML`() {
pluginSettings().isGlobalIgnoresFeatureEnabled = true

every { issue.details() } returns "<html><head>\${ideStyle}</head>HTML message</html>"
every { issue.canLoadSuggestionPanelFromHTML() } returns true
cut = SuggestionDescriptionPanelFromLS(snykFile, issue)

val actual = cut.getStyledHTML()
assertFalse(actual.contains("\${ideStyle}"))
assertFalse(actual.contains("\${nonce}"))
assertTrue(actual.contains(".ignore-warning"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,22 @@ import com.intellij.psi.PsiFile
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkAll
import io.snyk.plugin.Severity
import io.snyk.plugin.pluginSettings
import io.snyk.plugin.resetSettings
import io.snyk.plugin.SnykFile
import io.snyk.plugin.ui.jcef.JCEFUtils
import io.snyk.plugin.ui.toolwindow.panels.SuggestionDescriptionPanelFromLS
import org.eclipse.lsp4j.Position
import org.eclipse.lsp4j.Range
import org.junit.Before
import org.junit.Test
import snyk.UIComponentFinder.getActionLinkByText
import snyk.UIComponentFinder.getJBCEFBrowser
import snyk.UIComponentFinder.getJButtonByText
import snyk.UIComponentFinder.getJLabelByText
import snyk.UIComponentFinder.getJPanelByName
import snyk.code.annotator.SnykCodeAnnotator
import snyk.common.ProductType
import snyk.common.lsp.CommitChangeLine
import snyk.common.lsp.DataFlow
import snyk.common.lsp.ExampleCommitFix
import snyk.common.lsp.IssueData
import snyk.common.lsp.ScanIssue
import java.nio.file.Paths
import javax.swing.JLabel

class SuggestionDescriptionPanelFromLSOSSTest : BasePlatformTestCase() {
private lateinit var cut: SuggestionDescriptionPanelFromLS
Expand Down Expand Up @@ -115,4 +105,19 @@ class SuggestionDescriptionPanelFromLSOSSTest : BasePlatformTestCase() {
val ossOverviewPanel = getJPanelByName(cut, "overviewPanel")
assertNotNull(ossOverviewPanel)
}

@Test
fun `test getStyledHTML should inject CSS into the HTML`() {
pluginSettings().isGlobalIgnoresFeatureEnabled = true

every { issue.details() } returns "<html><head><style>\${ideStyle}</style></head>HTML message</html>"
every { issue.canLoadSuggestionPanelFromHTML() } returns true
cut = SuggestionDescriptionPanelFromLS(snykFile, issue)

val actual = cut.getStyledHTML()

// we don't apply any custom style for oss
assertFalse(actual.contains("\${ideStyle}"))
assertFalse(actual.contains(".ignore-warning"))
}
}
Loading