fix(traces): readable flame graph labels, unclipped chart tooltips, outlined close button (#16119)

Co-authored-by: Ashish Kolhe <35160958+oasisk@users.noreply.github.com>
This commit is contained in:
Omkar Kesarkhane
2026-11-01 10:30:03 +00:00
committed by GitHub
co-authored by Ashish Kolhe
parent 901ee86830
commit c02ee4b6f2
8 changed files with 55 additions and 5 deletions
@@ -70,6 +70,8 @@ vi.mock("@/utils/traces/serviceColorRegistry", () => ({
"service-1": "#4caf50",
"service-2": "#2196f3",
"service-3": "#ff9800",
"lime-service": "#84cc16",
"violet-service": "#7c3aed",
};
return colorMap[name] || "#9CA3AF";
}),
@@ -1483,6 +1485,21 @@ describe("FlameGraphView span event markers", () => {
expect(result.children.slice(1).every((c: any) => c.silent)).toBe(true);
});
it.each([
["lime-service", "#000000"],
["violet-service", "#ffffff"],
])("picks a label colour that contrasts with the %s bar", (service, expected) => {
const wrapper = mount(FlameGraphView, {
props: {
spans: [{ ...spanWithEvents, resolvedIdentity: service, events: [] }],
traceDuration: 100,
selectedSpanId: null,
},
});
expect(renderBlock(wrapper, 100).textContent.style.fill).toBe(expected);
});
it("returns a bare rect for a span with no events", () => {
const wrapper = mount(FlameGraphView, {
props: {
+5 -1
View File
@@ -163,6 +163,7 @@ import { type EnrichedSpan } from "@/ts/interfaces/traces/span.types";
import { formatDuration } from "@/composables/traces/useTraceProcessing";
import { maxOf } from "@/plugins/traces/threadView.utils";
import { getOrSetServiceColor } from "@/utils/traces/serviceColorRegistry";
import { getContrastTextColor } from "@/utils/traces/traceColors";
import { escapeHtml } from "@/utils/html";
import {
normalizeSpanEvents,
@@ -649,7 +650,10 @@ const chartOptions = computed(() => {
type: "text",
style: {
text: data[params.dataIndex].spanData.operationName,
fill: "#ffffff",
fill:
getContrastTextColor(data[params.dataIndex].itemStyle.color) === "black"
? "#000000"
: "#ffffff",
font: "0.6875rem Inter, sans-serif",
overflow: "truncate",
width: rectWidth - 8,
@@ -1153,6 +1153,14 @@ describe("TraceDetails", () => {
expect(closeBtn.exists()).toBe(true);
});
it("renders the close button as outline with a Close tooltip", () => {
const closeBtn = wrapper.findComponent('[data-test="trace-details-close-btn"]');
expect(closeBtn.exists()).toBe(true);
expect(closeBtn.attributes("data-o2-variant")).toBe("outline");
const tooltip = closeBtn.findComponent({ name: "OTooltip" });
expect(tooltip.props("content")).toBe(i18n.global.t("common.close"));
});
it("should show share button when showShareButton is true in standalone mode", () => {
const shareBtn = wrapper.find('[data-test="trace-details-share-link-btn"]');
expect(shareBtn.exists()).toBe(true);
+4 -4
View File
@@ -215,12 +215,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<OButton
v-if="showCloseButton"
data-test="trace-details-close-btn"
variant="ghost"
variant="outline"
size="icon-xs"
@click="handleBackOrClose"
>
<OIcon name="close" size="sm" />
<OTooltip :content="t('common.cancel')" />
<OTooltip :content="t('common.close')" />
</OButton>
</template>
</OPageHeader>
@@ -392,13 +392,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<OButton
v-if="isStandaloneMode && showCloseButton"
data-test="trace-details-close-btn"
variant="ghost"
variant="outline"
size="icon-xs"
class="me-1!"
@click="handleBackOrClose"
>
<OIcon name="close" size="sm" />
<OTooltip :content="t('common.cancel')" />
<OTooltip :content="t('common.close')" />
</OButton>
</div>
</header>
@@ -164,6 +164,14 @@ describe("TracesComparisonField", () => {
expect(o.series[1].itemStyle.color).toBe("#0a4ce8");
});
it("renders the histogram tooltip on the body, so the card does not clip it", async () => {
wrapper = mountField(numeric());
await settle();
const o = chartOptions(wrapper);
expect(o.tooltip.appendToBody).toBe(true);
expect(o.tooltip.className).toBe("o2-echarts-tooltip");
});
it("pre-selects the most different bin and marks it", async () => {
const field = numeric();
wrapper = mountField(field);
@@ -210,6 +210,9 @@ const options = computed(() => {
grid: { left: 4, right: 4, top: 6, bottom: 20 },
tooltip: {
trigger: "axis",
// An overflow-hidden card ancestor clips a tooltip rendered inside the chart.
appendToBody: true,
className: "o2-echarts-tooltip",
axisPointer: { type: "shadow" },
backgroundColor: chartColor("--color-tooltip-bg"),
borderWidth: 0,
@@ -276,6 +276,13 @@ describe("TracesLatencyHeatmap", () => {
expect(o.series[0].type).toBe("heatmap");
});
it("renders the tooltip on the body, so the card does not clip it", async () => {
wrapper = await mountHeatmap();
const o = options(wrapper);
expect(o.tooltip.appendToBody).toBe(true);
expect(o.tooltip.className).toBe("o2-echarts-tooltip");
});
it("draws cells without borders, so a dense grid is not striped", async () => {
wrapper = await mountHeatmap();
expect(options(wrapper).series[0].itemStyle.borderWidth).toBe(0);
@@ -238,6 +238,9 @@ const options = computed(() => {
grid: { left: 52, right: 8, top: 8, bottom: 22 },
tooltip: {
trigger: "item",
// An overflow-hidden card ancestor clips a tooltip rendered inside the chart.
appendToBody: true,
className: "o2-echarts-tooltip",
backgroundColor: chartColor("--color-tooltip-bg"),
borderWidth: 0,
textStyle: { color: chartColor("--color-tooltip-text"), fontSize: 12 },