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 = {
|
||||
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,
|
||||
} from '../utils';
|
||||
import {
|
||||
expectedOutputQBv5MultiAggregations,
|
||||
expectedOutputWithLegends,
|
||||
tableDataMultipleQueriesSuccessResponse,
|
||||
tableDataQBv5MultiAggregations,
|
||||
widgetQueryQBv5MultiAggregations,
|
||||
widgetQueryWithLegend,
|
||||
} from './response';
|
||||
|
||||
@ -67,6 +70,7 @@ describe('Table Panel utils', () => {
|
||||
isValueColumn: true,
|
||||
name: 'A',
|
||||
queryName: 'A',
|
||||
id: 'A',
|
||||
};
|
||||
// A has value and value is considered bigger than n/a hence 1
|
||||
expect(sortFunction(rowA, rowB, item)).toBe(1);
|
||||
@ -128,3 +132,96 @@ describe('Table Panel utils', () => {
|
||||
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;
|
||||
queryName: string;
|
||||
isValueColumn: boolean;
|
||||
id: string;
|
||||
},
|
||||
): number {
|
||||
const colId = item.id;
|
||||
const colName = item.name;
|
||||
// assumption :- number values is bigger than 'n/a'
|
||||
const valueA = Number(a[`${item.name}_without_unit`] ?? a[item.name]);
|
||||
const valueB = Number(b[`${item.name}_without_unit`] ?? b[item.name]);
|
||||
const valueA = Number(a[`${colId}_without_unit`] ?? a[colId] ?? a[colName]);
|
||||
const valueB = Number(b[`${colId}_without_unit`] ?? b[colId] ?? b[colName]);
|
||||
|
||||
// if both the values are numbers then return the difference here
|
||||
if (!isNaN(valueA) && !isNaN(valueB)) {
|
||||
@ -172,10 +175,11 @@ export function sortFunction(
|
||||
}
|
||||
|
||||
// if both of them are strings do the localecompare
|
||||
return ((a[item.name] as string) || '').localeCompare(
|
||||
(b[item.name] as string) || '',
|
||||
return ((a[colId] as string) || (a[colName] as string) || '').localeCompare(
|
||||
(b[colId] as string) || (b[colName] as string) || '',
|
||||
);
|
||||
}
|
||||
|
||||
export function createColumnsAndDataSource(
|
||||
data: TableData,
|
||||
currentQuery: Query,
|
||||
@ -198,7 +202,7 @@ export function createColumnsAndDataSource(
|
||||
// if no legend present then rely on the column name value
|
||||
title: !isNewAggregation && !isEmpty(legend) ? legend : item.name,
|
||||
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),
|
||||
};
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user