mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-02 15:24:51 +00:00
feat: enhance the trace tree to support displaying multiple refs of spans and distinguishing different parents. (#462)
This commit is contained in:
parent
df2d07f508
commit
5c322d960f
@ -428,12 +428,21 @@ limitations under the License. -->
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trace-node.highlighted .node-text {
|
.trace-node.highlighted .node-text,
|
||||||
|
.trace-node.highlightedParent .node-text {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
fill: #409eff;
|
fill: #409eff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trace-node.highlightedParent .node-text {
|
.highlightedParent .node,
|
||||||
|
.highlighted .node {
|
||||||
|
stroke-width: 4;
|
||||||
|
fill: var(--font-color);
|
||||||
|
stroke: var(--font-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trace-node.highlighted .trace-node-text,
|
||||||
|
.trace-node.highlightedParent .trace-node-text {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
fill: #409eff;
|
fill: #409eff;
|
||||||
}
|
}
|
||||||
|
@ -115,6 +115,14 @@ export default class ListGraph {
|
|||||||
this.root = d3.hierarchy(this.data, (d) => d.children);
|
this.root = d3.hierarchy(this.data, (d) => d.children);
|
||||||
this.root.x0 = 0;
|
this.root.x0 = 0;
|
||||||
this.root.y0 = 0;
|
this.root.y0 = 0;
|
||||||
|
const t = this;
|
||||||
|
d3.select("svg.trace-list").on("click", function (event: MouseEvent) {
|
||||||
|
if (event.target === this) {
|
||||||
|
d3.select("#trace-action-box").style("display", "none");
|
||||||
|
t.selectedNode && t.selectedNode.classed("highlighted", false);
|
||||||
|
t.clearParentHighlight();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
draw(callback: Function) {
|
draw(callback: Function) {
|
||||||
this.update(this.root, callback);
|
this.update(this.root, callback);
|
||||||
@ -155,15 +163,7 @@ export default class ListGraph {
|
|||||||
})
|
})
|
||||||
.on("click", function (event: MouseEvent, d: Trace & { id: string }) {
|
.on("click", function (event: MouseEvent, d: Trace & { id: string }) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const hasClass = d3.select(this).classed("highlighted");
|
t.tip.hide(d, this);
|
||||||
if (t.selectedNode) {
|
|
||||||
t.selectedNode.classed("highlighted", false);
|
|
||||||
d3.select("#trace-action-box").style("display", "none");
|
|
||||||
}
|
|
||||||
if (hasClass) {
|
|
||||||
t.selectedNode = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
d3.select(this).classed("highlighted", true);
|
d3.select(this).classed("highlighted", true);
|
||||||
const nodeBox = this.getBoundingClientRect();
|
const nodeBox = this.getBoundingClientRect();
|
||||||
const svgBox = (d3.select(`.${t.el?.className} .trace-list`) as any).node().getBoundingClientRect();
|
const svgBox = (d3.select(`.${t.el?.className} .trace-list`) as any).node().getBoundingClientRect();
|
||||||
@ -407,14 +407,17 @@ export default class ListGraph {
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clearParentHighlight() {
|
||||||
|
return this.root.descendants().map((node: { id: number }) => {
|
||||||
|
d3.select(`#list-node-${node.id}`).classed("highlightedParent", false);
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
}
|
||||||
highlightParents(span: Recordable) {
|
highlightParents(span: Recordable) {
|
||||||
if (!span) {
|
if (!span) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nodes = this.root.descendants().map((node: { id: number }) => {
|
const nodes = this.clearParentHighlight();
|
||||||
d3.select(`#list-node-${node.id}`).classed("highlightedParent", false);
|
|
||||||
return node;
|
|
||||||
});
|
|
||||||
const parentSpan = nodes.find(
|
const parentSpan = nodes.find(
|
||||||
(node: Recordable) =>
|
(node: Recordable) =>
|
||||||
span.spanId === node.data.spanId &&
|
span.spanId === node.data.spanId &&
|
||||||
|
@ -46,6 +46,7 @@ export default class TraceMap {
|
|||||||
private topChildMax: number[] = [];
|
private topChildMax: number[] = [];
|
||||||
private topChildMin: number[] = [];
|
private topChildMin: number[] = [];
|
||||||
private nodeUpdate: Nullable<any> = null;
|
private nodeUpdate: Nullable<any> = null;
|
||||||
|
private selectedNode: any = null;
|
||||||
|
|
||||||
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
|
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
|
||||||
this.el = el;
|
this.el = el;
|
||||||
@ -80,6 +81,7 @@ export default class TraceMap {
|
|||||||
this.svg.call(this.tip);
|
this.svg.call(this.tip);
|
||||||
}
|
}
|
||||||
init(data: Recordable, row: Recordable) {
|
init(data: Recordable, row: Recordable) {
|
||||||
|
d3.select("#trace-action-box").style("display", "none");
|
||||||
this.treemap = d3.tree().size([row.length * 35, this.width]);
|
this.treemap = d3.tree().size([row.length * 35, this.width]);
|
||||||
this.row = row;
|
this.row = row;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
@ -124,24 +126,33 @@ export default class TraceMap {
|
|||||||
this.update(this.root);
|
this.update(this.root);
|
||||||
}
|
}
|
||||||
update(source: Recordable) {
|
update(source: Recordable) {
|
||||||
|
const t = this;
|
||||||
const appStore = useAppStoreWithOut();
|
const appStore = useAppStoreWithOut();
|
||||||
const that: any = this;
|
const that: any = this;
|
||||||
const treeData = this.treemap(this.root);
|
const treeData = this.treemap(this.root);
|
||||||
const nodes = treeData.descendants(),
|
const nodes = treeData.descendants();
|
||||||
links = treeData.descendants().slice(1);
|
const links = treeData.descendants().slice(1);
|
||||||
|
|
||||||
nodes.forEach(function (d: Recordable) {
|
nodes.forEach(function (d: Recordable) {
|
||||||
d.y = d.depth * 140;
|
d.y = d.depth * 140;
|
||||||
});
|
});
|
||||||
|
|
||||||
const node = this.svg.selectAll("g.node").data(nodes, (d: Recordable) => {
|
const node = this.svg.selectAll("g.trace-node").data(nodes, (d: Recordable) => {
|
||||||
return d.id || (d.id = ++this.i);
|
return d.id || (d.id = ++this.i);
|
||||||
});
|
});
|
||||||
|
d3.select("svg.d3-trace-tree").on("click", function (event: MouseEvent) {
|
||||||
|
if (event.target === this) {
|
||||||
|
d3.select("#trace-action-box").style("display", "none");
|
||||||
|
t.selectedNode && t.selectedNode.classed("highlighted", false);
|
||||||
|
t.clearParentHighlight();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const nodeEnter = node
|
const nodeEnter = node
|
||||||
.enter()
|
.enter()
|
||||||
.append("g")
|
.append("g")
|
||||||
.attr("class", "node")
|
.attr("class", "trace-node")
|
||||||
|
.attr("id", (d: Recordable) => `trace-node-${d.id}`)
|
||||||
.attr("cursor", "pointer")
|
.attr("cursor", "pointer")
|
||||||
.attr("transform", function () {
|
.attr("transform", function () {
|
||||||
return "translate(" + source.y0 + "," + source.x0 + ")";
|
return "translate(" + source.y0 + "," + source.x0 + ")";
|
||||||
@ -165,9 +176,6 @@ export default class TraceMap {
|
|||||||
if (_node.length) {
|
if (_node.length) {
|
||||||
that.timeTip.hide(d, _node[0].children[1]);
|
that.timeTip.hide(d, _node[0].children[1]);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.on("click", function (event: MouseEvent, d: Recordable) {
|
|
||||||
that.handleSelectSpan(d);
|
|
||||||
});
|
});
|
||||||
nodeEnter
|
nodeEnter
|
||||||
.append("circle")
|
.append("circle")
|
||||||
@ -208,15 +216,15 @@ export default class TraceMap {
|
|||||||
nodeEnter
|
nodeEnter
|
||||||
.append("circle")
|
.append("circle")
|
||||||
.attr("class", "node")
|
.attr("class", "node")
|
||||||
.attr("r", 1e-6)
|
.attr("r", 2)
|
||||||
.style("fill", (d: Recordable) =>
|
|
||||||
d._children ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
|
|
||||||
)
|
|
||||||
.attr("stroke", (d: Recordable) => this.sequentialScale(this.list.indexOf(d.data.serviceCode)))
|
.attr("stroke", (d: Recordable) => this.sequentialScale(this.list.indexOf(d.data.serviceCode)))
|
||||||
.attr("stroke-width", 2.5);
|
.attr("stroke-width", 2.5)
|
||||||
|
.attr("fill", (d: Recordable) =>
|
||||||
|
d.data.children.length ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
|
||||||
|
);
|
||||||
nodeEnter
|
nodeEnter
|
||||||
.append("text")
|
.append("text")
|
||||||
|
.attr("class", "trace-node-text")
|
||||||
.attr("font-size", 11)
|
.attr("font-size", 11)
|
||||||
.attr("dy", "-0.5em")
|
.attr("dy", "-0.5em")
|
||||||
.attr("x", function (d: Recordable) {
|
.attr("x", function (d: Recordable) {
|
||||||
@ -230,24 +238,27 @@ export default class TraceMap {
|
|||||||
? (d.data.isError ? "◉ " : "") + d.data.label.slice(0, 10) + "..."
|
? (d.data.isError ? "◉ " : "") + d.data.label.slice(0, 10) + "..."
|
||||||
: (d.data.isError ? "◉ " : "") + d.data.label,
|
: (d.data.isError ? "◉ " : "") + d.data.label,
|
||||||
)
|
)
|
||||||
.style("fill", (d: Recordable) =>
|
.attr("fill", (d: Recordable) =>
|
||||||
!d.data.isError ? (appStore.theme === Themes.Dark ? "#eee" : "#3d444f") : "#E54C17",
|
!d.data.isError ? (appStore.theme === Themes.Dark ? "#eee" : "#3d444f") : "#E54C17",
|
||||||
);
|
);
|
||||||
nodeEnter
|
nodeEnter
|
||||||
.append("text")
|
.append("text")
|
||||||
.attr("class", "node-text")
|
.attr("class", "node-text")
|
||||||
.attr("x", function (d: Recordable) {
|
.attr("x", function (d: Recordable) {
|
||||||
return d.children || d._children ? -45 : 15;
|
return d.children || d._children ? -30 : 15;
|
||||||
})
|
})
|
||||||
.attr("dy", "1em")
|
.attr("dy", "1.5em")
|
||||||
.attr("fill", appStore.theme === Themes.Dark ? "#888" : "#bbb")
|
.attr("fill", appStore.theme === Themes.Dark ? "#888" : "#bbb")
|
||||||
.attr("text-anchor", function (d: Recordable) {
|
.attr("text-anchor", function (d: Recordable) {
|
||||||
return d.children || d._children ? "end" : "start";
|
return d.children || d._children ? "end" : "start";
|
||||||
})
|
})
|
||||||
.style("font-size", "10px")
|
.style("font-size", "10px")
|
||||||
.text(
|
.text((d: Recordable) => {
|
||||||
(d: Recordable) => `${d.data.layer || ""}${d.data.component ? "-" + d.data.component : d.data.component || ""}`,
|
const label = d.data.component
|
||||||
);
|
? " - " + (d.data.component.length > 10 ? d.data.label.slice(0, 10) + "..." : d.data.component)
|
||||||
|
: "";
|
||||||
|
return `${d.data.layer || ""}${label}`;
|
||||||
|
});
|
||||||
nodeEnter
|
nodeEnter
|
||||||
.append("rect")
|
.append("rect")
|
||||||
.attr("rx", 1)
|
.attr("rx", 1)
|
||||||
@ -290,12 +301,37 @@ export default class TraceMap {
|
|||||||
nodeUpdate
|
nodeUpdate
|
||||||
.select("circle.node")
|
.select("circle.node")
|
||||||
.attr("r", 5)
|
.attr("r", 5)
|
||||||
.style("fill", (d: Recordable) =>
|
|
||||||
d._children ? this.sequentialScale(this.list.indexOf(d.data.serviceCode)) : "#fff",
|
|
||||||
)
|
|
||||||
.attr("cursor", "pointer")
|
.attr("cursor", "pointer")
|
||||||
.on("click", (event: any, d: Recordable) => {
|
.on("click", function (event: MouseEvent, d: Trace & { id: string }) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
t.tip.hide(d, this);
|
||||||
|
d3.select(this.parentNode).classed("highlighted", true);
|
||||||
|
const nodeBox = this.getBoundingClientRect();
|
||||||
|
const svgBox = (d3.select(`.${t.el?.className} .d3-trace-tree`) as any).node().getBoundingClientRect();
|
||||||
|
const offsetX = nodeBox.x - svgBox.x;
|
||||||
|
const offsetY = nodeBox.y - svgBox.y;
|
||||||
|
d3.select("#trace-action-box")
|
||||||
|
.style("display", "block")
|
||||||
|
.style("left", `${offsetX + 30}px`)
|
||||||
|
.style("top", `${offsetY + 40}px`);
|
||||||
|
t.selectedNode = d3.select(this.parentNode);
|
||||||
|
if (t.handleSelectSpan) {
|
||||||
|
t.handleSelectSpan(d);
|
||||||
|
}
|
||||||
|
t.root.descendants().map((node: { id: number }) => {
|
||||||
|
d3.select(`#trace-node-${node.id}`).classed("highlightedParent", false);
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.on("dblclick", function (event: MouseEvent, d: Recordable) {
|
||||||
|
event.stopPropagation();
|
||||||
|
t.tip.hide(d, this);
|
||||||
|
if (d.data.children.length === 0) return;
|
||||||
|
click(d);
|
||||||
|
})
|
||||||
|
.on("contextmenu", function (event: MouseEvent, d: Recordable) {
|
||||||
|
event.stopPropagation();
|
||||||
|
t.tip.hide(d, this);
|
||||||
if (d.data.children.length === 0) return;
|
if (d.data.children.length === 0) return;
|
||||||
click(d);
|
click(d);
|
||||||
});
|
});
|
||||||
@ -369,6 +405,39 @@ export default class TraceMap {
|
|||||||
that.update(d);
|
that.update(d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
clearParentHighlight() {
|
||||||
|
return this.root.descendants().map((node: { id: number }) => {
|
||||||
|
d3.select(`#trace-node-${node.id}`).classed("highlightedParent", false);
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
highlightParents(span: Recordable) {
|
||||||
|
if (!span) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nodes = this.clearParentHighlight();
|
||||||
|
const parentSpan = nodes.find(
|
||||||
|
(node: Recordable) =>
|
||||||
|
span.spanId === node.data.spanId &&
|
||||||
|
span.segmentId === node.data.segmentId &&
|
||||||
|
span.traceId === node.data.traceId,
|
||||||
|
);
|
||||||
|
if (!parentSpan) return;
|
||||||
|
d3.select(`#trace-node-${parentSpan.id}`).classed("highlightedParent", true);
|
||||||
|
d3.select("#trace-action-box").style("display", "none");
|
||||||
|
this.selectedNode.classed("highlighted", false);
|
||||||
|
const container = document.querySelector(".trace-chart .charts");
|
||||||
|
const containerRect = container?.getBoundingClientRect();
|
||||||
|
if (!containerRect) return;
|
||||||
|
const targetElement = document.querySelector(`#trace-node-${parentSpan.id}`);
|
||||||
|
if (!targetElement) return;
|
||||||
|
const targetRect = targetElement.getBoundingClientRect();
|
||||||
|
container?.scrollTo({
|
||||||
|
left: targetRect.left - containerRect.left + container?.scrollLeft,
|
||||||
|
top: targetRect.top - containerRect.top + container?.scrollTop - 100,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
setDefault() {
|
setDefault() {
|
||||||
d3.selectAll(".time-inner").style("opacity", 1);
|
d3.selectAll(".time-inner").style("opacity", 1);
|
||||||
d3.selectAll(".time-inner-duration").style("opacity", 0);
|
d3.selectAll(".time-inner-duration").style("opacity", 0);
|
||||||
|
Loading…
Reference in New Issue
Block a user