You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by jo...@apache.org on 2021/11/22 19:34:58 UTC

[isis] 03/06: ISIS-2348 Pull up refactoring/Command; EventCompareDialog added

This is an automated email from the ASF dual-hosted git repository.

joergrade pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git

commit 56a5e167ad4545625003cb0e58267f1e4800a057
Author: Jörg Rade <jo...@kuehne-nagel.com>
AuthorDate: Sun Nov 21 14:06:02 2021 +0100

    ISIS-2348 Pull up refactoring/Command; EventCompareDialog added
---
 .../isis/client/kroviz/core/event/LogEntry.kt      |  1 -
 .../client/kroviz/core/event/LogEntryComparison.kt | 67 ++++++++++++++++++++++
 .../isis/client/kroviz/core/event/ReplayCommand.kt |  2 +-
 .../isis/client/kroviz/ui/core/ColumnFactory.kt    |  1 -
 .../apache/isis/client/kroviz/ui/dialog/About.kt   |  6 --
 .../isis/client/kroviz/ui/dialog/ActionPrompt.kt   | 10 ++--
 .../apache/isis/client/kroviz/ui/dialog/Command.kt |  4 +-
 .../isis/client/kroviz/ui/dialog/DiagramDialog.kt  |  3 +-
 .../isis/client/kroviz/ui/dialog/DownloadDialog.kt |  5 +-
 .../client/kroviz/ui/dialog/EventCompareDialog.kt  | 56 ++++++++++++++++++
 .../isis/client/kroviz/ui/dialog/EventDialog.kt    |  9 +--
 .../client/kroviz/ui/dialog/EventExportDialog.kt   | 18 +++---
 .../client/kroviz/ui/dialog/EventImportDialog.kt   | 18 +++---
 .../isis/client/kroviz/ui/dialog/EventLogDetail.kt |  7 +--
 .../isis/client/kroviz/ui/dialog/LoginPrompt.kt    | 10 ++--
 .../client/kroviz/ui/dialog/NotificationDialog.kt  |  1 -
 .../client/kroviz/ui/dialog/ReplayDiffDialog.kt    | 36 +++++++-----
 .../isis/client/kroviz/ui/dialog/SvgInline.kt      |  6 --
 .../client/kroviz/ui/dialog/UndefinedDialog.kt     |  2 +-
 19 files changed, 182 insertions(+), 80 deletions(-)

diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt
index 6e6ebc1..e83d0e9 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntry.kt
@@ -47,7 +47,6 @@ enum class EventState(val id: String, val iconName: String, val style: ButtonSty
     CLOSED("CLOSED", "fas fa-eye-slash", ButtonStyle.OUTLINEINFO),
     RELOAD("RELOAD", "fas fa-retweet", ButtonStyle.OUTLINEWARNING),
     MISSING("MISSING", "fas fa-bug", ButtonStyle.OUTLINEDANGER),
-    DIFF("DIFF", "fas fa-bell", ButtonStyle.DANGER),
     // IMPROVE multiple aspects intermangled: req/resp, view, as well as cache
     // encapsulate access with managers?
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryComparison.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryComparison.kt
new file mode 100644
index 0000000..5274845
--- /dev/null
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/LogEntryComparison.kt
@@ -0,0 +1,67 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.isis.client.kroviz.core.event
+
+import io.kvision.html.ButtonStyle
+
+enum class ChangeType(val id: String, val iconName: String, val style: ButtonStyle) {
+    ADDED("ADDED", "fas fa-plus", ButtonStyle.INFO),
+    MISSING("MISSING", "fas fa-minus", ButtonStyle.DANGER),
+    DIFF("DIFF", "fas fa-bell", ButtonStyle.WARNING),
+    INFO("INFO", "fas fa-info-circle", ButtonStyle.OUTLINEINFO),
+    ERROR("ERROR", "fas fa-bug", ButtonStyle.OUTLINEDANGER),
+}
+
+class LogEntryComparison(val expected: LogEntry?, val actual: LogEntry?) {
+    var changeType: ChangeType
+    var iconName: String
+    var expectedResponse: String?
+    var actualResponse: String?
+
+    init {
+        if (expected == null && actual == null) {
+            throw Throwable("[LogEntryComparison.init] actual and expected not set")
+        } else {
+            changeType = compare()
+            iconName = changeType.iconName
+            expectedResponse = expected?.response
+            actualResponse = actual?.response
+        }
+    }
+
+    private fun compare(): ChangeType {
+        val responsesAreEqual = areResponsesEqual()
+        return when {
+            expected == null -> ChangeType.ADDED
+            actual == null -> ChangeType.MISSING
+            responsesAreEqual -> ChangeType.INFO
+            !responsesAreEqual -> ChangeType.DIFF
+            else -> ChangeType.ERROR
+        }
+    }
+
+    private fun areResponsesEqual(): Boolean {
+        val expectedBaseUrl: String = ""
+        val actualBaseUrl: String = "" //FIXME
+        val expected = expectedResponse?.replace(expectedBaseUrl, "")
+        val actual = actualResponse?.replace(actualBaseUrl, "")
+        return (expected == actual)
+    }
+
+}
\ No newline at end of file
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ReplayCommand.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ReplayCommand.kt
index 1a46437..4df6d24 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ReplayCommand.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/ReplayCommand.kt
@@ -56,7 +56,7 @@ class ReplayCommand : Command() {
         val uiEvents = filterReplayEvents(expectedEvents)
         replay(uiEvents, urlUnderTest)
 
-        val title = "Replay Events: " + oldBaseUrl + " -> " + urlUnderTest
+        val title = "Replay Events: $oldBaseUrl -> $urlUnderTest"
         val rdd = ReplayDiffDialog(expectedEvents, title)
         rdd.dialog.open()
     }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/ColumnFactory.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/ColumnFactory.kt
index 0c18bf5..f0349a9 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/ColumnFactory.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/ColumnFactory.kt
@@ -41,7 +41,6 @@ class ColumnFactory {
     }
 
     fun buildColumns(displayCollection: CollectionDM): List<ColumnDefinition<dynamic>> {
-
         val columns = mutableListOf<ColumnDefinition<Exposer>>()
         addColumnForObjectIcon(displayCollection, columns)
         addColumnsForProperties(displayCollection, columns)
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/About.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/About.kt
index 2885f91..7f8bc70 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/About.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/About.kt
@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 package org.apache.isis.client.kroviz.ui.dialog
 
 import org.apache.isis.client.kroviz.to.ValueType
@@ -25,14 +24,9 @@ import org.apache.isis.client.kroviz.ui.core.RoDialog
 import org.apache.isis.client.kroviz.utils.UUID
 
 class About : Command() {
-    var dialog: RoDialog
     private val formItems = mutableListOf<FormItem>()
     private val _LI = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
 
-    override fun open()  {
-        dialog.open()
-    }
-
     init {
         // see: https://upload.wikimedia.org/wikipedia/commons/6/6c/Trajans-Column-lower-animated.svg
         val fiImg = FormItem(
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ActionPrompt.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ActionPrompt.kt
index aafb462..5d8fbd5 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ActionPrompt.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ActionPrompt.kt
@@ -32,15 +32,13 @@ import org.apache.isis.client.kroviz.ui.core.RoDialog
 
 class ActionPrompt(val action: Action) : Command() {
 
-    private lateinit var form: RoDialog
-
     fun open(at: Point) {
         val formItems = buildFormItems()
-        form = RoDialog(
+        dialog = RoDialog(
                 caption = buildLabel(),
                 items = formItems,
                 command = this)
-        form.open(at)
+        dialog.open(at)
     }
 
     override fun execute(action:String?) {
@@ -81,11 +79,11 @@ class ActionPrompt(val action: Action) : Command() {
     }
 
     private fun extractUserInput(): Link {
-        //IMPROVE function has a side effect, ie. amends link with arguments
+        //IMPROVE function has a side effect, i.e. amends link with arguments
         val link = action.getInvokeLink()!!
         var value: String? = null
         var key: String? = null
-        val formPanel = form.formPanel
+        val formPanel = dialog.formPanel
         val kids = formPanel!!.getChildren()
         //iterate over FormItems (0,1) but not Buttons(2,3)
         for (i in kids) {
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/Command.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/Command.kt
index 0daaad9..168c8dc 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/Command.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/Command.kt
@@ -21,15 +21,17 @@ package org.apache.isis.client.kroviz.ui.dialog
 import org.apache.isis.client.kroviz.core.aggregator.ActionDispatcher
 import org.apache.isis.client.kroviz.core.event.ResourceProxy
 import org.apache.isis.client.kroviz.to.Link
+import org.apache.isis.client.kroviz.ui.core.RoDialog
 
 abstract class Command {
+    lateinit var dialog: RoDialog
 
     open fun execute(action: String? = null) {
         // subclass responsibility
     }
 
     open fun open() {
-        // subclass responsibility
+        dialog.open()
     }
 
     fun invoke(link: Link) {
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DiagramDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DiagramDialog.kt
index 623994d..7252338 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DiagramDialog.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DiagramDialog.kt
@@ -32,11 +32,10 @@ class DiagramDialog(
 ) : Command() {
 
     private var callBack: Any = UUID()
-    private var dialog: RoDialog
     private val formItems = mutableListOf<FormItem>()
 
     override fun open() {
-        dialog.open()
+        super.open()
         UmlUtils.generateJsonDiagram(pumlCode, callBack)
     }
 
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DownloadDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DownloadDialog.kt
index 043fb92..1d824b0 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DownloadDialog.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/DownloadDialog.kt
@@ -26,13 +26,12 @@ import org.apache.isis.client.kroviz.utils.DomUtil
 
 class DownloadDialog(val fileName:String, val content:String) : Command() {
 
-    private lateinit var form: RoDialog
     val formItems = mutableListOf<FormItem>()
 
     override fun open() {
         formItems.add(FormItem("Preview", ValueType.TEXT_AREA, content, 15))
-        form = RoDialog(caption = "Download: $fileName", items = formItems, command = this)
-        form.open()
+        dialog = RoDialog(caption = "Download: $fileName", items = formItems, command = this)
+        dialog.open()
     }
 
     override fun execute(action:String?) {
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventCompareDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventCompareDialog.kt
new file mode 100644
index 0000000..d5804f5
--- /dev/null
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventCompareDialog.kt
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.isis.client.kroviz.ui.dialog
+
+import io.kvision.core.CssSize
+import io.kvision.core.UNIT
+import io.kvision.panel.VPanel
+import org.apache.isis.client.kroviz.core.event.LogEntryComparison
+import org.apache.isis.client.kroviz.ui.core.RoDialog
+import org.apache.isis.client.kroviz.ui.core.UiManager
+import org.apache.isis.client.kroviz.ui.panel.EventLogTable
+
+class EventCompareDialog(val data: List<LogEntryComparison>) : Command() {
+
+    private val panel = VPanel(spacing = 3) {
+        width = CssSize(100, UNIT.perc)
+    }
+
+    init {
+        dialog = RoDialog(
+            caption = "Event Comparison",
+            items = mutableListOf(),
+            command = this,
+            defaultAction = "Pin",
+            widthPerc = 60,
+            heightPerc = 70,
+        )
+        //FIXME -> reuse -> ColumnFactory and RoTable if possible
+        val table = EventLogTable(UiManager.getEventStore().log)
+        table.tabulator.addCssClass("tabulator-in-dialog")
+        panel.add(table)
+
+        dialog.formPanel!!.add(panel)
+    }
+
+    override fun execute(action: String?) {
+    }
+
+}
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventDialog.kt
index 887d78c..49b172b 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventDialog.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventDialog.kt
@@ -30,8 +30,7 @@ import org.apache.isis.client.kroviz.ui.core.RoDialog
 import org.apache.isis.client.kroviz.ui.core.UiManager
 import org.apache.isis.client.kroviz.ui.panel.EventLogTable
 
-class EventDialog() : Command() {
-    var dialog: RoDialog
+class EventDialog : Command() {
 
     private val eventPanel = VPanel(spacing = 3) {
         width = CssSize(100, UNIT.perc)
@@ -84,7 +83,7 @@ class EventDialog() : Command() {
                 dialog.close()
             }
             action == REP -> {
-                LoginPrompt(ReplayCommand()).open()
+                LoginPrompt(nextCommand = ReplayCommand()).open()
                 dialog.close()
             }
             else -> {
@@ -92,8 +91,4 @@ class EventDialog() : Command() {
         }
     }
 
-    override fun open() {
-        dialog.open()
-    }
-
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventExportDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventExportDialog.kt
index 365fdaf..8eda5b1 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventExportDialog.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventExportDialog.kt
@@ -28,15 +28,14 @@ import io.kvision.form.select.SimpleSelect
 import org.apache.isis.client.kroviz.ui.core.FormItem
 import org.apache.isis.client.kroviz.ui.core.UiManager
 
-class EventExportDialog() : Command() {
+class EventExportDialog : Command() {
 
-    private lateinit var form: RoDialog
     private var output: String = ""
     val formItems = mutableListOf<FormItem>()
     val events = mutableListOf<ReplayEvent>()
 
     private fun collectReplayEvents() {
-        UiManager.getEventStore().log.forEach { it ->
+        UiManager.getEventStore().log.forEach {
             val re = buildExportEvent(it)
             when (it.state) {
                 EventState.SUCCESS_JS -> events.add(re)
@@ -67,8 +66,7 @@ class EventExportDialog() : Command() {
             else -> {
             }
         }
-        val format = extractUserInput("Format")
-        when (format) {
+        when (extractUserInput("Format")) {
             "CSV" -> {
                 output = asCsv(events)
                 fileName += ".csv"
@@ -84,7 +82,7 @@ class EventExportDialog() : Command() {
     }
 
     private fun extractUserInput(fieldName: String): String? {
-        val formPanel = form.formPanel
+        val formPanel = dialog.formPanel
         val kids = formPanel!!.getChildren()
         //iterate over FormItems (0,1) but not Buttons(2,3)
         for (i in kids) {
@@ -102,7 +100,7 @@ class EventExportDialog() : Command() {
     }
 
     private fun collectUnfinishedEvents() {
-        UiManager.getEventStore().log.forEach { it ->
+        UiManager.getEventStore().log.forEach {
             val re = buildExportEvent(it)
             when (it.state) {
                 EventState.RUNNING -> events.add(re)
@@ -114,7 +112,7 @@ class EventExportDialog() : Command() {
     }
 
     private fun collectAllEvents() {
-        UiManager.getEventStore().log.forEach { it ->
+        UiManager.getEventStore().log.forEach {
             val re = buildExportEvent(it)
             events.add(re)
         }
@@ -131,8 +129,8 @@ class EventExportDialog() : Command() {
         filter.add(StringPair("UNFINISHED", "Unfinished"))
         formItems.add(FormItem("Filter", ValueType.SIMPLE_SELECT, filter))
 
-        form = RoDialog(caption = "Export", items = formItems, command = this)
-        form.open()
+        dialog = RoDialog(caption = "Export", items = formItems, command = this)
+        dialog.open()
     }
 
     private fun asCsv(events: MutableList<ReplayEvent>): String {
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventImportDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventImportDialog.kt
index 0ae1366..b41085e 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventImportDialog.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventImportDialog.kt
@@ -28,15 +28,14 @@ import io.kvision.form.select.SimpleSelect
 import org.apache.isis.client.kroviz.ui.core.FormItem
 import org.apache.isis.client.kroviz.ui.core.UiManager
 
-class EventImportDialog() : Command() {
+class EventImportDialog : Command() {
 
-    private lateinit var form: RoDialog
     private var output: String = ""
     val formItems = mutableListOf<FormItem>()
     val events = mutableListOf<ReplayEvent>()
 
     private fun collectReplayEvents() {
-        UiManager.getEventStore().log.forEach { it ->
+        UiManager.getEventStore().log.forEach {
             val re = buildExportEvent(it)
             when (it.state) {
                 EventState.SUCCESS_JS -> events.add(re)
@@ -67,8 +66,7 @@ class EventImportDialog() : Command() {
             else -> {
             }
         }
-        val format = extractUserInput("Format")
-        when (format) {
+        when (extractUserInput("Format")) {
             "CSV" -> {
                 output = asCsv(events)
                 fileName += ".csv"
@@ -84,7 +82,7 @@ class EventImportDialog() : Command() {
     }
 
     private fun extractUserInput(fieldName: String): String? {
-        val formPanel = form.formPanel
+        val formPanel = dialog.formPanel
         val kids = formPanel!!.getChildren()
         //iterate over FormItems (0,1) but not Buttons(2,3)
         for (i in kids) {
@@ -102,7 +100,7 @@ class EventImportDialog() : Command() {
     }
 
     private fun collectUnfinishedEvents() {
-        UiManager.getEventStore().log.forEach { it ->
+        UiManager.getEventStore().log.forEach {
             val re = buildExportEvent(it)
             when (it.state) {
                 EventState.RUNNING -> events.add(re)
@@ -114,7 +112,7 @@ class EventImportDialog() : Command() {
     }
 
     private fun collectAllEvents() {
-        UiManager.getEventStore().log.forEach { it ->
+        UiManager.getEventStore().log.forEach {
             val re = buildExportEvent(it)
             events.add(re)
         }
@@ -131,8 +129,8 @@ class EventImportDialog() : Command() {
         filter.add(StringPair("UNFINISHED", "Unfinished"))
         formItems.add(FormItem("Filter", ValueType.SIMPLE_SELECT, filter))
 
-        form = RoDialog(caption = "Export", items = formItems, command = this)
-        form.open()
+        dialog = RoDialog(caption = "Export", items = formItems, command = this)
+        dialog.open()
     }
 
     private fun asCsv(events: MutableList<ReplayEvent>): String {
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventLogDetail.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventLogDetail.kt
index 8002a38..676c074 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventLogDetail.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventLogDetail.kt
@@ -33,13 +33,12 @@ import org.apache.isis.client.kroviz.utils.Flatted
 import org.apache.isis.client.kroviz.utils.StringUtils
 import org.apache.isis.client.kroviz.utils.XmlHelper
 
-class EventLogDetail(val logEntryFromTabulator: LogEntry) : Command() {
+class EventLogDetail(logEntryFromTabulator: LogEntry) : Command() {
     private var logEntry: LogEntry
-    private lateinit var dialog: RoDialog
 
     init {
         // For a yet unknown reason, aggregators are not transmitted via tabulator.
-        // As a WORKAROUND, we fetch the full blown LogEntry from the EventStore again.
+        // As a WORKAROUND, we fetch the full-blown LogEntry from the EventStore again.
         val rs = ResourceSpecification(logEntryFromTabulator.title)
         logEntry = UiManager.getEventStore().findBy(rs) ?: logEntryFromTabulator  // in case of xml, we use the entry passed in
     }
@@ -90,7 +89,7 @@ class EventLogDetail(val logEntryFromTabulator: LogEntry) : Command() {
             }
             else -> {
                 console.log(logEntry)
-                console.log("Action not defined yet: " + action)
+                console.log("Action not defined yet: $action")
             }
         }
     }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/LoginPrompt.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/LoginPrompt.kt
index b55e5f8..081aa52 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/LoginPrompt.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/LoginPrompt.kt
@@ -32,8 +32,6 @@ import org.apache.isis.client.kroviz.ui.core.UiManager
 
 class LoginPrompt(val nextCommand: Command? = null) : Command() {
 
-    private lateinit var form: RoDialog
-
     //Default values
     private var url = Constants.demoUrl
     private var username = Constants.demoUser
@@ -47,9 +45,9 @@ class LoginPrompt(val nextCommand: Command? = null) : Command() {
         formItems.add(FormItem("Url", ValueType.SIMPLE_SELECT, urlList))
         formItems.add(FormItem("User", ValueType.TEXT, username))
         formItems.add(FormItem("Password", ValueType.PASSWORD, password))
-        form = RoDialog(caption = "Connect", items = formItems, command = this, heightPerc = 27)
+        dialog = RoDialog(caption = "Connect", items = formItems, command = this, heightPerc = 27)
         val at = UiManager.position!!
-        form.open(at)
+        dialog.open(at)
     }
 
     override fun execute(action: String?) {
@@ -61,14 +59,14 @@ class LoginPrompt(val nextCommand: Command? = null) : Command() {
             UiManager.login(url, username, password)
             val link = Link(href = url + Constants.restInfix)
             invoke(link)
-            UiManager.closeDialog(form)
+            UiManager.closeDialog(dialog)
         }
     }
 
     private fun extractUserInput() {
         //TODO function has a side effect, ie. changes variable values
         var key: String?
-        val formPanel = form.formPanel
+        val formPanel = dialog.formPanel
         val kids = formPanel!!.getChildren()
         //iterate over FormItems (0,1,2) but not Buttons(3,4)
         for (i in kids) {
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/NotificationDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/NotificationDialog.kt
index bde8185..b03f86f 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/NotificationDialog.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/NotificationDialog.kt
@@ -21,7 +21,6 @@ package org.apache.isis.client.kroviz.ui.dialog
 import org.apache.isis.client.kroviz.to.ValueType
 import org.apache.isis.client.kroviz.ui.core.FormItem
 import org.apache.isis.client.kroviz.ui.core.RoDialog
-import org.apache.isis.client.kroviz.ui.core.RoStatusBar
 import org.apache.isis.client.kroviz.ui.core.UiManager
 
 class NotificationDialog(val message: String) : Command() {
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ReplayDiffDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ReplayDiffDialog.kt
index 55d3a85..c5d0a68 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ReplayDiffDialog.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ReplayDiffDialog.kt
@@ -25,8 +25,10 @@ import io.kvision.core.UNIT
 import io.kvision.panel.Direction
 import io.kvision.panel.SplitPanel
 import io.kvision.panel.VPanel
-import org.apache.isis.client.kroviz.core.event.EventState
+import io.kvision.state.ObservableList
+import org.apache.isis.client.kroviz.core.event.EventStore
 import org.apache.isis.client.kroviz.core.event.LogEntry
+import org.apache.isis.client.kroviz.core.event.LogEntryComparison
 import org.apache.isis.client.kroviz.core.event.ResourceSpecification
 import org.apache.isis.client.kroviz.ui.core.RoDialog
 import org.apache.isis.client.kroviz.ui.core.UiManager
@@ -36,7 +38,6 @@ class ReplayDiffDialog(
     private val expectedEvents: List<LogEntry>,
     title: String
 ) : Command() {
-    var dialog: RoDialog
 
     private val expectedPanel = VPanel(spacing = 3) {
         width = CssSize(20, UNIT.perc)
@@ -73,19 +74,26 @@ class ReplayDiffDialog(
     }
 
     override fun execute(action: String?) {
-        // iterate over expected and set each status to DIFF, where responses don't match
-        expectedEvents.forEach { xp ->
-            val rs = ResourceSpecification(xp.url, xp.subType)
-            val actual = UiManager.getEventStore().findBy(rs)
-            if (actual != null) {
-                val isSame = actual.response == xp.response
-                if (!isSame) {
-                    xp.state = EventState.DIFF
-                }
-            } else {
-                xp.state = EventState.DIFF
-            }
+        val comparisons = mutableListOf<LogEntryComparison>()
+        // first pass: iterate over expected
+        val actualStore = UiManager.getEventStore()
+        expectedEvents.forEach {
+            val rs = ResourceSpecification(it.url, it.subType)
+            val actualEvent: LogEntry? = actualStore.findBy(rs)
+            comparisons.add(LogEntryComparison(it, actualEvent))
         }
+
+        // second pass: iterate over actual
+        val actualEvents = actualStore.log
+        val expectedStore = EventStore()
+        expectedStore.log = expectedEvents as ObservableList<LogEntry>
+        actualEvents.forEach {
+            val rs = ResourceSpecification(it.url, it.subType)
+            val expectedEvent = expectedStore.findBy(rs)
+            //TODO check for duplicates?
+            comparisons.add(LogEntryComparison(expectedEvent, it))
+        }
+        EventCompareDialog(comparisons).open()
     }
 
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/SvgInline.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/SvgInline.kt
index 1343af8..03f142c 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/SvgInline.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/SvgInline.kt
@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 package org.apache.isis.client.kroviz.ui.dialog
 
 import org.apache.isis.client.kroviz.core.aggregator.SvgDispatcher
@@ -29,13 +28,8 @@ import org.apache.isis.client.kroviz.ui.core.RoDialog
 import org.apache.isis.client.kroviz.utils.UUID
 
 class SvgInline : Command() {
-    private var dialog: RoDialog
     private val formItems = mutableListOf<FormItem>()
 
-    override fun open() {
-        dialog.open()
-    }
-
     init {
         val callBack = UUID()
         val fiImg = FormItem(
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/UndefinedDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/UndefinedDialog.kt
index f362aa2..7621fef 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/UndefinedDialog.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/UndefinedDialog.kt
@@ -25,7 +25,7 @@ import org.apache.isis.client.kroviz.ui.core.RoDialog
 
 class UndefinedDialog(val logEntry: LogEntry) : Command() {
 
-    val instruction = """1. Create a ResponseClass under test/kotlin/org.ro.urls with
+    private val instruction = """1. Create a ResponseClass under test/kotlin/org.ro.urls with
     - url 
     - str (JSON)
 2. Create a TestCase under test/kotlin/org.ro.to