logs-analyzer/signoz/pkg/query-service/postprocess/table_test.go

613 lines
18 KiB
Go
Raw Normal View History

2024-09-02 22:47:30 +03:00
package postprocess
import (
"bytes"
"encoding/json"
"reflect"
"testing"
"go.signoz.io/signoz/pkg/query-service/constants"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
)
func TestSortRows(t *testing.T) {
tests := []struct {
name string
rows []*v3.TableRow
columns []*v3.TableColumn
builderQueries map[string]*v3.BuilderQuery
queryNames []string
expected []*v3.TableRow
}{
{
name: "Sort by single numeric query, ascending order",
rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service2", "A": 20.0}},
{Data: map[string]interface{}{"service": "service1", "A": 10.0}},
{Data: map[string]interface{}{"service": "service3", "A": 30.0}},
},
columns: []*v3.TableColumn{
{Name: "service_name"},
{Name: "A"},
},
builderQueries: map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "asc"}}},
},
queryNames: []string{"A"},
expected: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": 10.0}},
{Data: map[string]interface{}{"service": "service2", "A": 20.0}},
{Data: map[string]interface{}{"service": "service3", "A": 30.0}},
},
},
{
name: "Sort by single numeric query, descending order",
rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service2", "A": 20.0}},
{Data: map[string]interface{}{"service": "service1", "A": 10.0}},
{Data: map[string]interface{}{"service": "service3", "A": 30.0}},
},
columns: []*v3.TableColumn{
{Name: "service_name"},
{Name: "A"},
},
builderQueries: map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "desc"}}},
},
queryNames: []string{"A"},
expected: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service3", "A": 30.0}},
{Data: map[string]interface{}{"service": "service2", "A": 20.0}},
{Data: map[string]interface{}{"service": "service1", "A": 10.0}},
},
},
{
name: "Sort by single string query, ascending order",
rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service2", "A": "b"}},
{Data: map[string]interface{}{"service": "service1", "A": "c"}},
{Data: map[string]interface{}{"service": "service3", "A": "a"}},
},
columns: []*v3.TableColumn{
{Name: "service_name"},
{Name: "A"},
},
builderQueries: map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: "A", Order: "asc"}}},
},
queryNames: []string{"A"},
expected: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service3", "A": "a"}},
{Data: map[string]interface{}{"service": "service2", "A": "b"}},
{Data: map[string]interface{}{"service": "service1", "A": "c"}},
},
},
{
name: "Sort with n/a values",
rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": 10.0}},
{Data: map[string]interface{}{"service": "service2", "B": 15.0}},
{Data: map[string]interface{}{"service": "service3", "A": 30.0, "B": 25.0}},
{Data: map[string]interface{}{"service": "service4"}},
},
columns: []*v3.TableColumn{
{Name: "service_name"},
{Name: "A"},
{Name: "B"},
},
builderQueries: map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "asc"}}},
"B": {OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "desc"}}},
},
queryNames: []string{"A", "B"},
expected: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": 10.0}},
{Data: map[string]interface{}{"service": "service3", "A": 30.0, "B": 25.0}},
{Data: map[string]interface{}{"service": "service2", "B": 15.0}},
{Data: map[string]interface{}{"service": "service4"}},
},
},
{
name: "Sort with SigNozOrderByValue",
rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": 20.0}},
{Data: map[string]interface{}{"service": "service2", "A": 10.0}},
{Data: map[string]interface{}{"service": "service3", "A": 30.0}},
},
columns: []*v3.TableColumn{
{Name: "service_name"},
{Name: "A"},
},
builderQueries: map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "desc"}}},
},
queryNames: []string{"A"},
expected: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service3", "A": 30.0}},
{Data: map[string]interface{}{"service": "service1", "A": 20.0}},
{Data: map[string]interface{}{"service": "service2", "A": 10.0}},
},
},
{
name: "Sort with all n/a values",
rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": "n/a", "B": "n/a"}},
{Data: map[string]interface{}{"service": "service2", "A": "n/a", "B": "n/a"}},
{Data: map[string]interface{}{"service": "service3", "A": "n/a", "B": "n/a"}},
},
columns: []*v3.TableColumn{
{Name: "service_name"},
{Name: "A"},
{Name: "B"},
},
builderQueries: map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: "A", Order: "asc"}}},
"B": {OrderBy: []v3.OrderBy{{ColumnName: "B", Order: "desc"}}},
},
queryNames: []string{"A", "B"},
expected: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": "n/a", "B": "n/a"}},
{Data: map[string]interface{}{"service": "service2", "A": "n/a", "B": "n/a"}},
{Data: map[string]interface{}{"service": "service3", "A": "n/a", "B": "n/a"}},
},
},
{
name: "Sort with negative numbers",
rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": -10.0}},
{Data: map[string]interface{}{"service": "service2", "A": 20.0}},
{Data: map[string]interface{}{"service": "service3", "A": -30.0}},
{Data: map[string]interface{}{"service": "service4", "A": 0.0}},
},
columns: []*v3.TableColumn{
{Name: "service_name"},
{Name: "A"},
},
builderQueries: map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: "A", Order: "asc"}}},
},
queryNames: []string{"A"},
expected: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service3", "A": -30.0}},
{Data: map[string]interface{}{"service": "service1", "A": -10.0}},
{Data: map[string]interface{}{"service": "service4", "A": 0.0}},
{Data: map[string]interface{}{"service": "service2", "A": 20.0}},
},
},
{
name: "Sort with mixed case strings",
rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": "Apple"}},
{Data: map[string]interface{}{"service": "service2", "A": "banana"}},
{Data: map[string]interface{}{"service": "service3", "A": "Cherry"}},
{Data: map[string]interface{}{"service": "service4", "A": "date"}},
},
columns: []*v3.TableColumn{
{Name: "service_name"},
{Name: "A"},
},
builderQueries: map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: "A", Order: "asc"}}},
},
queryNames: []string{"A"},
expected: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": "Apple"}},
{Data: map[string]interface{}{"service": "service3", "A": "Cherry"}},
{Data: map[string]interface{}{"service": "service2", "A": "banana"}},
{Data: map[string]interface{}{"service": "service4", "A": "date"}},
},
},
{
name: "Sort with empty strings",
rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": ""}},
{Data: map[string]interface{}{"service": "service2", "A": "b"}},
{Data: map[string]interface{}{"service": "service3", "A": ""}},
{Data: map[string]interface{}{"service": "service4", "A": "a"}},
},
columns: []*v3.TableColumn{
{Name: "service_name"},
{Name: "A"},
},
builderQueries: map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: "A", Order: "asc"}}},
},
queryNames: []string{"A"},
expected: []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": ""}},
{Data: map[string]interface{}{"service": "service3", "A": ""}},
{Data: map[string]interface{}{"service": "service4", "A": "a"}},
{Data: map[string]interface{}{"service": "service2", "A": "b"}},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sortRows(tt.rows, tt.builderQueries, tt.queryNames)
if !reflect.DeepEqual(tt.rows, tt.expected) {
exp, _ := json.Marshal(tt.expected)
got, _ := json.Marshal(tt.rows)
t.Errorf("sortRows() = %v, want %v", string(got), string(exp))
}
})
}
}
func TestSortRowsWithEmptyQueries(t *testing.T) {
rows := []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": 20.0}},
{Data: map[string]interface{}{"service": "service2", "A": 10.0}},
{Data: map[string]interface{}{"service": "service3", "A": 30.0}},
}
builderQueries := map[string]*v3.BuilderQuery{}
queryNames := []string{}
sortRows(rows, builderQueries, queryNames)
// Expect the original order to be maintained
expected := []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": 20.0}},
{Data: map[string]interface{}{"service": "service2", "A": 10.0}},
{Data: map[string]interface{}{"service": "service3", "A": 30.0}},
}
if !reflect.DeepEqual(rows, expected) {
t.Errorf("sortRows() with empty queries = %v, want %v", rows, expected)
}
}
func TestSortRowsWithInvalidColumnName(t *testing.T) {
rows := []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": 20.0}},
{Data: map[string]interface{}{"service": "service2", "A": 10.0}},
{Data: map[string]interface{}{"service": "service3", "A": 30.0}},
}
builderQueries := map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: "InvalidColumn", Order: "asc"}}},
}
queryNames := []string{"A"}
sortRows(rows, builderQueries, queryNames)
// Expect the original order to be maintained
expected := []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": 20.0}},
{Data: map[string]interface{}{"service": "service2", "A": 10.0}},
{Data: map[string]interface{}{"service": "service3", "A": 30.0}},
}
if !reflect.DeepEqual(rows, expected) {
t.Errorf("sortRows() with invalid column name = %v, want %v", rows, expected)
}
}
func TestSortRowsStability(t *testing.T) {
rows := []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": 10.0, "B": "a"}},
{Data: map[string]interface{}{"service": "service2", "A": 10.0, "B": "b"}},
{Data: map[string]interface{}{"service": "service3", "A": 10.0, "B": "c"}},
}
builderQueries := map[string]*v3.BuilderQuery{
"A": {OrderBy: []v3.OrderBy{{ColumnName: "A", Order: "asc"}}},
}
queryNames := []string{"A"}
sortRows(rows, builderQueries, queryNames)
// Expect the original order to be maintained for equal values
expected := []*v3.TableRow{
{Data: map[string]interface{}{"service": "service1", "A": 10.0, "B": "a"}},
{Data: map[string]interface{}{"service": "service2", "A": 10.0, "B": "b"}},
{Data: map[string]interface{}{"service": "service3", "A": 10.0, "B": "c"}},
}
if !reflect.DeepEqual(rows, expected) {
t.Errorf("sortRows() stability test failed = %v, want %v", rows, expected)
}
}
func TestTransformToTableForClickHouseQueries(t *testing.T) {
tests := []struct {
name string
input []*v3.Result
expected []*v3.Result
}{
{
name: "Empty input",
input: []*v3.Result{},
expected: []*v3.Result{},
},
{
name: "Single result with one series",
input: []*v3.Result{
{
QueryName: "A",
Series: []*v3.Series{
{
LabelsArray: []map[string]string{
{"service": "frontend"},
},
Points: []v3.Point{
{Value: 10.0},
},
},
},
},
},
expected: []*v3.Result{
{
Table: &v3.Table{
Columns: []*v3.TableColumn{
{Name: "service"},
{Name: "A", QueryName: "A", IsValueColumn: true},
},
Rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "frontend", "A": 10.0}},
},
},
},
},
},
{
name: "Multiple results with multiple series",
input: []*v3.Result{
{
QueryName: "A",
Series: []*v3.Series{
{
LabelsArray: []map[string]string{
{"service": "frontend"},
{"env": "prod"},
},
Points: []v3.Point{
{Value: 10.0},
},
},
{
LabelsArray: []map[string]string{
{"service": "backend"},
{"env": "prod"},
},
Points: []v3.Point{
{Value: 20.0},
},
},
},
},
{
QueryName: "B",
Series: []*v3.Series{
{
LabelsArray: []map[string]string{
{"service": "frontend"},
{"env": "prod"},
},
Points: []v3.Point{
{Value: 15.0},
},
},
{
LabelsArray: []map[string]string{
{"service": "backend"},
{"env": "prod"},
},
Points: []v3.Point{
{Value: 25.0},
},
},
},
},
},
expected: []*v3.Result{
{
Table: &v3.Table{
Columns: []*v3.TableColumn{
{Name: "service"},
{Name: "env"},
{Name: "A", QueryName: "A", IsValueColumn: true},
{Name: "B", QueryName: "B", IsValueColumn: true},
},
Rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "frontend", "env": "prod", "A": 10.0, "B": "n/a"}},
{Data: map[string]interface{}{"service": "backend", "env": "prod", "A": 20.0, "B": "n/a"}},
{Data: map[string]interface{}{"service": "frontend", "env": "prod", "A": "n/a", "B": 15.0}},
{Data: map[string]interface{}{"service": "backend", "env": "prod", "A": "n/a", "B": 25.0}},
},
},
},
},
},
{
name: "Results with missing labels",
input: []*v3.Result{
{
QueryName: "A",
Series: []*v3.Series{
{
LabelsArray: []map[string]string{
{"service": "frontend"},
},
Points: []v3.Point{
{Value: 10.0},
},
},
},
},
{
QueryName: "B",
Series: []*v3.Series{
{
LabelsArray: []map[string]string{
{"env": "prod"},
},
Points: []v3.Point{
{Value: 20.0},
},
},
},
},
},
expected: []*v3.Result{
{
Table: &v3.Table{
Columns: []*v3.TableColumn{
{Name: "service"},
{Name: "env"},
{Name: "A", QueryName: "A", IsValueColumn: true},
{Name: "B", QueryName: "B", IsValueColumn: true},
},
Rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "frontend", "env": "n/a", "A": 10.0, "B": "n/a"}},
{Data: map[string]interface{}{"service": "n/a", "env": "prod", "A": "n/a", "B": 20.0}},
},
},
},
},
},
{
name: "Results with empty series",
input: []*v3.Result{
{
QueryName: "A",
Series: []*v3.Series{
{
LabelsArray: []map[string]string{
{"service": "frontend"},
},
Points: []v3.Point{
{Value: 10.0},
},
},
},
},
{
QueryName: "B",
Series: []*v3.Series{},
},
},
expected: []*v3.Result{
{
Table: &v3.Table{
Columns: []*v3.TableColumn{
{Name: "service"},
{Name: "A", QueryName: "A", IsValueColumn: true},
},
Rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "frontend", "A": 10.0}},
},
},
},
},
},
{
name: "Results with empty points",
input: []*v3.Result{
{
QueryName: "A",
Series: []*v3.Series{
{
LabelsArray: []map[string]string{
{"service": "frontend"},
},
Points: []v3.Point{},
},
},
},
{
QueryName: "B",
Series: []*v3.Series{
{
LabelsArray: []map[string]string{
{"service": "backend"},
},
Points: []v3.Point{
{Value: 20.0},
},
},
},
},
},
expected: []*v3.Result{
{
Table: &v3.Table{
Columns: []*v3.TableColumn{
{Name: "service"},
{Name: "B", QueryName: "B", IsValueColumn: true},
},
Rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "frontend", "B": "n/a"}},
{Data: map[string]interface{}{"service": "backend", "B": 20.0}},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := TransformToTableForClickHouseQueries(tt.input)
exp, _ := json.Marshal(tt.expected)
got, _ := json.Marshal(result)
if !bytes.Equal(got, exp) {
t.Errorf("TransformToTableForClickHouseQueries() = %v, want %v", string(got), string(exp))
}
})
}
}
func TestTransformToTableForClickHouseQueriesSorting(t *testing.T) {
input := []*v3.Result{
{
QueryName: "B",
Series: []*v3.Series{
{
LabelsArray: []map[string]string{
{"service": "frontend"},
},
Points: []v3.Point{
{Value: 10.0},
},
},
},
},
{
QueryName: "A",
Series: []*v3.Series{
{
LabelsArray: []map[string]string{
{"service": "backend"},
},
Points: []v3.Point{
{Value: 20.0},
},
},
},
},
}
expected := []*v3.Result{
{
Table: &v3.Table{
Columns: []*v3.TableColumn{
{Name: "service"},
{Name: "A", QueryName: "A", IsValueColumn: true},
{Name: "B", QueryName: "B", IsValueColumn: true},
},
Rows: []*v3.TableRow{
{Data: map[string]interface{}{"service": "backend", "A": 20.0, "B": "n/a"}},
{Data: map[string]interface{}{"service": "frontend", "A": "n/a", "B": 10.0}},
},
},
},
}
result := TransformToTableForClickHouseQueries(input)
exp, _ := json.Marshal(expected)
got, _ := json.Marshal(result)
if !bytes.Equal(got, exp) {
t.Errorf("TransformToTableForClickHouseQueries() sorting test failed. Got %v, want %v", string(got), string(exp))
}
}