Skip to content

Commit 11b362d

Browse files
authored
Merge pull request #48 from advanced-security/improve_ui
improve UI
2 parents 4a1a2b9 + ed384c3 commit 11b362d

File tree

2 files changed

+138
-72
lines changed

2 files changed

+138
-72
lines changed

src/main/kotlin/com/github/adrienpessu/sarifviewer/models/Leaf.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ data class Leaf(
2222
else -> ""
2323
}
2424

25-
return "$icon $address"
25+
return "$icon ${address.split("/").last()} $ruleDescription"
2626
}
2727
}

src/main/kotlin/com/github/adrienpessu/sarifviewer/toolWindow/SarifViewerWindowFactory.kt

Lines changed: 137 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import com.github.adrienpessu.sarifviewer.models.Leaf
1313
import com.github.adrienpessu.sarifviewer.models.View
1414
import com.github.adrienpessu.sarifviewer.services.SarifService
1515
import com.github.adrienpessu.sarifviewer.utils.GitHubInstance
16-
import com.intellij.icons.AllIcons
1716
import com.intellij.notification.NotificationGroupManager
1817
import com.intellij.notification.NotificationType
1918
import com.intellij.openapi.actionSystem.ActionManager
@@ -38,26 +37,29 @@ import com.intellij.ui.awt.RelativePoint
3837
import com.intellij.ui.components.JBPanel
3938
import com.intellij.ui.components.JBTabbedPane
4039
import com.intellij.ui.content.ContentFactory
40+
import com.intellij.ui.table.JBTable
4141
import git4idea.GitLocalBranch
4242
import git4idea.repo.GitRepository
4343
import git4idea.repo.GitRepositoryChangeListener
4444
import git4idea.repo.GitRepositoryManager
4545
import java.awt.Component
46+
import java.awt.Cursor
4647
import java.awt.Desktop
4748
import java.awt.Dimension
4849
import java.awt.event.ActionListener
50+
import java.awt.event.MouseAdapter
51+
import java.awt.event.MouseEvent
4952
import java.io.File
5053
import java.net.URI
5154
import java.nio.charset.Charset
5255
import java.nio.file.Files
5356
import java.nio.file.Path
5457
import javax.swing.*
55-
import javax.swing.event.HyperlinkEvent
56-
import javax.swing.event.HyperlinkListener
5758
import javax.swing.event.TreeSelectionEvent
5859
import javax.swing.event.TreeSelectionListener
5960
import javax.swing.filechooser.FileNameExtensionFilter
60-
import javax.swing.text.html.HTMLEditorKit
61+
import javax.swing.table.DefaultTableCellRenderer
62+
import javax.swing.table.DefaultTableModel
6163
import javax.swing.tree.DefaultMutableTreeNode
6264
import javax.swing.tree.DefaultTreeModel
6365

@@ -86,37 +88,34 @@ class SarifViewerWindowFactory : ToolWindowFactory {
8688
(openLocalFileAction as OpenLocalAction).myToolWindow = this
8789
val refreshAction = actionManager.getAction("RefreshAction")
8890
(refreshAction as RefreshAction).myToolWindow = this
89-
val actions = ArrayList<AnAction>();
91+
val actions = ArrayList<AnAction>()
9092
actions.add(openLocalFileAction)
9193
actions.add(refreshAction)
9294

9395
toolWindow.setTitleActions(actions)
9496
}
9597

9698
internal var github: GitHubInstance? = null
97-
get() = field
9899
internal var repositoryFullName: String? = null
99-
get() = field
100100
internal var currentBranch: GitLocalBranch? = null
101-
get() = field
102101

103102
private var localMode = false
104103
private val service = toolWindow.project.service<SarifService>()
105104
private val project = toolWindow.project
106105
private var main = ScrollPaneFactory.createScrollPane()
107106
private val details = JBTabbedPane()
108107
private val splitPane = JSplitPane(JSplitPane.VERTICAL_SPLIT, false, main, details)
109-
private var myList = JTree()
108+
private var myList = com.intellij.ui.treeStructure.Tree()
110109
private var comboBranchPR = ComboBox(arrayOf(BranchItemComboBox(0, "main", "", "")))
111-
private val infos = JEditorPane()
112-
private val steps = JEditorPane()
110+
private val tableInfos = JBTable(DefaultTableModel(arrayOf<Any>("Property", "Value"), 0))
111+
private val tableSteps = JBTable(DefaultTableModel(arrayOf<Any>("Path"), 0))
112+
private val steps = JPanel()
113113
private val errorField = JLabel("Error message here ")
114114
private val errorToolbar = JToolBar("", JToolBar.HORIZONTAL)
115115
private val loadingPanel = JPanel()
116116
private var sarifGitHubRef = ""
117117
private var loading = false
118118
private var disableComboBoxEvent = false
119-
private val views = View.views
120119
private var currentView = View.RULE
121120
private var cacheSarif: SarifSchema210? = null
122121

@@ -263,26 +262,14 @@ class SarifViewerWindowFactory : ToolWindowFactory {
263262
}
264263

265264
private fun JBPanel<JBPanel<*>>.buildSkeleton() {
266-
infos.isEditable = false
267-
infos.addHyperlinkListener(object : HyperlinkListener {
268-
override fun hyperlinkUpdate(hle: HyperlinkEvent?) {
269-
if (HyperlinkEvent.EventType.ACTIVATED == hle?.eventType && hle?.description != null) {
270-
Desktop.getDesktop().browse(URI(hle.description))
271-
}
272-
}
273-
})
274-
steps.isEditable = false
275-
steps.addHyperlinkListener(object : HyperlinkListener {
276-
override fun hyperlinkUpdate(hle: HyperlinkEvent?) {
277-
if (HyperlinkEvent.EventType.ACTIVATED == hle?.eventType) {
278-
hle?.description.toString().split(":").let { location ->
279-
openFile(project, location[0], location[1].toInt())
280-
}
281-
}
282-
}
283-
})
265+
steps.layout = BoxLayout(steps, BoxLayout.Y_AXIS)
266+
tableSteps.size = Dimension(steps.width, steps.height)
267+
steps.add(tableSteps)
284268

285-
details.addTab("Infos", infos)
269+
// Add the table to a scroll pane
270+
val scrollPane = JScrollPane(tableInfos)
271+
272+
details.addTab("Infos", scrollPane)
286273
details.addTab("Steps", steps)
287274

288275
layout = BoxLayout(this, BoxLayout.Y_AXIS)
@@ -436,17 +423,18 @@ class SarifViewerWindowFactory : ToolWindowFactory {
436423
val root = DefaultMutableTreeNode(project.name)
437424

438425
map.forEach { (key, value) ->
439-
val ruleNode = DefaultMutableTreeNode(key)
426+
val ruleNode = DefaultMutableTreeNode("$key (${value.size})")
440427
value.forEach { location ->
441428
val locationNode = DefaultMutableTreeNode(location)
442429
ruleNode.add(locationNode)
443430
}
444431
root.add(ruleNode)
445432
}
446433

447-
myList = JTree(root)
434+
myList = com.intellij.ui.treeStructure.Tree(root)
448435

449436
myList.isRootVisible = false
437+
myList.showsRootHandles = true
450438
main = ScrollPaneFactory.createScrollPane(myList)
451439

452440
details.isVisible = false
@@ -457,7 +445,7 @@ class SarifViewerWindowFactory : ToolWindowFactory {
457445
myList.addTreeSelectionListener(object : TreeSelectionListener {
458446
override fun valueChanged(e: TreeSelectionEvent?) {
459447
if (e != null && e.isAddedPath) {
460-
val leaves = map[e.path.parentPath.lastPathComponent.toString()]
448+
val leaves = map[e.path.parentPath.lastPathComponent.toString().split(" ").first()]
461449
if (!leaves.isNullOrEmpty()) {
462450
val leaf = try {
463451
leaves.first { it.address == ((e.path.lastPathComponent as DefaultMutableTreeNode).userObject as Leaf).address }
@@ -470,18 +458,91 @@ class SarifViewerWindowFactory : ToolWindowFactory {
470458
.replace("api/v3/", "")
471459
.replace("repos/", "")
472460
.replace("code-scanning/alerts", "security/code-scanning")
473-
val githubURL = "<a target=\"_BLANK\" href=\"$githubAlertUrl\">$githubAlertUrl</a>"
474461

475-
infos.contentType = "text/html"
462+
tableInfos.clearSelection()
463+
// Create a table model with "Property" and "Value" columns
464+
val defaultTableModel: DefaultTableModel =
465+
object : DefaultTableModel(arrayOf<Any>("Property", "Value"), 0) {
466+
override fun isCellEditable(row: Int, column: Int): Boolean {
467+
return false
468+
}
469+
}
470+
tableInfos.model = defaultTableModel
471+
472+
// Add some data
473+
defaultTableModel.addRow(arrayOf<Any>("Name", leaf.leafName))
474+
defaultTableModel.addRow(arrayOf<Any>("Level", leaf.level))
475+
defaultTableModel.addRow(arrayOf<Any>("Rule's name", leaf.ruleName))
476+
defaultTableModel.addRow(arrayOf<Any>("Rule's description", leaf.ruleDescription))
477+
defaultTableModel.addRow(arrayOf<Any>("Location", leaf.location))
478+
defaultTableModel.addRow(arrayOf<Any>("GitHub alert number", leaf.githubAlertNumber))
479+
defaultTableModel.addRow(
480+
arrayOf<Any>(
481+
"GitHub alert url",
482+
"<a href=\"$githubAlertUrl\">$githubAlertUrl</a"
483+
)
484+
)
485+
486+
tableInfos.setDefaultRenderer(Object::class.java, object : DefaultTableCellRenderer() {
487+
override fun getTableCellRendererComponent(
488+
table: JTable?,
489+
value: Any?,
490+
isSelected: Boolean,
491+
hasFocus: Boolean,
492+
row: Int,
493+
column: Int
494+
): Component {
495+
var c = super.getTableCellRendererComponent(
496+
table,
497+
value,
498+
isSelected,
499+
hasFocus,
500+
row,
501+
column
502+
)
503+
if (row == tableInfos.rowCount - 1 && column == tableInfos.columnCount - 1) {
504+
val url = tableInfos.getValueAt(row, column).toString()
505+
c = JLabel("<html><a href='$url'>$url</a></html>")
506+
c.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
507+
}
508+
return c
509+
}
510+
})
511+
512+
513+
tableInfos.addMouseListener(object : MouseAdapter() {
514+
override fun mouseClicked(e: MouseEvent) {
515+
val row = tableInfos.rowAtPoint(e.point)
516+
val column = tableInfos.columnAtPoint(e.point)
517+
if (row == tableInfos.rowCount - 1) {
518+
if (column == tableInfos.columnCount - 1) {
519+
if (Desktop.isDesktopSupported() && Desktop.getDesktop()
520+
.isSupported(Desktop.Action.BROWSE)
521+
) {
522+
Desktop.getDesktop().browse(URI(githubAlertUrl))
523+
}
524+
}
525+
}
526+
}
527+
})
528+
529+
tableInfos.updateUI()
476530

477-
infos.text =
478-
"${leaf.leafName} <br/> Level: ${leaf.level} <br/>Rule's name: ${leaf.ruleName} <br/>Rule's description ${leaf.ruleDescription} <br/>Location ${leaf.location} <br/>GitHub alert number: ${leaf.githubAlertNumber} <br/>GitHub alert url ${githubURL}\n"
531+
tableSteps.clearSelection()
479532

480-
steps.read(leaf.steps.joinToString("<br/>") { step ->
481-
"<a href=\"$step\">${step.split("/").last()}</a>"
482-
}.byteInputStream(Charset.defaultCharset()), HTMLEditorKit::class.java)
533+
tableSteps.model = DefaultTableModel(arrayOf<Any>("Path"), 0)
483534

484-
steps.contentType = "text/html"
535+
leaf.steps.forEachIndexed { index, step ->
536+
(tableSteps.model as DefaultTableModel).addRow(arrayOf<Any>("$index $step"))
537+
}
538+
539+
tableSteps.addMouseListener(object : MouseAdapter() {
540+
override fun mouseClicked(e: MouseEvent) {
541+
val row = tableInfos.rowAtPoint(e.point)
542+
val path = leaf.steps[row].split(":")
543+
openFile(project, path[0], path[1].toInt())
544+
}
545+
})
485546

486547
details.isVisible = true
487548
openFile(
@@ -532,32 +593,33 @@ class SarifViewerWindowFactory : ToolWindowFactory {
532593
true // request focus to editor
533594
)
534595
FileDocumentManager.getInstance().getDocument(virtualFile)?.let { document ->
535-
val lineStartOffset = document.getLineStartOffset(lineNumber - 1)
536-
val lineEndOffset = document.getLineEndOffset(lineNumber - 1)
537-
val editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return
538-
editor.caretModel.moveToOffset(lineStartOffset)
539-
editor.scrollingModel.scrollToCaret(ScrollType.CENTER)
540-
editor.selectionModel.setSelection(lineStartOffset, lineEndOffset)
541-
542-
val messageType = when (level) {
543-
"error" -> MessageType.ERROR
544-
"warning" -> MessageType.WARNING
545-
else -> MessageType.INFO
546-
}
547-
548-
// add a balloon on the selection
549-
val balloon = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(
550-
ruleDescription,
551-
messageType,
552-
null
553-
).createBalloon()
554-
balloon.show(
555-
RelativePoint(
556-
editor.contentComponent,
557-
editor.visualPositionToXY(editor.caretModel.visualPosition)
558-
), Balloon.Position.above
559-
)
596+
if (ruleDescription.isNotEmpty()) {
597+
val lineStartOffset = document.getLineStartOffset(lineNumber - 1)
598+
val lineEndOffset = document.getLineEndOffset(lineNumber - 1)
599+
val editor = FileEditorManager.getInstance(project).selectedTextEditor ?: return
600+
editor.caretModel.moveToOffset(lineStartOffset)
601+
editor.scrollingModel.scrollToCaret(ScrollType.CENTER)
602+
editor.selectionModel.setSelection(lineStartOffset, lineEndOffset)
603+
604+
val messageType = when (level) {
605+
"error" -> MessageType.ERROR
606+
"warning" -> MessageType.WARNING
607+
else -> MessageType.INFO
608+
}
560609

610+
// add a balloon on the selection
611+
val balloon = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(
612+
ruleDescription,
613+
messageType,
614+
null
615+
).createBalloon()
616+
balloon.show(
617+
RelativePoint(
618+
editor.contentComponent,
619+
editor.visualPositionToXY(editor.caretModel.visualPosition)
620+
), Balloon.Position.above
621+
)
622+
}
561623
}
562624

563625
}
@@ -571,8 +633,10 @@ class SarifViewerWindowFactory : ToolWindowFactory {
571633
}
572634
}
573635

574-
infos.text = ""
575-
steps.text = ""
636+
tableInfos.clearSelection()
637+
tableInfos.updateUI()
638+
tableSteps.clearSelection()
639+
tableSteps.updateUI()
576640
details.isVisible = false
577641
errorToolbar.isVisible = false
578642
}
@@ -592,8 +656,10 @@ class SarifViewerWindowFactory : ToolWindowFactory {
592656
val mainResults: List<Result> = sarifMainBranch.flatMap { it.runs?.get(0)?.results ?: emptyList() }
593657

594658
for (currentResult in results) {
595-
if (mainResults.none { it.ruleId == currentResult.ruleId
596-
&& ("${currentResult.locations[0].physicalLocation.artifactLocation.uri}:${currentResult.locations[0].physicalLocation.region.startLine}" == "${it.locations[0].physicalLocation.artifactLocation.uri}:${it.locations[0].physicalLocation.region.startLine}") }) {
659+
if (mainResults.none {
660+
it.ruleId == currentResult.ruleId
661+
&& ("${currentResult.locations[0].physicalLocation.artifactLocation.uri}:${currentResult.locations[0].physicalLocation.region.startLine}" == "${it.locations[0].physicalLocation.artifactLocation.uri}:${it.locations[0].physicalLocation.region.startLine}")
662+
}) {
597663
resultsToDisplay.add(currentResult)
598664
}
599665
}

0 commit comments

Comments
 (0)