mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-02 15:04:38 +00:00
feat: enhance trace list graph (#459)
This commit is contained in:
parent
0ea8335fee
commit
39b4626317
9
src/types/trace.d.ts
vendored
9
src/types/trace.d.ts
vendored
@ -50,7 +50,7 @@ export interface Span {
|
|||||||
refs?: Ref[];
|
refs?: Ref[];
|
||||||
}
|
}
|
||||||
export type Ref = {
|
export type Ref = {
|
||||||
type: string;
|
type?: string;
|
||||||
parentSegmentId: string;
|
parentSegmentId: string;
|
||||||
parentSpanId: number;
|
parentSpanId: number;
|
||||||
traceId: string;
|
traceId: string;
|
||||||
@ -60,13 +60,6 @@ export interface log {
|
|||||||
data: Map<string, string>;
|
data: Map<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Ref {
|
|
||||||
traceId: string;
|
|
||||||
parentSegmentId: string;
|
|
||||||
parentSpanId: number;
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StatisticsSpan {
|
export interface StatisticsSpan {
|
||||||
groupRef: StatisticsGroupRef;
|
groupRef: StatisticsGroupRef;
|
||||||
maxTime: number;
|
maxTime: number;
|
||||||
|
@ -77,6 +77,7 @@ limitations under the License. -->
|
|||||||
border-bottom: 1px solid $border-color-primary;
|
border-bottom: 1px solid $border-color-primary;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
min-height: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.log-header {
|
.log-header {
|
||||||
|
@ -15,7 +15,24 @@ limitations under the License. -->
|
|||||||
<Icon iconName="spinner" size="sm" />
|
<Icon iconName="spinner" size="sm" />
|
||||||
</div>
|
</div>
|
||||||
<div ref="traceGraph" class="d3-graph"></div>
|
<div ref="traceGraph" class="d3-graph"></div>
|
||||||
<el-dialog v-model="showDetail" :destroy-on-close="true" fullscreen @closed="showDetail = false">
|
<div id="trace-action-box">
|
||||||
|
<div @click="showDetail = true">Span details</div>
|
||||||
|
<div v-for="span in parentSpans" :key="span.segmentId" @click="viewParentSpan(span)">
|
||||||
|
{{ `Parent span: ${span.endpointName} -> Start time: ${visDate(span.startTime)}` }}
|
||||||
|
</div>
|
||||||
|
<div v-for="span in refParentSpans" :key="span.segmentId" @click="viewParentSpan(span)">
|
||||||
|
{{ `Ref to span: ${span.endpointName} -> Start time: ${visDate(span.startTime)}` }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-dialog
|
||||||
|
v-model="showDetail"
|
||||||
|
width="60%"
|
||||||
|
center
|
||||||
|
align-center
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@closed="showDetail = false"
|
||||||
|
v-if="currentSpan?.segmentId"
|
||||||
|
>
|
||||||
<SpanDetail :currentSpan="currentSpan" />
|
<SpanDetail :currentSpan="currentSpan" />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
@ -23,6 +40,7 @@ limitations under the License. -->
|
|||||||
import { ref, watch, onBeforeUnmount, onMounted } from "vue";
|
import { ref, watch, onBeforeUnmount, onMounted } from "vue";
|
||||||
import type { PropType } from "vue";
|
import type { PropType } from "vue";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import ListGraph from "../../utils/d3-trace-list";
|
import ListGraph from "../../utils/d3-trace-list";
|
||||||
import TreeGraph from "../../utils/d3-trace-tree";
|
import TreeGraph from "../../utils/d3-trace-tree";
|
||||||
import type { Span, Ref } from "@/types/trace";
|
import type { Span, Ref } from "@/types/trace";
|
||||||
@ -42,11 +60,14 @@ limitations under the License. -->
|
|||||||
const showDetail = ref<boolean>(false);
|
const showDetail = ref<boolean>(false);
|
||||||
const fixSpansSize = ref<number>(0);
|
const fixSpansSize = ref<number>(0);
|
||||||
const segmentId = ref<Recordable[]>([]);
|
const segmentId = ref<Recordable[]>([]);
|
||||||
const currentSpan = ref<Array<Span>>([]);
|
const currentSpan = ref<Nullable<Span>>(null);
|
||||||
const refSpans = ref<Array<Ref>>([]);
|
const refSpans = ref<Array<Ref>>([]);
|
||||||
const tree = ref<Nullable<any>>(null);
|
const tree = ref<Nullable<any>>(null);
|
||||||
const traceGraph = ref<Nullable<HTMLDivElement>>(null);
|
const traceGraph = ref<Nullable<HTMLDivElement>>(null);
|
||||||
|
const parentSpans = ref<Array<Span>>([]);
|
||||||
|
const refParentSpans = ref<Array<Span>>([]);
|
||||||
const debounceFunc = debounce(draw, 500);
|
const debounceFunc = debounce(draw, 500);
|
||||||
|
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") => dayjs(date).format(pattern);
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
tree,
|
tree,
|
||||||
@ -77,16 +98,54 @@ limitations under the License. -->
|
|||||||
d3.selectAll(".d3-tip").remove();
|
d3.selectAll(".d3-tip").remove();
|
||||||
if (props.type === "List") {
|
if (props.type === "List") {
|
||||||
tree.value = new ListGraph(traceGraph.value, handleSelectSpan);
|
tree.value = new ListGraph(traceGraph.value, handleSelectSpan);
|
||||||
tree.value.init({ label: "TRACE_ROOT", children: segmentId.value }, props.data, fixSpansSize.value);
|
tree.value.init(
|
||||||
|
{ label: "TRACE_ROOT", children: segmentId.value },
|
||||||
|
getRefsAllNodes({ label: "TRACE_ROOT", children: segmentId.value }),
|
||||||
|
fixSpansSize.value,
|
||||||
|
);
|
||||||
tree.value.draw();
|
tree.value.draw();
|
||||||
} else {
|
} else {
|
||||||
tree.value = new TreeGraph(traceGraph.value, handleSelectSpan);
|
tree.value = new TreeGraph(traceGraph.value, handleSelectSpan);
|
||||||
tree.value.init({ label: `${props.traceId}`, children: segmentId.value }, props.data);
|
tree.value.init(
|
||||||
|
{ label: `${props.traceId}`, children: segmentId.value },
|
||||||
|
getRefsAllNodes({ label: "TRACE_ROOT", children: segmentId.value }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function handleSelectSpan(i: Recordable) {
|
function handleSelectSpan(i: Recordable) {
|
||||||
|
const spans = [];
|
||||||
|
const refSpans = [];
|
||||||
|
parentSpans.value = [];
|
||||||
|
refParentSpans.value = [];
|
||||||
currentSpan.value = i.data;
|
currentSpan.value = i.data;
|
||||||
showDetail.value = true;
|
if (!currentSpan.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const ref of currentSpan.value.refs || []) {
|
||||||
|
refSpans.push(ref);
|
||||||
|
}
|
||||||
|
if (currentSpan.value.parentSpanId > -1) {
|
||||||
|
spans.push({
|
||||||
|
parentSegmentId: currentSpan.value.segmentId,
|
||||||
|
parentSpanId: currentSpan.value.parentSpanId,
|
||||||
|
traceId: currentSpan.value.traceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const span of refSpans) {
|
||||||
|
const item = props.data.find(
|
||||||
|
(d) => d.segmentId === span.parentSegmentId && d.spanId === span.parentSpanId && d.traceId === span.traceId,
|
||||||
|
);
|
||||||
|
item && refParentSpans.value.push(item);
|
||||||
|
}
|
||||||
|
for (const span of spans) {
|
||||||
|
const item = props.data.find(
|
||||||
|
(d) => d.segmentId === span.parentSegmentId && d.spanId === span.parentSpanId && d.traceId === span.traceId,
|
||||||
|
);
|
||||||
|
item && parentSpans.value.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function viewParentSpan(span: Recordable) {
|
||||||
|
tree.value.highlightParents(span);
|
||||||
}
|
}
|
||||||
function traverseTree(node: Recordable, spanId: string, segmentId: string, data: Recordable) {
|
function traverseTree(node: Recordable, spanId: string, segmentId: string, data: Recordable) {
|
||||||
if (!node || node.isBroken) {
|
if (!node || node.isBroken) {
|
||||||
@ -272,21 +331,12 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const i in segmentGroup) {
|
for (const i in segmentGroup) {
|
||||||
if (segmentGroup[i].refs.length) {
|
for (const ref of segmentGroup[i].refs) {
|
||||||
let exit = null;
|
if (!segmentGroup[ref.parentSegmentId]) {
|
||||||
for (const ref of segmentGroup[i].refs) {
|
|
||||||
const e = props.data.find(
|
|
||||||
(i: Recordable) =>
|
|
||||||
ref.traceId === i.traceId && ref.parentSegmentId === i.segmentId && ref.parentSpanId === i.spanId,
|
|
||||||
);
|
|
||||||
if (e) {
|
|
||||||
exit = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (exit) {
|
|
||||||
segmentId.value.push(segmentGroup[i]);
|
segmentId.value.push(segmentGroup[i]);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
if (!segmentGroup[i].refs.length && segmentGroup[i].parentSpanId === -1) {
|
||||||
segmentId.value.push(segmentGroup[i]);
|
segmentId.value.push(segmentGroup[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,6 +360,23 @@ limitations under the License. -->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function getRefsAllNodes(tree: Recordable) {
|
||||||
|
let nodes = [];
|
||||||
|
let stack = [tree];
|
||||||
|
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const node = stack.pop();
|
||||||
|
nodes.push(node);
|
||||||
|
|
||||||
|
if (node?.children && node.children.length > 0) {
|
||||||
|
for (let i = node.children.length - 1; i >= 0; i--) {
|
||||||
|
stack.push(node.children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
function compare(p: string) {
|
function compare(p: string) {
|
||||||
return (m: Recordable, n: Recordable) => {
|
return (m: Recordable, n: Recordable) => {
|
||||||
const a = m[p];
|
const a = m[p];
|
||||||
@ -346,7 +413,7 @@ limitations under the License. -->
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.d3-graph {
|
.d3-graph {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@ -356,36 +423,41 @@ limitations under the License. -->
|
|||||||
fill-opacity: 0;
|
fill-opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.trace-node-container {
|
|
||||||
fill: rgb(0 0 0 / 0%);
|
|
||||||
stroke-width: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
fill: rgb(0 0 0 / 5%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.trace-node .node-text {
|
.trace-node .node-text {
|
||||||
font: 12.5px sans-serif;
|
font: 12px sans-serif;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.domain {
|
.trace-node.highlighted .node-text {
|
||||||
|
font-weight: bold;
|
||||||
|
fill: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trace-node.highlightedParent .node-text {
|
||||||
|
font-weight: bold;
|
||||||
|
fill: #409eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#trace-action-box {
|
||||||
|
position: absolute;
|
||||||
|
color: $font-color;
|
||||||
|
cursor: pointer;
|
||||||
|
border: var(--sw-topology-border);
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: $theme-background;
|
||||||
|
padding: 10px 0;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
|
||||||
|
|
||||||
.time-charts-item {
|
div {
|
||||||
display: inline-block;
|
height: 30px;
|
||||||
padding: 2px 8px;
|
line-height: 30px;
|
||||||
border: 1px solid;
|
text-align: left;
|
||||||
font-size: 11px;
|
padding: 0 15px;
|
||||||
border-radius: 4px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-c-text {
|
div:hover {
|
||||||
white-space: pre;
|
color: $active-color;
|
||||||
overflow: auto;
|
background-color: $popper-hover-bg-color;
|
||||||
font-family: monospace;
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -87,7 +87,14 @@ limitations under the License. -->
|
|||||||
{{ t("relatedTraceLogs") }}
|
{{ t("relatedTraceLogs") }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-dialog v-model="showEventDetail" :destroy-on-close="true" fullscreen @closed="showEventDetail = false">
|
<el-dialog
|
||||||
|
v-model="showEventDetail"
|
||||||
|
width="60%"
|
||||||
|
center
|
||||||
|
align-center
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@closed="showEventDetail = false"
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-10">
|
<div class="mb-10">
|
||||||
<span class="grey title">Name:</span>
|
<span class="grey title">Name:</span>
|
||||||
@ -115,7 +122,14 @@ limitations under the License. -->
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
<el-dialog v-model="showRelatedLogs" :destroy-on-close="true" fullscreen @closed="showRelatedLogs = false">
|
<el-dialog
|
||||||
|
v-model="showRelatedLogs"
|
||||||
|
width="60%"
|
||||||
|
center
|
||||||
|
align-center
|
||||||
|
:destroy-on-close="true"
|
||||||
|
@closed="showRelatedLogs = false"
|
||||||
|
>
|
||||||
<el-pagination
|
<el-pagination
|
||||||
v-model="pageNum"
|
v-model="pageNum"
|
||||||
:page-size="pageSize"
|
:page-size="pageSize"
|
||||||
@ -295,4 +309,10 @@ limitations under the License. -->
|
|||||||
.link {
|
.link {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.log-tips {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
margin: 50px 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -31,8 +31,10 @@ limitations under the License. -->
|
|||||||
import type { PropType } from "vue";
|
import type { PropType } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import * as d3 from "d3";
|
import * as d3 from "d3";
|
||||||
|
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||||
import type { Span } from "@/types/trace";
|
import type { Span } from "@/types/trace";
|
||||||
import Graph from "./D3Graph/Index.vue";
|
import Graph from "./D3Graph/Index.vue";
|
||||||
|
import { Themes } from "@/constants/data";
|
||||||
|
|
||||||
/* global defineProps, Recordable*/
|
/* global defineProps, Recordable*/
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -40,6 +42,7 @@ limitations under the License. -->
|
|||||||
traceId: { type: String, default: "" },
|
traceId: { type: String, default: "" },
|
||||||
});
|
});
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const appStore = useAppStoreWithOut();
|
||||||
const list = computed(() => Array.from(new Set(props.data.map((i: Span) => i.serviceCode))));
|
const list = computed(() => Array.from(new Set(props.data.map((i: Span) => i.serviceCode))));
|
||||||
|
|
||||||
function computedScale(i: number) {
|
function computedScale(i: number) {
|
||||||
@ -52,13 +55,13 @@ limitations under the License. -->
|
|||||||
|
|
||||||
function downloadTrace() {
|
function downloadTrace() {
|
||||||
const serializer = new XMLSerializer();
|
const serializer = new XMLSerializer();
|
||||||
const svgNode: any = d3.select(".trace-list-dowanload").node();
|
const svgNode: any = d3.select(".trace-list").node();
|
||||||
const source = `<?xml version="1.0" standalone="no"?>\r\n${serializer.serializeToString(svgNode)}`;
|
const source = `<?xml version="1.0" standalone="no"?>\r\n${serializer.serializeToString(svgNode)}`;
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
const context: any = canvas.getContext("2d");
|
const context: any = canvas.getContext("2d");
|
||||||
canvas.width = (d3.select(".trace-list-dowanload") as Recordable)._groups[0][0].clientWidth;
|
canvas.width = (d3.select(".trace-list") as Recordable)._groups[0][0].clientWidth;
|
||||||
canvas.height = (d3.select(".trace-list-dowanload") as Recordable)._groups[0][0].clientHeight;
|
canvas.height = (d3.select(".trace-list") as Recordable)._groups[0][0].clientHeight;
|
||||||
context.fillStyle = "#fff";
|
context.fillStyle = appStore.theme === Themes.Dark ? "#212224" : `#fff`;
|
||||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`;
|
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`;
|
||||||
@ -93,6 +96,7 @@ limitations under the License. -->
|
|||||||
|
|
||||||
.list {
|
.list {
|
||||||
height: calc(100% - 150px);
|
height: calc(100% - 150px);
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.event-tag {
|
.event-tag {
|
||||||
|
@ -89,12 +89,6 @@ limitations under the License. -->
|
|||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.dialog-c-text {
|
|
||||||
white-space: pre;
|
|
||||||
overflow: auto;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trace-tips {
|
.trace-tips {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -42,16 +42,17 @@ export default class ListGraph {
|
|||||||
private xAxis: any = null;
|
private xAxis: any = null;
|
||||||
private sequentialScale: any = null;
|
private sequentialScale: any = null;
|
||||||
private root: any = null;
|
private root: any = null;
|
||||||
|
private selectedNode: any = null;
|
||||||
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
|
constructor(el: HTMLDivElement, handleSelectSpan: (i: Trace) => void) {
|
||||||
this.handleSelectSpan = handleSelectSpan;
|
this.handleSelectSpan = handleSelectSpan;
|
||||||
this.el = el;
|
this.el = el;
|
||||||
this.width = el.getBoundingClientRect().width - 10;
|
this.width = el.getBoundingClientRect().width - 10;
|
||||||
this.height = el.getBoundingClientRect().height - 10;
|
this.height = el.getBoundingClientRect().height - 10;
|
||||||
d3.select(".trace-list-dowanload").remove();
|
d3.select(`.${this.el?.className} .trace-list`).remove();
|
||||||
this.svg = d3
|
this.svg = d3
|
||||||
.select(this.el)
|
.select(this.el)
|
||||||
.append("svg")
|
.append("svg")
|
||||||
.attr("class", "trace-list-dowanload")
|
.attr("class", "trace-list")
|
||||||
.attr("width", this.width > 0 ? this.width : 10)
|
.attr("width", this.width > 0 ? this.width : 10)
|
||||||
.attr("height", this.height > 0 ? this.height : 10)
|
.attr("height", this.height > 0 ? this.height : 10)
|
||||||
.attr("transform", `translate(-5, 0)`);
|
.attr("transform", `translate(-5, 0)`);
|
||||||
@ -85,7 +86,8 @@ export default class ListGraph {
|
|||||||
L${d.target.y} ${d.target.x - 5}`;
|
L${d.target.y} ${d.target.x - 5}`;
|
||||||
}
|
}
|
||||||
init(data: Recordable, row: Recordable[], fixSpansSize: number) {
|
init(data: Recordable, row: Recordable[], fixSpansSize: number) {
|
||||||
d3.select(".trace-xaxis").remove();
|
d3.select(`.${this.el?.className} .trace-xaxis`).remove();
|
||||||
|
d3.select("#trace-action-box").style("display", "none");
|
||||||
this.row = row;
|
this.row = row;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.min = d3.min(this.row.map((i) => i.startTime));
|
this.min = d3.min(this.row.map((i) => i.startTime));
|
||||||
@ -142,19 +144,43 @@ export default class ListGraph {
|
|||||||
.enter()
|
.enter()
|
||||||
.append("g")
|
.append("g")
|
||||||
.attr("transform", `translate(${source.y0},${source.x0})`)
|
.attr("transform", `translate(${source.y0},${source.x0})`)
|
||||||
|
.attr("id", (d: Recordable) => `list-node-${d.id}`)
|
||||||
.attr("class", "trace-node")
|
.attr("class", "trace-node")
|
||||||
.attr("style", "cursor: pointer")
|
.attr("style", "cursor: pointer")
|
||||||
.style("opacity", 0)
|
|
||||||
.on("mouseover", function (event: MouseEvent, d: Trace) {
|
.on("mouseover", function (event: MouseEvent, d: Trace) {
|
||||||
t.tip.show(d, this);
|
t.tip.show(d, this);
|
||||||
})
|
})
|
||||||
.on("mouseout", function (event: MouseEvent, d: Trace) {
|
.on("mouseout", function (event: MouseEvent, d: Trace) {
|
||||||
t.tip.hide(d, this);
|
t.tip.hide(d, this);
|
||||||
})
|
})
|
||||||
.on("click", (event: MouseEvent, d: Trace) => {
|
.on("click", function (event: MouseEvent, d: Trace & { id: string }) {
|
||||||
if (this.handleSelectSpan) {
|
event.stopPropagation();
|
||||||
this.handleSelectSpan(d);
|
const hasClass = d3.select(this).classed("highlighted");
|
||||||
|
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);
|
||||||
|
const nodeBox = this.getBoundingClientRect();
|
||||||
|
const svgBox = (d3.select(`.${t.el?.className} .trace-list`) 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);
|
||||||
|
if (t.handleSelectSpan) {
|
||||||
|
t.handleSelectSpan(d);
|
||||||
|
}
|
||||||
|
t.root.descendants().map((node: { id: number }) => {
|
||||||
|
d3.select(`#list-node-${node.id}`).classed("highlightedParent", false);
|
||||||
|
return node;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
nodeEnter
|
nodeEnter
|
||||||
.append("rect")
|
.append("rect")
|
||||||
@ -246,7 +272,7 @@ export default class ListGraph {
|
|||||||
})
|
})
|
||||||
.attr("cy", -5)
|
.attr("cy", -5)
|
||||||
.attr("fill", "none")
|
.attr("fill", "none")
|
||||||
.attr("stroke", appStore.theme === Themes.Dark ? "#666" : "#e66")
|
.attr("stroke", "#e66")
|
||||||
.style("opacity", (d: Recordable) => {
|
.style("opacity", (d: Recordable) => {
|
||||||
const events = d.data.attachedEvents;
|
const events = d.data.attachedEvents;
|
||||||
if (events && events.length) {
|
if (events && events.length) {
|
||||||
@ -259,7 +285,7 @@ export default class ListGraph {
|
|||||||
.append("text")
|
.append("text")
|
||||||
.attr("x", 267)
|
.attr("x", 267)
|
||||||
.attr("y", -1)
|
.attr("y", -1)
|
||||||
.attr("fill", appStore.theme === Themes.Dark ? "#666" : "#e66")
|
.attr("fill", "#e66")
|
||||||
.style("font-size", "10px")
|
.style("font-size", "10px")
|
||||||
.text((d: Recordable) => {
|
.text((d: Recordable) => {
|
||||||
const events = d.data.attachedEvents;
|
const events = d.data.attachedEvents;
|
||||||
@ -381,6 +407,36 @@ export default class ListGraph {
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
highlightParents(span: Recordable) {
|
||||||
|
if (!span) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const nodes = this.root.descendants().map((node: { id: number }) => {
|
||||||
|
d3.select(`#list-node-${node.id}`).classed("highlightedParent", false);
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
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(`#list-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(`#list-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",
|
||||||
|
});
|
||||||
|
}
|
||||||
visDate(date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") {
|
visDate(date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") {
|
||||||
return dayjs(date).format(pattern);
|
return dayjs(date).format(pattern);
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ export default class TraceMap {
|
|||||||
this.topChild = [];
|
this.topChild = [];
|
||||||
this.width = el.clientWidth - 20;
|
this.width = el.clientWidth - 20;
|
||||||
this.height = el.clientHeight - 30;
|
this.height = el.clientHeight - 30;
|
||||||
d3.select(".d3-trace-tree").remove();
|
d3.select(`.${this.el?.className} .d3-trace-tree`).remove();
|
||||||
this.body = d3
|
this.body = d3
|
||||||
.select(this.el)
|
.select(this.el)
|
||||||
.append("svg")
|
.append("svg")
|
||||||
|
Loading…
Reference in New Issue
Block a user