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:35:00 UTC

[isis] 05/06: ISIS-2348 EventComparison works

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 b1bf26066b18a794dbf04279506796bd69be7f3c
Author: Jörg Rade <jo...@kuehne-nagel.com>
AuthorDate: Mon Nov 22 20:34:24 2021 +0100

    ISIS-2348 EventComparison works
---
 .../isis/client/kroviz/core/event/EventStore.kt    |  5 +++
 .../client/kroviz/core/event/LogEntryComparison.kt | 28 +++++++-----
 .../isis/client/kroviz/core/event/ReplayCommand.kt |  4 +-
 .../apache/isis/client/kroviz/ui/core/RoMenuBar.kt | 23 ----------
 .../client/kroviz/ui/diagram/LinkTreeDiagram.kt    |  4 +-
 .../client/kroviz/ui/dialog/EventCompareDialog.kt  |  9 ++--
 .../{ReplayDiffDialog.kt => EventReplayDialog.kt}  | 28 +++++++-----
 ...ompareDialog.kt => ResponseComparisonDialog.kt} | 41 ++++++++++-------
 .../client/kroviz/ui/panel/EventComparisonTable.kt | 51 ++++++++++------------
 .../apache/isis/client/kroviz/utils/StringUtils.kt |  5 ++-
 .../isis/client/kroviz/util/StringUtilsTest.kt     |  2 +-
 11 files changed, 97 insertions(+), 103 deletions(-)

diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/EventStore.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/EventStore.kt
index a0ae52b..eb8eab8 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/EventStore.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/core/event/EventStore.kt
@@ -26,6 +26,7 @@ import org.apache.isis.client.kroviz.to.HasLinks
 import org.apache.isis.client.kroviz.to.TObject
 import org.apache.isis.client.kroviz.to.mb.Menubars
 import org.apache.isis.client.kroviz.ui.core.UiManager
+import org.apache.isis.client.kroviz.utils.StringUtils
 import org.apache.isis.client.kroviz.utils.UUID
 import org.w3c.files.Blob
 
@@ -150,6 +151,10 @@ class EventStore {
         }
     }
 
+    fun findBy(shortTitle: String): LogEntry? {
+        return log.firstOrNull { StringUtils.shortTitle(it.title) == shortTitle }
+    }
+
     fun findBy(aggregator: BaseAggregator): LogEntry? {
         return log.firstOrNull { it.getAggregator() == aggregator }
     }
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
index 37119b9..d8825f1 100644
--- 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
@@ -20,6 +20,7 @@ package org.apache.isis.client.kroviz.core.event
 
 import io.kvision.html.ButtonStyle
 import kotlinx.serialization.Serializable
+import org.apache.isis.client.kroviz.ui.core.Constants
 
 enum class ChangeType(val id: String, val iconName: String, val style: ButtonStyle) {
     ADDED("ADDED", "fas fa-plus", ButtonStyle.INFO),
@@ -30,12 +31,14 @@ enum class ChangeType(val id: String, val iconName: String, val style: ButtonSty
 }
 
 @Serializable
-data class LogEntryComparison(val expected: LogEntry?, val actual: LogEntry?) {
+data class LogEntryComparison(val title: String, val expected: LogEntry?, val actual: LogEntry?) {
     var changeType: ChangeType
-    var title: String
     var iconName: String
     var expectedResponse: String? = null
     var actualResponse: String? = null
+    var expectedBaseUrl = ""
+    var actualBaseUrl = ""
+
 
     init {
         if (expected == null && actual == null) {
@@ -43,17 +46,22 @@ data class LogEntryComparison(val expected: LogEntry?, val actual: LogEntry?) {
         } else {
             changeType = compare()
             iconName = changeType.iconName
-            if (expected == null) {
-                actualResponse = actual?.response
-                title = actual?.title.toString()
-            } else {
-                expectedResponse = expected.response
-                title = expected.title
+            actualResponse = actual?.response
+            expectedResponse = expected?.response
+            if (expected != null) {
+                expectedBaseUrl = extractBaseUrl(expected)
+            }
+            if (actual != null) {
+                actualBaseUrl = extractBaseUrl(actual)
             }
-
         }
     }
 
+    private fun extractBaseUrl(event: LogEntry): String {
+        val title = event.title
+        return title.split(Constants.restInfix).first()
+    }
+
     private fun compare(): ChangeType {
         val responsesAreEqual = areResponsesEqual()
         return when {
@@ -66,8 +74,6 @@ data class LogEntryComparison(val expected: LogEntry?, val actual: LogEntry?) {
     }
 
     private fun areResponsesEqual(): Boolean {
-        val expectedBaseUrl = ""
-        val actualBaseUrl = "" //FIXME
         val expected = expectedResponse?.replace(expectedBaseUrl, "")
         val actual = actualResponse?.replace(actualBaseUrl, "")
         return (expected == actual)
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 4df6d24..3cc1997 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
@@ -30,7 +30,7 @@ import org.apache.isis.client.kroviz.to.TObject
 import org.apache.isis.client.kroviz.ui.core.Constants
 import org.apache.isis.client.kroviz.ui.core.UiManager
 import org.apache.isis.client.kroviz.ui.dialog.Command
-import org.apache.isis.client.kroviz.ui.dialog.ReplayDiffDialog
+import org.apache.isis.client.kroviz.ui.dialog.EventReplayDialog
 
 val AppScope = CoroutineScope(window.asCoroutineDispatcher())
 
@@ -57,7 +57,7 @@ class ReplayCommand : Command() {
         replay(uiEvents, urlUnderTest)
 
         val title = "Replay Events: $oldBaseUrl -> $urlUnderTest"
-        val rdd = ReplayDiffDialog(expectedEvents, title)
+        val rdd = EventReplayDialog(expectedEvents, title)
         rdd.dialog.open()
     }
 
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt
index cb11e4d..7c636ee 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/core/RoMenuBar.kt
@@ -21,14 +21,11 @@ package org.apache.isis.client.kroviz.ui.core
 import io.kvision.core.CssSize
 import io.kvision.core.UNIT
 import io.kvision.dropdown.DropDown
-import io.kvision.html.Button
 import io.kvision.html.ButtonStyle
 import io.kvision.html.Link
 import io.kvision.navbar.*
 import io.kvision.panel.SimplePanel
 import io.kvision.panel.vPanel
-import kotlinx.browser.window
-import org.apache.isis.client.kroviz.core.event.ReplayCommand
 import org.apache.isis.client.kroviz.to.mb.Menubars
 import org.apache.isis.client.kroviz.ui.chart.SampleChartModel
 import org.apache.isis.client.kroviz.ui.dialog.About
@@ -50,7 +47,6 @@ class RoMenuBar : SimplePanel() {
                 marginLeft = CssSize(-32, UNIT.px)
                 height = CssSize(40, UNIT.px)
                 nav = nav()
-//                logoButton() leaves an empty space here without network connection
                 val mainEntry = buildMainMenu()
                 nav.add(mainEntry)
             }
@@ -86,14 +82,6 @@ class RoMenuBar : SimplePanel() {
             buildMenuEntry("Events", "Event", { EventDialog().open() })
         )
 
-        mainMenu.add(
-            buildMenuEntry("Replay", "Replay", {  })
-        )
-
-        mainMenu.add(
-            buildMenuEntry("Event Log", "History", { UiManager.add("Event Log", EventLogTable(UiManager.getEventStore().log)) })
-        )
-
         val chartTitle = "Sample Chart"
         mainMenu.add(
             buildMenuEntry(chartTitle, "Chart", { UiManager.add(chartTitle, EventChart(SampleChartModel())) })
@@ -128,7 +116,6 @@ class RoMenuBar : SimplePanel() {
     }
 
     fun amendMenu(menuBars: Menubars) {
-        //       logoButton()
         menuBars.primary.menu.forEach { m ->
             val dd = MenuFactory.buildForMenu(m)
             if (dd.getChildren().isNotEmpty()) nav.add(dd)
@@ -137,14 +124,4 @@ class RoMenuBar : SimplePanel() {
         nav.add(MenuFactory.buildForMenu(menuBars.tertiary.menu.first()))
     }
 
-    private fun logoButton() {
-        val classNames = "isis-logo-button-image logo-button"
-        val logo = Button("", style = ButtonStyle.LINK)
-        logo.addCssClass(classNames)
-        logo.onClick {
-            window.open("https://isis.apache.org")
-        }
-        nav.add(logo)
-    }
-
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt
index 80b8a10..1e80749 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/diagram/LinkTreeDiagram.kt
@@ -50,7 +50,7 @@ object LinkTreeDiagram {
         val le = UiManager.getEventStore().findBy(rs)
         val pc = PumlCode()
         if (le != null) {
-            val title = StringUtils.shortTitle(url, protocolHostPort)
+            val title = StringUtils.shortTitle(url)
             pc.addStereotype(le.type)
             pc.addLink(url, title)
             pc.addHorizontalLine()
@@ -78,7 +78,7 @@ object LinkTreeDiagram {
                 obj.links.forEach {
                     if (it.relation() != Relation.SELF) {
                         val url = it.href
-                        val title = StringUtils.shortTitle(url, protocolHostPort)
+                        val title = StringUtils.shortTitle(url)
                         pc.addLink(url, title)
                     }
                 }
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
index 2f4c960..8a64691 100644
--- 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
@@ -16,7 +16,6 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 package org.apache.isis.client.kroviz.ui.dialog
 
 import io.kvision.core.CssSize
@@ -27,6 +26,7 @@ import org.apache.isis.client.kroviz.ui.core.RoDialog
 import org.apache.isis.client.kroviz.ui.panel.EventComparisonTable
 
 class EventCompareDialog(val data: List<LogEntryComparison>) : Command() {
+    private var table: EventComparisonTable
 
     private val panel = VPanel(spacing = 3) {
         width = CssSize(100, UNIT.perc)
@@ -41,15 +41,12 @@ class EventCompareDialog(val data: List<LogEntryComparison>) : Command() {
             widthPerc = 60,
             heightPerc = 70,
         )
-        //FIXME -> reuse -> ColumnFactory and RoTable if possible
-        val table = EventComparisonTable(data)
+        //IMPROVE: reuse ColumnFactory and RoTable if possible
+        table = EventComparisonTable(data)
         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/ReplayDiffDialog.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventReplayDialog.kt
similarity index 76%
rename from incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ReplayDiffDialog.kt
rename to incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventReplayDialog.kt
index 7f4a20e..3a1fdc9 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/EventReplayDialog.kt
@@ -25,7 +25,6 @@ import io.kvision.core.UNIT
 import io.kvision.panel.Direction
 import io.kvision.panel.SplitPanel
 import io.kvision.panel.VPanel
-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
@@ -33,8 +32,9 @@ 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
 import org.apache.isis.client.kroviz.ui.panel.EventLogTable
+import org.apache.isis.client.kroviz.utils.StringUtils
 
-class ReplayDiffDialog(
+class EventReplayDialog(
     private val expectedEvents: List<LogEntry>,
     title: String
 ) : Command() {
@@ -74,13 +74,14 @@ class ReplayDiffDialog(
     }
 
     override fun execute(action: String?) {
-        val comparisons = mutableListOf<LogEntryComparison>()
+        val comparisonMap = mutableMapOf<String, 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))
+            val shortTitle = StringUtils.shortTitle(it.title)
+            val actualEvent: LogEntry? = actualStore.findBy(shortTitle)
+            val lec = LogEntryComparison(shortTitle, it, actualEvent)
+            comparisonMap.put(shortTitle, lec)
         }
 
         // second pass: iterate over actual
@@ -88,12 +89,17 @@ class ReplayDiffDialog(
         val expectedStore = EventStore()
         expectedStore.log.addAll(expectedEvents)
         actualEvents.forEach {
-            val rs = ResourceSpecification(it.url, it.subType)
-            val expectedEvent = expectedStore.findBy(rs)
-            //TODO check for duplicates?
-            comparisons.add(LogEntryComparison(expectedEvent, it))
+            val shortTitle = StringUtils.shortTitle(it.title)
+            if (!comparisonMap.contains(shortTitle)) {
+                val rs = ResourceSpecification(it.url, it.subType)
+                val expectedEvent = expectedStore.findBy(rs)
+                val lec = LogEntryComparison(shortTitle, expectedEvent, it)
+                comparisonMap.put(shortTitle, lec)
+            }
         }
-        EventCompareDialog(comparisons).open()
+        val comparisonList = mutableListOf<LogEntryComparison>()
+        comparisonList.addAll(comparisonMap.values)
+        EventCompareDialog(comparisonList).open()
     }
 
 }
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/ResponseComparisonDialog.kt
similarity index 52%
copy from incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/EventCompareDialog.kt
copy to incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/dialog/ResponseComparisonDialog.kt
index 2f4c960..7ab8970 100644
--- 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/ResponseComparisonDialog.kt
@@ -16,40 +16,49 @@
  * 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.FlexDirection
 import io.kvision.core.UNIT
+import io.kvision.form.text.TextArea
+import io.kvision.panel.Direction
+import io.kvision.panel.SplitPanel
 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.panel.EventComparisonTable
 
-class EventCompareDialog(val data: List<LogEntryComparison>) : Command() {
+class ResponseComparisonDialog(obj: LogEntryComparison) : Command() {
+    private val title = "Response Diff"
 
-    private val panel = VPanel(spacing = 3) {
-        width = CssSize(100, UNIT.perc)
+    private val expectedPanel = VPanel(spacing = 3) {
+        width = CssSize(50, UNIT.perc)
+    }
+    private val actualPanel = VPanel(spacing = 3) {
+        width = CssSize(50, UNIT.perc)
     }
 
     init {
         dialog = RoDialog(
-            caption = "Event Comparison",
+            caption = title,
             items = mutableListOf(),
             command = this,
-            defaultAction = "Pin",
-            widthPerc = 60,
+            widthPerc = 80,
             heightPerc = 70,
+            customButtons = mutableListOf()
         )
-        //FIXME -> reuse -> ColumnFactory and RoTable if possible
-        val table = EventComparisonTable(data)
-        table.tabulator.addCssClass("tabulator-in-dialog")
-        panel.add(table)
-
-        dialog.formPanel!!.add(panel)
-    }
+        val expectedText = TextArea(label = "Expected", value = obj.expectedResponse, rows = 30)
+        expectedPanel.add(expectedText)
+        val actualText = TextArea(label = "Actual", value = obj.actualResponse, rows = 30)
+        actualPanel.add(actualText)
 
-    override fun execute(action: String?) {
+        val splitPanel = SplitPanel(direction = Direction.VERTICAL)
+        splitPanel.addCssClass("dialog-content")
+        splitPanel.flexDirection = FlexDirection.ROW
+        splitPanel.add(expectedPanel)
+        splitPanel.add(actualPanel)
+        dialog.formPanel!!.add(splitPanel)
+        dialog.open()
     }
 
 }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventComparisonTable.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventComparisonTable.kt
index d58c8c1..f350921 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventComparisonTable.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/ui/panel/EventComparisonTable.kt
@@ -20,17 +20,13 @@ package org.apache.isis.client.kroviz.ui.panel
 
 import io.kvision.core.*
 import io.kvision.html.Button
-import io.kvision.html.ButtonStyle
 import io.kvision.panel.VPanel
 import io.kvision.panel.hPanel
 import io.kvision.tabulator.*
 import io.kvision.utils.px
-import org.apache.isis.client.kroviz.core.event.LogEntry
 import org.apache.isis.client.kroviz.core.event.LogEntryComparison
-import org.apache.isis.client.kroviz.to.TObject
 import org.apache.isis.client.kroviz.ui.core.Constants
-import org.apache.isis.client.kroviz.ui.dialog.EventLogDetail
-import org.apache.isis.client.kroviz.utils.StringUtils
+import org.apache.isis.client.kroviz.ui.dialog.ResponseComparisonDialog
 
 class EventComparisonTable(val model: List<LogEntryComparison>) : VPanel() {
     val tabulator: Tabulator<LogEntryComparison>
@@ -41,58 +37,46 @@ class EventComparisonTable(val model: List<LogEntryComparison>) : VPanel() {
             title = "",
             field = "changeType",
             width = "50",
-   //         headerMenu = DynamicMenuBuilder().buildTableMenu(this),
             hozAlign = Align.CENTER,
             vertAlign = VAlign.MIDDLE,
             formatterComponentFunction = { _, _, data -> buildActionButton(data) }
         ),
         ColumnDefinition(
             download = false,
+            title = "Status",
+            field = "changeType",
+            headerFilter = Editor.INPUT,
+            width = "100",
+        ),
+        ColumnDefinition(
+            download = false,
             title = "Title",
             field = "title",
             headerFilter = Editor.INPUT,
-            width = "200",
-            formatterComponentFunction = { _, _, data -> buildObjectButton(data) }
+            width = "700",
         ),
         ColumnDefinition(
             download = false,
-            title = "expectedResponse",
+            title = "Expected Response",
             field = "expectedResponse",
             headerFilter = Editor.INPUT,
-            width = "400",
+            width = "150",
         ),
         ColumnDefinition(
             download = false,
-            title = "actualResponse",
+            title = "Actual Response",
             field = "actualResponse",
             headerFilter = Editor.INPUT,
-            width = "400",
+            width = "150",
         )
     )
 
-    private fun buildObjectButton(data: LogEntryComparison): Button {
-        val b = Button(
-            text = StringUtils.shorten(data.title),
-            icon = data.changeType.iconName,
-            style = ButtonStyle.LINK
-        )
-        b.onClick {
-            kotlinx.browser.window.open(data.title) //IMPROVE should be URL
-        }
-        //val tto = TooltipOptions(title = data.title)
-        // tabulator tooltip is buggy: often the tooltip doesn't go away and the color is not settable
-        //b.enableTooltip(tto)
-    //    if (data.obj is TObject) b.setDragDropData(Constants.stdMimeType, data.url)
-        return b
-    }
-
     private fun buildActionButton(data: LogEntryComparison): Button {
         val b = Button(
             text = "",
             icon = "fa fa-info-circle",
             style = data.changeType.style
         )
-//        b.onClick { EventLogDetail(data).open() }
         b.margin = CssSize(-10, UNIT.px)
         b.addCssClass("btn-sm")
         return b
@@ -117,6 +101,15 @@ class EventComparisonTable(val model: List<LogEntryComparison>) : VPanel() {
 
         tabulator = tabulator(model, options = options) {
             setEventListener<Tabulator<LogEntryComparison>> {
+                cellClickTabulator = {
+                    // can't check cast to external interface
+                    val cc = it.detail as io.kvision.tabulator.js.Tabulator.CellComponent
+                    val column = cc.getColumn().getField()
+                    if (column == "changeType") {
+                        val obj = cc.getData() as LogEntryComparison
+                        ResponseComparisonDialog(obj).open()
+                    }
+                }
             }
         }
     }
diff --git a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/StringUtils.kt b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/StringUtils.kt
index e2e77e8..a27637a 100644
--- a/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/StringUtils.kt
+++ b/incubator/clients/kroviz/src/main/kotlin/org/apache/isis/client/kroviz/utils/StringUtils.kt
@@ -64,9 +64,10 @@ object StringUtils {
         return if (input == outputWithoutWhiteSpace) input else output
     }
 
-    fun shortTitle(url: String, protocolHostPort: String): String {
-        var title = url
+    fun shortTitle(url: String): String {
         val signature = Constants.restInfix
+        val protocolHostPort = url.split(signature).first()
+        var title = url
         if (title.contains(signature)) {
             // strip off protocol, host, port
             title = title.replace(protocolHostPort + signature, "")
diff --git a/incubator/clients/kroviz/src/test/kotlin/org/apache/isis/client/kroviz/util/StringUtilsTest.kt b/incubator/clients/kroviz/src/test/kotlin/org/apache/isis/client/kroviz/util/StringUtilsTest.kt
index 9536a9d..e545aee 100644
--- a/incubator/clients/kroviz/src/test/kotlin/org/apache/isis/client/kroviz/util/StringUtilsTest.kt
+++ b/incubator/clients/kroviz/src/test/kotlin/org/apache/isis/client/kroviz/util/StringUtilsTest.kt
@@ -20,7 +20,7 @@ class StringUtilsTest {
         assertTrue(protocolHostPort.startsWith("http://"))
 
         // when
-        val actual = StringUtils.shortTitle(url, protocolHostPort)
+        val actual = StringUtils.shortTitle(url)
         // then
         val expected = "/domain-types/demo.JavaLangStrings/collections/entities"
         assertEquals(expected, actual)