Address PR comments. Add links from FlowTriage view to Network and to TransactionView with showing

relevant transactions/peers on the map.
This commit is contained in:
Katarzyna Streich 2017-05-19 11:31:31 +01:00
parent 67a417389b
commit 4f00bad908
9 changed files with 131 additions and 133 deletions

View File

@ -7,7 +7,6 @@ import javafx.collections.FXCollections
import net.corda.client.jfx.utils.LeftOuterJoinedMap
import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.getObservableValues
import net.corda.client.jfx.utils.lift
import net.corda.client.jfx.utils.recordAsAssociation
import net.corda.core.ErrorOr
import net.corda.core.flows.FlowInitiator

View File

@ -1,14 +1,26 @@
package net.corda.explorer.formatters
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
import net.corda.core.flows.FlowInitiator
object FlowInitiatorFormatter : Formatter<FlowInitiator> {
override fun format(value: FlowInitiator): String {
return when (value) {
is FlowInitiator.Scheduled -> "Started by scheduled state:: " + value.scheduledState.ref.toString() // TODO How do we want to format that?
is FlowInitiator.Shell -> "Started via shell"
is FlowInitiator.Peer -> "Peer legal name: " + PartyNameFormatter.short.format(value.party.name)
is FlowInitiator.RPC -> "Rpc username: " + value.username
is FlowInitiator.Scheduled -> value.scheduledState.ref.toString() // TODO How do we want to format that?
is FlowInitiator.Shell -> "Shell" // TODO We don't have much information about that user.
is FlowInitiator.Peer -> PartyNameFormatter.short.format(value.party.name)
is FlowInitiator.RPC -> value.username
}
}
fun withIcon(value: FlowInitiator): Pair<FontAwesomeIcon, String> {
val text = format(value)
return when (value) {
is FlowInitiator.Scheduled -> Pair(FontAwesomeIcon.CALENDAR, text)
is FlowInitiator.Shell -> Pair(FontAwesomeIcon.TERMINAL, text)
is FlowInitiator.Peer -> Pair(FontAwesomeIcon.GROUP, text)
is FlowInitiator.RPC -> Pair(FontAwesomeIcon.SHARE, text)
}
}
}

View File

@ -1,7 +1,13 @@
package net.corda.explorer.formatters
import org.apache.commons.lang.StringUtils.splitByCharacterTypeCamelCase
object FlowNameFormatter {
val boring = object : Formatter<String> {
override fun format(value: String) = value.split('.').last().replace("$", ": ") // TODO Better handling of names.
val camelCase = object : Formatter<String> {
override fun format(value: String): String {
val flowName = value.split('.', '$').last()
val split = splitByCharacterTypeCamelCase(flowName).filter { it.compareTo("Flow", true) != 0 } .joinToString(" ")
return split
}
}
}

View File

@ -195,6 +195,7 @@ fun identicon(secureHash: SecureHash, size: Double): ImageView {
return ImageView(IdenticonRenderer.getIdenticon(secureHash)).apply {
isPreserveRatio = true
fitWidth = size
styleClass += "identicon"
}
}

View File

@ -6,6 +6,7 @@ import javafx.animation.FadeTransition
import javafx.animation.TranslateTransition
import javafx.beans.binding.Bindings
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.geometry.Bounds
import javafx.geometry.Point2D
@ -41,6 +42,9 @@ class Network : CordaView() {
val notaries by observableList(NetworkIdentityModel::notaries)
val peers by observableList(NetworkIdentityModel::parties)
val transactions by observableList(TransactionDataModel::partiallyResolvedTransactions)
var centralPeer: String? = null
private var centralLabel: ObservableValue<Label?>
// UI components
private val myIdentityPane by fxid<BorderPane>()
private val notaryList by fxid<VBox>()
@ -114,7 +118,17 @@ class Network : CordaView() {
return MapViewComponents(this, button, mapLabel)
}
override fun onDock() {
centralLabel = mapLabels.firstOrDefault(myMapLabel, { centralPeer?.contains(it.text, true) ?: false })
}
override fun onUndock() {
centralPeer = null
centralLabel = myMapLabel
}
init {
centralLabel = mapLabels.firstOrDefault(myMapLabel, { centralPeer?.contains(it.text, true) ?: false })
myIdentityPane.centerProperty().bind(myButton)
Bindings.bindContent(notaryList.children, notaryButtons)
Bindings.bindContent(peerList.children, peerButtons)
@ -122,7 +136,7 @@ class Network : CordaView() {
// Run once when the screen is ready.
// TODO : Find a better way to do this.
mapPane.heightProperty().addListener { _, old, _ ->
if (old == 0.0) myMapLabel.value?.let { mapScrollPane.centerLabel(it) }
if (old == 0.0) centralLabel.value?.let { mapScrollPane.centerLabel(it) }
}
// Listen on zooming gesture, if device has gesture support.
mapPane.setOnZoom { zoom(it.zoomFactor, Point2D(it.x, it.y)) }
@ -142,6 +156,7 @@ class Network : CordaView() {
}
}
// TODO It doesn't work as expected.
private fun ScrollPane.centerLabel(label: Label) {
this.hvalue = (label.boundsInParent.width / 2 + label.boundsInParent.minX) / mapImageView.layoutBounds.width
this.vvalue = (label.boundsInParent.height / 2 + label.boundsInParent.minY) / mapImageView.layoutBounds.height

View File

@ -103,43 +103,29 @@ class StateMachineViewer : CordaView("Flow Triage") {
minWidth = 100.0
maxWidth = 200.0
}.setCustomCellFactory {
label("$it") {
val toDisplay = it.toString().removeSurrounding("[", "]")
label(toDisplay) {
val hash = SecureHash.sha256(it.toString())
graphic = identicon(hash, 15.0)
tooltip = identiconToolTip(hash, it.toString())
tooltip = identiconToolTip(hash, toDisplay)
}
}
column("Flow name", StateMachineData::stateMachineName).cellFormat { text = FlowNameFormatter.boring.format(it) }
column("Initiator", StateMachineData::flowInitiator).cellFormat { text = FlowInitiatorFormatter.format(it) }
column("Flow Status", StateMachineData::stateMachineStatus).cellFormat { text = it.status ?: "No progress data" }
column("Result", StateMachineData::addRmStatus).setCustomCellFactory {
if (it is StateMachineStatus.Removed) {
if (it.result.error == null) {
label("Success") {
graphic = FontAwesomeIconView(FontAwesomeIcon.CHECK).apply {
glyphSize = 15.0
textAlignment = TextAlignment.CENTER
style = "-fx-fill: green"
}
}
column("Flow name", StateMachineData::stateMachineName).cellFormat { text = FlowNameFormatter.camelCase.format(it) }
column("Initiator", StateMachineData::flowInitiator).setCustomCellFactory {
val (initIcon, initText) = FlowInitiatorFormatter.withIcon(it)
label { makeIconLabel(this, initIcon, initText, "-fx-fill: lightgray") }
}
column("Flow Status", StateMachineData::smmStatus).setCustomCellFactory {
val addRm = it.first.value
val progress = it.second.value.status ?: "No progress data"
if (addRm is StateMachineStatus.Removed) {
if (addRm.result.error == null) {
label { makeIconLabel(this, FontAwesomeIcon.CHECK, "Success", "-fx-fill: green") }
} else {
label("Error") {
graphic = FontAwesomeIconView(FontAwesomeIcon.BOLT).apply {
glyphSize = 15.0
textAlignment = TextAlignment.CENTER
style = "-fx-fill: -color-4"
}
}
label { makeIconLabel(this, FontAwesomeIcon.BOLT, progress, "-fx-fill: -color-4") }
}
} else {
label("In progress") {
graphic = FontAwesomeIconView(FontAwesomeIcon.ROCKET).apply {
// Blazing fast! Try not to blink.
glyphSize = 15.0
textAlignment = TextAlignment.CENTER
style = "-fx-fill: lightslategrey"
}
}
label { makeIconLabel(this, FontAwesomeIcon.ROCKET, progress, "-fx-fill: lightslategrey") }
}
}
}
@ -150,22 +136,22 @@ class StateMachineViewer : CordaView("Flow Triage") {
"Flow name" to { sm, s -> sm.stateMachineName.contains(s, true) },
"Initiator" to { sm, s -> FlowInitiatorFormatter.format(sm.flowInitiator).contains(s, true) },
"Flow Status" to { sm, s ->
val stat = sm.stateMachineStatus.value.status ?: "No progress data"
val stat = sm.smmStatus.second.value?.status ?: "No progress data"
stat.contains(s, true)
},
"Error" to { sm, _ ->
val smAddRm = sm.addRmStatus.value
val smAddRm = sm.smmStatus.first.value
if (smAddRm is StateMachineStatus.Removed)
smAddRm.result.error != null
else false
},
"Done" to { sm, _ ->
val smAddRm = sm.addRmStatus.value
val smAddRm = sm.smmStatus.first.value
if (smAddRm is StateMachineStatus.Removed)
smAddRm.result.error == null
else false
},
"In progress" to { sm, _ -> sm.addRmStatus.value !is StateMachineStatus.Removed },
"In progress" to { sm, _ -> sm.smmStatus.first.value !is StateMachineStatus.Removed },
disabledFields = listOf("Error", "Done", "In progress")
)
root.top = searchField.root
@ -175,31 +161,20 @@ class StateMachineViewer : CordaView("Flow Triage") {
})
}
private inner class StateMachineDetailsView(val smmData: StateMachineData) : Fragment() {
private inner class StateMachineDetailsView(smmData: StateMachineData) : Fragment() {
override val root by fxml<Parent>()
private val flowNameLabel by fxid<Label>()
private val flowProgressVBox by fxid<VBox>()
private val flowInitiatorGrid by fxid<GridPane>()
private val flowInitiatorTitle by fxid<Label>()
private val flowResultVBox by fxid<VBox>()
init {
flowNameLabel.apply {
text = FlowNameFormatter.boring.format(smmData.stateMachineName)
}
//TODO It would be nice to have flow graph with showing progress steps with subflows + timestamps (left it for second iteration).
flowProgressVBox.apply {
label {
text = smmData.stateMachineStatus.value?.status ?: "No progress data"
}
}
when (smmData.flowInitiator) {
is FlowInitiator.Shell -> makeShellGrid(flowInitiatorGrid, flowInitiatorTitle) // TODO Extend this when we will have more information on shell user.
is FlowInitiator.Peer -> makePeerGrid(flowInitiatorGrid, flowInitiatorTitle, smmData.flowInitiator as FlowInitiator.Peer)
is FlowInitiator.RPC -> makeRPCGrid(flowInitiatorGrid, flowInitiatorTitle, smmData.flowInitiator as FlowInitiator.RPC)
is FlowInitiator.Scheduled -> makeScheduledGrid(flowInitiatorGrid, flowInitiatorTitle, smmData.flowInitiator as FlowInitiator.Scheduled)
is FlowInitiator.Shell -> makeShellGrid(flowInitiatorGrid) // TODO Extend this when we will have more information on shell user.
is FlowInitiator.Peer -> makePeerGrid(flowInitiatorGrid, smmData.flowInitiator as FlowInitiator.Peer)
is FlowInitiator.RPC -> makeRPCGrid(flowInitiatorGrid, smmData.flowInitiator as FlowInitiator.RPC)
is FlowInitiator.Scheduled -> makeScheduledGrid(flowInitiatorGrid, smmData.flowInitiator as FlowInitiator.Scheduled)
}
val status = smmData.addRmStatus.value
val status = smmData.smmStatus.first.value
if (status is StateMachineStatus.Removed) {
status.result.match(onValue = { makeResultVBox(flowResultVBox, it) }, onError = { makeErrorVBox(flowResultVBox, it) })
}
@ -207,22 +182,23 @@ class StateMachineViewer : CordaView("Flow Triage") {
}
private fun <T> makeResultVBox(vbox: VBox, result: T) {
if (result == null) {
vbox.apply { label("No return value from flow.").apply { style { fontWeight = FontWeight.BOLD } } }
} else if (result is SignedTransaction) {
// TODO Make link to transaction view
if (result is SignedTransaction) {
vbox.apply {
label("Signed transaction").apply { style { fontWeight = FontWeight.BOLD } }
label {
style = "-fx-cursor: hand;"
setOnMouseClicked {
if (it.button == MouseButton.PRIMARY) {
selectedView.value = tornadofx.find<TransactionViewer>().apply { txIdToScroll = result.id }
}
}
text = result.id.toString()
graphic = identicon(result.id, 30.0)
tooltip = identiconToolTip(result.id)
}
}
} else if (result is Unit) {
vbox.apply { label("Flow completed with success.").apply { style { fontWeight = FontWeight.BOLD } } }
} else {
} else if (result != null && result !is Unit) {
// TODO Here we could have sth different than SignedTransaction/Unit
vbox.apply {
label("Flow completed with success. Result: ").apply { style { fontWeight = FontWeight.BOLD } }
@ -233,36 +209,35 @@ class StateMachineViewer : CordaView("Flow Triage") {
private fun makeErrorVBox(vbox: VBox, error: Throwable) {
vbox.apply {
label("Error") {
label {
text = error::class.simpleName
graphic = FontAwesomeIconView(FontAwesomeIcon.BOLT).apply {
glyphSize = 30
textAlignment = TextAlignment.CENTER
style = "-fx-fill: -color-4"
}
}
}
vbox.apply {
vbox {
spacing = 10.0
label { text = error::class.simpleName }
label { text = error.message }
}
label { text = error.message }
}
}
private fun makeShellGrid(gridPane: GridPane, title: Label) {
title.apply {
text = "Flow started by shell user"
}
}
private fun makePeerGrid(gridPane: GridPane, title: Label, initiator: FlowInitiator.Peer) {
title.apply {
text = "Flow started by a peer node"
}
private fun makeShellGrid(gridPane: GridPane) {
gridPane.apply {
label("Flow started by shell user")
}
}
private fun makePeerGrid(gridPane: GridPane, initiator: FlowInitiator.Peer) {
gridPane.apply {
style = "-fx-cursor: hand;"
setOnMouseClicked {
if (it.button == MouseButton.PRIMARY) {
val short = PartyNameFormatter.short.format(initiator.party.name)
selectedView.value = tornadofx.find<Network>().apply { centralPeer = short}
}
}
row {
label("Legal name: ") {
label("Peer legal name: ") {
gridpaneConstraints { hAlignment = HPos.LEFT }
style { fontWeight = FontWeight.BOLD }
minWidth = 150.0
@ -282,13 +257,10 @@ class StateMachineViewer : CordaView("Flow Triage") {
}
}
private fun makeRPCGrid(gridPane: GridPane, title: Label, initiator: FlowInitiator.RPC) {
title.apply {
text = "Flow started by a RPC user"
}
private fun makeRPCGrid(gridPane: GridPane, initiator: FlowInitiator.RPC) {
gridPane.apply {
row {
label("User name: ") {
label("RPC user name: ") {
gridpaneConstraints { hAlignment = HPos.LEFT }
style { fontWeight = FontWeight.BOLD }
prefWidth = 150.0
@ -299,10 +271,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
}
// TODO test
private fun makeScheduledGrid(gridPane: GridPane, title: Label, initiator: FlowInitiator.Scheduled) {
title.apply {
text = "Flow started as scheduled activity"
}
private fun makeScheduledGrid(gridPane: GridPane, initiator: FlowInitiator.Scheduled) {
gridPane.apply {
row {
label("Scheduled state: ") {

View File

@ -76,6 +76,24 @@ class TransactionViewer : CordaView("Transactions") {
data class Inputs(val resolved: ObservableList<StateAndRef<ContractState>>, val unresolved: ObservableList<StateRef>)
override fun onDock() {
txIdToScroll?.let {
scrollPosition = transactionViewTable.items.indexOfFirst { it.id == txIdToScroll }
if (scrollPosition > 0) {
expander.toggleExpanded(scrollPosition)
val tx = transactionViewTable.items[scrollPosition]
transactionViewTable.scrollTo(tx)
}
}
}
override fun onUndock() {
val isExpanded = expander.getExpandedProperty(transactionViewTable.items[scrollPosition]) // It is evil.
if (isExpanded.value) expander.toggleExpanded(scrollPosition)
scrollPosition = 0
txIdToScroll = null
}
/**
* We map the gathered data about transactions almost one-to-one to the nodes.
*/
@ -162,7 +180,7 @@ class TransactionViewer : CordaView("Transactions") {
titleProperty.bind(reportingCurrency.map { "Total value ($it equiv)" })
}
rowExpander {
expander = rowExpander {
add(ContractStatesView(it).root)
prefHeight = 400.0
}.apply {
@ -194,6 +212,8 @@ class TransactionViewer : CordaView("Transactions") {
init {
right {
label {
val hash = SecureHash.randomSHA256()
graphic = identicon(hash, 30.0)
textProperty().bind(Bindings.size(partiallyResolvedTransactions).map(Number::toString))
BorderPane.setAlignment(this, Pos.BOTTOM_RIGHT)
}

View File

@ -19,10 +19,10 @@
<Insets bottom="25" left="5" right="5" top="5"/>
</StackPane.margin>
<TitledPane styleClass="networkTile" text="My Identity">
<BorderPane fx:id="myIdentityPane" minHeight="150"/>
<BorderPane fx:id="myIdentityPane" minHeight="150" maxWidth="Infinity"/>
</TitledPane>
<TitledPane styleClass="networkTile" text="Notaries">
<BorderPane minHeight="150">
<BorderPane minHeight="150" maxWidth="Infinity">
<center>
<ScrollPane hbarPolicy="NEVER">
<VBox fx:id="notaryList" maxWidth="-Infinity"/>
@ -31,7 +31,7 @@
</BorderPane>
</TitledPane>
<TitledPane styleClass="networkTile" text="Peers" VBox.vgrow="ALWAYS">
<BorderPane minHeight="150">
<BorderPane minHeight="150" maxWidth="Infinity">
<center>
<ScrollPane hbarPolicy="NEVER">
<VBox fx:id="peerList" maxWidth="-Infinity">

View File

@ -1,48 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<GridPane styleClass="smm-detail-grid" stylesheets="@../css/corda.css" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
<GridPane styleClass="flow-expanded" stylesheets="@../css/corda.css" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
<padding>
<Insets bottom="5" left="5" right="5" top="5" />
</padding>
<TitledPane fx:id="flowNamePane" collapsible="false" text="Flow name" GridPane.columnSpan="2" GridPane.fillWidth="true" GridPane.rowIndex="0">
<Label fx:id="flowNameLabel" style="-fx-font-weight: bold"/>
</TitledPane>
<TitledPane fx:id="flowInitiatorPane" collapsible="false" maxHeight="Infinity" text="Flow initiator" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.rowIndex="1">
<ScrollPane maxHeight="Infinity" minHeight="120">
<GridPane fx:id="flowInitiatorGrid" hgap="10.0" vgap="10.0">
<padding>
<Insets bottom="5.0" left="5.0" right="10.0" top="10.0" />
</padding>
<Label fx:id="flowInitiatorTitle" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="LEFT" GridPane.rowIndex="0" style="-fx-font-weight: bold"/>
</GridPane>
</ScrollPane>
</TitledPane>
<TitledPane fx:id="flowResultPane" collapsible="false" maxHeight="Infinity" text="Result" GridPane.columnIndex="1" GridPane.rowIndex="1">
<ScrollPane maxHeight="Infinity">
<VBox fx:id="flowResultVBox" spacing="10.0">
<padding>
<Insets bottom="5" left="5" right="5" top="5" />
</padding>
</VBox>
</ScrollPane>
</TitledPane>
<TitledPane fx:id="flowProgressPane" collapsible="false" text="Progress steps" GridPane.columnSpan="2" GridPane.rowIndex="2">
<!--TODO add progress graph-->
<VBox fx:id="flowProgressVBox" spacing="10.0">
<padding>
<Insets bottom="5" left="5" right="5" top="5" />
</padding>
</VBox>
</TitledPane>
<GridPane fx:id="flowInitiatorGrid" hgap="10.0" vgap="10.0" maxHeight="Infinity" maxWidth="Infinity" GridPane.fillWidth="true" GridPane.rowIndex="0">
<padding>
<Insets bottom="5.0" left="5.0" right="10.0" />
</padding>
</GridPane>
<VBox fx:id="flowResultVBox" spacing="10.0" maxHeight="Infinity" maxWidth="Infinity" GridPane.rowIndex="1" GridPane.fillWidth="true">
<padding>
<Insets bottom="5" left="5" right="5" top="5" />
</padding>
</VBox>
<columnConstraints>
<ColumnConstraints minWidth="450.0" />
<ColumnConstraints hgrow="ALWAYS" />
@ -50,6 +27,5 @@
<rowConstraints>
<RowConstraints vgrow="ALWAYS" />
<RowConstraints vgrow="ALWAYS" />
<RowConstraints vgrow="ALWAYS" />
</rowConstraints>
</GridPane>