Compare commits

...

2 Commits

Author SHA1 Message Date
Nikhil Soni
d47be641e3 feat(traces-qb): return root span fields in the trace view
Spans are counted before joining the root spans, so their fields are not copied onto every span.

Assisted-by: Claude Opus 5.5
2026-11-03 15:10:44 +05:30
Abhi kumar
65084ba064 fix(dashboards): label unstacked bar layout as overlap (#13369)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
#### Description

- Bar chart "Series layout" offered "Side by side", but unstacked bars
render overlapping at the same x. Renamed to "Overlap" with a matching
tile drawing and help text, consistent with the area chart's option.

Before
<img width="388" height="157" alt="image"
src="https://github.com/user-attachments/assets/0b9ecab4-ece8-472c-b924-66c053d2cdc9"
/>


After
<img width="420" height="184" alt="image"
src="https://github.com/user-attachments/assets/680c7bdc-0c49-4111-81f5-e0c633df6715"
/>


#### Issues closed by this PR

Closes SigNoz/pulse-pod#673
2026-11-03 06:09:19 +00:00
5 changed files with 48 additions and 31 deletions

View File

@@ -164,12 +164,12 @@ export const TILE_DRAWINGS = {
<path d="M4 20 C 20 20, 22 12, 32 12 S 44 4, 60 4" />
</TileSvg>
),
barsSideBySide: (
barsOverlap: (
<TileSvg>
<rect x={10} y={10} width={7} height={12} {...FILLED} opacity={0.5} />
<rect x={18} y={4} width={7} height={18} {...FILLED} />
<rect x={36} y={12} width={7} height={10} {...FILLED} opacity={0.5} />
<rect x={44} y={7} width={7} height={15} {...FILLED} />
<rect x={14} y={6} width={11} height={16} {...FILLED} opacity={0.5} />
<rect x={14} y={12} width={11} height={10} {...FILLED} />
<rect x={38} y={10} width={11} height={12} {...FILLED} opacity={0.5} />
<rect x={38} y={14} width={11} height={8} {...FILLED} />
</TileSvg>
),
barsStacked: (

View File

@@ -56,7 +56,7 @@ function VisualizationSection({
value?.timePreference ?? DashboardtypesTimePreferenceDTO.global_time;
const barLayout = value?.stackedBarChart
? BarLayout.STACKED
: BarLayout.SIDE_BY_SIDE;
: BarLayout.OVERLAP;
const stack = value?.stack ?? defaultValue?.stack;
return (

View File

@@ -4,15 +4,15 @@ import type { ConfigTileItem } from '../../controls/ConfigTiles/ConfigTiles';
import { TILE_DRAWINGS } from '../../controls/drawings/tileDrawings';
export enum BarLayout {
SIDE_BY_SIDE = 'side',
OVERLAP = 'overlap',
STACKED = 'stacked',
}
export const BAR_LAYOUT_OPTIONS: ConfigTileItem<BarLayout>[] = [
{
value: BarLayout.SIDE_BY_SIDE,
label: 'Side by side',
drawing: TILE_DRAWINGS.barsSideBySide,
value: BarLayout.OVERLAP,
label: 'Overlap',
drawing: TILE_DRAWINGS.barsOverlap,
},
{
value: BarLayout.STACKED,
@@ -22,8 +22,7 @@ export const BAR_LAYOUT_OPTIONS: ConfigTileItem<BarLayout>[] = [
];
export const BAR_LAYOUT_HELP: Record<BarLayout, string> = {
[BarLayout.SIDE_BY_SIDE]:
'Each series gets its own bar, side by side, for easy comparison.',
[BarLayout.OVERLAP]: 'Each series is drawn from zero and may hide others.',
[BarLayout.STACKED]:
'Bars sit on top of each other, so the height shows the total.',
};

View File

@@ -150,7 +150,7 @@ func (b *traceQueryStatementBuilder) Build(
end = querybuilder.ToNanoSecs(end)
isSelectFieldsEmpty := false
if requestType == qbtypes.RequestTypeRaw {
if requestType == qbtypes.RequestTypeRaw || requestType == qbtypes.RequestTypeTrace {
isSelectFieldsEmpty = len(query.SelectFields) == 0
// we are expanding here to ensure that all the conflicts are taken care in adjustKeys
// i.e if there is a conflict we strip away context of the key in adjustKeys
@@ -180,7 +180,7 @@ func (b *traceQueryStatementBuilder) Build(
case qbtypes.RequestTypeScalar:
return b.buildScalarQuery(ctx, orgID, q, query, start, end, keys, variables, false, false)
case qbtypes.RequestTypeTrace:
return b.buildTraceQuery(ctx, orgID, q, query, start, end, keys, variables)
return b.buildTraceQuery(ctx, orgID, q, query, start, end, keys, variables, isSelectFieldsEmpty)
}
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported request type: %s", requestType)
@@ -446,6 +446,7 @@ func (b *traceQueryStatementBuilder) buildTraceQuery(
start, end uint64,
keys map[string][]*telemetrytypes.TelemetryFieldKey,
variables map[string]qbtypes.VariableItem,
isSelectFieldsEmpty bool,
) (*qbtypes.Statement, error) {
startBucket := start/querybuilder.NsToSeconds - querybuilder.BucketAdjustment
@@ -483,6 +484,25 @@ func (b *traceQueryStatementBuilder) buildTraceQuery(
// Build the inner subquery for root spans
innerSB := sqlbuilder.NewSelectBuilder()
innerSB.Select("trace_id", "duration_nano", sqlbuilder.Escape("resource_string_service$$name as `service.name`"), "name")
var rootFieldAliases []string
info := querybuilder.NewQueryInfo(ctx, orgID, b.fl, telemetrytypes.SignalTraces, nil, start, end)
for i, field := range query.SelectFields {
expr, err := querybuilder.ResolveColumn(ctx, info, b.storage, &field, telemetrytypes.FieldDataTypeUnspecified, keys)
if err != nil {
return nil, err
}
alias := selectColumnAlias(i, field.Name)
innerSB.SelectMore(sqlbuilder.Escape(fmt.Sprintf("%s AS %s", expr, alias)))
rootFieldAliases = append(rootFieldAliases, alias)
}
if isSelectFieldsEmpty {
for _, col := range tracestelemetryschema.ContextualSpanColumns {
innerSB.SelectMore(col)
rootFieldAliases = append(rootFieldAliases, col)
}
}
innerSB.From(fmt.Sprintf("%s.%s", tracestelemetryschema.DBName, tracestelemetryschema.SpanIndexV3TableName))
innerSB.Where("parent_span_id = ''")
@@ -512,25 +532,23 @@ func (b *traceQueryStatementBuilder) buildTraceQuery(
mainSB.Select(
"__toe_duration_sorted.`service.name` AS `service.name`",
"__toe_duration_sorted.name AS `name`",
"count() AS span_count",
"__toe_span_count.span_count AS span_count",
"__toe_duration_sorted.duration_nano AS `duration_nano`",
"__toe_duration_sorted.trace_id AS `trace_id`",
)
for _, alias := range rootFieldAliases {
mainSB.SelectMore(sqlbuilder.Escape(fmt.Sprintf("__toe_duration_sorted.%s AS %s", alias, alias)))
}
// Join the distributed table with the inner subquery
mainSB.SQL("FROM __toe")
mainSB.SQL("INNER JOIN __toe_duration_sorted")
mainSB.SQL("ON __toe.trace_id = __toe_duration_sorted.trace_id")
// Group by trace-level fields
mainSB.GroupBy("trace_id", "duration_nano", "name", "`service.name`")
// spans are counted before the join so the root span fields are not copied onto every span
mainSB.SQL("FROM __toe_duration_sorted")
mainSB.SQL("INNER JOIN (SELECT trace_id, count() AS span_count FROM __toe GROUP BY trace_id) AS __toe_span_count")
mainSB.SQL("ON __toe_span_count.trace_id = __toe_duration_sorted.trace_id")
// order by duration only supported for now
mainSB.OrderBy("duration_nano DESC")
// Limit by trace_id to ensure one row per trace
mainSB.SQL("LIMIT 1 BY trace_id")
if query.Limit > 0 {
mainSB.Limit(query.Limit)
} else {

File diff suppressed because one or more lines are too long