mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
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:
parent
360e8309c8
commit
faadc60c74
@ -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,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -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),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user