diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-step-graph.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-step-graph.js
new file mode 100644
index 00000000000000..8ad69858f757ec
--- /dev/null
+++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-step-graph.js
@@ -0,0 +1,200 @@
+import { LitElement, html, css} from 'lit';
+import { JsonRpc } from 'jsonrpc';
+import 'echarts-force-graph';
+import '@vaadin/button';
+import '@vaadin/checkbox';
+import '@vaadin/checkbox-group';
+import '@vaadin/progress-bar';
+
+/**
+ * This component shows the Build Step Graph
+ */
+export class QwcBuildStepGraph extends LitElement {
+
+ static styles = css`
+ .top-bar {
+ display: flex;
+ align-items: baseline;
+ gap: 20px;
+ padding-left: 20px;
+ justify-content: space-between;
+ padding-right: 20px;
+ }
+
+ .top-bar h4 {
+ color: var(--lumo-contrast-60pct);
+ }
+ `;
+
+ static properties = {
+ stepId: {type: String},
+ extensionName: {type: String}, // TODO: Add 'pane' concept in router to register internal extension pages.
+ _edgeLength: {type: Number, state: true},
+ _dependencyGraph: {state: true},
+ _categories: {state: false},
+ _colors: {state: false},
+ _nodes: {state: true},
+ _links: {state: false},
+ _showSimpleDescription: {state: false}
+ };
+
+ constructor() {
+ super();
+ this.stepId = null;
+ this._dependencyGraph = null;
+ this._categories = ['root' , 'direct dependencies', 'direct dependents'];
+ this._categoriesEnum = ['root' , 'directDependency' , 'directDependent'];
+ this._colors = ['#ee6666', '#5470c6' , '#fac858'];
+ this._edgeLength = 120;
+ this._nodes = null;
+ this._links = null;
+ this._showSimpleDescription = [];
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.jsonRpc = new JsonRpc(this.extensionName);
+ this._fetchDependencyGraph();
+ }
+
+ _fetchDependencyGraph(){
+ if(this.stepId){
+ this.jsonRpc.getDependencyGraph({buildStepId: this.stepId}).then(jsonRpcResponse => {
+ this._dependencyGraph = jsonRpcResponse.result;
+ this._createNodes();
+ });
+ }
+ }
+
+ _createNodes(){
+ if(this._dependencyGraph){
+
+ let dependencyGraphNodes = this._dependencyGraph.nodes;
+ let dependencyGraphLinks = this._dependencyGraph.links;
+
+ this._links = []
+ this._nodes = []
+ for (var l = 0; l < dependencyGraphLinks.length; l++) {
+ let link = new Object();
+ link.source = dependencyGraphNodes.findIndex(item => item.stepId === dependencyGraphLinks[l].source);
+ link.target = dependencyGraphNodes.findIndex(item => item.stepId === dependencyGraphLinks[l].target);
+ let catindex = this._categoriesEnum.indexOf(dependencyGraphLinks[l].type);
+
+ this._addToNodes(dependencyGraphNodes[link.source],catindex);
+ this._addToNodes(dependencyGraphNodes[link.target],catindex);
+ this._links.push(link);
+ }
+ }
+
+ }
+
+ _addToNodes(dependencyGraphNode, catindex){
+ let newNode = this._createNode(dependencyGraphNode);
+ let index = this._nodes.findIndex(item => item.name === newNode.name);
+ if (index < 0 ) {
+ if(dependencyGraphNode.stepId === this.stepId){
+ newNode.category = 0; // Root
+ }else {
+ newNode.category = catindex;
+ }
+ this._nodes.push(newNode);
+ }
+ }
+
+ _createNode(node){
+ let nodeObject = new Object();
+ if(this._showSimpleDescription.length>0){
+ nodeObject.name = node.simpleName;
+ }else{
+ nodeObject.name = node.stepId;
+ }
+
+ nodeObject.value = 1;
+ nodeObject.id = node.stepId;
+ nodeObject.description = node.simpleName;
+ return nodeObject;
+ }
+
+ render() {
+ if(this.stepId && this._dependencyGraph){
+ return html`${this._renderTopBar()}
+
+ `;
+ } else if(this.stepId) {
+ return html`
+
+
Loading Dependency Graph...
+
+
+ `;
+ } else {
+ return html`No build step provided`;
+ }
+ }
+
+ _renderTopBar(){
+ return html`
+
+
+
+ Back
+
+
${this.stepId}
+
+ ${this._renderCheckbox()}
+
+
+
+
+
+
+
+
+
`;
+ }
+
+ _renderCheckbox(){
+ return html` {
+ this._showSimpleDescription = event.detail.value;
+ this._createNodes();
+ }}">
+
+ `;
+ }
+
+ _backAction(){
+ const back = new CustomEvent("build-steps-graph-back", {
+ detail: {},
+ bubbles: true,
+ cancelable: true,
+ composed: false,
+ });
+ this.dispatchEvent(back);
+ }
+
+ _zoomIn(){
+ if(this._edgeLength>10){
+ this._edgeLength = this._edgeLength - 10;
+ }else{
+ this._edgeLength = 10;
+ }
+ }
+
+ _zoomOut(){
+ this._edgeLength = this._edgeLength + 10;
+ }
+
+ _echartClicked(e){
+ this.stepId = e.detail.id;
+ this._fetchDependencyGraph();
+ }
+}
+customElements.define('qwc-build-step-graph', QwcBuildStepGraph);
\ No newline at end of file
diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js
index bb1b358090addc..77c6faa9c6e04e 100644
--- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js
+++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js
@@ -9,6 +9,9 @@ import '@vaadin/text-field';
import '@vaadin/vertical-layout';
import '@vaadin/horizontal-layout';
import '@vaadin/progress-bar';
+import './qwc-build-step-graph.js';
+
+
/**
* This component shows the Build Steps
*/
@@ -35,27 +38,44 @@ export class QwcBuildSteps extends QwcHotReloadElement {
.datatable {
width: 100%;
- }`;
+ }
+
+ .graph-icon {
+ font-size: small;
+ color: var(--lumo-contrast-50pct);
+ cursor: pointer;
+ }
+
+ .graph {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ height: 100%;
+ }
+ `;
static properties = {
- _buildStepsMetrics: { state: true },
+ _buildMetrics: { state: true },
+ _selectedBuildStep: {state: true},
_filtered: {state: true, type: Array}
};
constructor() {
super();
+ this._buildMetrics = null;
+ this._selectedBuildStep = null;
this.hotReload();
}
hotReload(){
- this.jsonRpc.getBuildStepsMetrics().then(e => {
- this._buildStepsMetrics = e.result;
- this._filtered = this._buildStepsMetrics.records;
+ this.jsonRpc.getBuildMetrics().then(e => {
+ this._buildMetrics = e.result;
+ this._filtered = this._buildMetrics.records;
});
}
render() {
- if (this._buildStepsMetrics && this._filtered) {
+ if (this._buildMetrics && this._filtered) {
return this._render();
}else {
return html`
@@ -77,18 +97,27 @@ export class QwcBuildSteps extends QwcHotReloadElement {
_filter(e) {
const searchTerm = (e.detail.value || '').trim();
if (searchTerm === '') {
- this._filtered = this._buildStepsMetrics.records;
+ this._filtered = this._buildMetrics.records;
return;
}
- this._filtered = this._buildStepsMetrics.records.filter((record) => {
+ this._filtered = this._buildMetrics.records.filter((record) => {
return this._match(record.stepId, searchTerm);
});
}
_render() {
+ if(this._selectedBuildStep){
+ return this._renderBuildStepGraph();
+ }else{
+ return this._renderBuildStepList();
+ }
+ }
+
+ _renderBuildStepList(){
+
return html`
-
Executed ${this._buildStepsMetrics.records.length} build steps on ${Object.keys(this._buildStepsMetrics.threadSlotRecords).length} threads in ${this._buildStepsMetrics.duration} ms.
+
Executed ${this._buildMetrics.records.length} build steps on ${this._buildMetrics.numberOfThreads} threads in ${this._buildMetrics.duration} ms.
+
+
+
`;
}
+ _renderBuildStepGraph(){
+
+ return html``;
+
+ }
+
_stepIdRenderer(record) {
return html`${record.stepId}
`;
}
+
+ _graphIconRenderer(buildStep){
+ return html` this._showGraph(buildStep)}>`;
+ }
+
+ _showGraph(buildStep){
+ this._selectedBuildStep = buildStep;
+ }
+
+ _showBuildStepsList(){
+ this._selectedBuildStep = null;
+ }
+
}
customElements.define('qwc-build-steps', QwcBuildSteps);
\ No newline at end of file
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java
index 1698e90cf9fab9..b8d410bc66543a 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java
@@ -4,14 +4,45 @@
import jakarta.enterprise.context.ApplicationScoped;
-import io.smallrye.mutiny.Uni;
-import io.smallrye.mutiny.infrastructure.Infrastructure;
+import io.vertx.core.json.JsonArray;
+import io.vertx.core.json.JsonObject;
@ApplicationScoped
public class BuildMetricsJsonRPCService {
- public Uni