Handle cycles while rendering json dependency tree to json

This commit is contained in:
Nima Taheri 2021-09-27 22:13:12 -07:00 committed by Nima Taheri
parent c2cd9a0a71
commit fcd7a3bef2
2 changed files with 81 additions and 14 deletions

View File

@ -10,14 +10,17 @@ package internal
package graph
package rendering
import java.io.{ OutputStream, InputStream, FileOutputStream, File }
import java.net.URI
import graph.{ Module, ModuleGraph }
import sbt.io.IO
import scala.annotation.{ nowarn, tailrec }
import scala.util.parsing.json.{ JSONArray, JSONObject }
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.net.URI
import scala.annotation.nowarn
import scala.annotation.tailrec
import scala.util.parsing.json.JSONArray
import scala.util.parsing.json.JSONObject
@nowarn object TreeView {
def createJson(graph: ModuleGraph): String = {
@ -36,18 +39,27 @@ import scala.util.parsing.json.{ JSONArray, JSONObject }
new URI(graphHTML.toURI.toString)
}
private def processSubtree(graph: ModuleGraph, module: Module): JSONObject = {
val children = graph.dependencyMap
.getOrElse(module.id, List())
.map(module => processSubtree(graph, module))
.toList
moduleAsJson(module, children)
private[rendering] def processSubtree(
graph: ModuleGraph,
module: Module,
parents: Set[GraphModuleId] = Set()
): JSONObject = {
val cycle = parents.contains(module.id)
val dependencies = if (cycle) List() else graph.dependencyMap.getOrElse(module.id, List())
val children =
dependencies.map(dependency => processSubtree(graph, dependency, parents + module.id)).toList
moduleAsJson(module, cycle, children)
}
private def moduleAsJson(module: Module, children: List[JSONObject]): JSONObject = {
private def moduleAsJson(
module: Module,
isCycle: Boolean,
children: List[JSONObject]
): JSONObject = {
val eviction = module.evictedByVersion.map(version => s" (evicted by $version)").getOrElse("")
val cycle = if (isCycle) " (cycle)" else ""
val error = module.error.map(err => s" (errors: $err)").getOrElse("")
val text = module.id.idString + eviction + error
val text = module.id.idString + eviction + error + cycle
JSONObject(Map("text" -> text, "children" -> JSONArray(children)))
}

View File

@ -0,0 +1,55 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt
package internal
package graph
package rendering
import org.scalatest.DiagrammedAssertions
import org.scalatest.FunSuite
import scala.annotation.nowarn
import scala.util.parsing.json.JSONArray
import scala.util.parsing.json.JSONObject
@nowarn("msg=class JSONObject in package json is deprecated")
class TreeViewTest extends FunSuite with DiagrammedAssertions {
val modA = GraphModuleId("orgA", "nameA", "1.0")
val modB = GraphModuleId("orgB", "nameB", "2.0")
val graph = ModuleGraph(
nodes = Seq(Module(modA), Module(modB)),
edges = Seq(
modA -> modA,
modA -> modB,
)
)
test("TreeView should detect cycles and truncate") {
val json = TreeView.processSubtree(graph, Module(modA))
val (rootText, children) = parseTree(json)
assert(rootText == modA.idString)
val childrenText = children.map(parseTree).map(_._1)
val expected = List(s"${modA.idString} (cycle)", modB.idString)
assert(childrenText == expected)
}
@nowarn("cat=unchecked")
def parseTree(json: JSONObject): (String, List[JSONObject]) = {
(json.obj.get("text"), json.obj.get("children")) match {
case (Some(text: String), Some(JSONArray(children: List[JSONObject])))
if children.forall(_.isInstanceOf[JSONObject]) =>
text -> children
case _ =>
fail("a string field 'text' and an array of objects in 'children' field were expected!")
}
}
}