diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java index 743663e82edcf..5e9763a0a166e 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/ArcDevConsoleProcessor.java @@ -45,8 +45,12 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.dev.console.DevConsoleManager; +import io.quarkus.devconsole.spi.DevConsoleRouteBuildItem; import io.quarkus.devconsole.spi.DevConsoleRuntimeTemplateInfoBuildItem; import io.quarkus.devconsole.spi.DevConsoleTemplateInfoBuildItem; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; public class ArcDevConsoleProcessor { @@ -102,8 +106,9 @@ public void transform(TransformationContext transformationContext) { } @BuildStep(onlyIf = IsDevelopment.class) - public DevConsoleTemplateInfoBuildItem collectBeanInfo(ValidationPhaseBuildItem validationPhaseBuildItem, - CompletedApplicationClassPredicateBuildItem predicate) { + public void collectBeanInfo(ValidationPhaseBuildItem validationPhaseBuildItem, + CompletedApplicationClassPredicateBuildItem predicate, BuildProducer templates, + BuildProducer routes) { BeanDeploymentValidator.ValidationContext validationContext = validationPhaseBuildItem.getContext(); DevBeanInfos beanInfos = new DevBeanInfos(); for (BeanInfo bean : validationContext.beans()) { @@ -157,9 +162,53 @@ public DevConsoleTemplateInfoBuildItem collectBeanInfo(ValidationPhaseBuildItem } beanInfos.sort(); - return new DevConsoleTemplateInfoBuildItem("devBeanInfos", beanInfos); + templates.produce(new DevConsoleTemplateInfoBuildItem("devBeanInfos", beanInfos)); + + routes.produce(new DevConsoleRouteBuildItem("toggleBeanDescription", "POST", new Handler() { + @Override + public void handle(RoutingContext context) { + Object val = DevConsoleManager.getGlobal(BEAN_DESCRIPTION); + if (val != null && val.equals("simple")) { + val = "full"; + } else { + val = "simple"; + } + DevConsoleManager.setGlobal(BEAN_DESCRIPTION, val); + context.response() + .putHeader("location", "beanDependencyGraph?beanId=" + context.request().getParam("beanId")) + .setStatusCode(302).end(); + } + })); + + routes.produce(new DevConsoleRouteBuildItem("setMaxDependencyLevel", "POST", new Handler() { + @Override + public void handle(RoutingContext context) { + context.request().setExpectMultipart(true); + context.request().endHandler(new Handler() { + @Override + public void handle(Void ignore) { + Integer val = null; + try { + val = Integer.parseInt(context.request().getFormAttribute("maxDepLevel")); + } catch (NumberFormatException ignored) { + } + if (val != null) { + DevConsoleManager.setGlobal(MAX_DEPENDENCY_LEVEL, val); + } + context.response() + .putHeader("location", "beanDependencyGraph?beanId=" + context.request().getParam("beanId")) + .setStatusCode(302).end(); + } + }); + + } + })); } + static final String BEAN_DESCRIPTION = "io.quarkus.arc.beanDescription"; + static final String MAX_DEPENDENCY_LEVEL = "io.quarkus.arc.maxDependencyLevel"; + static final int DEFAULT_MAX_DEPENDENCY_LEVEL = 10; + private boolean isAdditionalBeanDefiningAnnotationOn(ClassInfo beanClass, List beanDefiningAnnotations) { for (BeanDefiningAnnotationBuildItem beanDefiningAnnotation : beanDefiningAnnotations) { @@ -176,31 +225,31 @@ DependencyGraph buildDependencyGraph(BeanInfo bean, ValidationContext validation Map> directDependents) { Set nodes = new HashSet<>(); Set links = new HashSet<>(); - addNodesDependencies(bean, nodes, links, bean, devBeanInfos); - addNodesDependents(bean, nodes, links, bean, allInjectionPoints, declaringToProducers, resolver, devBeanInfos, + addNodesDependencies(0, bean, nodes, links, bean, devBeanInfos); + addNodesDependents(0, bean, nodes, links, bean, allInjectionPoints, declaringToProducers, resolver, devBeanInfos, directDependents); return new DependencyGraph(nodes, links); } - void addNodesDependencies(BeanInfo root, Set nodes, Set links, BeanInfo bean, + void addNodesDependencies(int level, BeanInfo root, Set nodes, Set links, BeanInfo bean, DevBeanInfos devBeanInfos) { if (nodes.add(devBeanInfos.getBean(bean.getIdentifier()))) { if (bean.isProducerField() || bean.isProducerMethod()) { - links.add(Link.producer(bean.getIdentifier(), bean.getDeclaringBean().getIdentifier())); - addNodesDependencies(root, nodes, links, bean.getDeclaringBean(), devBeanInfos); + links.add(Link.producer(bean.getIdentifier(), bean.getDeclaringBean().getIdentifier(), level)); + addNodesDependencies(level + 1, root, nodes, links, bean.getDeclaringBean(), devBeanInfos); } for (InjectionPointInfo injectionPoint : bean.getAllInjectionPoints()) { BeanInfo resolved = injectionPoint.getResolvedBean(); if (resolved != null && !resolved.equals(bean)) { - links.add(Link.dependency(root.equals(bean), bean.getIdentifier(), resolved.getIdentifier())); + links.add(Link.dependency(bean.getIdentifier(), resolved.getIdentifier(), level)); // add transient dependencies - addNodesDependencies(root, nodes, links, injectionPoint.getResolvedBean(), devBeanInfos); + addNodesDependencies(level + 1, root, nodes, links, injectionPoint.getResolvedBean(), devBeanInfos); } } } } - void addNodesDependents(BeanInfo root, Set nodes, Set links, BeanInfo bean, + void addNodesDependents(int level, BeanInfo root, Set nodes, Set links, BeanInfo bean, List injectionPoints, Map> declaringToProducers, BeanResolver resolver, DevBeanInfos devBeanInfos, Map> directDependents) { List direct = directDependents.get(bean); @@ -228,23 +277,23 @@ void addNodesDependents(BeanInfo root, Set nodes, Set links, BeanInfo dependent = ip.getTargetBean().get(); Link link; if (ip.getResolvedBean() == null) { - link = Link.lookup(dependent.getIdentifier(), bean.getIdentifier()); + link = Link.lookup(dependent.getIdentifier(), bean.getIdentifier(), level); } else { - link = Link.dependent(root.equals(bean), dependent.getIdentifier(), bean.getIdentifier()); + link = Link.dependent(dependent.getIdentifier(), bean.getIdentifier(), level); } links.add(link); if (nodes.add(devBeanInfos.getBean(dependent.getIdentifier()))) { // add transient dependents - addNodesDependents(root, nodes, links, dependent, injectionPoints, declaringToProducers, resolver, + addNodesDependents(level + 1, root, nodes, links, dependent, injectionPoints, declaringToProducers, resolver, devBeanInfos, directDependents); } } for (BeanInfo producer : declaringToProducers.getOrDefault(bean, Collections.emptyList())) { - links.add(Link.producer(producer.getIdentifier(), bean.getIdentifier())); + links.add(Link.producer(producer.getIdentifier(), bean.getIdentifier(), level)); if (nodes.add(devBeanInfos.getBean(producer.getIdentifier()))) { // add transient dependents - addNodesDependents(root, nodes, links, producer, injectionPoints, declaringToProducers, resolver, + addNodesDependents(level + 1, root, nodes, links, producer, injectionPoints, declaringToProducers, resolver, devBeanInfos, directDependents); } } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DependencyGraph.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DependencyGraph.java index ab422207e5cb0..ccfa47dec2764 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DependencyGraph.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DependencyGraph.java @@ -1,5 +1,6 @@ package io.quarkus.arc.deployment.devconsole; +import java.util.HashSet; import java.util.Objects; import java.util.Set; @@ -7,38 +8,63 @@ public class DependencyGraph { public final Set nodes; public final Set links; + public final int maxLevel; public DependencyGraph(Set nodes, Set links) { this.nodes = nodes; this.links = links; + this.maxLevel = links.stream().mapToInt(l -> l.level).max().orElse(0); + } + + DependencyGraph forLevel(int level) { + // Filter out links first + Set newLinks = new HashSet<>(); + Set newNodes = new HashSet<>(); + Set usedIds = new HashSet<>(); + for (Link link : links) { + if (link.level <= level) { + newLinks.add(link); + usedIds.add(link.source); + usedIds.add(link.target); + } + } + // Now keep only nodes for which a link exists... + for (DevBeanInfo node : nodes) { + if (usedIds.contains(node.getId())) { + newNodes.add(node); + } + } + return new DependencyGraph(newNodes, newLinks); } public static class Link { - static Link dependent(boolean direct, String source, String target) { - return new Link(source, target, direct ? "directDependent" : "dependency"); + static Link dependent(String source, String target, int level) { + return new Link(source, target, level == 0 ? "directDependent" : "dependency", level); } - static Link dependency(boolean direct, String source, String target) { - return new Link(source, target, direct ? "directDependency" : "dependency"); + static Link dependency(String source, String target, int level) { + return new Link(source, target, level == 0 ? "directDependency" : "dependency", level); } - static Link lookup(String source, String target) { - return new Link(source, target, "lookup"); + static Link lookup(String source, String target, int level) { + return new Link(source, target, "lookup", level); } - static Link producer(String source, String target) { - return new Link(source, target, "producer"); + static Link producer(String source, String target, int level) { + return new Link(source, target, "producer", level); } public final String source; public final String target; public final String type; + public final int level; - public Link(String source, String target, String type) { + public Link(String source, String target, String type, int level) { this.source = source; this.target = target; this.type = type; + this.level = level; } @Override diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DevBeanInfo.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DevBeanInfo.java index ab3087a495852..d8ce275ab7a3c 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DevBeanInfo.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DevBeanInfo.java @@ -162,18 +162,43 @@ public List getInterceptors() { } public String getDescription() { + return description(false); + } + + public String getSimpleDescription() { + return description(true); + } + + private String description(boolean simple) { + String typeInfo = typeInfo(simple); switch (kind) { - case CLASS: - return providerType.toString(); case FIELD: - return declaringClass.toString() + "#" + memberName; + return typeInfo + "#" + memberName; case METHOD: - return declaringClass.toString() + "#" + memberName + "()"; + return typeInfo + "#" + memberName + "()"; case SYNTHETIC: - return "Synthetic: " + providerType.toString(); + return "Synthetic: " + typeInfo; + default: + return typeInfo; + } + } + + public String typeInfo(boolean simple) { + String type; + switch (kind) { + case FIELD: + case METHOD: + type = declaringClass.toString(); + break; default: - return providerType.toString(); + type = providerType.toString(); + break; + } + if (simple) { + int idx = type.lastIndexOf("."); + return idx != -1 && type.length() > 1 ? type.substring(idx + 1) : type; } + return type; } @Override diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DevBeanInfos.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DevBeanInfos.java index 9fd613cc94224..90884bfff2cfc 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DevBeanInfos.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/devconsole/DevBeanInfos.java @@ -6,6 +6,8 @@ import java.util.List; import java.util.Map; +import io.quarkus.dev.console.DevConsoleManager; + public class DevBeanInfos { private final List beans; @@ -56,6 +58,15 @@ public List getRemovedDecorators() { return removedDecorators; } + public String getBeanDescription() { + return DevConsoleManager.getGlobal(ArcDevConsoleProcessor.BEAN_DESCRIPTION); + } + + public int getMaxDependencyLevel() { + Integer val = DevConsoleManager.getGlobal(ArcDevConsoleProcessor.MAX_DEPENDENCY_LEVEL); + return val != null ? val : ArcDevConsoleProcessor.DEFAULT_MAX_DEPENDENCY_LEVEL; + } + public DevBeanInfo getBean(String id) { for (DevBeanInfo bean : beans) { if (bean.getId().equals(id)) { @@ -75,7 +86,12 @@ public DevInterceptorInfo getInterceptor(String id) { } public DependencyGraph getDependencyGraph(String beanId) { - return dependencyGraphs.get(beanId); + Integer maxLevel = DevConsoleManager.getGlobal(ArcDevConsoleProcessor.MAX_DEPENDENCY_LEVEL); + if (maxLevel == null) { + maxLevel = ArcDevConsoleProcessor.DEFAULT_MAX_DEPENDENCY_LEVEL; + } + DependencyGraph graph = dependencyGraphs.get(beanId); + return graph.maxLevel <= maxLevel ? graph : graph.forLevel(maxLevel); } public int getRemovedComponents() { diff --git a/extensions/arc/deployment/src/main/resources/dev-templates/beanDependencyGraph.html b/extensions/arc/deployment/src/main/resources/dev-templates/beanDependencyGraph.html index 7abec7169c0f0..0be4624dcb02e 100644 --- a/extensions/arc/deployment/src/main/resources/dev-templates/beanDependencyGraph.html +++ b/extensions/arc/deployment/src/main/resources/dev-templates/beanDependencyGraph.html @@ -6,7 +6,7 @@ const nodes = [ {#each info:devBeanInfos.getDependencyGraph(currentRequest.getParam('beanId')).nodes} - { id:"{it.id}", kind:"{it.kind}", description:"{it.description}", root:{#if it.id == currentRequest.getParam('beanId')}true{#else}false{/if} }, + { id:"{it.id}", kind:"{it.kind}", description:"{#if info:devBeanInfos.beanDescription ne "simple"}{it.description}{#else}{it.simpleDescription}{/if}", root:{#if it.id == currentRequest.getParam('beanId')}true{#else}false{/if} }, {/each} ]; const links = [ @@ -16,11 +16,12 @@ ]; const beanId = "{currentRequest.getParam('beanId')}"; + const chartContainer = document.getElementById('beanDepGraph_container'); {| const types = ['directDependency','directDependent','dependency','lookup','producer']; - const height = 600; - const width = 1200; + const width = chartContainer.clientWidth; + const height = chartContainer.clientHeight; const color = d3.scaleOrdinal(types, d3.schemeCategory10); // Legend colors @@ -48,6 +49,7 @@ return d.source.id === beanId || d.target.id === beanId ? 150 : 75; })) .force("charge", d3.forceManyBody().strength(-400)) + .force("center", d3.forceCenter(width / 3, height / 2)) .force("x", d3.forceX()) .force("y", d3.forceY()); @@ -68,9 +70,27 @@ d.fy = event.y; } + let onZoom = function (e) { + d3.select('svg g').attr('transform', e.transform); + } + + const d3Zoom = d3.zoom() + .scaleExtent([0, 1]) + .on("zoom", onZoom); + const svg = d3.select("#beanDepGraph_area") - .attr("viewBox", [-width / 3, -height / 3, width, height]) - .style("font", "12px sans-serif"); + .attr("preserveAspectRatio", "xMinYMin meet") + .attr("viewBox", [0, 0, width, height]) + .style("font", "1rem sans-serif") + .call(d3Zoom) + .append("g"); + + d3.select("#zoom_in").on("click", function() { + d3Zoom.scaleBy(svg.transition().duration(750), 1.2); + }); + d3.select("#zoom_out").on("click", function() { + d3Zoom.scaleBy(svg.transition().duration(750), 0.8); + }); svg.append("defs").selectAll("marker") .data(types) @@ -124,11 +144,16 @@ }); |} - + {/script} + {#style} + #beanDepGraph_container { + min-height: 800px; + } + {/style} {#breadcrumbs} Beans{/breadcrumbs} {#title}Bean Dependency Graph{/title} {#body} - {#set bean=info:devBeanInfos.getBean(currentRequest.getParam('beanId'))} + {#let bean=info:devBeanInfos.getBean(currentRequest.getParam('beanId'))}
@@ -137,7 +162,7 @@
-
+
  • root
  • direct dependencies
  • @@ -145,14 +170,41 @@
  • dependencies
  • declaring bean of a producer
  • potential dependency
     programmatic lookup
  • +
  • +
    + + +
    +
  • +
  • +
    +
    + + +
    +
    +
  • +
  • +
    + + + Value 0 means only direct dependencies +
    +
-
- +
+
+ +
- {/set} + {/let} {/body} {#scriptref}