diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt index 09fd747209..8656f81ed2 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt @@ -6,31 +6,15 @@ import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClientConfiguration import net.corda.core.flows.StateMachineRunId import net.corda.core.messaging.CordaRPCOps -import net.corda.core.messaging.StateMachineInfo import net.corda.core.messaging.StateMachineUpdate import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.StateMachineTransactionMapping import net.corda.core.node.services.Vault import net.corda.core.seconds import net.corda.core.transactions.SignedTransaction -import net.corda.core.utilities.ProgressTracker import rx.Observable import rx.subjects.PublishSubject -data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String, val currentState: ProgressTracker?) { // TODO: RG Not a string, but a proper tracking object. - companion object { - fun createStreamFromStateMachineInfo(stateMachine: StateMachineInfo): Observable? { - return stateMachine.progressTrackerStepAndUpdates?.let { pair -> - val (current, future) = pair - - future.map { ProgressTrackingEvent(stateMachine.id, it, null ) }.startWith(ProgressTrackingEvent(stateMachine.id, current, null)) - } - } - } -} - - - /** * This model exposes raw event streams to and from the node. */ diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/StateMachineDataModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/StateMachineDataModel.kt new file mode 100644 index 0000000000..4c83be7934 --- /dev/null +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/StateMachineDataModel.kt @@ -0,0 +1,86 @@ +package net.corda.client.jfx.model + +import javafx.beans.binding.Bindings +import javafx.beans.property.SimpleObjectProperty +import javafx.beans.value.ObservableValue +import javafx.collections.FXCollections +import net.corda.client.jfx.utils.LeftOuterJoinedMap +import net.corda.client.jfx.utils.flatten +import net.corda.client.jfx.utils.fold +import net.corda.client.jfx.utils.getObservableValues +import net.corda.client.jfx.utils.map +import net.corda.client.jfx.utils.recordAsAssociation +import net.corda.core.ErrorOr +import net.corda.core.flows.FlowInitiator +import net.corda.core.flows.StateMachineRunId +import net.corda.core.messaging.StateMachineInfo +import net.corda.core.messaging.StateMachineUpdate +import rx.Observable + +data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String) { + companion object { + fun createStreamFromStateMachineInfo(stateMachine: StateMachineInfo): Observable? { + return stateMachine.progressTrackerStepAndUpdates?.let { pair -> + val (current, future) = pair + future.map { ProgressTrackingEvent(stateMachine.id, it) }.startWith(ProgressTrackingEvent(stateMachine.id, current)) + } + } + } +} + +data class ProgressStatus(val status: String) + +sealed class StateMachineStatus { + data class Added(val stateMachineName: String, val flowInitiator: FlowInitiator) : StateMachineStatus() + data class Removed(val result: ErrorOr<*>) : StateMachineStatus() +} + +// TODO StateMachineData and StateMachineInfo +data class StateMachineData( + val id: StateMachineRunId, + val stateMachineName: String, + val flowInitiator: FlowInitiator, + val addRmStatus: ObservableValue, + val stateMachineStatus: ObservableValue +) + +class StateMachineDataModel { + private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates) + private val progressTracking by observable(NodeMonitorModel::progressTracking) + private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId) + + private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableHashMap>()) { map, update -> + when (update) { + is StateMachineUpdate.Added -> { + val flowInitiator= update.stateMachineInfo.initiator + val added: SimpleObjectProperty = + SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName, flowInitiator)) + map[update.id] = added + } + is StateMachineUpdate.Removed -> { + val added = map[update.id] + added ?: throw Exception("State machine removed with unknown id ${update.id}") + added.set(StateMachineStatus.Removed(update.result)) + } + } + } + + val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress -> + val smStatus = status.value as StateMachineStatus.Added // TODO not always added + // todo easybind + Bindings.createObjectBinding({ + StateMachineData(id, smStatus.stateMachineName, smStatus.flowInitiator, status, progress.map { it?.let { ProgressStatus(it.message) } }) + }, arrayOf(progress, status)) + }.getObservableValues().flatten() + + val stateMachinesInProgress = stateMachineDataList.filtered { it.addRmStatus.value !is StateMachineStatus.Removed } + val stateMachinesDone = stateMachineDataList.filtered { it.addRmStatus.value is StateMachineStatus.Removed } + val stateMachinesFinished = stateMachinesDone.filtered { + val res = it.addRmStatus.value as StateMachineStatus.Removed + res.result.error == null + } + val stateMachinesError = stateMachinesDone.filtered { + val res = it.addRmStatus.value as StateMachineStatus.Removed + res.result.error != null + } +} diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt index 3537dc82ef..934758e2d9 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt @@ -1,9 +1,6 @@ package net.corda.client.jfx.model -import javafx.beans.binding.Bindings -import javafx.beans.property.SimpleObjectProperty import javafx.beans.value.ObservableValue -import javafx.collections.FXCollections import javafx.collections.ObservableList import javafx.collections.ObservableMap import net.corda.client.jfx.utils.* @@ -11,8 +8,6 @@ import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef import net.corda.core.crypto.SecureHash -import net.corda.core.flows.StateMachineRunId -import net.corda.core.messaging.StateMachineUpdate import net.corda.core.transactions.SignedTransaction import org.fxmisc.easybind.EasyBind @@ -59,159 +54,17 @@ data class PartiallyResolvedTransaction( } } -data class FlowStatus(val status: String) -//todo after rebase - remove it rather -//class FlowStatus( -// val status: String, -// pt: ProgressTrackingEvent?, -// val progress: FlowLogic.ProgressTrackerDisplayProxy = convert(pt) -//) { -// -// companion object { -// fun convert(pt: ProgressTrackingEvent?) : FlowLogic.ProgressTrackerDisplayProxy { -// return FlowLogic.ProgressTrackerDisplayProxy(null, false, false) -///* pt?. == ProgressTracker.DONE, -// pt == null)*/ -// } -// } -//} - -sealed class StateMachineStatus { - abstract val stateMachineName: String - - data class Added(override val stateMachineName: String) : StateMachineStatus() - data class Removed(override val stateMachineName: String) : StateMachineStatus() -} - -/*data class StateMachineData( - val id: StateMachineRunId, - val flowStatus: ObservableValue, - val stateMachineStatus: ObservableValue -) -*/ - -data class StateMachineData( - val id: StateMachineRunId, - val flowStatus: FlowStatus?, - val stateMachineStatus: StateMachineStatus -) - - +// TODO Do we want to have mapping tx <-> StateMachine? /** * This model provides an observable list of transactions and what state machines/flows recorded them */ class TransactionDataModel { private val transactions by observable(NodeMonitorModel::transactions) - private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates) - private val progressTracking by observable(NodeMonitorModel::progressTracking) - private val stateMachineTransactionMapping by observable(NodeMonitorModel::stateMachineTransactionMapping) private val collectedTransactions = transactions.recordInSequence() private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id) - private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId) - private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableHashMap>()) { map, update -> - when (update) { - is StateMachineUpdate.Added -> { - val added: SimpleObjectProperty = - SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName)) - map[update.id] = added - } - is StateMachineUpdate.Removed -> { - val added = map[update.id] - added ?: throw Exception("State machine removed with unknown id ${update.id}") - added.set(StateMachineStatus.Removed(added.value.stateMachineName)) - } - } - } - val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress -> - Bindings.createObjectBinding({ StateMachineData(id, progress.value?.message?.let(::FlowStatus), status.get()) }, arrayOf(progress, status)) - }.getObservableValues().flatten() - - /* - private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress -> - StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status) - }.getObservableValues()*/ - - // TODO : Create a new screen for state machines. - private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id) - private val smTxMappingList = stateMachineTransactionMapping.recordInSequence() val partiallyResolvedTransactions = collectedTransactions.map { PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap) } } - - -class StateMachineDataModel { - private val transactions by observable(NodeMonitorModel::transactions) - - private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates) - private val progressTracking by observable(NodeMonitorModel::progressTracking) - private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId) - - private val progressList = progressEvents.getObservableValues() - - private val stateMachineTransactionMapping by observable(NodeMonitorModel::stateMachineTransactionMapping) - private val collectedTransactions = transactions.recordInSequence() - private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id) - - private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableHashMap>()) { map, update -> - println("**** $update") - when (update) { - is StateMachineUpdate.Added -> { - val added: SimpleObjectProperty = - SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName)) - map[update.id] = added - } - is StateMachineUpdate.Removed -> { - val added = map[update.id] - added ?: throw Exception("State machine removed with unknown id ${update.id}") - added.set(StateMachineStatus.Removed(added.value.stateMachineName)) - } - } - } - - - val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress -> - Bindings.createObjectBinding({ StateMachineData(id, progress.value?.message?.let(::FlowStatus), status.get()) }, arrayOf(progress, status)) - }.getObservableValues().flatten() - - /* - val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress -> - StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status) - }.getObservableValues()*/ - - // TODO : Create a new screen for state machines. - private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id) - private val smTxMappingList = stateMachineTransactionMapping.recordInSequence() - - - - - val partiallyResolvedTransactions = collectedTransactions.map { - PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap) - } - val flowsInProgress = partiallyResolvedTransactions - -/* - data class SomeStructure( - val someInt: ObservableValue, - val otherData: ObservableValue - ) -*/ - val progressObservableList = progressList.map{ struct -> struct.message } - - fun asd() { - progressEvents // .flatMap { it -> it.key} - progressEvents.keys.map{ it -> { if (true) {null }else { it}}} - // val a1 = progressEvents.map{ struct -> struct.key. { if ( true) { null } else {struct }} } - // val a: ObservableList = null!! -// val x: ObservableList = ObservableList(); - // val a3: ObservableList = listOf(Pair(1,"hey")) - // val x = a.map{ struct -> struct. { if ( it.javaClass == StateMachineStatus::Removed) { null} else {struct }} } - // val b = a.map { struct -> struct.someInt.map { if (it.javaClass == StateMachineStatus::Removed) { null } else { struct } } } - // val c = b.flatten().filterNotNull() - //return c - } - -} \ No newline at end of file diff --git a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt index 3acf3eee19..190f2456a0 100644 --- a/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt +++ b/core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt @@ -30,7 +30,6 @@ import java.util.* @CordaSerializable data class StateMachineInfo( val id: StateMachineRunId, - val sessionId: Long, val flowLogicClassName: String, val initiator: FlowInitiator, val progressTrackerStepAndUpdates: Pair>? diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt index a4a806344b..cd480be67d 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/Main.kt @@ -131,7 +131,7 @@ class Main : App(MainView::class) { // Stock Views. registerView() registerView() - registerView() + registerView() // CordApps Views. registerView() // Tools. diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/formatters/FlowInitiatorFormatter.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/formatters/FlowInitiatorFormatter.kt new file mode 100644 index 0000000000..ef156d1bc5 --- /dev/null +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/formatters/FlowInitiatorFormatter.kt @@ -0,0 +1,14 @@ +package net.corda.explorer.formatters + +import net.corda.core.flows.FlowInitiator + +object FlowInitiatorFormatter : Formatter { + override fun format(value: FlowInitiator): String { + return when (value) { + is FlowInitiator.Scheduled -> "Started by scheduled state:: " + value.scheduledState.ref.toString() // TODO format that + is FlowInitiator.Shell -> "Started via shell" + is FlowInitiator.Peer -> "Peer legal name: " + value.party.name //TODO format that + is FlowInitiator.RPC -> "Rpc username: " + value.username + } + } +} diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/formatters/FlowNameFormatter.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/formatters/FlowNameFormatter.kt new file mode 100644 index 0000000000..cb45988186 --- /dev/null +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/formatters/FlowNameFormatter.kt @@ -0,0 +1,7 @@ +package net.corda.explorer.formatters + +object FlowNameFormatter { + val boring = object : Formatter { + override fun format(value: String) = value.split('.').last().replace("$", ": ") // TODO Better handling of names. + } +} \ No newline at end of file diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/StateMachineViewer.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/StateMachineViewer.kt index 7d3c4e815d..a9870c1877 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/StateMachineViewer.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/StateMachineViewer.kt @@ -1,43 +1,55 @@ package net.corda.explorer.views import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon +import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView import javafx.beans.binding.Bindings -import javafx.beans.property.ReadOnlyObjectWrapper +import javafx.collections.ObservableList +import javafx.geometry.HPos +import javafx.geometry.Insets import javafx.geometry.Pos -import javafx.scene.control.TableColumn +import javafx.scene.Parent +import javafx.scene.control.Label +import javafx.scene.control.ScrollPane +import javafx.scene.control.TabPane import javafx.scene.control.TableView +import javafx.scene.control.TitledPane import javafx.scene.layout.BorderPane +import javafx.scene.layout.GridPane +import javafx.scene.layout.VBox +import javafx.scene.text.FontWeight +import javafx.scene.text.TextAlignment +import net.corda.client.jfx.model.StateMachineData import net.corda.client.jfx.model.StateMachineDataModel +import net.corda.client.jfx.model.StateMachineStatus +import net.corda.client.jfx.model.observableList import net.corda.client.jfx.model.observableListReadOnly import net.corda.client.jfx.utils.map -//import net.corda.client.fxutils.map -//import net.corda.client.model.StateMachineDataModel -//import net.corda.client.model.observableListReadOnly import net.corda.core.crypto.SecureHash +import net.corda.core.crypto.toBase58String +import net.corda.core.flows.FlowInitiator +import net.corda.core.transactions.SignedTransaction +import net.corda.explorer.formatters.FlowInitiatorFormatter +import net.corda.explorer.formatters.FlowNameFormatter import net.corda.explorer.identicon.identicon import net.corda.explorer.identicon.identiconToolTip import net.corda.explorer.model.CordaView import net.corda.explorer.model.CordaWidget import net.corda.explorer.ui.setCustomCellFactory -import tornadofx.column -import tornadofx.label -import tornadofx.observable -import tornadofx.right +import tornadofx.* - -class FlowViewer : CordaView("Flow Triage") { - override val root by fxml() +// TODO Rethink whole idea of showing communication as table, it should be tree view for each StateMachine (with subflows and other communication) +class StateMachineViewer : CordaView("Flow Triage") { + override val root by fxml() override val icon = FontAwesomeIcon.HEARTBEAT - override val widgets = listOf(CordaWidget(title, FlowViewer.StateMachineWidget())).observable() + override val widgets = listOf(CordaWidget(title, StateMachineViewer.StateMachineWidget())).observable() + private val progressViewTable by fxid>() + private val doneViewTable by fxid>() + private val errorViewTable by fxid>() - private val flowViewTable by fxid>() - private val flowColumnSessionId by fxid>() - private val flowColumnInternalId by fxid>() - private val flowColumnState by fxid>() - - private class StateMachineWidget() : BorderPane() { - private val flows by observableListReadOnly(StateMachineDataModel::flowsInProgress) + private class StateMachineWidget : BorderPane() { + private val flows by observableListReadOnly(StateMachineDataModel::stateMachinesInProgress) + // TODO can add stats: in progress, errored, maybe done to the widget? init { right { label { @@ -48,284 +60,276 @@ class FlowViewer : CordaView("Flow Triage") { } } - data class Flow(val id: String, val latestProgress: String) + private val stateMachinesInProgress by observableList(StateMachineDataModel::stateMachinesInProgress) + private val stateMachinesFinished by observableList(StateMachineDataModel::stateMachinesFinished) + private val stateMachinesError by observableList(StateMachineDataModel::stateMachinesError) - private val stateMachines by observableListReadOnly(StateMachineDataModel::stateMachineDataList) - - // private val flows = stateMachines.map { it -> Flow(it.id.toString(), it.flowStatus.map { it.toString() }) }.filtered { ! it.latestProgress.value.contains("Done") } - - private val flows = stateMachines.map { it -> Flow(it.id.toString(), it.flowStatus.toString()) }.filtered { - println("--> $it") - println("Status:${it.latestProgress}") - //it.id.startsWith("[3") or it.id.startsWith("[9") - println(it.latestProgress) - println(it.latestProgress.contains("status=Done")) - !it.latestProgress.contains("status=Done") && it.latestProgress != "null" - - //it.latestProgress != null - } - - init { - flowViewTable.apply { - items = flows - column("ID", Flow::id) { maxWidth = 200.0 } - .setCustomCellFactory { - label("$it") { - val hash = SecureHash.Companion.randomSHA256() - graphic = identicon(hash, 15.0) - tooltip = identiconToolTip(hash) - } - } - } - - flowColumnSessionId.apply { - setCellValueFactory { - ReadOnlyObjectWrapper(0) + fun makeColumns(table: TableView, tableItems: ObservableList, withResult: Boolean = true) { + table.apply { + items = tableItems + if (withResult) { + rowExpander(expandOnDoubleClick = true) { + add(StateMachineDetailsView(it).root) + }.apply { + // Column stays the same size, but we don't violate column restricted resize policy for the whole table view. + minWidth = 26.0 + maxWidth = 26.0 + } } - } - - flowColumnInternalId.setCellValueFactory { - ReadOnlyObjectWrapper(it.value.id) - } - - flowColumnState.setCellValueFactory { - ReadOnlyObjectWrapper(it.value.latestProgress) -// it.value.latestProgress - } - - - } -} -/* -class StateMachineViewer2 : CordaView("Transactions") { - override val root by fxml() - override val icon = FontAwesomeIcon.RANDOM - - private val transactionViewTable by fxid>() - private val matchingTransactionsLabel by fxid