fix: fixed table panels not sorting, due to mismatch in lookup (id vs name) for aggregations (#9002)

* fix: fixed table panels not sorting, due to mismatch in id for aggregations

* fix: added test cases for the sort and util for qbv5 aggregation
This commit is contained in:
SagarRajput-7 2025-09-04 18:44:38 +05:30 committed by GitHub
parent 360e8309c8
commit faadc60c74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 382 additions and 5 deletions

View File

@ -1,3 +1,4 @@
/* eslint-disable sonarjs/no-duplicate-string */
export const tableDataMultipleQueriesSuccessResponse = { export const tableDataMultipleQueriesSuccessResponse = {
columns: [ columns: [
{ {
@ -210,3 +211,278 @@ export const expectedOutputWithLegends = {
}, },
], ],
}; };
// QB v5 Aggregations Mock Data
export const tableDataQBv5MultiAggregations = {
columns: [
{
name: 'service.name',
queryName: 'A',
isValueColumn: false,
id: 'service.name',
},
{
name: 'host.name',
queryName: 'A',
isValueColumn: false,
id: 'host.name',
},
{
name: 'count()',
queryName: 'A',
isValueColumn: true,
id: 'A.count()',
},
{
name: 'count_distinct(app.ads.count)',
queryName: 'A',
isValueColumn: true,
id: 'A.count_distinct(app.ads.count)',
},
{
name: 'count()',
queryName: 'B',
isValueColumn: true,
id: 'B.count()',
},
{
name: 'count_distinct(app.ads.count)',
queryName: 'B',
isValueColumn: true,
id: 'B.count_distinct(app.ads.count)',
},
{
name: 'count()',
queryName: 'C',
isValueColumn: true,
id: 'C.count()',
},
{
name: 'count_distinct(app.ads.count)',
queryName: 'C',
isValueColumn: true,
id: 'C.count_distinct(app.ads.count)',
},
],
rows: [
{
data: {
'service.name': 'frontend-proxy',
'host.name': 'test-host.name',
'A.count()': 144679,
'A.count_distinct(app.ads.count)': 0,
'B.count()': 144679,
'B.count_distinct(app.ads.count)': 0,
'C.count()': 144679,
'C.count_distinct(app.ads.count)': 0,
},
},
{
data: {
'service.name': 'frontend',
'host.name': 'test-host.name',
'A.count()': 142311,
'A.count_distinct(app.ads.count)': 0,
'B.count()': 142311,
'B.count_distinct(app.ads.count)': 0,
'C.count()': 142311,
'C.count_distinct(app.ads.count)': 0,
},
},
],
};
export const widgetQueryQBv5MultiAggregations = {
clickhouse_sql: [
{
name: 'A',
legend: 'p99',
disabled: false,
query: '',
},
{
name: 'B',
legend: '',
disabled: false,
query: '',
},
{
name: 'C',
legend: 'max',
disabled: false,
query: '',
},
],
promql: [
{
name: 'A',
query: '',
legend: 'p99',
disabled: false,
},
{
name: 'B',
query: '',
legend: '',
disabled: false,
},
{
name: 'C',
query: '',
legend: 'max',
disabled: false,
},
],
builder: {
queryData: [
{
dataSource: 'metrics',
queryName: 'A',
aggregateOperator: 'count',
aggregateAttribute: {
dataType: 'float64',
id: 'signoz_latency--float64--ExponentialHistogram--true',
key: 'signoz_latency',
type: 'ExponentialHistogram',
},
timeAggregation: '',
spaceAggregation: 'p90',
functions: [],
filters: {
items: [],
op: 'AND',
},
expression: 'A',
disabled: false,
stepInterval: 60,
having: [],
limit: null,
orderBy: [],
groupBy: [
{
dataType: 'string',
key: 'service.name',
type: 'tag',
id: 'service.name--string--tag--false',
},
{
dataType: 'string',
key: 'host.name',
type: 'tag',
id: 'host.name--string--tag--false',
},
],
legend: 'p99',
reduceTo: 'avg',
},
{
dataSource: 'metrics',
queryName: 'B',
aggregateOperator: 'rate',
aggregateAttribute: {
dataType: 'float64',
id: 'system_disk_operations--float64--Sum--true',
key: 'system_disk_operations',
type: 'Sum',
},
timeAggregation: 'rate',
spaceAggregation: 'sum',
functions: [],
filters: {
items: [],
op: 'AND',
},
expression: 'B',
disabled: false,
stepInterval: 60,
having: [],
limit: null,
orderBy: [],
groupBy: [
{
dataType: 'string',
key: 'service.name',
type: 'tag',
id: 'service.name--string--tag--false',
},
{
dataType: 'string',
key: 'host.name',
type: 'tag',
id: 'host.name--string--tag--false',
},
],
legend: '',
reduceTo: 'avg',
},
{
dataSource: 'metrics',
queryName: 'C',
aggregateOperator: 'count',
aggregateAttribute: {
dataType: 'float64',
id: 'signoz_latency--float64--ExponentialHistogram--true',
key: 'signoz_latency',
type: 'ExponentialHistogram',
},
timeAggregation: '',
spaceAggregation: 'p90',
functions: [],
filters: {
items: [],
op: 'AND',
},
expression: 'C',
disabled: false,
stepInterval: 60,
having: [],
limit: null,
orderBy: [],
groupBy: [
{
dataType: 'string',
key: 'service.name',
type: 'tag',
id: 'service.name--string--tag--false',
},
{
dataType: 'string',
key: 'host.name',
type: 'tag',
id: 'host.name--string--tag--false',
},
],
legend: 'max',
reduceTo: 'avg',
},
],
queryFormulas: [],
},
id: 'qb-v5-multi-aggregations-test',
queryType: 'builder',
};
export const expectedOutputQBv5MultiAggregations = {
dataSource: [
{
'service.name': 'frontend-proxy',
'host.name': 'test-host.name',
'A.count()': 144679,
'A.count_distinct(app.ads.count)': 0,
'B.count()': 144679,
'B.count_distinct(app.ads.count)': 0,
'C.count()': 144679,
'C.count_distinct(app.ads.count)': 0,
},
{
'service.name': 'frontend',
'host.name': 'test-host.name',
'A.count()': 142311,
'A.count_distinct(app.ads.count)': 0,
'B.count()': 142311,
'B.count_distinct(app.ads.count)': 0,
'C.count()': 142311,
'C.count_distinct(app.ads.count)': 0,
},
],
};

View File

@ -6,8 +6,11 @@ import {
sortFunction, sortFunction,
} from '../utils'; } from '../utils';
import { import {
expectedOutputQBv5MultiAggregations,
expectedOutputWithLegends, expectedOutputWithLegends,
tableDataMultipleQueriesSuccessResponse, tableDataMultipleQueriesSuccessResponse,
tableDataQBv5MultiAggregations,
widgetQueryQBv5MultiAggregations,
widgetQueryWithLegend, widgetQueryWithLegend,
} from './response'; } from './response';
@ -67,6 +70,7 @@ describe('Table Panel utils', () => {
isValueColumn: true, isValueColumn: true,
name: 'A', name: 'A',
queryName: 'A', queryName: 'A',
id: 'A',
}; };
// A has value and value is considered bigger than n/a hence 1 // A has value and value is considered bigger than n/a hence 1
expect(sortFunction(rowA, rowB, item)).toBe(1); expect(sortFunction(rowA, rowB, item)).toBe(1);
@ -128,3 +132,96 @@ describe('Table Panel utils', () => {
expect(sortFunction(rowA, rowB, item)).toBe(0); expect(sortFunction(rowA, rowB, item)).toBe(0);
}); });
}); });
describe('Table Panel utils with QB v5 aggregations', () => {
it('createColumnsAndDataSource function - QB v5 multi-aggregations', () => {
const data = tableDataQBv5MultiAggregations;
const query = widgetQueryQBv5MultiAggregations as Query;
const { columns, dataSource } = createColumnsAndDataSource(data, query);
// Verify column structure for multi-aggregations
expect(columns).toHaveLength(8);
expect(columns[0].title).toBe('service.name');
expect(columns[1].title).toBe('host.name');
// All columns with queryName 'A' get the legend 'p99'
expect(columns[2].title).toBe('p99'); // A.count() uses legend from query A
expect(columns[3].title).toBe('p99'); // A.count_distinct() uses legend from query A
expect(columns[4].title).toBe('count()'); // B.count() uses column name (no legend)
expect(columns[5].title).toBe('count_distinct(app.ads.count)'); // B.count_distinct() uses column name
expect(columns[6].title).toBe('max'); // C.count() uses legend from query C
expect(columns[7].title).toBe('max'); // C.count_distinct() uses legend from query C
// Verify dataIndex mapping
expect((columns[0] as any).dataIndex).toBe('service.name');
expect((columns[2] as any).dataIndex).toBe('A.count()');
expect((columns[3] as any).dataIndex).toBe('A.count_distinct(app.ads.count)');
// Verify dataSource structure
expect(dataSource).toStrictEqual(
expectedOutputQBv5MultiAggregations.dataSource,
);
});
it('getQueryLegend function - QB v5 multi-query support', () => {
const query = widgetQueryQBv5MultiAggregations as Query;
expect(getQueryLegend(query, 'A')).toBe('p99');
expect(getQueryLegend(query, 'B')).toBeUndefined();
expect(getQueryLegend(query, 'C')).toBe('max');
expect(getQueryLegend(query, 'D')).toBeUndefined();
});
it('sorter function - QB v5 multi-aggregation columns', () => {
const item = {
isValueColumn: true,
name: 'count()',
queryName: 'A',
id: 'A.count()',
};
// Test numeric sorting
expect(
sortFunction(
{ 'A.count()': 100, key: '1', timestamp: 1000 },
{ 'A.count()': 200, key: '2', timestamp: 1000 },
item,
),
).toBe(-100);
// Test n/a handling
expect(
sortFunction(
{ 'A.count()': 'n/a', key: '1', timestamp: 1000 },
{ 'A.count()': 100, key: '2', timestamp: 1000 },
item,
),
).toBe(-1);
expect(
sortFunction(
{ 'A.count()': 100, key: '1', timestamp: 1000 },
{ 'A.count()': 'n/a', key: '2', timestamp: 1000 },
item,
),
).toBe(1);
// Test string sorting
expect(
sortFunction(
{ 'A.count()': 'read', key: '1', timestamp: 1000 },
{ 'A.count()': 'write', key: '2', timestamp: 1000 },
item,
),
).toBe(-1);
// Test equal values
expect(
sortFunction(
{ 'A.count()': 'n/a', key: '1', timestamp: 1000 },
{ 'A.count()': 'n/a', key: '2', timestamp: 1000 },
item,
),
).toBe(0);
});
});

View File

@ -150,11 +150,14 @@ export function sortFunction(
name: string; name: string;
queryName: string; queryName: string;
isValueColumn: boolean; isValueColumn: boolean;
id: string;
}, },
): number { ): number {
const colId = item.id;
const colName = item.name;
// assumption :- number values is bigger than 'n/a' // assumption :- number values is bigger than 'n/a'
const valueA = Number(a[`${item.name}_without_unit`] ?? a[item.name]); const valueA = Number(a[`${colId}_without_unit`] ?? a[colId] ?? a[colName]);
const valueB = Number(b[`${item.name}_without_unit`] ?? b[item.name]); const valueB = Number(b[`${colId}_without_unit`] ?? b[colId] ?? b[colName]);
// if both the values are numbers then return the difference here // if both the values are numbers then return the difference here
if (!isNaN(valueA) && !isNaN(valueB)) { if (!isNaN(valueA) && !isNaN(valueB)) {
@ -172,10 +175,11 @@ export function sortFunction(
} }
// if both of them are strings do the localecompare // if both of them are strings do the localecompare
return ((a[item.name] as string) || '').localeCompare( return ((a[colId] as string) || (a[colName] as string) || '').localeCompare(
(b[item.name] as string) || '', (b[colId] as string) || (b[colName] as string) || '',
); );
} }
export function createColumnsAndDataSource( export function createColumnsAndDataSource(
data: TableData, data: TableData,
currentQuery: Query, currentQuery: Query,
@ -198,7 +202,7 @@ export function createColumnsAndDataSource(
// if no legend present then rely on the column name value // if no legend present then rely on the column name value
title: !isNewAggregation && !isEmpty(legend) ? legend : item.name, title: !isNewAggregation && !isEmpty(legend) ? legend : item.name,
width: QUERY_TABLE_CONFIG.width, width: QUERY_TABLE_CONFIG.width,
render: renderColumnCell && renderColumnCell[item.name], render: renderColumnCell && renderColumnCell[item.id],
sorter: (a: RowData, b: RowData): number => sortFunction(a, b, item), sorter: (a: RowData, b: RowData): number => sortFunction(a, b, item),
}; };