From daa5a05677f1041264d7bad8935511312d5c5660 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Wed, 26 Jun 2024 14:34:27 +0530 Subject: [PATCH] chore: update table response format (#5349) --- Makefile | 1 + .../app/clickhouseReader/reader.go | 16 +- pkg/query-service/model/v3/v3.go | 8 +- pkg/query-service/postprocess/table.go | 120 ++++---- pkg/query-service/postprocess/table_test.go | 288 +++++++----------- 5 files changed, 191 insertions(+), 242 deletions(-) diff --git a/Makefile b/Makefile index 95cf7afdb9..5f4a3c1ac2 100644 --- a/Makefile +++ b/Makefile @@ -188,3 +188,4 @@ test: go test ./pkg/query-service/tests/integration/... go test ./pkg/query-service/rules/... go test ./pkg/query-service/collectorsimulator/... + go test ./pkg/query-service/postprocess/... diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index 20eb11d479..c82afb9861 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -1993,8 +1993,8 @@ func (r *ClickHouseReader) SearchTraces(ctx context.Context, params *model.Searc } } - searchSpansResult[0].StartTimestampMillis = startTime - (durationNano/1000000) - searchSpansResult[0].EndTimestampMillis = endTime + (durationNano/1000000) + searchSpansResult[0].StartTimestampMillis = startTime - (durationNano / 1000000) + searchSpansResult[0].EndTimestampMillis = endTime + (durationNano / 1000000) return &searchSpansResult, nil } @@ -4434,8 +4434,8 @@ func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([ case *time.Time: point.Timestamp = v.UnixMilli() case *float64, *float32: - isValidPoint = true if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 { + isValidPoint = true point.Value = float64(reflect.ValueOf(v).Elem().Float()) } else { groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Float())) @@ -4447,9 +4447,9 @@ func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([ case **float64, **float32: val := reflect.ValueOf(v) if val.IsValid() && !val.IsNil() && !val.Elem().IsNil() { - isValidPoint = true value := reflect.ValueOf(v).Elem().Elem().Float() if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 { + isValidPoint = true point.Value = value } else { groupBy = append(groupBy, fmt.Sprintf("%v", value)) @@ -4460,8 +4460,8 @@ func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([ } } case *uint, *uint8, *uint64, *uint16, *uint32: - isValidPoint = true if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 { + isValidPoint = true point.Value = float64(reflect.ValueOf(v).Elem().Uint()) } else { groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Uint())) @@ -4473,9 +4473,9 @@ func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([ case **uint, **uint8, **uint64, **uint16, **uint32: val := reflect.ValueOf(v) if val.IsValid() && !val.IsNil() && !val.Elem().IsNil() { - isValidPoint = true value := reflect.ValueOf(v).Elem().Elem().Uint() if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 { + isValidPoint = true point.Value = float64(value) } else { groupBy = append(groupBy, fmt.Sprintf("%v", value)) @@ -4486,8 +4486,8 @@ func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([ } } case *int, *int8, *int16, *int32, *int64: - isValidPoint = true if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 { + isValidPoint = true point.Value = float64(reflect.ValueOf(v).Elem().Int()) } else { groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Int())) @@ -4499,9 +4499,9 @@ func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([ case **int, **int8, **int16, **int32, **int64: val := reflect.ValueOf(v) if val.IsValid() && !val.IsNil() && !val.Elem().IsNil() { - isValidPoint = true value := reflect.ValueOf(v).Elem().Elem().Int() if _, ok := constants.ReservedColumnTargetAliases[colName]; ok || countOfNumberCols == 1 { + isValidPoint = true point.Value = float64(value) } else { groupBy = append(groupBy, fmt.Sprintf("%v", value)) diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index 7e6daa3751..7b9fd8d989 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -990,10 +990,16 @@ type QueryRangeResponse struct { type TableColumn struct { Name string `json:"name"` + // QueryName is the name of the query that this column belongs to + QueryName string `json:"queryName"` + // IsValueColumn is true if this column is a value column + // i.e it is the column that contains the actual value that is being plotted + IsValueColumn bool `json:"isValueColumn"` } type TableRow struct { - Data []interface{} `json:"data"` + Data map[string]interface{} `json:"data"` + QueryName string `json:"-"` } type Table struct { diff --git a/pkg/query-service/postprocess/table.go b/pkg/query-service/postprocess/table.go index 1599bf37be..01be79c153 100644 --- a/pkg/query-service/postprocess/table.go +++ b/pkg/query-service/postprocess/table.go @@ -2,6 +2,7 @@ package postprocess import ( "fmt" + "math" "sort" "strings" @@ -9,20 +10,21 @@ import ( v3 "go.signoz.io/signoz/pkg/query-service/model/v3" ) -func getAutoColNameForQuery(queryName string, params *v3.QueryRangeParamsV3) string { - q := params.CompositeQuery.BuilderQueries[queryName] - if q.DataSource == v3.DataSourceTraces || q.DataSource == v3.DataSourceLogs { - if q.AggregateAttribute.Key != "" { - return fmt.Sprintf("%s(%s)", q.AggregateOperator, q.AggregateAttribute.Key) - } - return string(q.AggregateOperator) - } else if q.DataSource == v3.DataSourceMetrics { - if q.SpaceAggregation != "" && params.Version == "v4" { - return fmt.Sprintf("%s(%s)", q.SpaceAggregation, q.AggregateAttribute.Key) - } - return fmt.Sprintf("%s(%s)", q.AggregateOperator, q.AggregateAttribute.Key) +func roundToTwoDecimal(number float64) float64 { + // Handle very small numbers + if math.Abs(number) < 0.000001 { + return 0 } - return queryName + + // Determine the number of decimal places to round to + decimalPlaces := 2 + if math.Abs(number) < 0.01 { + decimalPlaces = int(math.Ceil(-math.Log10(math.Abs(number)))) + 1 + } + + // Round to the determined number of decimal places + scale := math.Pow(10, float64(decimalPlaces)) + return math.Round(number*scale) / scale } func TransformToTableForBuilderQueries(results []*v3.Result, params *v3.QueryRangeParamsV3) []*v3.Result { @@ -55,10 +57,10 @@ func TransformToTableForBuilderQueries(results []*v3.Result, params *v3.QueryRan // There will be one column for each label key and one column for each query name columns := make([]*v3.TableColumn, 0, len(labelKeys)+len(results)) for _, key := range labelKeys { - columns = append(columns, &v3.TableColumn{Name: key}) + columns = append(columns, &v3.TableColumn{Name: key, IsValueColumn: false}) } for _, result := range results { - columns = append(columns, &v3.TableColumn{Name: result.QueryName}) + columns = append(columns, &v3.TableColumn{Name: result.QueryName, QueryName: result.QueryName, IsValueColumn: true}) } // Create a map to store unique rows @@ -72,8 +74,8 @@ func TransformToTableForBuilderQueries(results []*v3.Result, params *v3.QueryRan // Create a key for the row based on labels var keyParts []string - rowData := make([]interface{}, len(columns)) - for i, key := range labelKeys { + rowData := make(map[string]interface{}, len(columns)) + for _, key := range labelKeys { value := "n/a" for _, labels := range series.LabelsArray { if v, ok := labels[key]; ok { @@ -82,21 +84,21 @@ func TransformToTableForBuilderQueries(results []*v3.Result, params *v3.QueryRan } } keyParts = append(keyParts, fmt.Sprintf("%s=%s", key, value)) - rowData[i] = value + rowData[key] = value } rowKey := strings.Join(keyParts, ",") // Get or create the row row, ok := rowMap[rowKey] if !ok { - row = &v3.TableRow{Data: rowData} + row = &v3.TableRow{Data: rowData, QueryName: result.QueryName} rowMap[rowKey] = row } // Add the value for this query - for i, col := range columns { + for _, col := range columns { if col.Name == result.QueryName { - row.Data[i] = series.Points[0].Value + row.Data[col.Name] = roundToTwoDecimal(series.Points[0].Value) break } } @@ -106,11 +108,6 @@ func TransformToTableForBuilderQueries(results []*v3.Result, params *v3.QueryRan // Convert rowMap to a slice of TableRows rows := make([]*v3.TableRow, 0, len(rowMap)) for _, row := range rowMap { - for i, value := range row.Data { - if value == nil { - row.Data[i] = "n/a" - } - } rows = append(rows, row) } @@ -122,11 +119,15 @@ func TransformToTableForBuilderQueries(results []*v3.Result, params *v3.QueryRan sort.Strings(queryNames) // Sort rows based on OrderBy from BuilderQueries - sortRows(rows, columns, params.CompositeQuery.BuilderQueries, queryNames) + sortRows(rows, params.CompositeQuery.BuilderQueries, queryNames) - for _, column := range columns { - if _, exists := params.CompositeQuery.BuilderQueries[column.Name]; exists { - column.Name = getAutoColNameForQuery(column.Name, params) + for _, row := range rows { + for _, col := range columns { + if col.IsValueColumn { + if row.Data[col.Name] == nil { + row.Data[col.Name] = "n/a" + } + } } } @@ -141,9 +142,11 @@ func TransformToTableForBuilderQueries(results []*v3.Result, params *v3.QueryRan return []*v3.Result{&tableResult} } -func sortRows(rows []*v3.TableRow, columns []*v3.TableColumn, builderQueries map[string]*v3.BuilderQuery, queryNames []string) { - sort.SliceStable(rows, func(i, j int) bool { - for _, queryName := range queryNames { +func sortRows(rows []*v3.TableRow, builderQueries map[string]*v3.BuilderQuery, queryNames []string) { + // use reverse order of queryNames + for i := len(queryNames) - 1; i >= 0; i-- { + queryName := queryNames[i] + sort.SliceStable(rows, func(i, j int) bool { query := builderQueries[queryName] orderByList := query.OrderBy if len(orderByList) == 0 { @@ -155,23 +158,12 @@ func sortRows(rows []*v3.TableRow, columns []*v3.TableColumn, builderQueries map if name == constants.SigNozOrderByValue { name = queryName } - colIndex := -1 - for k, col := range columns { - if col.Name == name { - colIndex = k - break - } - } - if colIndex == -1 { - continue - } - valI := rows[i].Data[colIndex] - valJ := rows[j].Data[colIndex] + valI := rows[i].Data[name] + valJ := rows[j].Data[name] - // Handle "n/a" values - if valI == "n/a" && valJ == "n/a" { - continue + if valI == nil || valJ == nil { + return rows[i].QueryName < rows[j].QueryName } // Compare based on the data type @@ -211,9 +203,9 @@ func sortRows(rows []*v3.TableRow, columns []*v3.TableColumn, builderQueries map } } } - } - return false - }) + return false + }) + } } func TransformToTableForClickHouseQueries(results []*v3.Result) []*v3.Result { @@ -248,11 +240,11 @@ func TransformToTableForClickHouseQueries(results []*v3.Result) []*v3.Result { // So we create a column for each query name that has at least one point columns := make([]*v3.TableColumn, 0) for _, key := range labelKeys { - columns = append(columns, &v3.TableColumn{Name: key}) + columns = append(columns, &v3.TableColumn{Name: key, IsValueColumn: false}) } for _, result := range results { if len(result.Series) > 0 && len(result.Series[0].Points) > 0 { - columns = append(columns, &v3.TableColumn{Name: result.QueryName}) + columns = append(columns, &v3.TableColumn{Name: result.QueryName, QueryName: result.QueryName, IsValueColumn: true}) } } @@ -261,8 +253,8 @@ func TransformToTableForClickHouseQueries(results []*v3.Result) []*v3.Result { for _, series := range result.Series { // Create a key for the row based on labels - rowData := make([]interface{}, len(columns)) - for i, key := range labelKeys { + rowData := make(map[string]interface{}, len(columns)) + for _, key := range labelKeys { value := "n/a" for _, labels := range series.LabelsArray { if v, ok := labels[key]; ok { @@ -270,16 +262,16 @@ func TransformToTableForClickHouseQueries(results []*v3.Result) []*v3.Result { break } } - rowData[i] = value + rowData[key] = value } // Get or create the row - row := &v3.TableRow{Data: rowData} + row := &v3.TableRow{Data: rowData, QueryName: result.QueryName} // Add the value for this query - for i, col := range columns { + for _, col := range columns { if col.Name == result.QueryName && len(series.Points) > 0 { - row.Data[i] = series.Points[0].Value + row.Data[col.Name] = roundToTwoDecimal(series.Points[0].Value) break } } @@ -287,6 +279,16 @@ func TransformToTableForClickHouseQueries(results []*v3.Result) []*v3.Result { } } + for _, row := range rows { + for _, col := range columns { + if col.IsValueColumn { + if row.Data[col.Name] == nil { + row.Data[col.Name] = "n/a" + } + } + } + } + // Create the final result tableResult := v3.Result{ Table: &v3.Table{ diff --git a/pkg/query-service/postprocess/table_test.go b/pkg/query-service/postprocess/table_test.go index 6e8f588a5f..04a835c2ff 100644 --- a/pkg/query-service/postprocess/table_test.go +++ b/pkg/query-service/postprocess/table_test.go @@ -1,6 +1,7 @@ package postprocess import ( + "bytes" "encoding/json" "reflect" "testing" @@ -21,9 +22,9 @@ func TestSortRows(t *testing.T) { { name: "Sort by single numeric query, ascending order", rows: []*v3.TableRow{ - {Data: []interface{}{"service2", 20.0}}, - {Data: []interface{}{"service1", 10.0}}, - {Data: []interface{}{"service3", 30.0}}, + {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"}, @@ -34,17 +35,17 @@ func TestSortRows(t *testing.T) { }, queryNames: []string{"A"}, expected: []*v3.TableRow{ - {Data: []interface{}{"service1", 10.0}}, - {Data: []interface{}{"service2", 20.0}}, - {Data: []interface{}{"service3", 30.0}}, + {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: []interface{}{"service2", 20.0}}, - {Data: []interface{}{"service1", 10.0}}, - {Data: []interface{}{"service3", 30.0}}, + {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"}, @@ -55,17 +56,17 @@ func TestSortRows(t *testing.T) { }, queryNames: []string{"A"}, expected: []*v3.TableRow{ - {Data: []interface{}{"service3", 30.0}}, - {Data: []interface{}{"service2", 20.0}}, - {Data: []interface{}{"service1", 10.0}}, + {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: []interface{}{"service2", "b"}}, - {Data: []interface{}{"service1", "c"}}, - {Data: []interface{}{"service3", "a"}}, + {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"}, @@ -76,18 +77,18 @@ func TestSortRows(t *testing.T) { }, queryNames: []string{"A"}, expected: []*v3.TableRow{ - {Data: []interface{}{"service3", "a"}}, - {Data: []interface{}{"service2", "b"}}, - {Data: []interface{}{"service1", "c"}}, + {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: []interface{}{"service1", 10.0, "n/a"}}, - {Data: []interface{}{"service2", "n/a", 15.0}}, - {Data: []interface{}{"service3", 30.0, 25.0}}, - {Data: []interface{}{"service4", "n/a", "n/a"}}, + {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"}, @@ -100,43 +101,18 @@ func TestSortRows(t *testing.T) { }, queryNames: []string{"A", "B"}, expected: []*v3.TableRow{ - {Data: []interface{}{"service1", 10.0, "n/a"}}, - {Data: []interface{}{"service3", 30.0, 25.0}}, - {Data: []interface{}{"service4", "n/a", "n/a"}}, - {Data: []interface{}{"service2", "n/a", 15.0}}, - }, - }, - { - name: "Sort with different data types", - rows: []*v3.TableRow{ - {Data: []interface{}{"service1", "string", 10.0, true}}, - {Data: []interface{}{"service2", 20.0, "string", false}}, - {Data: []interface{}{"service3", true, 30.0, "string"}}, - }, - columns: []*v3.TableColumn{ - {Name: "service_name"}, - {Name: "A"}, - {Name: "B"}, - {Name: "C"}, - }, - builderQueries: map[string]*v3.BuilderQuery{ - "A": {OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "asc"}}}, - "B": {OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "desc"}}}, - "C": {OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "asc"}}}, - }, - queryNames: []string{"A", "B", "C"}, - expected: []*v3.TableRow{ - {Data: []interface{}{"service2", 20.0, "string", false}}, - {Data: []interface{}{"service1", "string", 10.0, true}}, - {Data: []interface{}{"service3", true, 30.0, "string"}}, + {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: []interface{}{"service1", 20.0}}, - {Data: []interface{}{"service2", 10.0}}, - {Data: []interface{}{"service3", 30.0}}, + {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"}, @@ -147,44 +123,17 @@ func TestSortRows(t *testing.T) { }, queryNames: []string{"A"}, expected: []*v3.TableRow{ - {Data: []interface{}{"service3", 30.0}}, - {Data: []interface{}{"service1", 20.0}}, - {Data: []interface{}{"service2", 10.0}}, - }, - }, - { - name: "Sort by multiple queries with mixed types", - rows: []*v3.TableRow{ - {Data: []interface{}{"service1", 10.0, "b", true}}, - {Data: []interface{}{"service2", 20.0, "a", false}}, - {Data: []interface{}{"service3", 10.0, "c", true}}, - {Data: []interface{}{"service4", 20.0, "b", false}}, - }, - columns: []*v3.TableColumn{ - {Name: "service_name"}, - {Name: "A"}, - {Name: "B"}, - {Name: "C"}, - }, - builderQueries: map[string]*v3.BuilderQuery{ - "A": {OrderBy: []v3.OrderBy{{ColumnName: "A", Order: "asc"}}}, - "B": {OrderBy: []v3.OrderBy{{ColumnName: "B", Order: "desc"}}}, - "C": {OrderBy: []v3.OrderBy{{ColumnName: "C", Order: "asc"}}}, - }, - queryNames: []string{"A", "B", "C"}, - expected: []*v3.TableRow{ - {Data: []interface{}{"service3", 10.0, "c", true}}, - {Data: []interface{}{"service1", 10.0, "b", true}}, - {Data: []interface{}{"service4", 20.0, "b", false}}, - {Data: []interface{}{"service2", 20.0, "a", false}}, + {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: []interface{}{"service1", "n/a", "n/a"}}, - {Data: []interface{}{"service2", "n/a", "n/a"}}, - {Data: []interface{}{"service3", "n/a", "n/a"}}, + {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"}, @@ -197,18 +146,18 @@ func TestSortRows(t *testing.T) { }, queryNames: []string{"A", "B"}, expected: []*v3.TableRow{ - {Data: []interface{}{"service1", "n/a", "n/a"}}, - {Data: []interface{}{"service2", "n/a", "n/a"}}, - {Data: []interface{}{"service3", "n/a", "n/a"}}, + {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: []interface{}{"service1", -10.0}}, - {Data: []interface{}{"service2", 20.0}}, - {Data: []interface{}{"service3", -30.0}}, - {Data: []interface{}{"service4", 0.0}}, + {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"}, @@ -219,19 +168,19 @@ func TestSortRows(t *testing.T) { }, queryNames: []string{"A"}, expected: []*v3.TableRow{ - {Data: []interface{}{"service3", -30.0}}, - {Data: []interface{}{"service1", -10.0}}, - {Data: []interface{}{"service4", 0.0}}, - {Data: []interface{}{"service2", 20.0}}, + {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: []interface{}{"service1", "Apple"}}, - {Data: []interface{}{"service2", "banana"}}, - {Data: []interface{}{"service3", "Cherry"}}, - {Data: []interface{}{"service4", "date"}}, + {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"}, @@ -242,19 +191,19 @@ func TestSortRows(t *testing.T) { }, queryNames: []string{"A"}, expected: []*v3.TableRow{ - {Data: []interface{}{"service1", "Apple"}}, - {Data: []interface{}{"service3", "Cherry"}}, - {Data: []interface{}{"service2", "banana"}}, - {Data: []interface{}{"service4", "date"}}, + {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: []interface{}{"service1", ""}}, - {Data: []interface{}{"service2", "b"}}, - {Data: []interface{}{"service3", ""}}, - {Data: []interface{}{"service4", "a"}}, + {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"}, @@ -265,17 +214,17 @@ func TestSortRows(t *testing.T) { }, queryNames: []string{"A"}, expected: []*v3.TableRow{ - {Data: []interface{}{"service1", ""}}, - {Data: []interface{}{"service3", ""}}, - {Data: []interface{}{"service4", "a"}}, - {Data: []interface{}{"service2", "b"}}, + {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.columns, tt.builderQueries, tt.queryNames) + sortRows(tt.rows, tt.builderQueries, tt.queryNames) if !reflect.DeepEqual(tt.rows, tt.expected) { exp, _ := json.Marshal(tt.expected) got, _ := json.Marshal(tt.rows) @@ -287,24 +236,20 @@ func TestSortRows(t *testing.T) { func TestSortRowsWithEmptyQueries(t *testing.T) { rows := []*v3.TableRow{ - {Data: []interface{}{"service1", 20.0}}, - {Data: []interface{}{"service2", 10.0}}, - {Data: []interface{}{"service3", 30.0}}, - } - columns := []*v3.TableColumn{ - {Name: "service_name"}, - {Name: "A"}, + {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, columns, builderQueries, queryNames) + sortRows(rows, builderQueries, queryNames) // Expect the original order to be maintained expected := []*v3.TableRow{ - {Data: []interface{}{"service1", 20.0}}, - {Data: []interface{}{"service2", 10.0}}, - {Data: []interface{}{"service3", 30.0}}, + {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) { @@ -314,26 +259,22 @@ func TestSortRowsWithEmptyQueries(t *testing.T) { func TestSortRowsWithInvalidColumnName(t *testing.T) { rows := []*v3.TableRow{ - {Data: []interface{}{"service1", 20.0}}, - {Data: []interface{}{"service2", 10.0}}, - {Data: []interface{}{"service3", 30.0}}, - } - columns := []*v3.TableColumn{ - {Name: "service_name"}, - {Name: "A"}, + {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, columns, builderQueries, queryNames) + sortRows(rows, builderQueries, queryNames) // Expect the original order to be maintained expected := []*v3.TableRow{ - {Data: []interface{}{"service1", 20.0}}, - {Data: []interface{}{"service2", 10.0}}, - {Data: []interface{}{"service3", 30.0}}, + {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) { @@ -343,27 +284,22 @@ func TestSortRowsWithInvalidColumnName(t *testing.T) { func TestSortRowsStability(t *testing.T) { rows := []*v3.TableRow{ - {Data: []interface{}{"service1", 10.0, "a"}}, - {Data: []interface{}{"service2", 10.0, "b"}}, - {Data: []interface{}{"service3", 10.0, "c"}}, - } - columns := []*v3.TableColumn{ - {Name: "service_name"}, - {Name: "A"}, - {Name: "B"}, + {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, columns, builderQueries, queryNames) + sortRows(rows, builderQueries, queryNames) // Expect the original order to be maintained for equal values expected := []*v3.TableRow{ - {Data: []interface{}{"service1", 10.0, "a"}}, - {Data: []interface{}{"service2", 10.0, "b"}}, - {Data: []interface{}{"service3", 10.0, "c"}}, + {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) { @@ -404,10 +340,10 @@ func TestTransformToTableForClickHouseQueries(t *testing.T) { Table: &v3.Table{ Columns: []*v3.TableColumn{ {Name: "service"}, - {Name: "A"}, + {Name: "A", QueryName: "A", IsValueColumn: true}, }, Rows: []*v3.TableRow{ - {Data: []interface{}{"frontend", 10.0}}, + {Data: map[string]interface{}{"service": "frontend", "A": 10.0}}, }, }, }, @@ -465,14 +401,14 @@ func TestTransformToTableForClickHouseQueries(t *testing.T) { Columns: []*v3.TableColumn{ {Name: "service"}, {Name: "env"}, - {Name: "A"}, - {Name: "B"}, + {Name: "A", QueryName: "A", IsValueColumn: true}, + {Name: "B", QueryName: "B", IsValueColumn: true}, }, Rows: []*v3.TableRow{ - {Data: []interface{}{"frontend", "prod", 10.0, nil}}, - {Data: []interface{}{"backend", "prod", 20.0, nil}}, - {Data: []interface{}{"frontend", "prod", nil, 15.0}}, - {Data: []interface{}{"backend", "prod", nil, 25.0}}, + {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}}, }, }, }, @@ -514,12 +450,12 @@ func TestTransformToTableForClickHouseQueries(t *testing.T) { Columns: []*v3.TableColumn{ {Name: "service"}, {Name: "env"}, - {Name: "A"}, - {Name: "B"}, + {Name: "A", QueryName: "A", IsValueColumn: true}, + {Name: "B", QueryName: "B", IsValueColumn: true}, }, Rows: []*v3.TableRow{ - {Data: []interface{}{"frontend", "n/a", 10.0, nil}}, - {Data: []interface{}{"n/a", "prod", nil, 20.0}}, + {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}}, }, }, }, @@ -551,10 +487,10 @@ func TestTransformToTableForClickHouseQueries(t *testing.T) { Table: &v3.Table{ Columns: []*v3.TableColumn{ {Name: "service"}, - {Name: "A"}, + {Name: "A", QueryName: "A", IsValueColumn: true}, }, Rows: []*v3.TableRow{ - {Data: []interface{}{"frontend", 10.0}}, + {Data: map[string]interface{}{"service": "frontend", "A": 10.0}}, }, }, }, @@ -593,11 +529,11 @@ func TestTransformToTableForClickHouseQueries(t *testing.T) { Table: &v3.Table{ Columns: []*v3.TableColumn{ {Name: "service"}, - {Name: "B"}, + {Name: "B", QueryName: "B", IsValueColumn: true}, }, Rows: []*v3.TableRow{ - {Data: []interface{}{"frontend", nil}}, - {Data: []interface{}{"backend", 20.0}}, + {Data: map[string]interface{}{"service": "frontend", "B": "n/a"}}, + {Data: map[string]interface{}{"service": "backend", "B": 20.0}}, }, }, }, @@ -608,8 +544,10 @@ func TestTransformToTableForClickHouseQueries(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := TransformToTableForClickHouseQueries(tt.input) - if !reflect.DeepEqual(result, tt.expected) { - t.Errorf("TransformToTableForClickHouseQueries() = %v, want %v", result, tt.expected) + exp, _ := json.Marshal(tt.expected) + got, _ := json.Marshal(result) + if !bytes.Equal(got, exp) { + t.Errorf("TransformToTableForClickHouseQueries() = %v, want %v", string(got), string(exp)) } }) } @@ -650,19 +588,21 @@ func TestTransformToTableForClickHouseQueriesSorting(t *testing.T) { Table: &v3.Table{ Columns: []*v3.TableColumn{ {Name: "service"}, - {Name: "A"}, - {Name: "B"}, + {Name: "A", QueryName: "A", IsValueColumn: true}, + {Name: "B", QueryName: "B", IsValueColumn: true}, }, Rows: []*v3.TableRow{ - {Data: []interface{}{"backend", 20.0, nil}}, - {Data: []interface{}{"frontend", nil, 10.0}}, + {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) - if !reflect.DeepEqual(result, expected) { - t.Errorf("TransformToTableForClickHouseQueries() sorting test failed. Got %v, want %v", result, expected) + 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)) } }