diff --git a/workflow-trace-viewer/api/workflow-trace-viewer.api b/workflow-trace-viewer/api/workflow-trace-viewer.api index c06abc9a6b..ee30c6ebaa 100644 --- a/workflow-trace-viewer/api/workflow-trace-viewer.api +++ b/workflow-trace-viewer/api/workflow-trace-viewer.api @@ -16,10 +16,19 @@ public final class com/squareup/workflow1/traceviewer/MainKt { public final class com/squareup/workflow1/traceviewer/model/Node { public static final field $stable I - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/List;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun equals (Ljava/lang/Object;)Z public final fun getChildren ()Ljava/util/List; public final fun getId ()Ljava/lang/String; public final fun getName ()Ljava/lang/String; + public final fun getParent ()Ljava/lang/String; + public final fun getParentId ()Ljava/lang/String; + public final fun getProps ()Ljava/lang/Object; + public final fun getRenderings ()Ljava/lang/Object; + public final fun getState ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class com/squareup/workflow1/traceviewer/ui/FrameSelectTabKt { @@ -42,6 +51,7 @@ public final class com/squareup/workflow1/traceviewer/util/ComposableSingletons$ } public final class com/squareup/workflow1/traceviewer/util/JsonParserKt { + public static final field ROOT_ID Ljava/lang/String; public static final fun parseTrace (Lio/github/vinceglb/filekit/PlatformFile;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/model/Node.kt b/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/model/Node.kt index 412eb5a7c5..2e5b7a7fcb 100644 --- a/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/model/Node.kt +++ b/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/model/Node.kt @@ -8,7 +8,25 @@ package com.squareup.workflow1.traceviewer.model * TBD what more metadata should be involved with each node, e.g. (props, states, # of render passes) */ public class Node( - val id: String, val name: String, - val children: List -) + val id: String, + val parent: String, + val parentId: String, + val props: Any? = null, + val state: Any? = null, + val renderings: Any? = null, + val children: List, +) { + override fun toString(): String { + return "Node(name='$name', parent='$parent', children=${children.size})" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Node) return false + return this.id == other.id + } + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/FrameSelectTab.kt b/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/FrameSelectTab.kt index 0dccfa4e8b..fbc8a08a4e 100644 --- a/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/FrameSelectTab.kt +++ b/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/FrameSelectTab.kt @@ -38,7 +38,7 @@ public fun FrameSelectTab( ) { items(frames.size) { index -> Text( - text = "State ${index + 1}", + text = "Frame ${index + 1}", color = if (index == currentIndex) Color.Black else Color.LightGray, modifier = Modifier .clip(RoundedCornerShape(16.dp)) diff --git a/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowInfoPanel.kt b/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowInfoPanel.kt index f7c959abe2..82317b00e2 100644 --- a/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowInfoPanel.kt +++ b/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowInfoPanel.kt @@ -22,6 +22,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.squareup.workflow1.traceviewer.model.Node @@ -83,11 +85,22 @@ private fun NodePanelDetails( return@Column } - Text("only visible with a node selected") - Text( - text = "This is a node panel for ${node.name}", - fontSize = 20.sp, - modifier = Modifier.padding(8.dp) + val textModifier = Modifier.padding(8.dp) + val textStyle = TextStyle(fontSize = 16.sp, textAlign = TextAlign.Center) + val fields = mapOf( + "Name" to node.name, + "ID" to node.id, + "Props" to node.props.toString(), + "State" to node.state.toString(), + "Renderings" to node.renderings.toString() ) + + fields.forEach { (label, value) -> + Text( + text = "$label: $value", + modifier = textModifier, + style = textStyle + ) + } } } diff --git a/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowTree.kt b/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowTree.kt index 59277e82f0..e2b12817f4 100644 --- a/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowTree.kt +++ b/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/ui/WorkflowTree.kt @@ -64,8 +64,6 @@ public fun RenderDiagram( if (!isLoading) { DrawTree(frames[frameInd], onNodeSelect) } - - // TODO: catch errors and display UI here } /** diff --git a/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/util/JsonParser.kt b/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/util/JsonParser.kt index 6c1b7e2575..9c2aea472c 100644 --- a/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/util/JsonParser.kt +++ b/workflow-trace-viewer/src/jvmMain/kotlin/com/squareup/workflow1/traceviewer/util/JsonParser.kt @@ -8,8 +8,15 @@ import com.squareup.workflow1.traceviewer.model.Node import io.github.vinceglb.filekit.PlatformFile import io.github.vinceglb.filekit.readString +/* + The root workflow Node uses an ID of 0, and since we are filtering childrenByParent by the + parentId, the root node has a parent of -1 ID. This is reflected seen inside android-register + */ +const val ROOT_ID: String = "-1" + /** - * Parses a given file's JSON String into [Node] with Moshi adapters. + * Parses a given file's JSON String into a list of [Node]s with Moshi adapters. Each of these nodes + * count as the root of a tree which forms a Frame. * * @return A [ParseResult] representing result of parsing, either an error related to the * format of the JSON, or a success and a parsed trace. @@ -19,18 +26,63 @@ public suspend fun parseTrace( ): ParseResult { return try { val jsonString = file.readString() - val moshi = Moshi.Builder() - .add(KotlinJsonAdapterFactory()) - .build() - val workflowList = Types.newParameterizedType(List::class.java, Node::class.java) - val workflowAdapter: JsonAdapter> = moshi.adapter(workflowList) - val trace = workflowAdapter.fromJson(jsonString) - ParseResult.Success(trace) + val workflowAdapter = createMoshiAdapter() + val parsedRenderPasses = workflowAdapter.fromJson(jsonString) + + val parsedFrames = mutableListOf() + parsedRenderPasses?.forEach { renderPass -> + val parsed = getFrameFromRenderPass(renderPass) + parsedFrames.add(parsed) + } + + ParseResult.Success(parsedFrames) } catch (e: Exception) { ParseResult.Failure(e) } } +/** + * Creates a Moshi adapter for parsing the JSON trace file. + */ +private fun createMoshiAdapter(): JsonAdapter>> { + val moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + val workflowList = Types.newParameterizedType( + List::class.java, + Types.newParameterizedType(List::class.java, Node::class.java) + ) + val adapter: JsonAdapter>> = moshi.adapter(workflowList) + return adapter +} + +/** + * We take an unparsed render pass and build up a tree structure from it to form a Frame. + * + * @return Node the root node of the tree for that specific frame. + */ +private fun getFrameFromRenderPass(renderPass: List): Node { + val childrenByParent: Map> = renderPass.groupBy { it.parentId } + val root = childrenByParent[ROOT_ID]?.single() + return buildTree(root!!, childrenByParent) +} + +/** + * Recursively builds a tree using each node's children. + */ +private fun buildTree(node: Node, childrenByParent: Map>): Node { + val children = (childrenByParent[node.id] ?: emptyList()) + return Node( + name = node.name, + id = node.id, + parent = node.parent, + parentId = node.parentId, + props = node.props, + state = node.state, + children = children.map { buildTree(it, childrenByParent) }, + ) +} + sealed interface ParseResult { class Success(val trace: List?) : ParseResult class Failure(val error: Throwable) : ParseResult