mirror of
https://github.com/openobserve/openobserve.git
synced 2026-11-06 00:09:22 +00:00
chore: traces UI changes (#8728)
### **PR Type** Enhancement, Tests ___ ### **Description** - Refactor header to class-based styling - Redesign Trace Details layout and styles - Add span count badges and connectors - Update tests for new selectors/classes ___ ### Diagram Walkthrough ```mermaid flowchart LR TraceHeaderSpec["TraceHeader.spec.ts tests"] -- align with classes --> TraceHeaderVue["TraceHeader.vue"] TraceTreeSpec["TraceTree.spec.ts tests"] -- new badge selectors --> TraceTreeVue["TraceTree.vue"] TraceDetailsVue["TraceDetails.vue"] -- layout + styles --> SidebarVue["TraceDetailsSidebar.vue"] TraceTreeVue -- span badges + connectors --> SpanBlockVue["SpanBlock.vue"] ``` <details> <summary><h3> File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Tests</strong></td><td><details><summary>2 files</summary><table> <tr> <td><strong>TraceHeader.spec.ts</strong><dd><code>Update tests to assert class-based styles</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8728/files#diff-d8a57095481665bdacd7cc816c63d4c742048b832c15641ce20adaa4d3a58854">+8/-18</a> </td> </tr> <tr> <td><strong>TraceTree.spec.ts</strong><dd><code>Adjust tests for badge collapse and icons</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8728/files#diff-0e779df03c894e381582de27f5c6dc36ac4905e01e12a501e0a4ca12599d4531">+9/-9</a> </td> </tr> </table></details></td></tr><tr><td><strong>Enhancement</strong></td><td><details><summary>5 files</summary><table> <tr> <td><strong>SpanBlock.vue</strong><dd><code>Use rem for label offset positioning</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8728/files#diff-21a7d32870e3d6d3fc8098b06fd8e88ba20afea0c48cfa1ad7b309a9faf8cafc">+1/-1</a> </td> </tr> <tr> <td><strong>TraceDetails.vue</strong><dd><code>Major layout refactor and glassmorphism styles</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8728/files#diff-bc7fe6b6339ee2cde6494c0982d4b860847447e79f7431f07d598e0446df06f2">+297/-149</a></td> </tr> <tr> <td><strong>TraceDetailsSidebar.vue</strong><dd><code>Restyle tables with glassmorphic theme support</code> </dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8728/files#diff-0017bc08d8da96af6433770d0b274f825798d24401b33ce091b16a50fbaf4b57">+75/-5</a> </td> </tr> <tr> <td><strong>TraceHeader.vue</strong><dd><code>Replace inline styles with CSS classes for header and tics</code></dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8728/files#diff-b782486701e65fc550a1f3ad8303922c7aa141b7bbbab2b8a606b3115af7029b">+38/-23</a> </td> </tr> <tr> <td><strong>TraceTree.vue</strong><dd><code>Add span count badges and SVG connectors; hover highlights</code></dd></td> <td><a href="https://github.com/openobserve/openobserve/pull/8728/files#diff-5d1fa8ba4d433439860b4bc036fb80e3e1b51948616dc04b1bfd1f9110a87091">+303/-69</a></td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___ --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
greptile-apps[bot]
parent
d85e5e62fc
commit
c96999049c
@@ -254,7 +254,7 @@ export default defineComponent({
|
||||
spanBlockWidth.value
|
||||
) {
|
||||
style.right = 0;
|
||||
style.top = "-5px";
|
||||
style.top = "-0.3125rem";
|
||||
} else if (leftPosition.value > 50) {
|
||||
style.left = leftPosition.value * onePercent - labelWidth + "px";
|
||||
} else {
|
||||
|
||||
@@ -15,9 +15,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="trace-details full-width" :style="backgroundStyle">
|
||||
<div class="trace-details" :style="backgroundStyle">
|
||||
<div
|
||||
class="row q-px-sm"
|
||||
class="trace-details-content"
|
||||
v-if="
|
||||
traceTree.length &&
|
||||
spanList.length &&
|
||||
@@ -27,17 +27,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
)
|
||||
"
|
||||
>
|
||||
<div class="full-width flex items-center toolbar flex justify-between">
|
||||
<div class="flex items-center">
|
||||
<div class="trace-combined-header-wrapper" :class="store.state.theme === 'dark' ? 'bg-dark' : 'bg-white'">
|
||||
<div class="full-width flex items-center toolbar flex justify-between q-pb-sm">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
data-test="trace-details-back-btn"
|
||||
class="flex justify-center items-center q-mr-sm cursor-pointer"
|
||||
style="
|
||||
border: 1.5px solid;
|
||||
border-radius: 50%;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
"
|
||||
class="flex justify-center items-center q-mr-sm cursor-pointer trace-back-btn"
|
||||
title="Traces List"
|
||||
@click="routeToTracesList"
|
||||
>
|
||||
@@ -162,7 +157,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="flex justify-center items-center tw-border tw-pl-2 tw-rounded-sm tw-border-gray-300"
|
||||
class="flex justify-center items-center tw-border tw-pl-2 tw-border-gray-300 trace-search-container"
|
||||
>
|
||||
<q-input
|
||||
data-test="trace-details-search-input"
|
||||
@@ -232,77 +227,78 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
@click="routeToTracesList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-separator style="width: 100%" />
|
||||
<div class="col-12 flex justify-between items-end q-pr-sm q-pt-sm">
|
||||
<div
|
||||
data-test="trace-details-toggle-timeline-btn"
|
||||
class="trace-chart-btn flex items-center no-wrap cursor-pointer q-mb-sm"
|
||||
@click="toggleTimeline"
|
||||
>
|
||||
<q-icon
|
||||
name="expand_more"
|
||||
:class="!isTimelineExpanded ? 'rotate-270' : ''"
|
||||
size="22px"
|
||||
class="cursor-pointer text-grey-10"
|
||||
/>
|
||||
<q-separator class="q-my-sm" />
|
||||
|
||||
<div class="flex justify-between items-end q-pr-sm q-pb-sm">
|
||||
<div
|
||||
data-test="trace-details-visual-title"
|
||||
class="text-subtitle2 text-bold"
|
||||
data-test="trace-details-toggle-timeline-btn"
|
||||
class="trace-chart-btn flex items-center no-wrap cursor-pointer"
|
||||
@click="toggleTimeline"
|
||||
>
|
||||
{{
|
||||
activeVisual === "timeline"
|
||||
? "Trace Timeline"
|
||||
: "Trace Service Map"
|
||||
}}
|
||||
<q-icon
|
||||
name="expand_more"
|
||||
:class="!isTimelineExpanded ? 'rotate-270' : ''"
|
||||
size="22px"
|
||||
class="cursor-pointer text-grey-10"
|
||||
/>
|
||||
<div
|
||||
data-test="trace-details-visual-title"
|
||||
class="text-subtitle2 text-bold"
|
||||
>
|
||||
{{
|
||||
activeVisual === "timeline"
|
||||
? "Trace Timeline"
|
||||
: "Trace Service Map"
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isTimelineExpanded"
|
||||
class="rounded-borders visual-selector-container"
|
||||
:class="store.state.theme === 'dark' ? 'bg-dark' : 'bg-white'"
|
||||
>
|
||||
<template v-for="visual in traceVisuals" :key="visual.value">
|
||||
<q-btn
|
||||
:data-test="`trace-details-visual-${visual.value}-btn`"
|
||||
:color="visual.value === activeVisual ? 'primary' : ''"
|
||||
:flat="visual.value === activeVisual ? false : true"
|
||||
dense
|
||||
no-caps
|
||||
size="11px"
|
||||
class="q-px-sm visual-selection-btn"
|
||||
@click="activeVisual = visual.value"
|
||||
>
|
||||
<q-icon><component :is="visual.icon" /></q-icon>
|
||||
{{ visual.label }}</q-btn
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isTimelineExpanded"
|
||||
class="rounded-borders"
|
||||
style="border: 1px solid #cacaca; padding: 2px"
|
||||
v-show="isTimelineExpanded"
|
||||
class="chart-container-inner q-px-sm q-pb-sm"
|
||||
:key="isTimelineExpanded.toString()"
|
||||
>
|
||||
<template v-for="visual in traceVisuals" :key="visual.value">
|
||||
<q-btn
|
||||
:data-test="`trace-details-visual-${visual.value}-btn`"
|
||||
:color="visual.value === activeVisual ? 'primary' : ''"
|
||||
:flat="visual.value === activeVisual ? false : true"
|
||||
dense
|
||||
no-caps
|
||||
size="11px"
|
||||
class="q-px-sm visual-selection-btn"
|
||||
@click="activeVisual = visual.value"
|
||||
>
|
||||
<q-icon><component :is="visual.icon" /></q-icon>
|
||||
{{ visual.label }}</q-btn
|
||||
>
|
||||
</template>
|
||||
<ChartRenderer
|
||||
data-test="trace-details-timeline-chart"
|
||||
v-if="activeVisual === 'timeline'"
|
||||
class="trace-details-chart trace-chart-height"
|
||||
id="trace_details_gantt_chart"
|
||||
:data="ChartData"
|
||||
@updated:chart="updateChart"
|
||||
/>
|
||||
<ChartRenderer
|
||||
data-test="trace-details-service-map-chart"
|
||||
v-else
|
||||
:data="traceServiceMap"
|
||||
class="trace-chart-height"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-show="isTimelineExpanded"
|
||||
class="col-12"
|
||||
:key="isTimelineExpanded.toString()"
|
||||
>
|
||||
<ChartRenderer
|
||||
data-test="trace-details-timeline-chart"
|
||||
v-if="activeVisual === 'timeline'"
|
||||
class="trace-details-chart"
|
||||
id="trace_details_gantt_chart"
|
||||
:data="ChartData"
|
||||
@updated:chart="updateChart"
|
||||
style="height: 200px"
|
||||
/>
|
||||
<ChartRenderer
|
||||
data-test="trace-details-service-map-chart"
|
||||
v-else
|
||||
:data="traceServiceMap"
|
||||
style="height: 200px"
|
||||
/>
|
||||
</div>
|
||||
<q-separator style="width: 100%" class="q-mb-sm" />
|
||||
<div style="display: flex; flex: 1; min-height: 0;">
|
||||
<div
|
||||
class="histogram-spans-container"
|
||||
:class="[
|
||||
@@ -311,68 +307,75 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
]"
|
||||
ref="parentContainer"
|
||||
>
|
||||
<trace-header
|
||||
data-test="trace-details-header"
|
||||
:baseTracePosition="baseTracePosition"
|
||||
:splitterWidth="leftWidth"
|
||||
@resize-start="startResize"
|
||||
/>
|
||||
<div class="relative-position full-height">
|
||||
<div
|
||||
class="trace-tree-container"
|
||||
:class="store.state.theme === 'dark' ? 'bg-dark' : 'bg-white'"
|
||||
data-test="trace-details-tree-container"
|
||||
>
|
||||
<div class="position-relative">
|
||||
<div
|
||||
class="trace-tree-wrapper"
|
||||
:class="store.state.theme === 'dark' ? 'bg-dark' : 'bg-white'"
|
||||
>
|
||||
<trace-header
|
||||
data-test="trace-details-header"
|
||||
:baseTracePosition="baseTracePosition"
|
||||
:splitterWidth="leftWidth"
|
||||
@resize-start="startResize"
|
||||
/>
|
||||
<div style="display: flex; flex: 1; min-height: 0;">
|
||||
<div class="relative-position trace-content-scroll">
|
||||
<div
|
||||
:style="{
|
||||
width: '1px',
|
||||
left: `${leftWidth}px`,
|
||||
backgroundColor:
|
||||
store.state.theme === 'dark' ? '#3c3c3c' : '#ececec',
|
||||
zIndex: 999,
|
||||
top: '-28px',
|
||||
height: `${parentHeight}px`,
|
||||
cursor: 'col-resize',
|
||||
}"
|
||||
class="absolute resize"
|
||||
@mousedown="startResize"
|
||||
/>
|
||||
<trace-tree
|
||||
data-test="trace-details-tree"
|
||||
:collapseMapping="collapseMapping"
|
||||
:spans="spanPositionList"
|
||||
class="trace-tree-container"
|
||||
data-test="trace-details-tree-container"
|
||||
>
|
||||
<div class="position-relative">
|
||||
<div
|
||||
:style="{
|
||||
width: '1px',
|
||||
left: `${leftWidth}px`,
|
||||
backgroundColor:
|
||||
store.state.theme === 'dark' ? '#3c3c3c' : '#ececec',
|
||||
zIndex: 999,
|
||||
top: '-28px',
|
||||
height: `${spanPositionList.length * spanDimensions.height + 28}px`,
|
||||
cursor: 'col-resize',
|
||||
}"
|
||||
class="absolute resize"
|
||||
@mousedown="startResize"
|
||||
/>
|
||||
<trace-tree
|
||||
data-test="trace-details-tree"
|
||||
:collapseMapping="collapseMapping"
|
||||
:spans="spanPositionList"
|
||||
:baseTracePosition="baseTracePosition"
|
||||
:spanDimensions="spanDimensions"
|
||||
:spanMap="spanMap"
|
||||
:leftWidth="leftWidth"
|
||||
ref="traceTreeRef"
|
||||
:search-query="searchQuery"
|
||||
:spanList="spanList"
|
||||
@toggle-collapse="toggleSpanCollapse"
|
||||
@select-span="updateSelectedSpan"
|
||||
@update-current-index="handleIndexUpdate"
|
||||
@search-result="handleSearchResult"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<q-separator v-if="isSidebarOpen && (selectedSpanId || showTraceDetails)" vertical />
|
||||
<div
|
||||
v-if="isSidebarOpen && (selectedSpanId || showTraceDetails)"
|
||||
class="histogram-sidebar-inner"
|
||||
:class="isTimelineExpanded ? '' : 'full'"
|
||||
>
|
||||
<trace-details-sidebar
|
||||
data-test="trace-details-sidebar"
|
||||
:span="spanMap[selectedSpanId as string]"
|
||||
:baseTracePosition="baseTracePosition"
|
||||
:spanDimensions="spanDimensions"
|
||||
:spanMap="spanMap"
|
||||
:leftWidth="leftWidth"
|
||||
ref="traceTreeRef"
|
||||
:search-query="searchQuery"
|
||||
:spanList="spanList"
|
||||
@toggle-collapse="toggleSpanCollapse"
|
||||
@select-span="updateSelectedSpan"
|
||||
@update-current-index="handleIndexUpdate"
|
||||
@search-result="handleSearchResult"
|
||||
@view-logs="redirectToLogs"
|
||||
@close="closeSidebar"
|
||||
@open-trace="openTraceLink"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<q-separator vertical />
|
||||
<div
|
||||
v-if="isSidebarOpen && (selectedSpanId || showTraceDetails)"
|
||||
class="histogram-sidebar"
|
||||
:class="isTimelineExpanded ? '' : 'full'"
|
||||
>
|
||||
<trace-details-sidebar
|
||||
data-test="trace-details-sidebar"
|
||||
:span="spanMap[selectedSpanId as string]"
|
||||
:baseTracePosition="baseTracePosition"
|
||||
:search-query="searchQuery"
|
||||
@view-logs="redirectToLogs"
|
||||
@close="closeSidebar"
|
||||
@open-trace="openTraceLink"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
@@ -1402,7 +1405,22 @@ $traceChartCollapseHeight: 42px;
|
||||
height: $toolbarHeight;
|
||||
}
|
||||
.trace-details {
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.trace-details-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding: 0 0.75rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.histogram-container-full {
|
||||
width: 100%;
|
||||
@@ -1411,34 +1429,46 @@ $traceChartCollapseHeight: 42px;
|
||||
width: calc(100% - $sidebarWidth - $separatorWidth);
|
||||
}
|
||||
|
||||
.histogram-sidebar {
|
||||
.histogram-sidebar-inner {
|
||||
width: $sidebarWidth;
|
||||
height: calc(
|
||||
100vh - $toolbarHeight - $traceChartHeight - 44px - $appNavbarHeight
|
||||
);
|
||||
flex-shrink: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&.full {
|
||||
height: calc(100vh - $toolbarHeight - 8px - 44px - $appNavbarHeight);
|
||||
}
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.histogram-spans-container {
|
||||
height: calc(
|
||||
100vh - $toolbarHeight - $traceChartHeight - 44px - $appNavbarHeight
|
||||
);
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
&.full {
|
||||
height: calc(100vh - $toolbarHeight - 8px - 44px - $appNavbarHeight);
|
||||
}
|
||||
.trace-tree-wrapper {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(0.625rem);
|
||||
border-radius: 0.5rem;
|
||||
border: 0.1875rem solid rgba(255, 255, 255, 0.3);
|
||||
overflow: hidden;
|
||||
height: calc(100% - 2.5rem);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.trace-tree-wrapper.bg-white {
|
||||
background: rgba(240, 240, 245, 0.8);
|
||||
backdrop-filter: blur(0.625rem);
|
||||
border: 0.1875rem solid rgba(100, 100, 120, 0.5);
|
||||
}
|
||||
|
||||
.trace-tree-container {
|
||||
overflow: auto;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.trace-chart-btn {
|
||||
@@ -1475,6 +1505,20 @@ $traceChartCollapseHeight: 42px;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
// Prevent parent containers from adding scrollbars
|
||||
body:has(.trace-details),
|
||||
html:has(.trace-details) {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.trace-content-scroll {
|
||||
flex: 1 !important;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
min-height: 0 !important;
|
||||
scrollbar-gutter: stable !important;
|
||||
}
|
||||
|
||||
.trace-details {
|
||||
.q-splitter__before,
|
||||
.q-splitter__after {
|
||||
@@ -1488,11 +1532,30 @@ $traceChartCollapseHeight: 42px;
|
||||
.trace-details-chart {
|
||||
.rangeslider-slidebox {
|
||||
fill: #7076be !important;
|
||||
opacity: 0.3 !important;
|
||||
}
|
||||
.rangeslider-mask-max,
|
||||
.rangeslider-mask-min {
|
||||
fill: #d2d2d2 !important;
|
||||
fill-opacity: 1 !important;
|
||||
fill-opacity: 0.15 !important;
|
||||
}
|
||||
.rangeslider-grabber {
|
||||
fill: #7076be !important;
|
||||
stroke: #ffffff !important;
|
||||
stroke-width: 2 !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
.rangeslider-grabber:hover {
|
||||
fill: #5a5fa0 !important;
|
||||
cursor: ew-resize !important;
|
||||
}
|
||||
// Enhance the line graph (trace duration) visibility
|
||||
.trace {
|
||||
stroke-width: 2 !important;
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
.scatterlayer .trace {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1502,6 +1565,44 @@ $traceChartCollapseHeight: 42px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.visual-selector-container {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(0.625rem);
|
||||
border-radius: 0.5rem;
|
||||
border: 0.0625rem solid rgba(255, 255, 255, 0.1);
|
||||
padding: 0.125rem;
|
||||
}
|
||||
|
||||
.visual-selector-container.bg-white {
|
||||
background: rgba(240, 240, 245, 0.8);
|
||||
border: 0.125rem solid rgba(100, 100, 120, 0.3);
|
||||
}
|
||||
|
||||
.trace-combined-header-wrapper {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(0.625rem);
|
||||
border-radius: 0.5rem;
|
||||
border: 0.0625rem solid rgba(255, 255, 255, 0.1);
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.trace-combined-header-wrapper.bg-white {
|
||||
background: rgba(240, 240, 245, 0.8);
|
||||
border: 0.125rem solid rgba(100, 100, 120, 0.3);
|
||||
}
|
||||
|
||||
.chart-container-inner {
|
||||
min-height: 12.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.trace-chart-height {
|
||||
height: 12.5rem !important;
|
||||
min-height: 12.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.no-select {
|
||||
@@ -1521,6 +1622,8 @@ $traceChartCollapseHeight: 42px;
|
||||
|
||||
.trace-logs-selector {
|
||||
.q-field {
|
||||
border-radius: 0.5rem 0 0 0.5rem;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
width: 180px;
|
||||
@@ -1529,20 +1632,37 @@ $traceChartCollapseHeight: 42px;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.q-field__control {
|
||||
border-radius: 0.5rem 0 0 0.5rem;
|
||||
}
|
||||
|
||||
.q-field__control:before,
|
||||
.q-field__control:after {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.log-stream-search-input {
|
||||
.q-field .q-field__control {
|
||||
padding: 0px 4px;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.q-field .q-field__control:before,
|
||||
.q-field .q-field__control:after {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.traces-view-logs-btn {
|
||||
height: 36px;
|
||||
margin-left: -1px;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-top-left-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-top-right-radius: 0.5rem !important;
|
||||
border-bottom-right-radius: 0.5rem !important;
|
||||
|
||||
.q-btn__content {
|
||||
span {
|
||||
@@ -1554,6 +1674,34 @@ $traceChartCollapseHeight: 42px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.trace-search-container {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.q-menu .q-item.q-item--active {
|
||||
background-color: rgba(25, 118, 210, 0.2) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
.q-dark .q-menu .q-item.q-item--active {
|
||||
background-color: rgba(144, 202, 249, 0.2) !important;
|
||||
}
|
||||
|
||||
.q-menu .q-item.q-manual-focusable--focused {
|
||||
background-color: rgba(25, 118, 210, 0.1) !important;
|
||||
}
|
||||
|
||||
.q-dark .q-menu .q-item.q-manual-focusable--focused {
|
||||
background-color: rgba(144, 202, 249, 0.1) !important;
|
||||
}
|
||||
|
||||
.trace-back-btn {
|
||||
border: 0.09375rem solid;
|
||||
border-radius: 50%;
|
||||
width: 1.375rem;
|
||||
height: 1.375rem;
|
||||
}
|
||||
|
||||
.custom-height .q-field__control,
|
||||
.custom-height .q-field__append {
|
||||
height: 100%; /* Ensures the input control fills the container height */
|
||||
|
||||
@@ -827,19 +827,55 @@ export default defineComponent({
|
||||
<style scoped lang="scss">
|
||||
.span_details_tab-panels {
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
/* Other styling properties */
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(0.625rem);
|
||||
border-radius: 0.5rem;
|
||||
border: 0.125rem solid rgba(255, 255, 255, 0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #f0f0f0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.15);
|
||||
text-align: left;
|
||||
padding: 4px 8px !important;
|
||||
padding: 8px 12px !important;
|
||||
font-size: 13px;
|
||||
/* Other styling properties */
|
||||
}
|
||||
|
||||
th:last-child,
|
||||
td:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
tbody tr:first-child td:first-child {
|
||||
border-top-left-radius: 0.5rem;
|
||||
}
|
||||
|
||||
tbody tr:first-child td:last-child {
|
||||
border-top-right-radius: 0.5rem;
|
||||
}
|
||||
|
||||
tbody tr:last-child td:first-child {
|
||||
border-bottom-left-radius: 0.5rem;
|
||||
}
|
||||
|
||||
tbody tr:last-child td:last-child {
|
||||
border-bottom-right-radius: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.span_details_tab-panels table.q-table {
|
||||
background: rgba(240, 240, 245, 0.8);
|
||||
backdrop-filter: blur(0.625rem);
|
||||
border: 0.125rem solid rgba(100, 100, 120, 0.5);
|
||||
}
|
||||
.attr-text {
|
||||
font-size: 12px;
|
||||
@@ -1040,3 +1076,37 @@ export default defineComponent({
|
||||
background-color: yellow; /* Adjust background color as desired */
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
// Dark theme support for glassmorphic tables
|
||||
.body--dark {
|
||||
.span_details_tab-panels {
|
||||
table {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 0.125rem solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Light theme support for glassmorphic tables
|
||||
.body--light {
|
||||
.span_details_tab-panels {
|
||||
table {
|
||||
background: rgba(240, 240, 245, 0.8);
|
||||
border: 0.125rem solid rgba(100, 100, 120, 0.5);
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border-bottom: 1px solid rgba(100, 100, 120, 0.2);
|
||||
border-right: 1px solid rgba(100, 100, 120, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -124,14 +124,12 @@ describe("TraceHeader", () => {
|
||||
|
||||
it("should have correct default height", () => {
|
||||
const headerContainer = wrapper.find('[data-test="trace-header"]');
|
||||
expect(headerContainer.attributes("style")).toContain("height: 30px");
|
||||
expect(headerContainer.classes()).toContain("trace-header-container");
|
||||
});
|
||||
|
||||
it("should have sticky positioning", () => {
|
||||
const headerContainer = wrapper.find('[data-test="trace-header"]');
|
||||
expect(headerContainer.attributes("style")).toContain("position: sticky");
|
||||
expect(headerContainer.attributes("style")).toContain("top: 0");
|
||||
expect(headerContainer.attributes("style")).toContain("z-index: 1999");
|
||||
expect(headerContainer.classes()).toContain("trace-header-container");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -232,7 +230,7 @@ describe("TraceHeader", () => {
|
||||
|
||||
it("should have correct z-index for first tic", () => {
|
||||
const firstTic = wrapper.find('[data-test="trace-header-tic-line-0"]');
|
||||
expect(firstTic.attributes("style")).toContain("z-index: 5");
|
||||
expect(firstTic.classes()).toContain("trace-tic-first");
|
||||
});
|
||||
|
||||
it("should have correct z-index for other tics", () => {
|
||||
@@ -240,7 +238,7 @@ describe("TraceHeader", () => {
|
||||
.findAll('[data-test^="trace-header-tic-line-"]')
|
||||
.slice(1);
|
||||
otherTics.forEach((tic) => {
|
||||
expect(tic.attributes("style")).toContain("z-index: 1");
|
||||
expect(tic.classes()).toContain("trace-tic");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -249,9 +247,7 @@ describe("TraceHeader", () => {
|
||||
it("should apply light theme by default", () => {
|
||||
const headerContainer = wrapper.find('[data-test="trace-header"]');
|
||||
expect(headerContainer.classes()).toContain("bg-grey-2");
|
||||
expect(headerContainer.attributes("style")).toContain(
|
||||
"border: 1px solid rgb(236, 236, 236)",
|
||||
);
|
||||
expect(headerContainer.classes()).toContain("trace-header-container");
|
||||
});
|
||||
|
||||
it("should apply dark theme when store theme is dark", async () => {
|
||||
@@ -287,9 +283,7 @@ describe("TraceHeader", () => {
|
||||
|
||||
const headerContainer = darkWrapper.find('[data-test="trace-header"]');
|
||||
expect(headerContainer.classes()).toContain("bg-grey-9");
|
||||
expect(headerContainer.attributes("style")).toContain(
|
||||
"border: 1px solid rgb(60, 60, 60)",
|
||||
);
|
||||
expect(headerContainer.classes()).toContain("trace-header-container");
|
||||
|
||||
darkWrapper.unmount();
|
||||
});
|
||||
@@ -329,9 +323,7 @@ describe("TraceHeader", () => {
|
||||
'[data-test^="trace-header-tic-line-"]',
|
||||
);
|
||||
ticLines.forEach((tic) => {
|
||||
expect(tic.attributes("style")).toContain(
|
||||
"background-color: rgb(60, 60, 60)",
|
||||
);
|
||||
expect(tic.classes()).toContain("bg-dark-tic");
|
||||
});
|
||||
|
||||
darkWrapper.unmount();
|
||||
@@ -340,9 +332,7 @@ describe("TraceHeader", () => {
|
||||
it("should apply light theme colors to tic lines", () => {
|
||||
const ticLines = wrapper.findAll('[data-test^="trace-header-tic-line-"]');
|
||||
ticLines.forEach((tic) => {
|
||||
expect(tic.attributes("style")).toContain(
|
||||
"background-color: rgb(202, 202, 202)",
|
||||
);
|
||||
expect(tic.classes()).toContain("trace-tic");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,20 +16,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
<template>
|
||||
<div
|
||||
:style="{
|
||||
height: '30px',
|
||||
border:
|
||||
store.state.theme === 'dark'
|
||||
? '1px solid #3c3c3c'
|
||||
: '1px solid #ececec',
|
||||
}"
|
||||
style="top: 0; z-index: 1999; position: sticky"
|
||||
class="flex justify-start items-center header-bg bg-grey-2"
|
||||
class="flex justify-start items-center header-bg bg-grey-2 trace-header-container"
|
||||
:class="store.state.theme === 'dark' ? 'bg-grey-9' : 'bg-grey-2'"
|
||||
data-test="trace-header"
|
||||
>
|
||||
<div
|
||||
class="tw-relative flex justify-start items-center no-wrap row q-px-sm"
|
||||
class="tw-relative flex justify-start items-center no-wrap row q-px-sm trace-header-left"
|
||||
:style="{
|
||||
width: splitterWidth + 'px',
|
||||
}"
|
||||
@@ -39,7 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
<q-avatar
|
||||
color="primary"
|
||||
text-color="white"
|
||||
size="20px"
|
||||
size="1.25rem"
|
||||
icon="drag_indicator"
|
||||
class="resize-btn"
|
||||
@mousedown="handleMouseDown"
|
||||
@@ -47,7 +39,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-start items-center no-wrap row relative-position"
|
||||
class="flex justify-start items-center no-wrap row relative-position trace-header-right"
|
||||
:style="{
|
||||
width: `calc(100% - ${splitterWidth}px)`,
|
||||
}"
|
||||
@@ -82,15 +74,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
<div
|
||||
v-for="(tick, index) in baseTracePosition['tics']"
|
||||
class="trace-tic"
|
||||
:class="{'trace-tic-first': index === 0, 'bg-dark-tic': store.state.theme === 'dark'}"
|
||||
:key="tick.value + index"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: tick.left,
|
||||
top: '-3px',
|
||||
width: '1px',
|
||||
backgroundColor: store.state.theme === 'dark' ? '#3c3c3c' : '#cacaca',
|
||||
zIndex: index === 0 ? '5' : '1',
|
||||
height: '26px',
|
||||
}"
|
||||
:data-test="`trace-header-tic-line-${index}`"
|
||||
></div>
|
||||
@@ -138,25 +125,53 @@ $toolbarHeight: 50px;
|
||||
$traceHeaderHeight: 30px;
|
||||
$traceChartHeight: 250px;
|
||||
|
||||
.trace-header-container {
|
||||
height: 1.875rem;
|
||||
top: 0;
|
||||
z-index: 1999;
|
||||
position: sticky;
|
||||
border-radius: 0.5rem 0.5rem 0 0;
|
||||
border: 0.0625rem solid #ececec;
|
||||
}
|
||||
|
||||
.trace-header-container.bg-grey-9 {
|
||||
border-color: #3c3c3c;
|
||||
}
|
||||
|
||||
.spans-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.collapse-btn {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
width: 0.625rem;
|
||||
height: 0.625rem;
|
||||
}
|
||||
|
||||
.trace-tic {
|
||||
height: calc(100vh - $toolbarHeight - 5px - $traceChartHeight);
|
||||
position: absolute;
|
||||
top: -0.1875rem;
|
||||
width: 0.0625rem;
|
||||
background-color: #cacaca;
|
||||
z-index: 1;
|
||||
height: 1.625rem;
|
||||
}
|
||||
|
||||
.trace-tic.bg-dark-tic {
|
||||
background-color: #3c3c3c;
|
||||
}
|
||||
|
||||
.trace-tic-first {
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.header-bg {
|
||||
background-color: color-mix(in srgb, currentColor 5%, transparent);
|
||||
}
|
||||
|
||||
.resize-btn {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: -2px;
|
||||
right: -0.625rem;
|
||||
top: -0.125rem;
|
||||
z-index: 10;
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
@@ -272,14 +272,14 @@ describe("TraceTree", () => {
|
||||
|
||||
it("should render collapse button for spans with children", () => {
|
||||
const collapseBtn = wrapper.find(
|
||||
'[data-test="trace-tree-span-collapse-btn-d9603ec7f76eb499"]',
|
||||
'[data-test="trace-tree-span-badge-collapse-btn-d9603ec7f76eb499"]',
|
||||
);
|
||||
expect(collapseBtn.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("should not render collapse button for spans without children", () => {
|
||||
const collapseBtn = wrapper.find(
|
||||
'[data-test="trace-tree-span-collapse-btn-6702b0494b2b6e57"]',
|
||||
'[data-test="trace-tree-span-badge-collapse-btn-6702b0494b2b6e57"]',
|
||||
);
|
||||
expect(collapseBtn.exists()).toBe(false);
|
||||
});
|
||||
@@ -316,7 +316,7 @@ describe("TraceTree", () => {
|
||||
describe("Span collapse functionality", () => {
|
||||
it("should emit toggleCollapse when collapse button is clicked", async () => {
|
||||
const collapseBtn = wrapper.find(
|
||||
'[data-test="trace-tree-span-collapse-btn-d9603ec7f76eb499"]',
|
||||
'[data-test="trace-tree-span-badge-collapse-btn-d9603ec7f76eb499"]',
|
||||
);
|
||||
await collapseBtn.trigger("click");
|
||||
|
||||
@@ -331,10 +331,10 @@ describe("TraceTree", () => {
|
||||
collapseMapping: { d9603ec7f76eb499: true },
|
||||
});
|
||||
|
||||
const collapseIcon = wrapper.find(
|
||||
'[data-test="trace-tree-span-collapse-btn-d9603ec7f76eb499"] .collapse-btn',
|
||||
const collapseBtn = wrapper.find(
|
||||
'[data-test="trace-tree-span-badge-collapse-btn-d9603ec7f76eb499"]',
|
||||
);
|
||||
expect(collapseIcon.attributes("style")).toContain("rotate: 0deg");
|
||||
expect(collapseBtn.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("should apply correct expand icon rotation", async () => {
|
||||
@@ -342,10 +342,10 @@ describe("TraceTree", () => {
|
||||
collapseMapping: { d9603ec7f76eb499: false },
|
||||
});
|
||||
|
||||
const collapseIcon = wrapper.find(
|
||||
'[data-test="trace-tree-span-collapse-btn-d9603ec7f76eb499"] .collapse-btn',
|
||||
const collapseBtn = wrapper.find(
|
||||
'[data-test="trace-tree-span-badge-collapse-btn-d9603ec7f76eb499"]',
|
||||
);
|
||||
expect(collapseIcon.attributes("style")).toContain("rotate: 270deg");
|
||||
expect(collapseBtn.exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -15,22 +15,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<template>
|
||||
<template v-for="(span, index) in spans as any[]" :key="span.spanId">
|
||||
<div
|
||||
:style="{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
overflow: 'visible',
|
||||
flexWrap: 'nowrap',
|
||||
}"
|
||||
class="flex"
|
||||
:data-test="`trace-tree-span-container-${span.spanId}`"
|
||||
>
|
||||
<div v-bind="$attrs" ref="rootContainer">
|
||||
<template v-for="(span, index) in spans as any[]" :key="span.spanId">
|
||||
<div
|
||||
:style="{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
flexWrap: 'nowrap',
|
||||
}"
|
||||
class="flex span-row"
|
||||
:class="spanHoveredIndex === index ? 'span-row-highlight' : ''"
|
||||
:data-test="`trace-tree-span-container-${span.spanId}`"
|
||||
@mouseover="() => (spanHoveredIndex = index)"
|
||||
@mouseout="() => (spanHoveredIndex = -1)"
|
||||
>
|
||||
<div :style="{ width: leftWidth + 'px' }">
|
||||
<div
|
||||
:style="{
|
||||
height: '100%',
|
||||
margin: `0 0px 0 ${
|
||||
margin: `0 0 0 ${
|
||||
span.hasChildSpans
|
||||
? span.style.left
|
||||
: parseInt(span.style.left) +
|
||||
@@ -44,9 +47,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
<div
|
||||
class="flex no-wrap q-pt-sm full-width relative-position operation-name-container"
|
||||
:class="store.state.theme === 'dark' ? 'bg-dark' : 'bg-white'"
|
||||
:style="{ height: '30px' }"
|
||||
@mouseover="() => (spanHoveredIndex = index)"
|
||||
@mouseout="() => (spanHoveredIndex = -1)"
|
||||
:data-test="`trace-tree-span-operation-name-container-${span.spanId}`"
|
||||
>
|
||||
<div
|
||||
@@ -57,7 +57,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
<q-btn
|
||||
class="q-mx-xs view-span-logs"
|
||||
:class="store.state.theme === 'dark' ? 'bg-dark' : 'bg-white'"
|
||||
size="10px"
|
||||
size="0.625rem"
|
||||
icon="search"
|
||||
dense
|
||||
no-caps
|
||||
@@ -70,31 +70,30 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
</div>
|
||||
<div
|
||||
v-if="span.hasChildSpans"
|
||||
class="span-count-box cursor-pointer"
|
||||
:ref="el => setBadgeRef(span.spanId, el)"
|
||||
:style="{
|
||||
width: spanDimensions.collapseWidth + 'px',
|
||||
height: spanDimensions.collapseHeight + 'px',
|
||||
borderColor: span.style.color,
|
||||
color: span.style.color,
|
||||
}"
|
||||
class="q-pt-xs flex justify-center items-center collapse-container cursor-pointer"
|
||||
@click.stop="toggleSpanCollapse(span.spanId)"
|
||||
:data-test="`trace-tree-span-collapse-btn-${span.spanId}`"
|
||||
:data-test="`trace-tree-span-badge-collapse-btn-${span.spanId}`"
|
||||
:title="`Click to ${collapseMapping[span.spanId] ? 'expand' : 'collapse'}`"
|
||||
>
|
||||
<q-icon
|
||||
dense
|
||||
round
|
||||
flat
|
||||
name="expand_more"
|
||||
class="collapse-btn"
|
||||
:style="{
|
||||
rotate: collapseMapping[span.spanId] ? '0deg' : '270deg',
|
||||
}"
|
||||
/>
|
||||
{{ getChildCount(span) }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="ellipsis q-pl-xs cursor-pointer"
|
||||
v-else
|
||||
class="span-leaf-dot"
|
||||
:ref="el => setBadgeRef(span.spanId, el)"
|
||||
:style="{
|
||||
paddingLeft: '4px',
|
||||
borderLeft: `3px solid ${span.style.color}`,
|
||||
backgroundColor: span.style.color,
|
||||
}"
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="ellipsis q-pl-xs cursor-pointer span-name-section"
|
||||
@click="selectSpan(span.spanId)"
|
||||
:data-test="`trace-tree-span-select-btn-${span.spanId}`"
|
||||
>
|
||||
@@ -130,45 +129,88 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="span-background-wrapper"
|
||||
:style="{
|
||||
backgroundColor: span.style.backgroundColor,
|
||||
height: `calc(100% - 30px)`,
|
||||
borderLeft: `3px solid ${span.style.color}`,
|
||||
marginLeft: span.hasChildSpans ? '14px' : '0',
|
||||
height: `calc(100% - 1.875rem)`,
|
||||
borderLeft: `0.1875rem solid ${span.style.color}`,
|
||||
marginLeft: span.hasChildSpans ? '0.875rem' : '0',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
}"
|
||||
:data-test="`trace-tree-span-background-${span.spanId}`"
|
||||
></div>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<span-block
|
||||
:span="span"
|
||||
:depth="depth"
|
||||
:baseTracePosition="baseTracePosition"
|
||||
:styleObj="{
|
||||
position: 'absolute',
|
||||
top: span.style.top,
|
||||
left: span.style.left,
|
||||
height: '60px',
|
||||
}"
|
||||
:style="{
|
||||
width: `calc(100% - ${leftWidth}px)`,
|
||||
}"
|
||||
:spanDimensions="spanDimensions"
|
||||
:isCollapsed="collapseMapping[span.spanId]"
|
||||
:spanData="spanMap[span.spanId]"
|
||||
@toggle-collapse="toggleSpanCollapse"
|
||||
@select-span="selectSpan"
|
||||
@mouseover="() => (spanHoveredIndex = index)"
|
||||
@mouseout="() => (spanHoveredIndex = -1)"
|
||||
@view-logs="viewSpanLogs(span)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<div :style="{ width: `calc(100% - ${leftWidth}px)` }">
|
||||
<span-block
|
||||
:span="span"
|
||||
:depth="depth"
|
||||
:baseTracePosition="baseTracePosition"
|
||||
:styleObj="{
|
||||
position: 'absolute',
|
||||
top: span.style.top,
|
||||
left: span.style.left,
|
||||
height: '3.75rem',
|
||||
}"
|
||||
:spanDimensions="spanDimensions"
|
||||
:isCollapsed="collapseMapping[span.spanId]"
|
||||
:spanData="spanMap[span.spanId]"
|
||||
:class="spanHoveredIndex === index ? 'span-block-highlight' : ''"
|
||||
@toggle-collapse="toggleSpanCollapse"
|
||||
@select-span="selectSpan"
|
||||
@view-logs="viewSpanLogs(span)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- SVG overlay for connectors -->
|
||||
<svg
|
||||
v-if="Object.keys(connectorPaths).length > 0"
|
||||
class="connector-overlay"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 1000,
|
||||
overflow: 'visible',
|
||||
}"
|
||||
>
|
||||
<template v-for="(connector, key) in connectorPaths" :key="key">
|
||||
<!-- Vertical line -->
|
||||
<line
|
||||
:x1="connector.x1"
|
||||
:y1="connector.y1"
|
||||
:x2="connector.x1"
|
||||
:y2="connector.y2"
|
||||
:stroke="connector.color"
|
||||
stroke-width="1.5"
|
||||
opacity="0.6"
|
||||
/>
|
||||
<!-- Horizontal lines to children -->
|
||||
<template v-for="(child, idx) in connector.children" :key="idx">
|
||||
<line
|
||||
:x1="connector.x1"
|
||||
:y1="child.y"
|
||||
:x2="child.x"
|
||||
:y2="child.y"
|
||||
:stroke="connector.color"
|
||||
stroke-width="1.5"
|
||||
opacity="0.6"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, nextTick, ref, watch, computed } from "vue";
|
||||
import { defineComponent, nextTick, ref, watch, computed, onMounted } from "vue";
|
||||
import { getImageURL } from "@/utils/zincutils";
|
||||
import useTraces from "@/composables/useTraces";
|
||||
import { useStore } from "vuex";
|
||||
@@ -178,6 +220,7 @@ import { useRouter } from "vue-router";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TraceTree",
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
spans: {
|
||||
type: Array,
|
||||
@@ -236,6 +279,16 @@ export default defineComponent({
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const badgeRefs = ref<Record<string, HTMLElement>>({});
|
||||
const connectorPaths = ref<Record<string, any>>({});
|
||||
const rootContainer = ref<HTMLElement | null>(null);
|
||||
|
||||
const setBadgeRef = (spanId: string, el: any) => {
|
||||
if (el) {
|
||||
badgeRefs.value[spanId] = el;
|
||||
}
|
||||
};
|
||||
|
||||
function toggleSpanCollapse(spanId: number | string) {
|
||||
emit("toggleCollapse", spanId);
|
||||
}
|
||||
@@ -248,6 +301,89 @@ export default defineComponent({
|
||||
const queryDetails = buildQueryDetails(span);
|
||||
navigateToLogs(queryDetails);
|
||||
};
|
||||
|
||||
const getChildCount = (span: any): number => {
|
||||
if (!span.spans || !Array.isArray(span.spans)) return 0;
|
||||
return span.spans.length;
|
||||
};
|
||||
|
||||
const getDirectChildren = (span: any) => {
|
||||
if (!span.spans || !Array.isArray(span.spans)) return [];
|
||||
return span.spans;
|
||||
};
|
||||
|
||||
const getChildrenHeight = (span: any): number => {
|
||||
if (!span.spans || !Array.isArray(span.spans) || !props.collapseMapping[span.spanId]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const countVisibleChildren = (children: any[]): number => {
|
||||
let count = 0;
|
||||
children.forEach((child: any) => {
|
||||
count++;
|
||||
if (child.spans && child.spans.length > 0 && props.collapseMapping[child.spanId]) {
|
||||
count += countVisibleChildren(child.spans);
|
||||
}
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
return countVisibleChildren(span.spans);
|
||||
};
|
||||
|
||||
const calculateConnectors = () => {
|
||||
if (!rootContainer.value) return;
|
||||
|
||||
const newConnectorPaths: Record<string, any> = {};
|
||||
const containerRect = rootContainer.value.getBoundingClientRect();
|
||||
if (props.spans && Array.isArray(props.spans)) {
|
||||
props.spans.forEach((span: any) => {
|
||||
if (span.hasChildSpans && props.collapseMapping[span.spanId]) {
|
||||
const parentBadge = badgeRefs.value[span.spanId];
|
||||
if (!parentBadge) return;
|
||||
|
||||
const parentRect = parentBadge.getBoundingClientRect();
|
||||
const parentX = parentRect.left - containerRect.left + parentRect.width / 2;
|
||||
const parentY = parentRect.bottom - containerRect.top;
|
||||
|
||||
const children = getDirectChildren(span);
|
||||
const childPositions: any[] = [];
|
||||
let lastChildY = parentY;
|
||||
|
||||
children.forEach((child: any) => {
|
||||
const childBadge = badgeRefs.value[child.spanId];
|
||||
if (childBadge) {
|
||||
const childRect = childBadge.getBoundingClientRect();
|
||||
const childX = childRect.left - containerRect.left;
|
||||
const childY = childRect.top - containerRect.top + childRect.height / 2;
|
||||
|
||||
childPositions.push({
|
||||
x: childX,
|
||||
y: childY,
|
||||
isLeaf: !child.hasChildSpans,
|
||||
});
|
||||
|
||||
lastChildY = childY;
|
||||
}
|
||||
});
|
||||
|
||||
if (childPositions.length > 0) {
|
||||
newConnectorPaths[span.spanId] = {
|
||||
x1: parentX,
|
||||
y1: parentY,
|
||||
x2: parentX,
|
||||
y2: lastChildY,
|
||||
color: span.style.color,
|
||||
children: childPositions,
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
connectorPaths.value = newConnectorPaths;
|
||||
};
|
||||
|
||||
const searchResults = ref<any[]>([]);
|
||||
const currentIndex = ref<number | null>(null);
|
||||
const currentSelectedValue = computed(() => {
|
||||
@@ -357,6 +493,29 @@ export default defineComponent({
|
||||
emit("search-result", newValue.length);
|
||||
});
|
||||
|
||||
// Watch for changes that require recalculating connectors
|
||||
watch(
|
||||
() => [props.spans, props.collapseMapping],
|
||||
() => {
|
||||
nextTick(() => {
|
||||
// Use setTimeout to ensure all refs are set after DOM updates
|
||||
setTimeout(() => {
|
||||
calculateConnectors();
|
||||
}, 0);
|
||||
});
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
// Use setTimeout to ensure all refs are set after DOM updates
|
||||
setTimeout(() => {
|
||||
calculateConnectors();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
toggleSpanCollapse,
|
||||
getImageURL,
|
||||
@@ -374,6 +533,13 @@ export default defineComponent({
|
||||
currentSelectedValue,
|
||||
scrollToMatch,
|
||||
findMatches,
|
||||
getChildCount,
|
||||
getDirectChildren,
|
||||
getChildrenHeight,
|
||||
setBadgeRef,
|
||||
connectorPaths,
|
||||
calculateConnectors,
|
||||
rootContainer,
|
||||
};
|
||||
},
|
||||
components: { SpanBlock },
|
||||
@@ -382,20 +548,53 @@ export default defineComponent({
|
||||
|
||||
<style scoped lang="scss">
|
||||
.view-logs-container {
|
||||
top: 7px;
|
||||
top: 0.25rem;
|
||||
right: 0;
|
||||
}
|
||||
.spans-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.collapse-btn {
|
||||
width: 14px;
|
||||
height: auto;
|
||||
opacity: 0.6;
|
||||
.span-count-box {
|
||||
min-width: 1.5rem;
|
||||
height: 1.25rem;
|
||||
padding: 0 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
border: 0.0625rem solid;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
margin-right: 0.25rem;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.bg-dark .span-count-box:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.span-leaf-dot {
|
||||
width: 0.375rem;
|
||||
height: 0.375rem;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.span-name-section {
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
|
||||
.operation-name-container {
|
||||
height: 1.875rem;
|
||||
overflow: visible;
|
||||
|
||||
.view-logs-container {
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -405,6 +604,34 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.span-row {
|
||||
position: relative;
|
||||
min-height: 1.875rem;
|
||||
|
||||
&.span-row-highlight {
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 1.875rem;
|
||||
background-color: rgba(0, 123, 255, 0.2);
|
||||
pointer-events: none;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.operation-name-container {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.view-logs-btn-text {
|
||||
@@ -414,7 +641,7 @@ export default defineComponent({
|
||||
background-color: inherit;
|
||||
.view-logs-btn-text {
|
||||
visibility: hidden;
|
||||
width: 0px;
|
||||
width: 0;
|
||||
transition: width 0.3s ease-in;
|
||||
}
|
||||
&:hover .view-logs-btn-text {
|
||||
@@ -423,7 +650,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
.border-right {
|
||||
border-right: 1px solid rgb(236, 236, 236);
|
||||
border-right: 0.0625rem solid rgb(236, 236, 236);
|
||||
}
|
||||
.highlighted {
|
||||
background-color: yellow;
|
||||
@@ -434,4 +661,11 @@ export default defineComponent({
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.span-block-highlight {
|
||||
.bg-dark,
|
||||
.bg-white {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user