From 608550c9209057fa6fe4c3ff962973f53f703ea2 Mon Sep 17 00:00:00 2001 From: Katarzyna Streich Date: Thu, 11 May 2017 19:04:17 +0100 Subject: [PATCH] Change tabs to search field in state machine view. Some carving of UI. --- .../client/jfx/model/NodeMonitorModel.kt | 5 +- .../client/jfx/model/StateMachineDataModel.kt | 38 +++--- .../client/jfx/model/TransactionDataModel.kt | 1 - .../net/corda/client/mock/EventGenerator.kt | 4 +- .../kotlin/net/corda/core/flows/FlowLogic.kt | 2 +- .../net/corda/explorer/ExplorerSimulation.kt | 5 +- .../formatters/FlowInitiatorFormatter.kt | 4 +- .../explorer/identicon/IdenticonRenderer.kt | 4 +- .../net/corda/explorer/views/SearchField.kt | 7 +- .../explorer/views/StateMachineViewer.kt | 109 +++++++++--------- .../corda/explorer/views/TransactionViewer.kt | 5 +- .../views/cordapps/cash/CashViewer.kt | 4 +- .../net/corda/explorer/css/corda.css | 9 +- .../views/StateMachineDetailsView.fxml | 53 +++++---- .../explorer/views/StateMachineViewer.fxml | 38 ++---- 15 files changed, 141 insertions(+), 147 deletions(-) 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 8656f81ed2..df54451540 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 @@ -4,7 +4,6 @@ import com.google.common.net.HostAndPort import javafx.beans.property.SimpleObjectProperty 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.StateMachineUpdate import net.corda.core.node.services.NetworkMapCache.MapChange @@ -63,7 +62,9 @@ class NodeMonitorModel { Observable.empty() } } - futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.subscribe(progressTrackingSubject) + + // We need to retry, because when flow errors, we unsubscribe from progressTrackingSubject. So we end up with stream of state machine updates and no progress trackers. + futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.retry().subscribe(progressTrackingSubject) // Now the state machines val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) } 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 index 4c83be7934..b473a4a5a8 100644 --- 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 @@ -1,20 +1,18 @@ 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 org.fxmisc.easybind.EasyBind import rx.Observable data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String) { @@ -28,20 +26,19 @@ data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val mess } } -data class ProgressStatus(val status: String) +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 + val stateMachineStatus: ObservableValue ) class StateMachineDataModel { @@ -65,22 +62,17 @@ class StateMachineDataModel { } } - 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() + private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress -> + val smStatus = status.value as StateMachineStatus.Added + StateMachineData(id, smStatus.stateMachineName, smStatus.flowInitiator, status, + EasyBind.map(progress) { + if (it == null) { + ProgressStatus(null) + } else { + ProgressStatus(it.message) + } + }) + }.getObservableValues() - 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 - } + val stateMachinesAll = stateMachineDataList } 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 934758e2d9..62d6da5dea 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 @@ -54,7 +54,6 @@ data class PartiallyResolvedTransaction( } } -// TODO Do we want to have mapping tx <-> StateMachine? /** * This model provides an observable list of transactions and what state machines/flows recorded them */ diff --git a/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt b/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt index 29892cb6b9..980c5e0951 100644 --- a/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt +++ b/client/mock/src/main/kotlin/net/corda/client/mock/EventGenerator.kt @@ -18,7 +18,7 @@ open class EventGenerator(val parties: List, val currencies: List OpaqueBytes(ByteArray(1, { number.toByte() })) } protected val amountGenerator = Generator.longRange(10000, 1000000) protected val currencyGenerator = Generator.pickOne(currencies) - protected val currencyMap: MutableMap = mutableMapOf(USD to 0L, GBP to 0L) // Used for rough estimation of how much money we have in general. + protected val currencyMap: MutableMap = mutableMapOf(USD to 0L, GBP to 0L) // Used for estimation of how much money we have in general. protected fun addToMap(ccy: Currency, amount: Long) { val value = currencyMap[ccy] @@ -47,7 +47,7 @@ open class EventGenerator(val parties: List, val currencies: List, currencies: List, notary: Party): EventGenerator(parties, currencies, notary) { diff --git a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt index 1fe15cd1f7..0530a4fa6b 100644 --- a/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt +++ b/core/src/main/kotlin/net/corda/core/flows/FlowLogic.kt @@ -217,7 +217,7 @@ abstract class FlowLogic { fun track(): Pair>? { // TODO this is not threadsafe, needs an atomic get-step-and-subscribe return progressTracker?.let { - it.currentStep.toString() to it.changes.map { it.toString() } + it.currentStep.label to it.changes.map { it.toString() } } } diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt index 1a7c1608d2..4891ea021f 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/ExplorerSimulation.kt @@ -5,14 +5,13 @@ import net.corda.client.mock.ErrorFlowsEventGenerator import net.corda.client.mock.EventGenerator import net.corda.client.mock.Generator import net.corda.client.mock.pickOne -import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCConnection import net.corda.contracts.asset.Cash import net.corda.core.contracts.Amount import net.corda.core.contracts.GBP import net.corda.core.contracts.USD -import net.corda.core.crypto.Party import net.corda.core.failure +import net.corda.core.identity.Party import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.FlowHandle import net.corda.core.node.services.ServiceInfo @@ -154,7 +153,7 @@ class ExplorerSimulation(val options: OptionSet) { is CashFlowCommand.IssueCash -> issuers[command.amount.token]?.let { println("${Instant.now()} [$i] ISSUING ${command.amount} with ref ${command.issueRef} to ${command.recipient}") command.startFlow(it).log(i, "${command.amount.token}Issuer") - } ?: command.startFlow(issuers[USD]!!).log(i, "${command.amount.token}Issuer") // TODO workaround + } is CashFlowCommand.ExitCash -> issuers[command.amount.token]?.let { println("${Instant.now()} [$i] EXITING ${command.amount} with ref ${command.issueRef}") command.startFlow(it).log(i, "${command.amount.token}Exit") 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 index ef156d1bc5..d678ca1ec8 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/formatters/FlowInitiatorFormatter.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/formatters/FlowInitiatorFormatter.kt @@ -5,9 +5,9 @@ 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.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: " + value.party.name //TODO format that + is FlowInitiator.Peer -> "Peer legal name: " + PartyNameFormatter.short.format(value.party.name) is FlowInitiator.RPC -> "Rpc username: " + value.username } } diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/identicon/IdenticonRenderer.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/identicon/IdenticonRenderer.kt index d7162986d5..11d20caa96 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/identicon/IdenticonRenderer.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/identicon/IdenticonRenderer.kt @@ -198,8 +198,8 @@ fun identicon(secureHash: SecureHash, size: Double): ImageView { } } -fun identiconToolTip(secureHash: SecureHash): Tooltip { - return Tooltip(Splitter.fixedLength(16).split("$secureHash").joinToString("\n")).apply { +fun identiconToolTip(secureHash: SecureHash, description: String? = null): Tooltip { + return Tooltip(Splitter.fixedLength(16).split("${description ?: secureHash}").joinToString("\n")).apply { contentDisplay = ContentDisplay.TOP textAlignment = TextAlignment.CENTER graphic = identicon(secureHash, 90.0) diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/SearchField.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/SearchField.kt index 2feeb8a938..5fc5d67b20 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/SearchField.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/SearchField.kt @@ -23,7 +23,8 @@ import tornadofx.* * TODO : Predictive text? * TODO : Regex? */ -class SearchField(private val data: ObservableList, vararg filterCriteria: Pair Boolean>) : UIComponent() { +class SearchField(private val data: ObservableList, val filterCriteria: List Boolean>>, + val disabledFields: List = emptyList()) : UIComponent() { override val root: Parent by fxml() private val textField by fxid() private val clearButton by fxid() @@ -34,7 +35,7 @@ class SearchField(private val data: ObservableList, vararg filterCriteria: val text = textField.text val category = searchCategory.value data.filtered { data -> - text.isNullOrBlank() || if (category == ALL) { + (text.isNullOrBlank() && textField.isVisible) || if (category == ALL) { filterCriteria.any { it.second(data, text) } } else { filterCriteria.toMap()[category]?.invoke(data, text) ?: false @@ -73,5 +74,7 @@ class SearchField(private val data: ObservableList, vararg filterCriteria: } "Filter by $category." }) + textField.visibleProperty().bind(searchCategory.valueProperty().map { it !in disabledFields }) + // TODO Maybe it will be better to replace these categories with comboBox? For example Result with choice: succes, in progress, error. } } \ 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 5faa561694..4b3a483b25 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 @@ -5,14 +5,10 @@ import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView import javafx.beans.binding.Bindings import javafx.collections.ObservableList import javafx.geometry.HPos -import javafx.geometry.Insets import javafx.geometry.Pos 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 @@ -37,33 +33,29 @@ import net.corda.explorer.model.CordaWidget import net.corda.explorer.ui.setCustomCellFactory import tornadofx.* -// TODO Rethink whole idea of showing communication as table, it should be tree view for each StateMachine (with subflows and other communication) +// TODO Rethink whole idea of showing communication as table, it should be tree view for each StateMachine (with subflows). class StateMachineViewer : CordaView("Flow Triage") { - override val root by fxml() + override val root by fxml() override val icon = FontAwesomeIcon.HEARTBEAT 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 allViewTable by fxid>() + private val matchingFlowsLabel by fxid