update trace table

This commit is contained in:
Fine 2025-04-10 16:16:51 +08:00
parent 20048e71c0
commit f80bd95bef
9 changed files with 93 additions and 131 deletions

View File

@ -43,7 +43,6 @@ limitations under the License. -->
<Table <Table
:data="profileStore.segmentSpans" :data="profileStore.segmentSpans"
:traceId="profileStore.currentSegment.traceId" :traceId="profileStore.currentSegment.traceId"
:showBtnDetail="true"
headerType="profile" headerType="profile"
@select="selectSpan" @select="selectSpan"
/> />
@ -53,7 +52,7 @@ limitations under the License. -->
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import Table from "../../trace/components/Table/Index.vue"; import Table from "../../trace/components/Table.vue";
import { useProfileStore } from "@/store/modules/profile"; import { useProfileStore } from "@/store/modules/profile";
import Selector from "@/components/Selector.vue"; import Selector from "@/components/Selector.vue";
import type { Span } from "@/types/trace"; import type { Span } from "@/types/trace";

View File

@ -14,7 +14,17 @@ limitations under the License. -->
<div class="trace-t-loading" v-show="loading"> <div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" /> <Icon iconName="spinner" size="sm" />
</div> </div>
<div ref="traceGraph" class="d3-graph"></div> <TableContainer
v-if="type === TraceGraphType.TABLE"
:tableData="segmentId"
:type="type"
:headerType="headerType"
:traceId="traceId"
@select="handleSelectSpan"
>
<div class="trace-tips" v-if="!segmentId.length">{{ $t("noData") }}</div>
</TableContainer>
<div v-else ref="traceGraph" class="d3-graph"></div>
<div id="trace-action-box"> <div id="trace-action-box">
<div @click="showDetail = true">Span details</div> <div @click="showDetail = true">Span details</div>
<div v-for="span in parentSpans" :key="span.segmentId" @click="viewParentSpan(span)"> <div v-for="span in parentSpans" :key="span.segmentId" @click="viewParentSpan(span)">
@ -45,6 +55,7 @@ limitations under the License. -->
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";
import SpanDetail from "./SpanDetail.vue"; import SpanDetail from "./SpanDetail.vue";
import TableContainer from "../Table/TableContainer.vue";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { debounce } from "@/utils/debounce"; import { debounce } from "@/utils/debounce";
import { mutationObserver } from "@/utils/mutation"; import { mutationObserver } from "@/utils/mutation";
@ -55,6 +66,7 @@ limitations under the License. -->
data: { type: Array as PropType<Span[]>, default: () => [] }, data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" }, traceId: { type: String, default: "" },
type: { type: String, default: TraceGraphType.LIST }, type: { type: String, default: TraceGraphType.LIST },
headerType: { type: String, default: "" },
}); });
const appStore = useAppStoreWithOut(); const appStore = useAppStoreWithOut();
const loading = ref<boolean>(false); const loading = ref<boolean>(false);
@ -76,25 +88,21 @@ limitations under the License. -->
onMounted(() => { onMounted(() => {
loading.value = true; loading.value = true;
changeTree(); changeTree();
if (!traceGraph.value) {
loading.value = false;
return;
}
if (props.type !== TraceGraphType.TABLE) {
draw(); draw();
}
loading.value = false; loading.value = false;
// monitor segment list width changes. // monitor segment list width changes.
mutationObserver.create("trigger-resize", () => { mutationObserver.create("trigger-resize", () => {
d3.selectAll(".d3-tip").remove(); d3.selectAll(".d3-tip").remove();
debounceFunc(); debounceFunc();
}); });
window.addEventListener("resize", debounceFunc); window.addEventListener("resize", debounceFunc);
}); });
function draw() { function draw() {
if (props.type === TraceGraphType.TABLE) {
segmentId.value = setLevel(segmentId.value);
return;
}
if (!traceGraph.value) { if (!traceGraph.value) {
return; return;
} }
@ -107,6 +115,7 @@ limitations under the License. -->
fixSpansSize.value, fixSpansSize.value,
); );
tree.value.draw(); tree.value.draw();
return;
} }
if (props.type === TraceGraphType.TREE) { if (props.type === TraceGraphType.TREE) {
tree.value = new TreeGraph(traceGraph.value, handleSelectSpan); tree.value = new TreeGraph(traceGraph.value, handleSelectSpan);
@ -117,6 +126,7 @@ limitations under the License. -->
} }
} }
function handleSelectSpan(i: Recordable) { function handleSelectSpan(i: Recordable) {
console.log(i);
const spans = []; const spans = [];
const refSpans = []; const refSpans = [];
parentSpans.value = []; parentSpans.value = [];
@ -364,6 +374,17 @@ limitations under the License. -->
} }
} }
} }
function setLevel(arr: Recordable[], level = 1, totalExec?: number) {
for (const item of arr) {
item.level = level;
totalExec = totalExec || item.endTime - item.startTime;
item.totalExec = totalExec;
if (item.children && item.children.length > 0) {
setLevel(item.children, level + 1, totalExec);
}
}
return arr;
}
function getRefsAllNodes(tree: Recordable) { function getRefsAllNodes(tree: Recordable) {
let nodes = []; let nodes = [];
let stack = [tree]; let stack = [tree];

View File

@ -17,7 +17,7 @@ limitations under the License. -->
<div class="trace-t-loading" v-show="loading"> <div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" /> <Icon iconName="spinner" size="sm" />
</div> </div>
<TableContainer :tableData="tableData" type="statistics" :HeaderType="HeaderType"> <TableContainer :tableData="tableData" :type="TraceGraphType.STATISTICS" :HeaderType="HeaderType">
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div> <div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
</TableContainer> </TableContainer>
</div> </div>
@ -28,6 +28,7 @@ limitations under the License. -->
import TableContainer from "./Table/TableContainer.vue"; import TableContainer from "./Table/TableContainer.vue";
import traceTable from "../utils/trace-table"; import traceTable from "../utils/trace-table";
import type { StatisticsSpan, Span, StatisticsGroupRef } from "@/types/trace"; import type { StatisticsSpan, Span, StatisticsGroupRef } from "@/types/trace";
import { TraceGraphType } from "./constant";
/* global defineProps, defineEmits, Recordable*/ /* global defineProps, defineEmits, Recordable*/
const props = defineProps({ const props = defineProps({

View File

@ -0,0 +1,38 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="charts">
<Graph :data="data" :traceId="traceId" :type="TraceGraphType.TABLE" :headerType="headerType" />
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import type { Span } from "@/types/trace";
import Graph from "./D3Graph/Index.vue";
import { TraceGraphType } from "./constant";
/* global defineProps, Recordable*/
defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
headerType: { type: String, default: "" },
});
</script>
<style lang="scss" scoped>
.charts {
overflow: auto;
padding: 10px;
height: 100%;
width: 100%;
}
</style>

View File

@ -1,102 +0,0 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="trace-table">
<div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" />
</div>
<TableContainer
:tableData="tableData"
type="table"
:headerType="headerType"
:traceId="traceId"
@select="handleSelectSpan"
>
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
</TableContainer>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
import type { PropType } from "vue";
import TableContainer from "./TableContainer.vue";
import traceTable from "../../utils/trace-table";
import type { Span } from "@/types/trace";
/* global defineProps, defineEmits, Recordable */
const props = defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
showBtnDetail: { type: Boolean, default: false },
headerType: { type: String, default: "" },
});
const emit = defineEmits(["select", "view", "load"]);
const loading = ref<boolean>(true);
const tableData = ref<Recordable[]>([]);
const showDetail = ref<boolean>(false);
const currentSpan = ref<Span | Recordable>({});
onMounted(() => {
tableData.value = formatData(traceTable.changeTree(props.data, props.traceId));
loading.value = false;
emit("load", () => {
loading.value = true;
});
});
function formatData(arr: Recordable[], level = 1, totalExec?: number) {
for (const item of arr) {
item.level = level;
totalExec = totalExec || item.endTime - item.startTime;
item.totalExec = totalExec;
if (item.children && item.children.length > 0) {
formatData(item.children, level + 1, totalExec);
}
}
return arr;
}
function handleSelectSpan(data: Span) {
currentSpan.value = data;
if (!props.showBtnDetail) {
showDetail.value = true;
}
emit("select", data);
}
watch(
() => props.data,
() => {
if (!props.data.length) {
tableData.value = [];
return;
}
tableData.value = formatData(traceTable.changeTree(props.data, props.traceId));
loading.value = false;
},
);
</script>
<style lang="scss" scoped>
.trace-tips {
width: 100%;
text-align: center;
margin-top: 10px;
}
.trace-table {
height: 100%;
width: 100%;
}
</style>

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="trace"> <div class="trace-table">
<div class="trace-header" v-if="type === 'statistics'"> <div class="trace-table-header" v-if="type === TraceGraphType.STATISTICS">
<div :class="item.label" v-for="(item, index) in headerData" :key="index"> <div :class="item.label" v-for="(item, index) in headerData" :key="index">
{{ item.value }} {{ item.value }}
<span <span
@ -28,7 +28,7 @@ limitations under the License. -->
</span> </span>
</div> </div>
</div> </div>
<div class="trace-header" v-else> <div class="trace-table-header" v-else>
<div class="method" :style="`width: ${method}px`"> <div class="method" :style="`width: ${method}px`">
<span class="cp dragger" ref="dragger"> <span class="cp dragger" ref="dragger">
<Icon iconName="settings_ethernet" size="sm" /> <Icon iconName="settings_ethernet" size="sm" />
@ -44,7 +44,7 @@ limitations under the License. -->
:traceId="traceId" :traceId="traceId"
v-for="(item, index) in tableData" v-for="(item, index) in tableData"
:data="item" :data="item"
:key="'key' + index" :key="`key${index}`"
:type="type" :type="type"
:headerType="headerType" :headerType="headerType"
@select="selectItem" @select="selectItem"
@ -58,6 +58,7 @@ limitations under the License. -->
import type { Span } from "@/types/trace"; import type { Span } from "@/types/trace";
import TableItem from "./TableItem.vue"; import TableItem from "./TableItem.vue";
import { ProfileConstant, TraceConstant, StatisticsConstant } from "./data"; import { ProfileConstant, TraceConstant, StatisticsConstant } from "./data";
import { TraceGraphType } from "../constant";
/* global defineProps, Nullable, defineEmits, Recordable*/ /* global defineProps, Nullable, defineEmits, Recordable*/
const props = defineProps({ const props = defineProps({
@ -76,11 +77,11 @@ limitations under the License. -->
if (props.headerType === "profile") { if (props.headerType === "profile") {
headerData = ProfileConstant; headerData = ProfileConstant;
} }
if (props.type === "statistics") { if (props.type === TraceGraphType.STATISTICS) {
headerData = StatisticsConstant; headerData = StatisticsConstant;
} }
onMounted(() => { onMounted(() => {
if (props.type === "statistics") { if (props.type === TraceGraphType.STATISTICS) {
return; return;
} }
const drag: Nullable<HTMLSpanElement> = dragger.value; const drag: Nullable<HTMLSpanElement> = dragger.value;
@ -102,6 +103,7 @@ limitations under the License. -->
}; };
}); });
function selectItem(span: Span) { function selectItem(span: Span) {
console.log(span);
emits("select", span); emits("select", span);
} }
function sortStatistics(key: string) { function sortStatistics(key: string) {
@ -152,7 +154,7 @@ limitations under the License. -->
<style lang="scss" scoped> <style lang="scss" scoped>
@import url("./table.scss"); @import url("./table.scss");
.trace { .trace-table {
font-size: $font-size-smaller; font-size: $font-size-smaller;
height: 100%; height: 100%;
overflow: auto; overflow: auto;
@ -163,7 +165,7 @@ limitations under the License. -->
float: right; float: right;
} }
.trace-header { .trace-table-header {
white-space: nowrap; white-space: nowrap;
user-select: none; user-select: none;
border-left: 0; border-left: 0;
@ -171,7 +173,7 @@ limitations under the License. -->
border-bottom: 1px solid var(--sw-trace-list-border); border-bottom: 1px solid var(--sw-trace-list-border);
} }
.trace-header div { .trace-table-header div {
display: inline-block; display: inline-block;
background-color: var(--sw-table-header); background-color: var(--sw-table-header);
padding: 0 4px; padding: 0 4px;

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div v-if="type === 'statistics'"> <div v-if="type === TraceGraphType.STATISTICS">
<div class="trace-item"> <div class="trace-item">
<div :class="['method']"> <div :class="['method']">
<el-tooltip :content="data.groupRef.endpointName" placement="bottom" :show-after="300"> <el-tooltip :content="data.groupRef.endpointName" placement="bottom" :show-after="300">
@ -149,6 +149,7 @@ limitations under the License. -->
import { dateFormat } from "@/utils/dateFormat"; import { dateFormat } from "@/utils/dateFormat";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { Themes } from "@/constants/data"; import { Themes } from "@/constants/data";
import { TraceGraphType } from "../constant";
/*global Recordable*/ /*global Recordable*/
const props = { const props = {
@ -213,8 +214,7 @@ limitations under the License. -->
} }
function selectSpan(event: Recordable) { function selectSpan(event: Recordable) {
const dom = event.composedPath().find((d: Recordable) => d.className.includes("trace-item")); const dom = event.composedPath().find((d: Recordable) => d.className.includes("trace-item"));
selectedItem(props.data);
emit("select", props.data);
if (props.headerType === "profile") { if (props.headerType === "profile") {
showSelectSpan(dom); showSelectSpan(dom);
return; return;
@ -226,14 +226,15 @@ limitations under the License. -->
emit("select", props.data); emit("select", props.data);
viewSpanDetail(dom); viewSpanDetail(dom);
} }
function selectedItem(data: Recordable) {
function selectedItem(data: HTMLSpanElement) {
emit("select", data); emit("select", data);
} }
function viewSpanDetail(dom: HTMLSpanElement) { function viewSpanDetail(dom: HTMLSpanElement) {
showSelectSpan(dom); showSelectSpan(dom);
if (props.type === TraceGraphType.STATISTICS) {
showDetail.value = true; showDetail.value = true;
} }
}
watch( watch(
() => appStore.theme, () => appStore.theme,
() => { () => {
@ -262,6 +263,7 @@ limitations under the License. -->
viewSpan, viewSpan,
t, t,
appStore, appStore,
TraceGraphType,
}; };
}, },
}); });

View File

@ -19,4 +19,5 @@ export enum TraceGraphType {
TREE = "Tree", TREE = "Tree",
LIST = "List", LIST = "List",
TABLE = "Table", TABLE = "Table",
STATISTICS = "Statistics",
} }

View File

@ -17,7 +17,7 @@
import List from "./List.vue"; import List from "./List.vue";
import Tree from "./Tree.vue"; import Tree from "./Tree.vue";
import Table from "./Table/Index.vue"; import Table from "./Table.vue";
import Statistics from "./Statistics.vue"; import Statistics from "./Statistics.vue";
export default { export default {