Skip to content

Improve workflow visualizer functionality #1343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions workflow-trace-viewer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ It can be run via Gradle using:
./gradlew :workflow-trace-viewer:run
```

### Terminology

**Trace**: A trace is a file — made up of frames — that contains the execution history of a Workflow. It includes information about render passes, how states have changed within workflows, and the specific props being passed through. The data collected to generate these should be in chronological order, and allows developers to step through the process easily.

**Frame**: Essentially a "snapshot" of the current "state" of the whole Workflow tree. It contains relevant information about the changes in workflow states and how props are passed throughout.

- Note that "snapshot" and "state" are different from `snapshotState` and `State`, which are idiomatic to the Workflow library.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set some standard for naming terminology in this project. Open to changing it though!

### External Libraries

[FileKit](https://github.com/vinceglb/FileKit) is an external library made to apply file operations on Kotlin and KMP projects. It's purpose in this app is to allow developers to upload their own json trace files. The motivation for its use is to quickly implement a file picker.
69 changes: 42 additions & 27 deletions workflow-trace-viewer/api/workflow-trace-viewer.api
Original file line number Diff line number Diff line change
Expand Up @@ -9,47 +9,62 @@ public final class com/squareup/workflow1/traceviewer/ComposableSingletons$MainK
public final fun getLambda-1$wf1_workflow_trace_viewer ()Lkotlin/jvm/functions/Function3;
}

public final class com/squareup/workflow1/traceviewer/ComposableSingletons$UploadFileKt {
public static final field INSTANCE Lcom/squareup/workflow1/traceviewer/ComposableSingletons$UploadFileKt;
public final class com/squareup/workflow1/traceviewer/MainKt {
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V
}

public final class com/squareup/workflow1/traceviewer/model/Node {
public static final field $stable I
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
public final fun getChildren ()Ljava/util/List;
public final fun getId ()Ljava/lang/String;
public final fun getName ()Ljava/lang/String;
}

public final class com/squareup/workflow1/traceviewer/ui/FrameSelectTabKt {
public static final fun StateSelectTab (Ljava/util/List;ILkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
}

public final class com/squareup/workflow1/traceviewer/ui/WorkflowInfoPanelKt {
public static final fun RightInfoPanel (Lcom/squareup/workflow1/traceviewer/model/Node;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
}

public final class com/squareup/workflow1/traceviewer/ui/WorkflowTreeKt {
public static final fun RenderDiagram (Lio/github/vinceglb/filekit/PlatformFile;ILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
}

public final class com/squareup/workflow1/traceviewer/util/ComposableSingletons$UploadFileKt {
public static final field INSTANCE Lcom/squareup/workflow1/traceviewer/util/ComposableSingletons$UploadFileKt;
public static field lambda-1 Lkotlin/jvm/functions/Function3;
public fun <init> ()V
public final fun getLambda-1$wf1_workflow_trace_viewer ()Lkotlin/jvm/functions/Function3;
}

public final class com/squareup/workflow1/traceviewer/MainKt {
public static final fun main ()V
public static synthetic fun main ([Ljava/lang/String;)V
public final class com/squareup/workflow1/traceviewer/util/JsonParserKt {
public static final fun parseTrace (Lio/github/vinceglb/filekit/PlatformFile;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class com/squareup/workflow1/traceviewer/SandboxBackgroundKt {
public static final fun SandboxBackground (Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
public abstract interface class com/squareup/workflow1/traceviewer/util/ParseResult {
}

public final class com/squareup/workflow1/traceviewer/UploadFileKt {
public static final fun UploadFile (Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
public final class com/squareup/workflow1/traceviewer/util/ParseResult$Failure : com/squareup/workflow1/traceviewer/util/ParseResult {
public static final field $stable I
public fun <init> (Ljava/lang/Throwable;)V
public final fun getError ()Ljava/lang/Throwable;
}

public final class com/squareup/workflow1/traceviewer/WorkflowJsonParserKt {
public static final fun parseTrace (Ljava/lang/String;)Lcom/squareup/workflow1/traceviewer/WorkflowNode;
public final class com/squareup/workflow1/traceviewer/util/ParseResult$Success : com/squareup/workflow1/traceviewer/util/ParseResult {
public static final field $stable I
public fun <init> (Ljava/util/List;)V
public final fun getTrace ()Ljava/util/List;
}

public final class com/squareup/workflow1/traceviewer/WorkflowNode {
public static final field $stable I
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/util/List;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lcom/squareup/workflow1/traceviewer/WorkflowNode;
public static synthetic fun copy$default (Lcom/squareup/workflow1/traceviewer/WorkflowNode;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lcom/squareup/workflow1/traceviewer/WorkflowNode;
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 fun hashCode ()I
public fun toString ()Ljava/lang/String;
public final class com/squareup/workflow1/traceviewer/util/SandboxBackgroundKt {
public static final fun SandboxBackground (Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V
}

public final class com/squareup/workflow1/traceviewer/WorkflowTreeKt {
public static final fun DrawWorkflowTree (Lcom/squareup/workflow1/traceviewer/WorkflowNode;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
public final class com/squareup/workflow1/traceviewer/util/UploadFileKt {
public static final fun UploadFile (Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V
}

Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package com.squareup.workflow1.traceviewer

import androidx.compose.foundation.layout.Box
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.squareup.workflow1.traceviewer.model.Node
import com.squareup.workflow1.traceviewer.ui.RenderDiagram
import com.squareup.workflow1.traceviewer.ui.RightInfoPanel
import com.squareup.workflow1.traceviewer.ui.StateSelectTab
import com.squareup.workflow1.traceviewer.util.SandboxBackground
import com.squareup.workflow1.traceviewer.util.UploadFile
import io.github.vinceglb.filekit.PlatformFile
import io.github.vinceglb.filekit.readString

/**
* Main composable that provides the different layers of UI.
Expand All @@ -19,28 +24,43 @@ import io.github.vinceglb.filekit.readString
public fun App(
modifier: Modifier = Modifier
) {
Box {
var selectedFile by remember { mutableStateOf<PlatformFile?>(null) }
var selectedTraceFile by remember { mutableStateOf<PlatformFile?>(null) }
var selectedNode by remember { mutableStateOf<Node?>(null) }
var workflowFrames by remember { mutableStateOf<List<Node>>(emptyList()) }
var frameIndex by remember { mutableIntStateOf(0) }

if (selectedFile != null) {
SandboxBackground { WorkflowContent(selectedFile!!) }
Box(
modifier = modifier
) {
// Main content
if (selectedTraceFile != null) {
SandboxBackground {
RenderDiagram(
traceFile = selectedTraceFile!!,
frameInd = frameIndex,
onFileParse = { workflowFrames = it },
onNodeSelect = { selectedNode = it }
)
}
}

UploadFile(onFileSelect = { selectedFile = it })
}
}
StateSelectTab(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering how many traces there are likely to be, you might consider using a dropdown instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, maybe even a combination of the 2, where with a dropdown you can instantly jump to a frame, and a scrolling bar could let you traverse locally. I think, though, this could be something I implement down the line as iterative improvements.

frames = workflowFrames,
currentIndex = frameIndex,
onIndexChange = { frameIndex = it },
modifier = Modifier.align(Alignment.TopCenter)
)

@Composable
private fun WorkflowContent(file: PlatformFile) {
var jsonString by remember { mutableStateOf<String?>(null) }
LaunchedEffect(file) {
jsonString = file.readString()
}
val root = jsonString?.let { parseTrace(it) }
RightInfoPanel(selectedNode)

if (root != null) {
DrawWorkflowTree(root)
} else {
Text("Empty data or failed to parse data") // TODO: proper handling of error
// The states are reset when a new file is selected.
UploadFile(
onFileSelect = {
selectedTraceFile = it
selectedNode = null
frameIndex = 0
},
modifier = Modifier.align(Alignment.BottomStart)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.squareup.workflow1.traceviewer

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import androidx.compose.ui.window.singleWindowApplication

/**
* Main entry point for the desktop application, see [README.md] for more details.
*/
fun main() {
singleWindowApplication(title = "Workflow Trace Viewer") {
App()
App(Modifier.fillMaxSize())
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.squareup.workflow1.traceviewer.model

/**
* Since the logic of Workflow is hierarchical (where each workflow may have parent workflows and/or
* children workflows, a tree structure is most appropriate for representing the data rather than
* using flat data structures like an array.
*
* 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<Node>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.squareup.workflow1.traceviewer.ui

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.squareup.workflow1.traceviewer.model.Node

/**
* A trace tab selector that allows devs to switch between different states within the provided trace.
*/
@Composable
public fun StateSelectTab(
frames: List<Node>,
currentIndex: Int,
onIndexChange: (Int) -> Unit,
modifier: Modifier = Modifier
) {
val state = rememberLazyListState()

Surface(
modifier = modifier
.padding(4.dp),
color = Color.White,
) {
LazyRow(
modifier = Modifier
.padding(8.dp),
state = state
) {
items(frames.size) { index ->
Text(
text = "State ${index + 1}",
color = if (index == currentIndex) Color.Black else Color.LightGray,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.clickable { onIndexChange(index) }
.padding(10.dp)
)
}
}
}
}
Loading