signoz/pkg/querier/postprocess_table_duplicate_test.go
2025-06-12 16:50:10 +05:30

242 lines
8.0 KiB
Go

package querier
import (
"testing"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestFormatScalarResultsAsTableDuplicateIssue reproduces the exact issue from the user's JSON
func TestFormatScalarResultsAsTableDuplicateIssue(t *testing.T) {
q := &querier{}
// Create results that exactly match the user's problematic case
// Query A has data for all services
// Query B also has data for all services
// But they're coming as separate ScalarData results
results := map[string]*qbtypes.Result{
"A": {
Value: &qbtypes.ScalarData{
Columns: []*qbtypes.ColumnDescriptor{
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "service.name"},
QueryName: "B", // Note: This says "B" in the user's JSON!
Type: qbtypes.ColumnTypeGroup,
},
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "__result_0"},
QueryName: "A",
AggregationIndex: 0,
Type: qbtypes.ColumnTypeAggregation,
},
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "__result_1"},
QueryName: "A",
AggregationIndex: 1,
Type: qbtypes.ColumnTypeAggregation,
},
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "__result_0"},
QueryName: "B",
AggregationIndex: 0,
Type: qbtypes.ColumnTypeAggregation,
},
},
Data: [][]any{
// These rows have values for A but "n/a" for B
{"currencyservice", 3380.0, 1.0, "n/a"},
{"producer-svc-3", 25.0, 1.0, "n/a"},
{"producer-svc-5", 45.0, 1.0, "n/a"},
{"mongodb", 5713.0, 1.0, "n/a"},
{"recommendationservice", 1724.0, 1.0, "n/a"},
{"producer-svc-1", 180.0, 1.0, "n/a"},
{"consumer-svc-4", 210.0, 1.0, "n/a"},
{"frauddetectionservice", 101.0, 1.0, "n/a"},
{"kafka", 1376.0, 1.0, "n/a"},
{"consumer-svc-3", 122.0, 1.0, "n/a"},
{"producer-svc-6", 60.0, 1.0, "n/a"},
{"cartservice", 3322.0, 1.0, "n/a"},
{"consumer-svc-2", 1080.0, 1.0, "n/a"},
{"adservice", 133.0, 1.0, "n/a"},
{"demo-app", 1449.0, 1.0, "n/a"},
{"quoteservice", 101.0, 1.0, "n/a"},
{"producer-svc-2", 360.0, 1.0, "n/a"},
{"producer-svc-4", 36.0, 1.0, "n/a"},
// These rows have "n/a" for A but values for B
{"consumer-svc-4", "n/a", "n/a", 1.0},
{"currencyservice", "n/a", "n/a", 1.0},
{"producer-svc-4", "n/a", "n/a", 1.0},
{"producer-svc-2", "n/a", "n/a", 1.0},
{"producer-svc-3", "n/a", "n/a", 1.0},
{"adservice", "n/a", "n/a", 1.0},
{"kafka", "n/a", "n/a", 1.0},
{"frauddetectionservice", "n/a", "n/a", 1.0},
{"recommendationservice", "n/a", "n/a", 1.0},
{"consumer-svc-3", "n/a", "n/a", 1.0},
{"consumer-svc-2", "n/a", "n/a", 1.0},
{"cartservice", "n/a", "n/a", 1.0},
{"quoteservice", "n/a", "n/a", 1.0},
{"producer-svc-5", "n/a", "n/a", 1.0},
{"demo-app", "n/a", "n/a", 1.0},
{"mongodb", "n/a", "n/a", 1.0},
{"producer-svc-6", "n/a", "n/a", 1.0},
{"producer-svc-1", "n/a", "n/a", 1.0},
},
},
},
}
req := &qbtypes.QueryRangeRequest{
CompositeQuery: qbtypes.CompositeQuery{
Queries: []qbtypes.QueryEnvelope{
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "A",
},
},
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "B",
},
},
},
},
}
// Format as table
result := q.formatScalarResultsAsTable(results, req)
// Get the table - it should be under "A" key now
var table *qbtypes.ScalarData
var ok bool
if tableResult, exists := result["A"]; exists {
table, ok = tableResult.(*qbtypes.ScalarData)
} else if tableResult, exists := result["table"]; exists {
table, ok = tableResult.(*qbtypes.ScalarData)
}
require.True(t, ok, "Expected table result, got: %+v", result)
// The problem: we should have 18 unique services, not 36 rows
assert.Len(t, table.Data, 18, "Should have 18 unique services, not duplicate rows")
// Create a map to check row values by service name
rowMap := make(map[string][]any)
for _, row := range table.Data {
serviceName := row[0].(string)
assert.NotContains(t, rowMap, serviceName, "Service %s should not appear twice", serviceName)
rowMap[serviceName] = row
}
// Check some specific services that appear in both lists
// currencyservice should have values from both A and B
currencyRow := rowMap["currencyservice"]
assert.Equal(t, "currencyservice", currencyRow[0])
assert.Equal(t, 3380.0, currencyRow[1]) // A result 0
assert.Equal(t, 1.0, currencyRow[2]) // A result 1
assert.Equal(t, 1.0, currencyRow[3]) // B result 0
}
// TestFormatScalarResultsAsTableSingleResultAlreadyMerged tests the case where
// a single result already contains all columns from multiple queries
func TestFormatScalarResultsAsTableSingleResultAlreadyMerged(t *testing.T) {
q := &querier{}
// This is what we're actually getting - a single result that already has columns from both queries
results := map[string]*qbtypes.Result{
"merged": {
Value: &qbtypes.ScalarData{
Columns: []*qbtypes.ColumnDescriptor{
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "service.name"},
QueryName: "B",
Type: qbtypes.ColumnTypeGroup,
},
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "__result_0"},
QueryName: "A",
AggregationIndex: 0,
Type: qbtypes.ColumnTypeAggregation,
},
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "__result_1"},
QueryName: "A",
AggregationIndex: 1,
Type: qbtypes.ColumnTypeAggregation,
},
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{Name: "__result_0"},
QueryName: "B",
AggregationIndex: 0,
Type: qbtypes.ColumnTypeAggregation,
},
},
Data: [][]any{
{"currencyservice", 3380.0, 1.0, "n/a"},
{"mongodb", 5713.0, 1.0, "n/a"},
{"currencyservice", "n/a", "n/a", 1.0},
{"mongodb", "n/a", "n/a", 1.0},
},
},
},
}
req := &qbtypes.QueryRangeRequest{
CompositeQuery: qbtypes.CompositeQuery{
Queries: []qbtypes.QueryEnvelope{
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "A",
},
},
{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]{
Name: "B",
},
},
},
},
}
// Format as table
result := q.formatScalarResultsAsTable(results, req)
// Get the table - it should be under "merged" key now
var table *qbtypes.ScalarData
var ok bool
if tableResult, exists := result["merged"]; exists {
table, ok = tableResult.(*qbtypes.ScalarData)
} else if tableResult, exists := result["table"]; exists {
table, ok = tableResult.(*qbtypes.ScalarData)
}
require.True(t, ok, "Expected table result, got: %+v", result)
// Should have 2 unique services, not 4 rows
assert.Len(t, table.Data, 2, "Should have 2 unique services after merging duplicates")
// Create a map to check row values by service name
rowMap := make(map[string][]any)
for _, row := range table.Data {
serviceName := row[0].(string)
rowMap[serviceName] = row
}
// Check that values are properly merged
currencyRow := rowMap["currencyservice"]
assert.Equal(t, "currencyservice", currencyRow[0])
assert.Equal(t, 3380.0, currencyRow[1]) // A result 0
assert.Equal(t, 1.0, currencyRow[2]) // A result 1
assert.Equal(t, 1.0, currencyRow[3]) // B result 0
mongoRow := rowMap["mongodb"]
assert.Equal(t, "mongodb", mongoRow[0])
assert.Equal(t, 5713.0, mongoRow[1]) // A result 0
assert.Equal(t, 1.0, mongoRow[2]) // A result 1
assert.Equal(t, 1.0, mongoRow[3]) // B result 0
}