From fce7ab7d244a02113fab2dfc4e06be728569a415 Mon Sep 17 00:00:00 2001 From: Prashanth Banda Date: Thu, 25 Jan 2024 00:47:09 +0530 Subject: [PATCH 01/16] fix(frontend,serviceMap): dynamically truncate service map node label (#4365) --- frontend/src/modules/Servicemap/Map.tsx | 10 ++++++++-- frontend/src/modules/Servicemap/utils.ts | 11 ++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/frontend/src/modules/Servicemap/Map.tsx b/frontend/src/modules/Servicemap/Map.tsx index b663aa5ed2..43cad85610 100644 --- a/frontend/src/modules/Servicemap/Map.tsx +++ b/frontend/src/modules/Servicemap/Map.tsx @@ -13,6 +13,8 @@ function ServiceMap({ fgRef, serviceMap }: any): JSX.Element { const graphData = { nodes, links }; + let zoomLevel = 1; + return ( d.value} nodeCanvasObject={(node, ctx) => { - const label = transformLabel(node.id); - const { fontSize } = node; + const label = transformLabel(node.id, zoomLevel); + let { fontSize } = node; + fontSize = (fontSize * 3) / zoomLevel; ctx.font = `${fontSize}px Roboto`; const { width } = node; @@ -43,6 +46,9 @@ function ServiceMap({ fgRef, serviceMap }: any): JSX.Element { tooltip.innerHTML = getTooltip(node); } }} + onZoom={(zoom) => { + zoomLevel = zoom.k; + }} nodePointerAreaPaint={(node, color, ctx) => { ctx.fillStyle = color; ctx.beginPath(); diff --git a/frontend/src/modules/Servicemap/utils.ts b/frontend/src/modules/Servicemap/utils.ts index f1da9e3c3a..500224241a 100644 --- a/frontend/src/modules/Servicemap/utils.ts +++ b/frontend/src/modules/Servicemap/utils.ts @@ -59,6 +59,7 @@ export const getGraphData = (serviceMap, isDarkMode): graphDataType => { width: MIN_WIDTH, color, nodeVal: MIN_WIDTH, + name: node, }; } if (service.errorRate > 0) { @@ -72,6 +73,7 @@ export const getGraphData = (serviceMap, isDarkMode): graphDataType => { width, color, nodeVal: width, + name: node, }; }); return { @@ -123,9 +125,12 @@ export const getTooltip = (link: { `; }; -export const transformLabel = (label: string) => { - const MAX_LENGTH = 13; - const MAX_SHOW = 10; +export const transformLabel = (label: string, zoomLevel: number) => { + //? 13 is the minimum label length. Scaling factor of 0.9 which is slightly less than 1 + //? ensures smoother zoom transitions, gradually increasing MAX_LENGTH, displaying more of the label as + //? zooming in. + const MAX_LENGTH = 13 * (zoomLevel / 0.9); + const MAX_SHOW = MAX_LENGTH - 3; if (label.length > MAX_LENGTH) { return `${label.slice(0, MAX_SHOW)}...`; } From 253137a6b80fc832aaf8e708ea7fefb95391c607 Mon Sep 17 00:00:00 2001 From: Vikrant Gupta Date: Thu, 25 Jan 2024 01:05:47 +0530 Subject: [PATCH 02/16] fix: center align the dashboard delete modal (#4429) --- .../ListOfDashboard/TableComponents/DeleteButton.styles.scss | 5 +++++ .../ListOfDashboard/TableComponents/DeleteButton.tsx | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.styles.scss diff --git a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.styles.scss b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.styles.scss new file mode 100644 index 0000000000..afd7a87d22 --- /dev/null +++ b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.styles.scss @@ -0,0 +1,5 @@ +.delete-modal { + .ant-modal-confirm-body { + align-items: center; + } +} diff --git a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx index 810b99a278..2791d365b8 100644 --- a/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx +++ b/frontend/src/container/ListOfDashboard/TableComponents/DeleteButton.tsx @@ -1,3 +1,5 @@ +import './DeleteButton.styles.scss'; + import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; import { Modal, Tooltip, Typography } from 'antd'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; @@ -64,6 +66,7 @@ function DeleteButton({ okText: 'Delete', okButtonProps: { danger: true }, centered: true, + className: 'delete-modal', }); }, [modal, name, deleteDashboardMutation, notifications, t, queryClient]); From be27a92fc9ae1a51c60be1fe85b92d376a1bdbbe Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Thu, 25 Jan 2024 01:14:45 +0530 Subject: [PATCH 03/16] chore: add functions support (#4381) --- pkg/query-service/app/having.go | 90 +++ pkg/query-service/app/having_test.go | 283 ++++++++ pkg/query-service/app/http_handler.go | 122 ++-- pkg/query-service/app/http_handler_test.go | 618 ----------------- pkg/query-service/app/limit.go | 81 +++ pkg/query-service/app/limit_test.go | 624 ++++++++++++++++++ .../app/queryBuilder/functions.go | 286 ++++++++ .../app/queryBuilder/functions_test.go | 604 +++++++++++++++++ pkg/query-service/app/reduce_to.go | 71 ++ pkg/query-service/app/reduce_to_test.go | 99 +++ pkg/query-service/model/v3/v3.go | 103 ++- 11 files changed, 2284 insertions(+), 697 deletions(-) create mode 100644 pkg/query-service/app/having.go create mode 100644 pkg/query-service/app/having_test.go create mode 100644 pkg/query-service/app/limit.go create mode 100644 pkg/query-service/app/limit_test.go create mode 100644 pkg/query-service/app/queryBuilder/functions.go create mode 100644 pkg/query-service/app/queryBuilder/functions_test.go create mode 100644 pkg/query-service/app/reduce_to.go create mode 100644 pkg/query-service/app/reduce_to_test.go diff --git a/pkg/query-service/app/having.go b/pkg/query-service/app/having.go new file mode 100644 index 0000000000..b99471ef4e --- /dev/null +++ b/pkg/query-service/app/having.go @@ -0,0 +1,90 @@ +package app + +import ( + "strings" + + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" +) + +// applyHavingClause applies the having clause to the result +// each query has its own having clause +// there can be multiple having clauses for each query +func applyHavingClause(result []*v3.Result, queryRangeParams *v3.QueryRangeParamsV3) { + for _, result := range result { + builderQueries := queryRangeParams.CompositeQuery.BuilderQueries + + if builderQueries != nil && (builderQueries[result.QueryName].DataSource == v3.DataSourceMetrics) { + havingClause := builderQueries[result.QueryName].Having + + for i := 0; i < len(result.Series); i++ { + for j := 0; j < len(result.Series[i].Points); j++ { + if !evaluateHavingClause(havingClause, result.Series[i].Points[j].Value) { + result.Series[i].Points = append(result.Series[i].Points[:j], result.Series[i].Points[j+1:]...) + j-- + } + } + } + } + } +} + +func evaluateHavingClause(having []v3.Having, value float64) bool { + if len(having) == 0 { + return true + } + + for _, h := range having { + switch h.Operator { + case v3.HavingOperatorEqual: + if value == h.Value.(float64) { + return true + } + case v3.HavingOperatorNotEqual: + if value != h.Value.(float64) { + return true + } + case v3.HavingOperatorGreaterThan: + if value > h.Value.(float64) { + return true + } + case v3.HavingOperatorGreaterThanOrEq: + if value >= h.Value.(float64) { + return true + } + case v3.HavingOperatorLessThan: + if value < h.Value.(float64) { + return true + } + case v3.HavingOperatorLessThanOrEq: + if value <= h.Value.(float64) { + return true + } + case v3.HavingOperatorIn, v3.HavingOperator(strings.ToLower(string(v3.HavingOperatorIn))): + values, ok := h.Value.([]interface{}) + if !ok { + return false + } + for _, v := range values { + if value == v.(float64) { + return true + } + } + case v3.HavingOperatorNotIn, v3.HavingOperator(strings.ToLower(string(v3.HavingOperatorNotIn))): + values, ok := h.Value.([]interface{}) + if !ok { + return true + } + found := false + for _, v := range values { + if value == v.(float64) { + found = true + break + } + } + if !found { + return true + } + } + } + return false +} diff --git a/pkg/query-service/app/having_test.go b/pkg/query-service/app/having_test.go new file mode 100644 index 0000000000..2eeafa1b65 --- /dev/null +++ b/pkg/query-service/app/having_test.go @@ -0,0 +1,283 @@ +package app + +import ( + "testing" + + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" +) + +func TestApplyHavingCaluse(t *testing.T) { + type testCase struct { + name string + results []*v3.Result + params *v3.QueryRangeParamsV3 + want []*v3.Result + } + + testCases := []testCase{ + { + name: "test having equal to", + results: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + }, + params: &v3.QueryRangeParamsV3{ + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + DataSource: v3.DataSourceMetrics, + Having: []v3.Having{ + { + Operator: v3.HavingOperatorEqual, + Value: 0.3, + }, + }, + }, + }, + }, + }, + want: []*v3.Result{ + { + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.3, + }, + }, + }, + }, + }, + }, + }, + { + name: "test having `in`", + results: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + }, + params: &v3.QueryRangeParamsV3{ + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + DataSource: v3.DataSourceMetrics, + Having: []v3.Having{ + { + Operator: v3.HavingOperatorIn, + Value: []interface{}{0.3, 0.4}, + }, + }, + }, + }, + }, + }, + want: []*v3.Result{ + { + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.4, + }, + { + Value: 0.3, + }, + }, + }, + }, + }, + }, + }, + { + name: "test having `not in` and multiple results", + results: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + { + QueryName: "B", + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + }, + params: &v3.QueryRangeParamsV3{ + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + DataSource: v3.DataSourceMetrics, + Having: []v3.Having{ + { + Operator: v3.HavingOperatorNotIn, + Value: []interface{}{0.3, 0.4}, + }, + }, + }, + "B": { + DataSource: v3.DataSourceMetrics, + Having: []v3.Having{ + { + Operator: v3.HavingOperatorNotIn, + Value: []interface{}{0.1}, + }, + }, + }, + }, + }, + }, + want: []*v3.Result{ + { + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + { + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + applyHavingClause(tc.results, tc.params) + + got := tc.results + + if len(got) != len(tc.want) { + t.Errorf("got %v, want %v", got, tc.want) + } + + for i := range got { + if len(got[i].Series) != len(tc.want[i].Series) { + t.Errorf("got %v, want %v", got, tc.want) + } + + for j := range got[i].Series { + if len(got[i].Series[j].Points) != len(tc.want[i].Series[j].Points) { + t.Errorf("got %v, want %v", len(got[i].Series[j].Points), len(tc.want[i].Series[j].Points)) + } + + for k := range got[i].Series[j].Points { + if got[i].Series[j].Points[k].Value != tc.want[i].Series[j].Points[k].Value { + t.Errorf("got %v, want %v", got, tc.want) + } + } + } + } + }) + } +} diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 173fe39ef4..57962fa3ac 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "net/http" - "sort" "strconv" "strings" "sync" @@ -3107,78 +3106,6 @@ func (aH *APIHandler) QueryRangeV3(w http.ResponseWriter, r *http.Request) { aH.queryRangeV3(r.Context(), queryRangeParams, w, r) } -func applyMetricLimit(results []*v3.Result, queryRangeParams *v3.QueryRangeParamsV3) { - // apply limit if any for metrics - // use the grouping set points to apply the limit - - for _, result := range results { - builderQueries := queryRangeParams.CompositeQuery.BuilderQueries - - if builderQueries != nil && (builderQueries[result.QueryName].DataSource == v3.DataSourceMetrics || - result.QueryName != builderQueries[result.QueryName].Expression) { - limit := builderQueries[result.QueryName].Limit - - orderByList := builderQueries[result.QueryName].OrderBy - if limit >= 0 { - if len(orderByList) == 0 { - // If no orderBy is specified, sort by value in descending order - orderByList = []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "desc"}} - } - sort.SliceStable(result.Series, func(i, j int) bool { - for _, orderBy := range orderByList { - if orderBy.ColumnName == constants.SigNozOrderByValue { - - // For table type queries (we rely on the fact that one value for row), sort - // based on final aggregation value - if len(result.Series[i].Points) == 1 && len(result.Series[j].Points) == 1 { - if orderBy.Order == "asc" { - return result.Series[i].Points[0].Value < result.Series[j].Points[0].Value - } else if orderBy.Order == "desc" { - return result.Series[i].Points[0].Value > result.Series[j].Points[0].Value - } - } - - // For graph type queries, sort based on GroupingSetsPoint - if result.Series[i].GroupingSetsPoint == nil || result.Series[j].GroupingSetsPoint == nil { - // Handle nil GroupingSetsPoint, if needed - // Here, we assume non-nil values are always less than nil values - return result.Series[i].GroupingSetsPoint != nil - } - if orderBy.Order == "asc" { - return result.Series[i].GroupingSetsPoint.Value < result.Series[j].GroupingSetsPoint.Value - } else if orderBy.Order == "desc" { - return result.Series[i].GroupingSetsPoint.Value > result.Series[j].GroupingSetsPoint.Value - } - } else { - // Sort based on Labels map - labelI, existsI := result.Series[i].Labels[orderBy.ColumnName] - labelJ, existsJ := result.Series[j].Labels[orderBy.ColumnName] - - if !existsI || !existsJ { - // Handle missing labels, if needed - // Here, we assume non-existent labels are always less than existing ones - return existsI - } - - if orderBy.Order == "asc" { - return strings.Compare(labelI, labelJ) < 0 - } else if orderBy.Order == "desc" { - return strings.Compare(labelI, labelJ) > 0 - } - } - } - // Preserve original order if no matching orderBy is found - return i < j - }) - - if limit > 0 && len(result.Series) > int(limit) { - result.Series = result.Series[:limit] - } - } - } - } -} - func (aH *APIHandler) liveTailLogs(w http.ResponseWriter, r *http.Request) { // get the param from url and add it to body @@ -3295,6 +3222,10 @@ func (aH *APIHandler) queryRangeV4(ctx context.Context, queryRangeParams *v3.Que return } + if queryRangeParams.CompositeQuery.QueryType == v3.QueryTypeBuilder { + postProcessResult(result, queryRangeParams) + } + resp := v3.QueryRangeResponse{ Result: result, } @@ -3322,3 +3253,48 @@ func (aH *APIHandler) QueryRangeV4(w http.ResponseWriter, r *http.Request) { aH.queryRangeV4(r.Context(), queryRangeParams, w, r) } + +// postProcessResult applies having clause, metric limit, reduce function to the result +// This function is effective for metrics data source for now, but it can be extended to other data sources +// if needed +// Much of this work can be done in the ClickHouse query, but we decided to do it here because: +// 1. Effective use of caching +// 2. Easier to add new functions +func postProcessResult(result []*v3.Result, queryRangeParams *v3.QueryRangeParamsV3) { + // Having clause is not part of the clickhouse query, so we need to apply it here + // It's not included in the query because it doesn't work nicely with caching + // With this change, if you have a query with a having clause, and then you change the having clause + // to something else, the query will still be cached. + applyHavingClause(result, queryRangeParams) + // We apply the metric limit here because it's not part of the clickhouse query + // The limit in the context of the time series query is the number of time series + // So for the limit to work, we need to know what series to keep and what to discard + // For us to know that, we need to execute the query first, and then apply the limit + // which we found expensive, because we are executing the query twice on the same data + // So we decided to apply the limit here, after the query is executed + // The function is named applyMetricLimit because it only applies to metrics data source + // In traces and logs, the limit is achieved using subqueries + applyMetricLimit(result, queryRangeParams) + // Each series in the result produces N number of points, where N is (end - start) / step + // For the panel type table, we need to show one point for each series in the row + // We do that by applying a reduce function to each series + applyReduceTo(result, queryRangeParams) + // We apply the functions here it's easier to add new functions + applyFunctions(result, queryRangeParams) +} + +// applyFunctions applies functions for each query in the composite query +// The functions can be more than one, and they are applied in the order they are defined +func applyFunctions(results []*v3.Result, queryRangeParams *v3.QueryRangeParamsV3) { + for idx, result := range results { + builderQueries := queryRangeParams.CompositeQuery.BuilderQueries + + if builderQueries != nil && (builderQueries[result.QueryName].DataSource == v3.DataSourceMetrics) { + functions := builderQueries[result.QueryName].Functions + + for _, function := range functions { + results[idx] = queryBuilder.ApplyFunction(function, result) + } + } + } +} diff --git a/pkg/query-service/app/http_handler_test.go b/pkg/query-service/app/http_handler_test.go index 014ec900e1..84782f7cae 100644 --- a/pkg/query-service/app/http_handler_test.go +++ b/pkg/query-service/app/http_handler_test.go @@ -8,9 +8,7 @@ import ( "strings" "testing" - "go.signoz.io/signoz/pkg/query-service/constants" "go.signoz.io/signoz/pkg/query-service/model" - v3 "go.signoz.io/signoz/pkg/query-service/model/v3" ) func TestPrepareQuery(t *testing.T) { @@ -132,619 +130,3 @@ func TestPrepareQuery(t *testing.T) { }) } } - -func TestApplyLimitOnMetricResult(t *testing.T) { - cases := []struct { - name string - inputResult []*v3.Result - params *v3.QueryRangeParamsV3 - expectedResult []*v3.Result - }{ - { - name: "test limit 1 without order", // top most (latency/error) as default - inputResult: []*v3.Result{ - { - QueryName: "A", - Series: []*v3.Series{ - { - Labels: map[string]string{ - "service_name": "frontend", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 19.2, - }, - { - Timestamp: 1689220096000, - Value: 19.5, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 19.3, - }, - }, - { - Labels: map[string]string{ - "service_name": "route", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 8.83, - }, - { - Timestamp: 1689220096000, - Value: 8.83, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 8.83, - }, - }, - }, - }, - }, - params: &v3.QueryRangeParamsV3{ - Start: 1689220036000, - End: 1689220096000, - Step: 60, - CompositeQuery: &v3.CompositeQuery{ - BuilderQueries: map[string]*v3.BuilderQuery{ - "A": { - QueryName: "A", - AggregateAttribute: v3.AttributeKey{Key: "signo_calls_total"}, - DataSource: v3.DataSourceMetrics, - AggregateOperator: v3.AggregateOperatorSumRate, - Expression: "A", - GroupBy: []v3.AttributeKey{{Key: "service_name"}}, - Limit: 1, - }, - }, - QueryType: v3.QueryTypeBuilder, - PanelType: v3.PanelTypeGraph, - }, - }, - expectedResult: []*v3.Result{ - { - QueryName: "A", - Series: []*v3.Series{ - { - Labels: map[string]string{ - "service_name": "frontend", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 19.2, - }, - { - Timestamp: 1689220096000, - Value: 19.5, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 19.3, - }, - }, - }, - }, - }, - }, - { - name: "test limit with order asc", - inputResult: []*v3.Result{ - { - QueryName: "A", - Series: []*v3.Series{ - { - Labels: map[string]string{ - "service_name": "frontend", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 19.2, - }, - { - Timestamp: 1689220096000, - Value: 19.5, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 19.3, - }, - }, - { - Labels: map[string]string{ - "service_name": "route", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 8.83, - }, - { - Timestamp: 1689220096000, - Value: 8.83, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 8.83, - }, - }, - }, - }, - }, - params: &v3.QueryRangeParamsV3{ - Start: 1689220036000, - End: 1689220096000, - Step: 60, - CompositeQuery: &v3.CompositeQuery{ - BuilderQueries: map[string]*v3.BuilderQuery{ - "A": { - QueryName: "A", - AggregateAttribute: v3.AttributeKey{Key: "signo_calls_total"}, - DataSource: v3.DataSourceMetrics, - AggregateOperator: v3.AggregateOperatorSumRate, - Expression: "A", - GroupBy: []v3.AttributeKey{{Key: "service_name"}}, - Limit: 1, - OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "asc"}}, - }, - }, - QueryType: v3.QueryTypeBuilder, - PanelType: v3.PanelTypeGraph, - }, - }, - expectedResult: []*v3.Result{ - { - QueryName: "A", - Series: []*v3.Series{ - { - Labels: map[string]string{ - "service_name": "route", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 8.83, - }, - { - Timestamp: 1689220096000, - Value: 8.83, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 8.83, - }, - }, - }, - }, - }, - }, - { - name: "test data source not metrics", - inputResult: []*v3.Result{ - { - QueryName: "A", - Series: []*v3.Series{ - { - Labels: map[string]string{ - "service_name": "frontend", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 69, - }, - { - Timestamp: 1689220096000, - Value: 240, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 154.5, - }, - }, - { - Labels: map[string]string{ - "service_name": "redis", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 420, - }, - { - Timestamp: 1689220096000, - Value: 260, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 340, - }, - }, - }, - }, - }, - params: &v3.QueryRangeParamsV3{ - Start: 1689220036000, - End: 1689220096000, - Step: 60, - CompositeQuery: &v3.CompositeQuery{ - BuilderQueries: map[string]*v3.BuilderQuery{ - "A": { - QueryName: "A", - AggregateAttribute: v3.AttributeKey{Key: "service_name"}, - DataSource: v3.DataSourceTraces, - AggregateOperator: v3.AggregateOperatorSum, - Expression: "A", - GroupBy: []v3.AttributeKey{{Key: "service_name"}}, - Limit: 1, - OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "asc"}}, - }, - }, - QueryType: v3.QueryTypeBuilder, - PanelType: v3.PanelTypeGraph, - }, - }, - expectedResult: []*v3.Result{ - { - QueryName: "A", - Series: []*v3.Series{ - { - Labels: map[string]string{ - "service_name": "frontend", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 69, - }, - { - Timestamp: 1689220096000, - Value: 240, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 154.5, - }, - }, - { - Labels: map[string]string{ - "service_name": "redis", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 420, - }, - { - Timestamp: 1689220096000, - Value: 260, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 340, - }, - }, - }, - }, - }, - }, - { - // ["GET /api/v1/health", "DELETE /api/v1/health"] so result should be ["DELETE /api/v1/health"] although it has lower value - name: "test limit with operation asc", - inputResult: []*v3.Result{ - { - QueryName: "A", - Series: []*v3.Series{ - { - Labels: map[string]string{ - "service_name": "frontend", - "operation": "GET /api/v1/health", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 19.2, - }, - { - Timestamp: 1689220096000, - Value: 19.5, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 19.3, - }, - }, - { - Labels: map[string]string{ - "service_name": "route", - "operation": "DELETE /api/v1/health", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 8.83, - }, - { - Timestamp: 1689220096000, - Value: 8.83, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 8.83, - }, - }, - }, - }, - }, - params: &v3.QueryRangeParamsV3{ - Start: 1689220036000, - End: 1689220096000, - Step: 60, - CompositeQuery: &v3.CompositeQuery{ - BuilderQueries: map[string]*v3.BuilderQuery{ - "A": { - QueryName: "A", - AggregateAttribute: v3.AttributeKey{Key: "signo_calls_total"}, - DataSource: v3.DataSourceMetrics, - AggregateOperator: v3.AggregateOperatorSumRate, - Expression: "A", - GroupBy: []v3.AttributeKey{{Key: "service_name"}}, - Limit: 1, - OrderBy: []v3.OrderBy{{ColumnName: "operation", Order: "asc"}}, - }, - }, - QueryType: v3.QueryTypeBuilder, - PanelType: v3.PanelTypeGraph, - }, - }, - expectedResult: []*v3.Result{ - { - QueryName: "A", - Series: []*v3.Series{ - { - Labels: map[string]string{ - "service_name": "route", - "operation": "DELETE /api/v1/health", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 8.83, - }, - { - Timestamp: 1689220096000, - Value: 8.83, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 8.83, - }, - }, - }, - }, - }, - }, - { - name: "test limit with multiple order by labels", - inputResult: []*v3.Result{ - { - QueryName: "A", - Series: []*v3.Series{ - { - Labels: map[string]string{ - "service_name": "frontend", - "operation": "GET /api/v1/health", - "status_code": "200", - "priority": "P0", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 19.2, - }, - { - Timestamp: 1689220096000, - Value: 19.5, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 19.3, - }, - }, - { - Labels: map[string]string{ - "service_name": "route", - "operation": "DELETE /api/v1/health", - "status_code": "301", - "priority": "P1", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 8.83, - }, - { - Timestamp: 1689220096000, - Value: 8.83, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 8.83, - }, - }, - { - Labels: map[string]string{ - "service_name": "route", - "operation": "DELETE /api/v1/health", - "status_code": "400", - "priority": "P0", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 8.83, - }, - { - Timestamp: 1689220096000, - Value: 8.83, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 8.83, - }, - }, - { - Labels: map[string]string{ - "service_name": "route", - "operation": "DELETE /api/v1/health", - "status_code": "200", - "priority": "P1", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 8.83, - }, - { - Timestamp: 1689220096000, - Value: 8.83, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 8.83, - }, - }, - }, - }, - }, - params: &v3.QueryRangeParamsV3{ - Start: 1689220036000, - End: 1689220096000, - Step: 60, - CompositeQuery: &v3.CompositeQuery{ - BuilderQueries: map[string]*v3.BuilderQuery{ - "A": { - QueryName: "A", - AggregateAttribute: v3.AttributeKey{Key: "signo_calls_total"}, - DataSource: v3.DataSourceMetrics, - AggregateOperator: v3.AggregateOperatorSumRate, - Expression: "A", - GroupBy: []v3.AttributeKey{{Key: "service_name"}, {Key: "operation"}, {Key: "status_code"}, {Key: "priority"}}, - Limit: 2, - OrderBy: []v3.OrderBy{ - {ColumnName: "priority", Order: "asc"}, - {ColumnName: "status_code", Order: "desc"}, - }, - }, - }, - QueryType: v3.QueryTypeBuilder, - PanelType: v3.PanelTypeGraph, - }, - }, - expectedResult: []*v3.Result{ - { - QueryName: "A", - Series: []*v3.Series{ - { - Labels: map[string]string{ - "service_name": "frontend", - "operation": "GET /api/v1/health", - "status_code": "200", - "priority": "P0", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 19.2, - }, - { - Timestamp: 1689220096000, - Value: 19.5, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 19.3, - }, - }, - { - Labels: map[string]string{ - "service_name": "route", - "operation": "DELETE /api/v1/health", - "status_code": "400", - "priority": "P0", - }, - Points: []v3.Point{ - { - Timestamp: 1689220036000, - Value: 8.83, - }, - { - Timestamp: 1689220096000, - Value: 8.83, - }, - }, - GroupingSetsPoint: &v3.Point{ - Timestamp: 0, - Value: 8.83, - }, - }, - }, - }, - }, - }, - } - - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - result := c.inputResult - applyMetricLimit(result, c.params) - if len(result) != len(c.expectedResult) { - t.Errorf("expected result length: %d, but got: %d", len(c.expectedResult), len(result)) - } - for i, r := range result { - if r.QueryName != c.expectedResult[i].QueryName { - t.Errorf("expected query name: %s, but got: %s", c.expectedResult[i].QueryName, r.QueryName) - } - if len(r.Series) != len(c.expectedResult[i].Series) { - t.Errorf("expected series length: %d, but got: %d", len(c.expectedResult[i].Series), len(r.Series)) - } - for j, s := range r.Series { - if len(s.Points) != len(c.expectedResult[i].Series[j].Points) { - t.Errorf("expected points length: %d, but got: %d", len(c.expectedResult[i].Series[j].Points), len(s.Points)) - } - for k, p := range s.Points { - if p.Timestamp != c.expectedResult[i].Series[j].Points[k].Timestamp { - t.Errorf("expected point timestamp: %d, but got: %d", c.expectedResult[i].Series[j].Points[k].Timestamp, p.Timestamp) - } - if p.Value != c.expectedResult[i].Series[j].Points[k].Value { - t.Errorf("expected point value: %f, but got: %f", c.expectedResult[i].Series[j].Points[k].Value, p.Value) - } - } - } - } - }) - } -} diff --git a/pkg/query-service/app/limit.go b/pkg/query-service/app/limit.go new file mode 100644 index 0000000000..7b6d728dd0 --- /dev/null +++ b/pkg/query-service/app/limit.go @@ -0,0 +1,81 @@ +package app + +import ( + "sort" + "strings" + + "go.signoz.io/signoz/pkg/query-service/constants" + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" +) + +// applyMetricLimit applies limit to the metrics query results +func applyMetricLimit(results []*v3.Result, queryRangeParams *v3.QueryRangeParamsV3) { + // apply limit if any for metrics + // use the grouping set points to apply the limit + + for _, result := range results { + builderQueries := queryRangeParams.CompositeQuery.BuilderQueries + + if builderQueries != nil && (builderQueries[result.QueryName].DataSource == v3.DataSourceMetrics) { + limit := builderQueries[result.QueryName].Limit + + orderByList := builderQueries[result.QueryName].OrderBy + if limit > 0 { + if len(orderByList) == 0 { + // If no orderBy is specified, sort by value in descending order + orderByList = []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "desc"}} + } + sort.SliceStable(result.Series, func(i, j int) bool { + for _, orderBy := range orderByList { + if orderBy.ColumnName == constants.SigNozOrderByValue { + + // For table type queries (we rely on the fact that one value for row), sort + // based on final aggregation value + if len(result.Series[i].Points) == 1 && len(result.Series[j].Points) == 1 { + if orderBy.Order == "asc" { + return result.Series[i].Points[0].Value < result.Series[j].Points[0].Value + } else if orderBy.Order == "desc" { + return result.Series[i].Points[0].Value > result.Series[j].Points[0].Value + } + } + + // For graph type queries, sort based on GroupingSetsPoint + if result.Series[i].GroupingSetsPoint == nil || result.Series[j].GroupingSetsPoint == nil { + // Handle nil GroupingSetsPoint, if needed + // Here, we assume non-nil values are always less than nil values + return result.Series[i].GroupingSetsPoint != nil + } + if orderBy.Order == "asc" { + return result.Series[i].GroupingSetsPoint.Value < result.Series[j].GroupingSetsPoint.Value + } else if orderBy.Order == "desc" { + return result.Series[i].GroupingSetsPoint.Value > result.Series[j].GroupingSetsPoint.Value + } + } else { + // Sort based on Labels map + labelI, existsI := result.Series[i].Labels[orderBy.ColumnName] + labelJ, existsJ := result.Series[j].Labels[orderBy.ColumnName] + + if !existsI || !existsJ { + // Handle missing labels, if needed + // Here, we assume non-existent labels are always less than existing ones + return existsI + } + + if orderBy.Order == "asc" { + return strings.Compare(labelI, labelJ) < 0 + } else if orderBy.Order == "desc" { + return strings.Compare(labelI, labelJ) > 0 + } + } + } + // Preserve original order if no matching orderBy is found + return i < j + }) + + if limit > 0 && len(result.Series) > int(limit) { + result.Series = result.Series[:limit] + } + } + } + } +} diff --git a/pkg/query-service/app/limit_test.go b/pkg/query-service/app/limit_test.go new file mode 100644 index 0000000000..d90d7b9417 --- /dev/null +++ b/pkg/query-service/app/limit_test.go @@ -0,0 +1,624 @@ +package app + +import ( + "testing" + + "go.signoz.io/signoz/pkg/query-service/constants" + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" +) + +func TestApplyLimitOnMetricResult(t *testing.T) { + cases := []struct { + name string + inputResult []*v3.Result + params *v3.QueryRangeParamsV3 + expectedResult []*v3.Result + }{ + { + name: "test limit 1 without order", // top most (latency/error) as default + inputResult: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Labels: map[string]string{ + "service_name": "frontend", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 19.2, + }, + { + Timestamp: 1689220096000, + Value: 19.5, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 19.3, + }, + }, + { + Labels: map[string]string{ + "service_name": "route", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 8.83, + }, + { + Timestamp: 1689220096000, + Value: 8.83, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 8.83, + }, + }, + }, + }, + }, + params: &v3.QueryRangeParamsV3{ + Start: 1689220036000, + End: 1689220096000, + Step: 60, + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "signo_calls_total"}, + DataSource: v3.DataSourceMetrics, + AggregateOperator: v3.AggregateOperatorSumRate, + Expression: "A", + GroupBy: []v3.AttributeKey{{Key: "service_name"}}, + Limit: 1, + }, + }, + QueryType: v3.QueryTypeBuilder, + PanelType: v3.PanelTypeGraph, + }, + }, + expectedResult: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Labels: map[string]string{ + "service_name": "frontend", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 19.2, + }, + { + Timestamp: 1689220096000, + Value: 19.5, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 19.3, + }, + }, + }, + }, + }, + }, + { + name: "test limit with order asc", + inputResult: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Labels: map[string]string{ + "service_name": "frontend", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 19.2, + }, + { + Timestamp: 1689220096000, + Value: 19.5, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 19.3, + }, + }, + { + Labels: map[string]string{ + "service_name": "route", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 8.83, + }, + { + Timestamp: 1689220096000, + Value: 8.83, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 8.83, + }, + }, + }, + }, + }, + params: &v3.QueryRangeParamsV3{ + Start: 1689220036000, + End: 1689220096000, + Step: 60, + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "signo_calls_total"}, + DataSource: v3.DataSourceMetrics, + AggregateOperator: v3.AggregateOperatorSumRate, + Expression: "A", + GroupBy: []v3.AttributeKey{{Key: "service_name"}}, + Limit: 1, + OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "asc"}}, + }, + }, + QueryType: v3.QueryTypeBuilder, + PanelType: v3.PanelTypeGraph, + }, + }, + expectedResult: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Labels: map[string]string{ + "service_name": "route", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 8.83, + }, + { + Timestamp: 1689220096000, + Value: 8.83, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 8.83, + }, + }, + }, + }, + }, + }, + { + name: "test data source not metrics", + inputResult: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Labels: map[string]string{ + "service_name": "frontend", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 69, + }, + { + Timestamp: 1689220096000, + Value: 240, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 154.5, + }, + }, + { + Labels: map[string]string{ + "service_name": "redis", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 420, + }, + { + Timestamp: 1689220096000, + Value: 260, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 340, + }, + }, + }, + }, + }, + params: &v3.QueryRangeParamsV3{ + Start: 1689220036000, + End: 1689220096000, + Step: 60, + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "service_name"}, + DataSource: v3.DataSourceTraces, + AggregateOperator: v3.AggregateOperatorSum, + Expression: "A", + GroupBy: []v3.AttributeKey{{Key: "service_name"}}, + Limit: 1, + OrderBy: []v3.OrderBy{{ColumnName: constants.SigNozOrderByValue, Order: "asc"}}, + }, + }, + QueryType: v3.QueryTypeBuilder, + PanelType: v3.PanelTypeGraph, + }, + }, + expectedResult: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Labels: map[string]string{ + "service_name": "frontend", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 69, + }, + { + Timestamp: 1689220096000, + Value: 240, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 154.5, + }, + }, + { + Labels: map[string]string{ + "service_name": "redis", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 420, + }, + { + Timestamp: 1689220096000, + Value: 260, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 340, + }, + }, + }, + }, + }, + }, + { + // ["GET /api/v1/health", "DELETE /api/v1/health"] so result should be ["DELETE /api/v1/health"] although it has lower value + name: "test limit with operation asc", + inputResult: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Labels: map[string]string{ + "service_name": "frontend", + "operation": "GET /api/v1/health", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 19.2, + }, + { + Timestamp: 1689220096000, + Value: 19.5, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 19.3, + }, + }, + { + Labels: map[string]string{ + "service_name": "route", + "operation": "DELETE /api/v1/health", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 8.83, + }, + { + Timestamp: 1689220096000, + Value: 8.83, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 8.83, + }, + }, + }, + }, + }, + params: &v3.QueryRangeParamsV3{ + Start: 1689220036000, + End: 1689220096000, + Step: 60, + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "signo_calls_total"}, + DataSource: v3.DataSourceMetrics, + AggregateOperator: v3.AggregateOperatorSumRate, + Expression: "A", + GroupBy: []v3.AttributeKey{{Key: "service_name"}}, + Limit: 1, + OrderBy: []v3.OrderBy{{ColumnName: "operation", Order: "asc"}}, + }, + }, + QueryType: v3.QueryTypeBuilder, + PanelType: v3.PanelTypeGraph, + }, + }, + expectedResult: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Labels: map[string]string{ + "service_name": "route", + "operation": "DELETE /api/v1/health", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 8.83, + }, + { + Timestamp: 1689220096000, + Value: 8.83, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 8.83, + }, + }, + }, + }, + }, + }, + { + name: "test limit with multiple order by labels", + inputResult: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Labels: map[string]string{ + "service_name": "frontend", + "operation": "GET /api/v1/health", + "status_code": "200", + "priority": "P0", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 19.2, + }, + { + Timestamp: 1689220096000, + Value: 19.5, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 19.3, + }, + }, + { + Labels: map[string]string{ + "service_name": "route", + "operation": "DELETE /api/v1/health", + "status_code": "301", + "priority": "P1", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 8.83, + }, + { + Timestamp: 1689220096000, + Value: 8.83, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 8.83, + }, + }, + { + Labels: map[string]string{ + "service_name": "route", + "operation": "DELETE /api/v1/health", + "status_code": "400", + "priority": "P0", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 8.83, + }, + { + Timestamp: 1689220096000, + Value: 8.83, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 8.83, + }, + }, + { + Labels: map[string]string{ + "service_name": "route", + "operation": "DELETE /api/v1/health", + "status_code": "200", + "priority": "P1", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 8.83, + }, + { + Timestamp: 1689220096000, + Value: 8.83, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 8.83, + }, + }, + }, + }, + }, + params: &v3.QueryRangeParamsV3{ + Start: 1689220036000, + End: 1689220096000, + Step: 60, + CompositeQuery: &v3.CompositeQuery{ + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + QueryName: "A", + AggregateAttribute: v3.AttributeKey{Key: "signo_calls_total"}, + DataSource: v3.DataSourceMetrics, + AggregateOperator: v3.AggregateOperatorSumRate, + Expression: "A", + GroupBy: []v3.AttributeKey{{Key: "service_name"}, {Key: "operation"}, {Key: "status_code"}, {Key: "priority"}}, + Limit: 2, + OrderBy: []v3.OrderBy{ + {ColumnName: "priority", Order: "asc"}, + {ColumnName: "status_code", Order: "desc"}, + }, + }, + }, + QueryType: v3.QueryTypeBuilder, + PanelType: v3.PanelTypeGraph, + }, + }, + expectedResult: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Labels: map[string]string{ + "service_name": "frontend", + "operation": "GET /api/v1/health", + "status_code": "200", + "priority": "P0", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 19.2, + }, + { + Timestamp: 1689220096000, + Value: 19.5, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 19.3, + }, + }, + { + Labels: map[string]string{ + "service_name": "route", + "operation": "DELETE /api/v1/health", + "status_code": "400", + "priority": "P0", + }, + Points: []v3.Point{ + { + Timestamp: 1689220036000, + Value: 8.83, + }, + { + Timestamp: 1689220096000, + Value: 8.83, + }, + }, + GroupingSetsPoint: &v3.Point{ + Timestamp: 0, + Value: 8.83, + }, + }, + }, + }, + }, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + result := c.inputResult + applyMetricLimit(result, c.params) + if len(result) != len(c.expectedResult) { + t.Errorf("expected result length: %d, but got: %d", len(c.expectedResult), len(result)) + } + for i, r := range result { + if r.QueryName != c.expectedResult[i].QueryName { + t.Errorf("expected query name: %s, but got: %s", c.expectedResult[i].QueryName, r.QueryName) + } + if len(r.Series) != len(c.expectedResult[i].Series) { + t.Errorf("expected series length: %d, but got: %d", len(c.expectedResult[i].Series), len(r.Series)) + } + for j, s := range r.Series { + if len(s.Points) != len(c.expectedResult[i].Series[j].Points) { + t.Errorf("expected points length: %d, but got: %d", len(c.expectedResult[i].Series[j].Points), len(s.Points)) + } + for k, p := range s.Points { + if p.Timestamp != c.expectedResult[i].Series[j].Points[k].Timestamp { + t.Errorf("expected point timestamp: %d, but got: %d", c.expectedResult[i].Series[j].Points[k].Timestamp, p.Timestamp) + } + if p.Value != c.expectedResult[i].Series[j].Points[k].Value { + t.Errorf("expected point value: %f, but got: %f", c.expectedResult[i].Series[j].Points[k].Value, p.Value) + } + } + } + } + }) + } +} diff --git a/pkg/query-service/app/queryBuilder/functions.go b/pkg/query-service/app/queryBuilder/functions.go new file mode 100644 index 0000000000..d71bfd0d54 --- /dev/null +++ b/pkg/query-service/app/queryBuilder/functions.go @@ -0,0 +1,286 @@ +package queryBuilder + +import ( + "math" + "sort" + + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" +) + +// funcCutOffMin cuts off values below the threshold and replaces them with NaN +func funcCutOffMin(result *v3.Result, threshold float64) *v3.Result { + for _, series := range result.Series { + for idx, point := range series.Points { + if point.Value < threshold { + point.Value = math.NaN() + } + series.Points[idx] = point + } + } + return result +} + +// funcCutOffMax cuts off values above the threshold and replaces them with NaN +func funcCutOffMax(result *v3.Result, threshold float64) *v3.Result { + for _, series := range result.Series { + for idx, point := range series.Points { + if point.Value > threshold { + point.Value = math.NaN() + } + series.Points[idx] = point + } + } + return result +} + +// funcClampMin cuts off values below the threshold and replaces them with the threshold +func funcClampMin(result *v3.Result, threshold float64) *v3.Result { + for _, series := range result.Series { + for idx, point := range series.Points { + if point.Value < threshold { + point.Value = threshold + } + series.Points[idx] = point + } + } + return result +} + +// funcClampMax cuts off values above the threshold and replaces them with the threshold +func funcClampMax(result *v3.Result, threshold float64) *v3.Result { + for _, series := range result.Series { + for idx, point := range series.Points { + if point.Value > threshold { + point.Value = threshold + } + series.Points[idx] = point + } + } + return result +} + +// funcAbsolute returns the absolute value of each point +func funcAbsolute(result *v3.Result) *v3.Result { + for _, series := range result.Series { + for idx, point := range series.Points { + point.Value = math.Abs(point.Value) + series.Points[idx] = point + } + } + return result +} + +// funcLog2 returns the log2 of each point +func funcLog2(result *v3.Result) *v3.Result { + for _, series := range result.Series { + for idx, point := range series.Points { + point.Value = math.Log2(point.Value) + series.Points[idx] = point + } + } + return result +} + +// funcLog10 returns the log10 of each point +func funcLog10(result *v3.Result) *v3.Result { + for _, series := range result.Series { + for idx, point := range series.Points { + point.Value = math.Log10(point.Value) + series.Points[idx] = point + } + } + return result +} + +// funcCumSum returns the cumulative sum for each point in a series +func funcCumSum(result *v3.Result) *v3.Result { + for _, series := range result.Series { + var sum float64 + for idx, point := range series.Points { + if !math.IsNaN(point.Value) { + sum += point.Value + } + point.Value = sum + series.Points[idx] = point + } + } + return result +} + +func funcEWMA(result *v3.Result, alpha float64) *v3.Result { + for _, series := range result.Series { + var ewma float64 + var initialized bool + + for i, point := range series.Points { + if !initialized { + if !math.IsNaN(point.Value) { + // Initialize EWMA with the first non-NaN value + ewma = point.Value + initialized = true + } + // Continue until the EWMA is initialized + continue + } + + if !math.IsNaN(point.Value) { + // Update EWMA with the current value + ewma = alpha*point.Value + (1-alpha)*ewma + } + // Set the EWMA value for the current point + series.Points[i].Value = ewma + } + } + return result +} + +// funcMedian3 returns the median of 3 points for each point in a series +func funcMedian3(result *v3.Result) *v3.Result { + for _, series := range result.Series { + median3 := make([]float64, 0) + for i := 1; i < len(series.Points)-1; i++ { + values := make([]float64, 0, 3) + + // Add non-NaN values to the slice + for j := -1; j <= 1; j++ { + if !math.IsNaN(series.Points[i+j].Value) { + values = append(values, series.Points[i+j].Value) + } + } + + // Handle the case where there are not enough values to calculate a median + if len(values) == 0 { + median3 = append(median3, math.NaN()) + continue + } + + median3 = append(median3, median(values)) + } + + // Set the median3 values for the series + for i := 1; i < len(series.Points)-1; i++ { + series.Points[i].Value = median3[i-1] + } + } + return result +} + +// funcMedian5 returns the median of 5 points for each point in a series +func funcMedian5(result *v3.Result) *v3.Result { + for _, series := range result.Series { + median5 := make([]float64, 0) + for i := 2; i < len(series.Points)-2; i++ { + values := make([]float64, 0, 5) + + // Add non-NaN values to the slice + for j := -2; j <= 2; j++ { + if !math.IsNaN(series.Points[i+j].Value) { + values = append(values, series.Points[i+j].Value) + } + } + + // Handle the case where there are not enough values to calculate a median + if len(values) == 0 { + median5 = append(median5, math.NaN()) + continue + } + + median5 = append(median5, median(values)) + } + + // Set the median5 values for the series + for i := 2; i < len(series.Points)-2; i++ { + series.Points[i].Value = median5[i-2] + } + } + return result +} + +// funcMedian7 returns the median of 7 points for each point in a series +func funcMedian7(result *v3.Result) *v3.Result { + for _, series := range result.Series { + median7 := make([]float64, 0) + for i := 3; i < len(series.Points)-3; i++ { + values := make([]float64, 0, 7) + + // Add non-NaN values to the slice + for j := -3; j <= 3; j++ { + if !math.IsNaN(series.Points[i+j].Value) { + values = append(values, series.Points[i+j].Value) + } + } + + // Handle the case where there are not enough values to calculate a median + if len(values) == 0 { + median7 = append(median7, math.NaN()) + continue + } + + median7 = append(median7, median(values)) + } + + // Set the median7 values for the series + for i := 3; i < len(series.Points)-3; i++ { + series.Points[i].Value = median7[i-3] + } + } + return result +} + +func median(values []float64) float64 { + sort.Float64s(values) + medianIndex := len(values) / 2 + if len(values)%2 == 0 { + return (values[medianIndex-1] + values[medianIndex]) / 2 + } + return values[medianIndex] +} + +func ApplyFunction(fn v3.Function, result *v3.Result) *v3.Result { + + switch fn.Name { + case v3.FunctionNameCutOffMin, v3.FunctionNameCutOffMax, v3.FunctionNameClampMin, v3.FunctionNameClampMax: + threshold, ok := fn.Args[0].(float64) + if !ok { + return result + } + switch fn.Name { + case v3.FunctionNameCutOffMin: + return funcCutOffMin(result, threshold) + case v3.FunctionNameCutOffMax: + return funcCutOffMax(result, threshold) + case v3.FunctionNameClampMin: + return funcClampMin(result, threshold) + case v3.FunctionNameClampMax: + return funcClampMax(result, threshold) + } + case v3.FunctionNameAbsolute: + return funcAbsolute(result) + case v3.FunctionNameLog2: + return funcLog2(result) + case v3.FunctionNameLog10: + return funcLog10(result) + case v3.FunctionNameCumSum: + return funcCumSum(result) + case v3.FunctionNameEWMA3, v3.FunctionNameEWMA5, v3.FunctionNameEWMA7: + alpha, ok := fn.Args[0].(float64) + if !ok { + // alpha = 2 / (n + 1) where n is the window size + if fn.Name == v3.FunctionNameEWMA3 { + alpha = 0.5 // 2 / (3 + 1) + } else if fn.Name == v3.FunctionNameEWMA5 { + alpha = 1 / float64(3) // 2 / (5 + 1) + } else if fn.Name == v3.FunctionNameEWMA7 { + alpha = 0.25 // 2 / (7 + 1) + } + } + return funcEWMA(result, alpha) + case v3.FunctionNameMedian3: + return funcMedian3(result) + case v3.FunctionNameMedian5: + return funcMedian5(result) + case v3.FunctionNameMedian7: + return funcMedian7(result) + } + return result +} diff --git a/pkg/query-service/app/queryBuilder/functions_test.go b/pkg/query-service/app/queryBuilder/functions_test.go new file mode 100644 index 0000000000..08b407a789 --- /dev/null +++ b/pkg/query-service/app/queryBuilder/functions_test.go @@ -0,0 +1,604 @@ +package queryBuilder + +import ( + "math" + "testing" + + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" +) + +func TestFuncCutOffMin(t *testing.T) { + type args struct { + result *v3.Result + threshold float64 + } + tests := []struct { + name string + args args + want *v3.Result + }{ + { + name: "test funcCutOffMin", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + threshold: 0.3, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: math.NaN(), + }, + { + Value: math.NaN(), + }, + }, + }, + }, + }, + }, + { + name: "test funcCutOffMin with threshold 0", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + threshold: 0, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + newResult := funcCutOffMin(tt.args.result, tt.args.threshold) + for j, series := range newResult.Series { + for k, point := range series.Points { + + if math.IsNaN(tt.want.Series[j].Points[k].Value) { + if !math.IsNaN(point.Value) { + t.Errorf("funcCutOffMin() = %v, want %v", point.Value, tt.want.Series[j].Points[k].Value) + } + continue + } + + if point.Value != tt.want.Series[j].Points[k].Value { + t.Errorf("funcCutOffMin() = %v, want %v", point.Value, tt.want.Series[j].Points[k].Value) + } + } + } + } +} + +func TestFuncCutOffMax(t *testing.T) { + type args struct { + result *v3.Result + threshold float64 + } + tests := []struct { + name string + args args + want *v3.Result + }{ + { + name: "test funcCutOffMax", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + threshold: 0.3, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: math.NaN(), + }, + { + Value: math.NaN(), + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + }, + { + name: "test funcCutOffMax with threshold 0", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + threshold: 0, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: math.NaN(), + }, + { + Value: math.NaN(), + }, + { + Value: math.NaN(), + }, + { + Value: math.NaN(), + }, + { + Value: math.NaN(), + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + newResult := funcCutOffMax(tt.args.result, tt.args.threshold) + for j, series := range newResult.Series { + for k, point := range series.Points { + + if math.IsNaN(tt.want.Series[j].Points[k].Value) { + if !math.IsNaN(point.Value) { + t.Errorf("funcCutOffMax() = %v, want %v", point.Value, tt.want.Series[j].Points[k].Value) + } + continue + } + + if point.Value != tt.want.Series[j].Points[k].Value { + t.Errorf("funcCutOffMax() = %v, want %v", point.Value, tt.want.Series[j].Points[k].Value) + } + } + } + } +} + +func TestCutOffMinCumSum(t *testing.T) { + type args struct { + result *v3.Result + threshold float64 + } + tests := []struct { + name string + args args + want *v3.Result + }{ + { + name: "test funcCutOffMin followed by funcCumulativeSum", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + }, + }, + }, + }, + threshold: 0.3, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.5, + }, + { + Value: 0.5, + }, + { + Value: 0.9, + }, + { + Value: 1.2, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + newResult := funcCutOffMin(tt.args.result, tt.args.threshold) + newResult = funcCumSum(newResult) + for j, series := range newResult.Series { + for k, point := range series.Points { + + if math.IsNaN(tt.want.Series[j].Points[k].Value) { + if !math.IsNaN(point.Value) { + t.Errorf("funcCutOffMin() = %v, want %v", point.Value, tt.want.Series[j].Points[k].Value) + } + continue + } + + if point.Value != tt.want.Series[j].Points[k].Value { + t.Errorf("funcCutOffMin() = %v, want %v", point.Value, tt.want.Series[j].Points[k].Value) + } + } + } + } +} + +func TestFuncMedian3(t *testing.T) { + type args struct { + result *v3.Result + } + + tests := []struct { + name string + args args + want *v3.Result + }{ + { + name: "Values", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 5}, {Timestamp: 2, Value: 3}, {Timestamp: 3, Value: 8}, {Timestamp: 4, Value: 2}, {Timestamp: 5, Value: 7}}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 5}, {Timestamp: 2, Value: 5}, {Timestamp: 3, Value: 3}, {Timestamp: 4, Value: 7}, {Timestamp: 5, Value: 7}}, + }, + }, + }, + }, + { + name: "NaNHandling", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: math.NaN()}, {Timestamp: 2, Value: 3}, {Timestamp: 3, Value: math.NaN()}, {Timestamp: 4, Value: 7}, {Timestamp: 5, Value: 9}}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: math.NaN()}, {Timestamp: 2, Value: 3}, {Timestamp: 3, Value: 5}, {Timestamp: 4, Value: 8}, {Timestamp: 5, Value: 9}}, + }, + }, + }, + }, + { + name: "UniformValues", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 7}, {Timestamp: 2, Value: 7}, {Timestamp: 3, Value: 7}, {Timestamp: 4, Value: 7}, {Timestamp: 5, Value: 7}}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 7}, {Timestamp: 2, Value: 7}, {Timestamp: 3, Value: 7}, {Timestamp: 4, Value: 7}, {Timestamp: 5, Value: 7}}, + }, + }, + }, + }, + { + name: "SingleValueSeries", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 9}}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 9}}, + }, + }, + }, + }, + { + name: "EmptySeries", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{}, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := funcMedian3(tt.args.result) + for j, series := range got.Series { + for k, point := range series.Points { + if point.Value != tt.want.Series[j].Points[k].Value && !math.IsNaN(tt.want.Series[j].Points[k].Value) { + t.Errorf("funcMedian3() = %v, want %v", point.Value, tt.want.Series[j].Points[k].Value) + } + } + } + }) + } +} + +func TestFuncMedian5(t *testing.T) { + type args struct { + result *v3.Result + } + + tests := []struct { + name string + args args + want *v3.Result + }{ + { + name: "Values", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 5}, {Timestamp: 2, Value: 3}, {Timestamp: 3, Value: 8}, {Timestamp: 4, Value: 2}, {Timestamp: 5, Value: 7}, {Timestamp: 6, Value: 9}, {Timestamp: 7, Value: 1}, {Timestamp: 8, Value: 4}, {Timestamp: 9, Value: 6}, {Timestamp: 10, Value: 10}}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 5}, {Timestamp: 2, Value: 3}, {Timestamp: 3, Value: 5}, {Timestamp: 4, Value: 7}, {Timestamp: 5, Value: 7}, {Timestamp: 6, Value: 4}, {Timestamp: 7, Value: 6}, {Timestamp: 8, Value: 6}, {Timestamp: 9, Value: 6}, {Timestamp: 10, Value: 10}}, + }, + }, + }, + }, + { + name: "NaNHandling", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: math.NaN()}, {Timestamp: 2, Value: 3}, {Timestamp: 3, Value: math.NaN()}, {Timestamp: 4, Value: 7}, {Timestamp: 5, Value: 9}, {Timestamp: 6, Value: 1}, {Timestamp: 7, Value: 4}, {Timestamp: 8, Value: 6}, {Timestamp: 9, Value: 10}, {Timestamp: 10, Value: 2}}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: math.NaN()}, {Timestamp: 2, Value: 3}, {Timestamp: 3, Value: 7}, {Timestamp: 4, Value: 5}, {Timestamp: 5, Value: 5.5}, {Timestamp: 6, Value: 6}, {Timestamp: 7, Value: 6}, {Timestamp: 8, Value: 4}, {Timestamp: 9, Value: 10}, {Timestamp: 10, Value: 2}}, + }, + }, + }, + }, + { + name: "UniformValues", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 7}, {Timestamp: 2, Value: 7}, {Timestamp: 3, Value: 7}, {Timestamp: 4, Value: 7}, {Timestamp: 5, Value: 7}}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 7}, {Timestamp: 2, Value: 7}, {Timestamp: 3, Value: 7}, {Timestamp: 4, Value: 7}, {Timestamp: 5, Value: 7}}, + }, + }, + }, + }, + { + name: "SingleValueSeries", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 9}}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{{Timestamp: 1, Value: 9}}, + }, + }, + }, + }, + { + name: "EmptySeries", + args: args{ + result: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{}, + }, + }, + }, + }, + want: &v3.Result{ + Series: []*v3.Series{ + { + Points: []v3.Point{}, + }, + }, + }, + }, + } + + for _, tt := range tests { + got := funcMedian5(tt.args.result) + for j, series := range got.Series { + for k, point := range series.Points { + if point.Value != tt.want.Series[j].Points[k].Value && !math.IsNaN(tt.want.Series[j].Points[k].Value) { + t.Errorf("funcMedian5() = %v, want %v", point.Value, tt.want.Series[j].Points[k].Value) + } + } + } + } +} diff --git a/pkg/query-service/app/reduce_to.go b/pkg/query-service/app/reduce_to.go new file mode 100644 index 0000000000..26c60d07c6 --- /dev/null +++ b/pkg/query-service/app/reduce_to.go @@ -0,0 +1,71 @@ +package app + +import ( + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" +) + +// applyReduceTo applies the reduceTo operator to each series +// and returns a new series with the reduced value +// reduceTo can be one of the following: +// - last +// - sum +// - avg +// - min +// - max +func applyReduceTo(result []*v3.Result, queryRangeParams *v3.QueryRangeParamsV3) { + for _, result := range result { + builderQueries := queryRangeParams.CompositeQuery.BuilderQueries + + // reduceTo is only applicable for metrics data source + // and for table and value panels + if builderQueries[result.QueryName] != nil && (builderQueries[result.QueryName].DataSource == v3.DataSourceMetrics && + (queryRangeParams.CompositeQuery.PanelType == v3.PanelTypeTable || queryRangeParams.CompositeQuery.PanelType == v3.PanelTypeValue)) { + reduceTo := builderQueries[result.QueryName].ReduceTo + + switch reduceTo { + case v3.ReduceToOperatorLast: + for i := 0; i < len(result.Series); i++ { + if len(result.Series[i].Points) > 0 { + result.Series[i].Points = []v3.Point{result.Series[i].Points[len(result.Series[i].Points)-1]} + } + } + case v3.ReduceToOperatorSum: + for i := 0; i < len(result.Series); i++ { + var sum float64 + for j := 0; j < len(result.Series[i].Points); j++ { + sum += result.Series[i].Points[j].Value + } + result.Series[i].Points = []v3.Point{{Value: sum}} + } + case v3.ReduceToOperatorAvg: + for i := 0; i < len(result.Series); i++ { + var sum float64 + for j := 0; j < len(result.Series[i].Points); j++ { + sum += result.Series[i].Points[j].Value + } + result.Series[i].Points = []v3.Point{{Value: sum / float64(len(result.Series[i].Points))}} + } + case v3.ReduceToOperatorMin: + for i := 0; i < len(result.Series); i++ { + var min float64 + for j := 0; j < len(result.Series[i].Points); j++ { + if j == 0 || result.Series[i].Points[j].Value < min { + min = result.Series[i].Points[j].Value + } + } + result.Series[i].Points = []v3.Point{{Value: min}} + } + case v3.ReduceToOperatorMax: + for i := 0; i < len(result.Series); i++ { + var max float64 + for j := 0; j < len(result.Series[i].Points); j++ { + if j == 0 || result.Series[i].Points[j].Value > max { + max = result.Series[i].Points[j].Value + } + } + result.Series[i].Points = []v3.Point{{Value: max}} + } + } + } + } +} diff --git a/pkg/query-service/app/reduce_to_test.go b/pkg/query-service/app/reduce_to_test.go new file mode 100644 index 0000000000..1f5a16d65b --- /dev/null +++ b/pkg/query-service/app/reduce_to_test.go @@ -0,0 +1,99 @@ +package app + +import ( + "testing" + + v3 "go.signoz.io/signoz/pkg/query-service/model/v3" +) + +func TestApplyReduceTo(t *testing.T) { + type testCase struct { + name string + results []*v3.Result + params *v3.QueryRangeParamsV3 + want []*v3.Result + } + + testCases := []testCase{ + { + name: "test reduce to", + results: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 0.5, + }, + { + Value: 0.4, + }, + { + Value: 0.3, + }, + { + Value: 0.2, + }, + { + Value: 0.1, + }, + }, + }, + }, + }, + }, + params: &v3.QueryRangeParamsV3{ + CompositeQuery: &v3.CompositeQuery{ + PanelType: v3.PanelTypeValue, + BuilderQueries: map[string]*v3.BuilderQuery{ + "A": { + DataSource: v3.DataSourceMetrics, + ReduceTo: v3.ReduceToOperatorSum, + }, + }, + }, + }, + want: []*v3.Result{ + { + QueryName: "A", + Series: []*v3.Series{ + { + Points: []v3.Point{ + { + Value: 1.5, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + applyReduceTo(tc.results, tc.params) + got := tc.results + + for _, gotResult := range got { + for _, wantResult := range tc.want { + if gotResult.QueryName == wantResult.QueryName { + if len(gotResult.Series) != len(wantResult.Series) { + t.Errorf("got %v, want %v", gotResult.Series, wantResult.Series) + } else { + for i, gotSeries := range gotResult.Series { + for j, gotPoint := range gotSeries.Points { + if gotPoint.Value != wantResult.Series[i].Points[j].Value { + t.Errorf("got %v, want %v", gotPoint.Value, wantResult.Series[i].Points[j].Value) + } + } + } + } + } + } + } + }) + } +} diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index a11e888c15..560a0b13f8 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -6,6 +6,7 @@ import ( "fmt" "sort" "strconv" + "strings" "time" "github.com/google/uuid" @@ -482,10 +483,50 @@ const ( SpaceAggregationCount SpaceAggregation = "count" ) +type FunctionName string + +const ( + FunctionNameCutOffMin FunctionName = "cutOffMin" + FunctionNameCutOffMax FunctionName = "cutOffMax" + FunctionNameClampMin FunctionName = "clampMin" + FunctionNameClampMax FunctionName = "clampMax" + FunctionNameAbsolute FunctionName = "absolute" + FunctionNameLog2 FunctionName = "log2" + FunctionNameLog10 FunctionName = "log10" + FunctionNameCumSum FunctionName = "cumSum" + FunctionNameEWMA3 FunctionName = "ewma3" + FunctionNameEWMA5 FunctionName = "ewma5" + FunctionNameEWMA7 FunctionName = "ewma7" + FunctionNameMedian3 FunctionName = "median3" + FunctionNameMedian5 FunctionName = "median5" + FunctionNameMedian7 FunctionName = "median7" +) + +func (f FunctionName) Validate() error { + switch f { + case FunctionNameCutOffMin, + FunctionNameCutOffMax, + FunctionNameClampMin, + FunctionNameClampMax, + FunctionNameAbsolute, + FunctionNameLog2, + FunctionNameLog10, + FunctionNameCumSum, + FunctionNameEWMA3, + FunctionNameEWMA5, + FunctionNameEWMA7, + FunctionNameMedian3, + FunctionNameMedian5, + FunctionNameMedian7: + return nil + default: + return fmt.Errorf("invalid function name: %s", f) + } +} + type Function struct { - Category string `json:"category"` - Name string `json:"name"` - Args []interface{} `json:"args,omitempty"` + Name FunctionName `json:"name"` + Args []interface{} `json:"args,omitempty"` } type BuilderQuery struct { @@ -562,6 +603,14 @@ func (b *BuilderQuery) Validate() error { } } + if b.Having != nil { + for _, having := range b.Having { + if err := having.Operator.Validate(); err != nil { + return fmt.Errorf("having operator is invalid: %w", err) + } + } + } + for _, selectColumn := range b.SelectColumns { if err := selectColumn.Validate(); err != nil { return fmt.Errorf("select column is invalid %w", err) @@ -571,6 +620,15 @@ func (b *BuilderQuery) Validate() error { if b.Expression == "" { return fmt.Errorf("expression is required") } + + if len(b.Functions) > 0 { + for _, function := range b.Functions { + if err := function.Name.Validate(); err != nil { + return fmt.Errorf("function name is invalid: %w", err) + } + } + } + return nil } @@ -655,10 +713,43 @@ type OrderBy struct { IsColumn bool `json:"-"` } +// See HAVING_OPERATORS in queryBuilder.ts + +type HavingOperator string + +const ( + HavingOperatorEqual HavingOperator = "=" + HavingOperatorNotEqual HavingOperator = "!=" + HavingOperatorGreaterThan HavingOperator = ">" + HavingOperatorGreaterThanOrEq HavingOperator = ">=" + HavingOperatorLessThan HavingOperator = "<" + HavingOperatorLessThanOrEq HavingOperator = "<=" + HavingOperatorIn HavingOperator = "IN" + HavingOperatorNotIn HavingOperator = "NOT_IN" +) + +func (h HavingOperator) Validate() error { + switch h { + case HavingOperatorEqual, + HavingOperatorNotEqual, + HavingOperatorGreaterThan, + HavingOperatorGreaterThanOrEq, + HavingOperatorLessThan, + HavingOperatorLessThanOrEq, + HavingOperatorIn, + HavingOperatorNotIn, + HavingOperator(strings.ToLower(string(HavingOperatorIn))), + HavingOperator(strings.ToLower(string(HavingOperatorNotIn))): + return nil + default: + return fmt.Errorf("invalid having operator: %s", h) + } +} + type Having struct { - ColumnName string `json:"columnName"` - Operator string `json:"op"` - Value interface{} `json:"value"` + ColumnName string `json:"columnName"` + Operator HavingOperator `json:"op"` + Value interface{} `json:"value"` } func (h *Having) CacheKey() string { From 085cf34a49434e90f8ec50428a52017e14a985a3 Mon Sep 17 00:00:00 2001 From: Raj Kamal Singh <1133322+raj-k-singh@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:35:36 +0530 Subject: [PATCH 04/16] fix: Logs UI: querybuildersearch: avoid emptying out query on sourceKeys update if tags are yet to be populated (#4355) * fix: querybuildersearch: do not call query onChange from sourceKeys useEffect if tags is empty * chore: add comment explaining change --- .../filters/QueryBuilderSearch/index.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx index 975c79a4a8..d12bc18add 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx @@ -150,6 +150,20 @@ function QueryBuilderSearch({ (item) => item.key as BaseAutocompleteData, ); + // Avoid updating query with onChange at the bottom of this useEffect + // if there are no `tags` that need to be normalized after receiving + // the latest `sourceKeys`. + // + // Executing the following logic for empty tags leads to emptying + // out of `query` via `onChange`. + // `tags` can contain stale empty value while being updated by `useTag` + // which maintains it as a state and updates it via useEffect when props change. + // This was observed when pipeline filters were becoming empty after + // returning from logs explorer. + if ((tags?.length || 0) < 1) { + return; + } + initialTagFilters.items = tags.map((tag, index) => { const isJsonTrue = query.filters?.items[index]?.key?.isJSON; From bb7417ffbdd8aee5f693a58398d0b513f8d5744f Mon Sep 17 00:00:00 2001 From: Keshav Gupta Date: Thu, 25 Jan 2024 19:42:14 +0530 Subject: [PATCH 05/16] fix:edit the nameon sign up page if name is blank (#4216) Co-authored-by: keshav --- frontend/src/pages/SignUp/SignUp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/SignUp/SignUp.tsx b/frontend/src/pages/SignUp/SignUp.tsx index 836b9f726f..35cca11603 100644 --- a/frontend/src/pages/SignUp/SignUp.tsx +++ b/frontend/src/pages/SignUp/SignUp.tsx @@ -344,7 +344,7 @@ function SignUp({ version }: SignUpProps): JSX.Element { placeholder={t('placeholder_firstname')} required id="signupFirstName" - disabled={isDetailsDisable} + disabled={isDetailsDisable && form.getFieldValue('firstName')} /> From 7e5cf65ea32e3938ea7d3a8a21c84a22df7f6ea8 Mon Sep 17 00:00:00 2001 From: Vikrant Gupta Date: Thu, 25 Jan 2024 23:12:19 +0530 Subject: [PATCH 06/16] fix: [GH-4434]: dashboard variables performance issues (#4437) --- .../VariableItem.tsx | 36 ++++--------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx index e636ced76c..da20efc590 100644 --- a/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx +++ b/frontend/src/container/NewDashboard/DashboardVariablesSelection/VariableItem.tsx @@ -5,9 +5,9 @@ import { WarningOutlined } from '@ant-design/icons'; import { Input, Popover, Select, Tooltip, Typography } from 'antd'; import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import useDebounce from 'hooks/useDebounce'; import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser'; import sortValues from 'lib/dashbaordVariables/sortVariableValues'; +import { debounce } from 'lodash-es'; import map from 'lodash-es/map'; import { useDashboard } from 'providers/Dashboard/Dashboard'; import { memo, useEffect, useMemo, useState } from 'react'; @@ -55,24 +55,8 @@ function VariableItem({ [], ); - const [variableValue, setVaribleValue] = useState( - variableData?.selectedValue?.toString() || '', - ); - - const debouncedVariableValue = useDebounce(variableValue, 500); - const [errorMessage, setErrorMessage] = useState(null); - useEffect(() => { - const { selectedValue } = variableData; - - if (selectedValue) { - setVaribleValue(selectedValue?.toString()); - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [variableData]); - const getDependentVariables = (queryValue: string): string[] => { const matches = queryValue.match(variableRegexPattern); @@ -204,6 +188,9 @@ function VariableItem({ } }; + // do not debounce the above function as we do not need debounce in select variables + const debouncedHandleChange = debounce(handleChange, 500); + const { selectedValue } = variableData; const selectedValueStringified = useMemo(() => getSelectValue(selectedValue), [ selectedValue, @@ -219,14 +206,6 @@ function VariableItem({ : undefined; const enableSelectAll = variableData.multiSelect && variableData.showALLOption; - useEffect(() => { - if (debouncedVariableValue !== variableData?.selectedValue?.toString()) { - handleChange(debouncedVariableValue); - } - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [debouncedVariableValue]); - useEffect(() => { // Fetch options for CUSTOM Type if (variableData.type === 'CUSTOM') { @@ -250,9 +229,9 @@ function VariableItem({ placeholder="Enter value" disabled={isDashboardLocked} bordered={false} - value={variableValue} + defaultValue={variableData.selectedValue?.toString()} onChange={(e): void => { - setVaribleValue(e.target.value || ''); + debouncedHandleChange(e.target.value || ''); }} style={{ width: @@ -263,7 +242,7 @@ function VariableItem({ !errorMessage && optionsData && ( {enableSelectAll && ( From 4db3e5e542d57ba68cea2d5b9b4bd5c2614f2cfd Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Mon, 29 Jan 2024 00:42:19 +0530 Subject: [PATCH 09/16] chore: include status (#4447) --- ee/query-service/app/api/api.go | 6 +++--- ee/query-service/app/api/license.go | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ee/query-service/app/api/api.go b/ee/query-service/app/api/api.go index ddb617188f..22f3ee67c2 100644 --- a/ee/query-service/app/api/api.go +++ b/ee/query-service/app/api/api.go @@ -152,9 +152,9 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew router.HandleFunc("/api/v2/metrics/query_range", am.ViewAccess(ah.queryRangeMetricsV2)).Methods(http.MethodPost) // PAT APIs - router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.createPAT)).Methods(http.MethodPost) - router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.getPATs)).Methods(http.MethodGet) - router.HandleFunc("/api/v1/pat/{id}", am.OpenAccess(ah.deletePAT)).Methods(http.MethodDelete) + router.HandleFunc("/api/v1/pat", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/pat", am.AdminAccess(ah.getPATs)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/pat/{id}", am.AdminAccess(ah.deletePAT)).Methods(http.MethodDelete) router.HandleFunc("/api/v1/checkout", am.AdminAccess(ah.checkout)).Methods(http.MethodPost) router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet) diff --git a/ee/query-service/app/api/license.go b/ee/query-service/app/api/license.go index e382461d54..c6fe43a6bb 100644 --- a/ee/query-service/app/api/license.go +++ b/ee/query-service/app/api/license.go @@ -40,6 +40,7 @@ type billingDetails struct { BillingPeriodEnd int64 `json:"billingPeriodEnd"` Details details `json:"details"` Discount float64 `json:"discount"` + SubscriptionStatus string `json:"subscriptionStatus"` } `json:"data"` } From 824d9aaf85af01f6a330def954be879e19edf729 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 29 Jan 2024 11:12:41 +0530 Subject: [PATCH 10/16] feat: users should choose either to broadcast all or enter specific channels to alert (#4441) * feat: users should choose either to broadcast all or enter specific channels to alert * fix: remove console logs --- frontend/public/locales/en-GB/alerts.json | 3 +- frontend/public/locales/en-GB/rules.json | 1 + frontend/public/locales/en/alerts.json | 3 +- frontend/public/locales/en/rules.json | 1 + .../CreateAlertRule/SelectAlertType/index.tsx | 11 ++- .../container/FormAlertRules/BasicInfo.tsx | 75 +++++++++++++++---- .../FormAlertRules/ChannelSelect/index.tsx | 4 + .../src/container/FormAlertRules/index.tsx | 36 +++++++-- frontend/src/types/api/alerts/def.ts | 1 + 9 files changed, 111 insertions(+), 24 deletions(-) diff --git a/frontend/public/locales/en-GB/alerts.json b/frontend/public/locales/en-GB/alerts.json index 98e9780e6a..5b102e147d 100644 --- a/frontend/public/locales/en-GB/alerts.json +++ b/frontend/public/locales/en-GB/alerts.json @@ -62,6 +62,7 @@ "button_cancel": "No", "field_promql_expr": "PromQL Expression", "field_alert_name": "Alert Name", + "field_notification_channel": "Notification Channel", "field_alert_desc": "Alert Description", "field_labels": "Labels", "field_severity": "Severity", @@ -100,7 +101,7 @@ "user_guide_ch_step3a": "Set alert severity, name and descriptions", "user_guide_ch_step3b": "Add tags to the alert in the Label field if needed", "user_tooltip_more_help": "More details on how to create alerts", - "choose_alert_type": "Choose a type for the alert:", + "choose_alert_type": "Choose a type for the alert", "metric_based_alert": "Metric based Alert", "metric_based_alert_desc": "Send a notification when a condition occurs in the metric data", "log_based_alert": "Log-based Alert", diff --git a/frontend/public/locales/en-GB/rules.json b/frontend/public/locales/en-GB/rules.json index c913dc08e0..9d55a0ba0f 100644 --- a/frontend/public/locales/en-GB/rules.json +++ b/frontend/public/locales/en-GB/rules.json @@ -54,6 +54,7 @@ "field_promql_expr": "PromQL Expression", "field_alert_name": "Alert Name", "field_alert_desc": "Alert Description", + "field_notification_channel": "Notification Channel", "field_labels": "Labels", "field_severity": "Severity", "option_critical": "Critical", diff --git a/frontend/public/locales/en/alerts.json b/frontend/public/locales/en/alerts.json index 98e9780e6a..455ade61e3 100644 --- a/frontend/public/locales/en/alerts.json +++ b/frontend/public/locales/en/alerts.json @@ -63,6 +63,7 @@ "field_promql_expr": "PromQL Expression", "field_alert_name": "Alert Name", "field_alert_desc": "Alert Description", + "field_notification_channel": "Notification Channel", "field_labels": "Labels", "field_severity": "Severity", "option_critical": "Critical", @@ -100,7 +101,7 @@ "user_guide_ch_step3a": "Set alert severity, name and descriptions", "user_guide_ch_step3b": "Add tags to the alert in the Label field if needed", "user_tooltip_more_help": "More details on how to create alerts", - "choose_alert_type": "Choose a type for the alert:", + "choose_alert_type": "Choose a type for the alert", "metric_based_alert": "Metric based Alert", "metric_based_alert_desc": "Send a notification when a condition occurs in the metric data", "log_based_alert": "Log-based Alert", diff --git a/frontend/public/locales/en/rules.json b/frontend/public/locales/en/rules.json index c913dc08e0..9d55a0ba0f 100644 --- a/frontend/public/locales/en/rules.json +++ b/frontend/public/locales/en/rules.json @@ -54,6 +54,7 @@ "field_promql_expr": "PromQL Expression", "field_alert_name": "Alert Name", "field_alert_desc": "Alert Description", + "field_notification_channel": "Notification Channel", "field_labels": "Labels", "field_severity": "Severity", "option_critical": "Critical", diff --git a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx index 65a5914fca..60bf658e5d 100644 --- a/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx +++ b/frontend/src/container/CreateAlertRule/SelectAlertType/index.tsx @@ -1,4 +1,4 @@ -import { Row } from 'antd'; +import { Row, Typography } from 'antd'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { AlertTypes } from 'types/api/alerts/alertTypes'; @@ -33,7 +33,14 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element { return ( -

{t('choose_alert_type')}

+ + {t('choose_alert_type')} + {renderOptions}
); diff --git a/frontend/src/container/FormAlertRules/BasicInfo.tsx b/frontend/src/container/FormAlertRules/BasicInfo.tsx index 3d96e7fb79..54877f5e0b 100644 --- a/frontend/src/container/FormAlertRules/BasicInfo.tsx +++ b/frontend/src/container/FormAlertRules/BasicInfo.tsx @@ -1,4 +1,5 @@ -import { Form, Select } from 'antd'; +import { Form, Select, Switch } from 'antd'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { AlertDef, Labels } from 'types/api/alerts/def'; import { requireErrorMessage } from 'utils/form/requireErrorMessage'; @@ -7,7 +8,6 @@ import { popupContainer } from 'utils/selectPopupContainer'; import ChannelSelect from './ChannelSelect'; import LabelSelect from './labels'; import { - ChannelSelectTip, FormContainer, FormItemMedium, InputSmall, @@ -19,14 +19,41 @@ import { const { Option } = Select; interface BasicInfoProps { + isNewRule: boolean; alertDef: AlertDef; setAlertDef: (a: AlertDef) => void; } -function BasicInfo({ alertDef, setAlertDef }: BasicInfoProps): JSX.Element { - // init namespace for translations +function BasicInfo({ + isNewRule, + alertDef, + setAlertDef, +}: BasicInfoProps): JSX.Element { const { t } = useTranslation('alerts'); + const [ + shouldBroadCastToAllChannels, + setShouldBroadCastToAllChannels, + ] = useState(false); + + useEffect(() => { + const hasPreferredChannels = + (alertDef.preferredChannels && alertDef.preferredChannels.length > 0) || + isNewRule; + + setShouldBroadCastToAllChannels(!hasPreferredChannels); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleBroadcastToAllChannels = (shouldBroadcast: boolean): void => { + setShouldBroadCastToAllChannels(shouldBroadcast); + + setAlertDef({ + ...alertDef, + broadcastToAll: shouldBroadcast, + }); + }; + return ( <> {t('alert_form_step3')} @@ -105,18 +132,38 @@ function BasicInfo({ alertDef, setAlertDef }: BasicInfoProps): JSX.Element { initialValues={alertDef.labels} /> - - { - setAlertDef({ - ...alertDef, - preferredChannels, - }); - }} + + + - {t('channel_select_tooltip')} + + {!shouldBroadCastToAllChannels && ( + + { + setAlertDef({ + ...alertDef, + preferredChannels, + }); + }} + /> + + )} ); diff --git a/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx b/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx index c2d78e661c..15f391c7cf 100644 --- a/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx +++ b/frontend/src/container/FormAlertRules/ChannelSelect/index.tsx @@ -8,11 +8,13 @@ import { useTranslation } from 'react-i18next'; import { StyledSelect } from './styles'; export interface ChannelSelectProps { + disabled?: boolean; currentValue?: string[]; onSelectChannels: (s: string[]) => void; } function ChannelSelect({ + disabled, currentValue, onSelectChannels, }: ChannelSelectProps): JSX.Element | null { @@ -52,6 +54,7 @@ function ChannelSelect({ }; return ( { - setAlertDef(initialValue); - }, [initialValue]); + const broadcastToSpecificChannels = + (initialValue && + initialValue.preferredChannels && + initialValue.preferredChannels.length > 0) || + isNewRule; + + setAlertDef({ + ...initialValue, + broadcastToAll: !broadcastToSpecificChannels, + }); + }, [initialValue, isNewRule]); useEffect(() => { // Set selectedQueryName based on the length of queryOptions @@ -243,6 +255,7 @@ function FormAlertRules({ const preparePostData = (): AlertDef => { const postableAlert: AlertDef = { ...alertDef, + preferredChannels: alertDef.broadcastToAll ? [] : alertDef.preferredChannels, alertType, source: window?.location.toString(), ruleType: @@ -386,7 +399,11 @@ function FormAlertRules({ }, [t, isFormValid, memoizedPreparePostData, notifications]); const renderBasicInfo = (): JSX.Element => ( - + ); const renderQBChartPreview = (): JSX.Element => ( @@ -421,8 +438,6 @@ function FormAlertRules({ /> ); - const isNewRule = ruleId === 0; - const isAlertNameMissing = !formInstance.getFieldValue('alert'); const isAlertAvialableToSave = @@ -442,6 +457,10 @@ function FormAlertRules({ })); }; + const isChannelConfigurationValid = + alertDef?.broadcastToAll || + (alertDef.preferredChannels && alertDef.preferredChannels.length > 0); + return ( <> {Element} @@ -489,7 +508,11 @@ function FormAlertRules({ type="primary" onClick={onSaveHandler} icon={} - disabled={isAlertNameMissing || isAlertAvialableToSave} + disabled={ + isAlertNameMissing || + isAlertAvialableToSave || + !isChannelConfigurationValid + } > {isNewRule ? t('button_createrule') : t('button_savechanges')} @@ -497,6 +520,7 @@ function FormAlertRules({ diff --git a/frontend/src/types/api/alerts/def.ts b/frontend/src/types/api/alerts/def.ts index a5420e7ef0..42d599948d 100644 --- a/frontend/src/types/api/alerts/def.ts +++ b/frontend/src/types/api/alerts/def.ts @@ -21,6 +21,7 @@ export interface AlertDef { source?: string; disabled?: boolean; preferredChannels?: string[]; + broadcastToAll?: boolean; } export interface RuleCondition { From e977963763d14868d386512a3d397984dd2c5070 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Mon, 29 Jan 2024 18:21:51 +0530 Subject: [PATCH 11/16] feat: show total items count in table (#4453) --- frontend/src/components/ResizeTable/ResizeTable.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ResizeTable/ResizeTable.tsx b/frontend/src/components/ResizeTable/ResizeTable.tsx index 90cc588c47..9cbaee0346 100644 --- a/frontend/src/components/ResizeTable/ResizeTable.tsx +++ b/frontend/src/components/ResizeTable/ResizeTable.tsx @@ -73,12 +73,18 @@ function ResizeTable({ } }, [columns]); + const paginationConfig = { + hideOnSinglePage: true, + showTotal: (total: number, range: number[]): string => + `${range[0]}-${range[1]} of ${total} items`, + }; + return onDragColumn ? ( - +
) : ( -
+
); } From 0200fb3a21dabe054694830fa05f0a87cba3d034 Mon Sep 17 00:00:00 2001 From: Yunus M Date: Tue, 30 Jan 2024 16:47:58 +0530 Subject: [PATCH 12/16] fix: close delete modal on delete success (#4459) --- .../GridCardLayout/GridCard/WidgetGraphComponent.tsx | 2 ++ .../src/container/GridCardLayout/GridCardLayout.tsx | 6 +++++- frontend/src/container/GridCardLayout/index.tsx | 11 ++--------- frontend/src/container/NewWidget/index.tsx | 8 +++++--- frontend/src/container/NewWidget/styles.ts | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx index f77f714b26..0af1b9474f 100644 --- a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx @@ -124,6 +124,7 @@ function WidgetGraphComponent({ if (setSelectedDashboard && updatedDashboard.payload) { setSelectedDashboard(updatedDashboard.payload); } + setDeleteModal(false); featureResponse.refetch(); }, onError: () => { @@ -255,6 +256,7 @@ function WidgetGraphComponent({ destroyOnClose onCancel={onDeleteModelHandler} open={deleteModal} + confirmLoading={updateDashboardMutation.isLoading} title="Delete" height="10vh" onOk={onDeleteHandler} diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.tsx b/frontend/src/container/GridCardLayout/GridCardLayout.tsx index 80221a3ef8..b4f1dbe19c 100644 --- a/frontend/src/container/GridCardLayout/GridCardLayout.tsx +++ b/frontend/src/container/GridCardLayout/GridCardLayout.tsx @@ -55,7 +55,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { const isDarkMode = useIsDarkMode(); - const [dashboardLayout, setDashboardLayout] = useState(layouts); + const [dashboardLayout, setDashboardLayout] = useState([]); const updateDashboardMutation = useUpdateDashboard(); @@ -77,6 +77,10 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { userRole, ); + useEffect(() => { + setDashboardLayout(layouts); + }, [layouts]); + const onSaveHandler = (): void => { if (!selectedDashboard) return; diff --git a/frontend/src/container/GridCardLayout/index.tsx b/frontend/src/container/GridCardLayout/index.tsx index a54daa313c..a2a82dc8ad 100644 --- a/frontend/src/container/GridCardLayout/index.tsx +++ b/frontend/src/container/GridCardLayout/index.tsx @@ -1,21 +1,14 @@ import { useDashboard } from 'providers/Dashboard/Dashboard'; import { useCallback } from 'react'; -import { Layout } from 'react-grid-layout'; -import { EMPTY_WIDGET_LAYOUT } from './config'; import GraphLayoutContainer from './GridCardLayout'; function GridGraph(): JSX.Element { - const { handleToggleDashboardSlider, setLayouts } = useDashboard(); + const { handleToggleDashboardSlider } = useDashboard(); const onEmptyWidgetHandler = useCallback(() => { handleToggleDashboardSlider(true); - - setLayouts((preLayout: Layout[]) => [ - EMPTY_WIDGET_LAYOUT, - ...(preLayout || []), - ]); - }, [handleToggleDashboardSlider, setLayouts]); + }, [handleToggleDashboardSlider]); return ; } diff --git a/frontend/src/container/NewWidget/index.tsx b/frontend/src/container/NewWidget/index.tsx index 9ea47330bf..5192644a5e 100644 --- a/frontend/src/container/NewWidget/index.tsx +++ b/frontend/src/container/NewWidget/index.tsx @@ -307,7 +307,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element { disabled={isSaveDisabled} onClick={onSaveDashboard} > - Save + Save Changes )} @@ -316,13 +316,14 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element { )} - + @@ -385,6 +386,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element { closable onCancel={closeModal} onOk={onClickSaveHandler} + confirmLoading={updateDashboardMutation.isLoading} centered open={saveModal} width={600} diff --git a/frontend/src/container/NewWidget/styles.ts b/frontend/src/container/NewWidget/styles.ts index c9941ad07c..7ae40d8b27 100644 --- a/frontend/src/container/NewWidget/styles.ts +++ b/frontend/src/container/NewWidget/styles.ts @@ -25,7 +25,7 @@ export const LeftContainerWrapper = styled(Col)` export const ButtonContainer = styled.div` display: flex; - gap: 1rem; + gap: 8px; margin-bottom: 1rem; justify-content: flex-end; `; From 01fc7a7fd4abdcf5c9709ed85b383ef7256f7dd8 Mon Sep 17 00:00:00 2001 From: Vikrant Gupta Date: Tue, 30 Jan 2024 18:50:02 +0530 Subject: [PATCH 13/16] fix: [GH-4451]: custom time range modal closed on focussing closed date (#4456) * fix: [GH-4451]: custom time range modal closed on focussing closed date * fix: jest test --- .../CustomDateTimeModal/CustomDateTimeModal.test.tsx | 1 + .../container/TopNav/CustomDateTimeModal/index.tsx | 11 +++++++---- .../src/container/TopNav/DateTimeSelection/index.tsx | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/src/container/TopNav/CustomDateTimeModal/CustomDateTimeModal.test.tsx b/frontend/src/container/TopNav/CustomDateTimeModal/CustomDateTimeModal.test.tsx index a178b2edec..e816059cba 100644 --- a/frontend/src/container/TopNav/CustomDateTimeModal/CustomDateTimeModal.test.tsx +++ b/frontend/src/container/TopNav/CustomDateTimeModal/CustomDateTimeModal.test.tsx @@ -12,6 +12,7 @@ describe('CustomDateTimeModal', () => { visible onCreate={handleCreate} onCancel={handleCancel} + setCustomDTPickerVisible={jest.fn()} />, ); }); diff --git a/frontend/src/container/TopNav/CustomDateTimeModal/index.tsx b/frontend/src/container/TopNav/CustomDateTimeModal/index.tsx index 57cc54e5e7..904bcd5fd0 100644 --- a/frontend/src/container/TopNav/CustomDateTimeModal/index.tsx +++ b/frontend/src/container/TopNav/CustomDateTimeModal/index.tsx @@ -1,6 +1,6 @@ import { DatePicker, Modal } from 'antd'; import dayjs, { Dayjs } from 'dayjs'; -import { useState } from 'react'; +import { Dispatch, SetStateAction, useState } from 'react'; export type DateTimeRangeType = [Dayjs | null, Dayjs | null] | null; @@ -10,12 +10,12 @@ function CustomDateTimeModal({ visible, onCreate, onCancel, + setCustomDTPickerVisible, }: CustomDateTimeModalProps): JSX.Element { const [selectedDate, setDateTime] = useState(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const onModalOkHandler = (date_time: any): void => { - onCreate(date_time); setDateTime(date_time); }; @@ -25,7 +25,10 @@ function CustomDateTimeModal({ }; const onOk = (): void => { - if (selectedDate) onCreate(selectedDate); + if (selectedDate) { + onCreate(selectedDate); + setCustomDTPickerVisible(false); + } }; return ( @@ -42,7 +45,6 @@ function CustomDateTimeModal({ allowClear onOk={onModalOkHandler} showTime - onCalendarChange={onModalOkHandler} /> ); @@ -52,6 +54,7 @@ interface CustomDateTimeModalProps { visible: boolean; onCreate: (dateTimeRange: DateTimeRangeType) => void; onCancel: () => void; + setCustomDTPickerVisible: Dispatch>; } export default CustomDateTimeModal; diff --git a/frontend/src/container/TopNav/DateTimeSelection/index.tsx b/frontend/src/container/TopNav/DateTimeSelection/index.tsx index d776eb25e2..58b090abb9 100644 --- a/frontend/src/container/TopNav/DateTimeSelection/index.tsx +++ b/frontend/src/container/TopNav/DateTimeSelection/index.tsx @@ -216,7 +216,6 @@ function DateTimeSelection({ if (dateTimeRange !== null) { const [startTimeMoment, endTimeMoment] = dateTimeRange; if (startTimeMoment && endTimeMoment) { - setCustomDTPickerVisible(false); updateTimeInterval('custom', [ startTimeMoment?.toDate().getTime() || 0, endTimeMoment?.toDate().getTime() || 0, @@ -352,6 +351,7 @@ function DateTimeSelection({ onCancel={(): void => { setCustomDTPickerVisible(false); }} + setCustomDTPickerVisible={setCustomDTPickerVisible} /> ); From 78c93306668501b4afb8a555eb0e43bad7608c8c Mon Sep 17 00:00:00 2001 From: Yunus M Date: Wed, 31 Jan 2024 14:25:27 +0530 Subject: [PATCH 14/16] fix: set light bg for full screen in dashboard (#4465) --- .../GridCardLayout/GridCardLayout.styles.scss | 8 ++++++++ frontend/src/container/SideNav/SideNav.tsx | 16 +++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss b/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss index ab90890541..f65cda37cc 100644 --- a/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss +++ b/frontend/src/container/GridCardLayout/GridCardLayout.styles.scss @@ -1,8 +1,10 @@ .fullscreen-grid-container { overflow: auto; + margin-top: 1rem; .react-grid-layout { border: none !important; + margin-top: 0; } } @@ -13,3 +15,9 @@ height: calc(100% - 30px); } } + +.lightMode { + .fullscreen-grid-container { + background-color: rgb(250, 250, 250); + } +} diff --git a/frontend/src/container/SideNav/SideNav.tsx b/frontend/src/container/SideNav/SideNav.tsx index 7f29b96c3a..29435a399b 100644 --- a/frontend/src/container/SideNav/SideNav.tsx +++ b/frontend/src/container/SideNav/SideNav.tsx @@ -74,6 +74,8 @@ function SideNav({ isCurrentVersionError, } = useSelector((state) => state.app); + const [licenseTag, setLicenseTag] = useState(''); + const userSettingsMenuItem = { key: ROUTES.MY_SETTINGS, label: user?.name || 'User', @@ -239,6 +241,18 @@ function SideNav({ } }; + useEffect(() => { + if (!isFetching) { + if (isCloudUserVal) { + setLicenseTag('Cloud'); + } else if (isEnterprise) { + setLicenseTag('Enterprise'); + } else { + setLicenseTag('Free'); + } + } + }, [isCloudUserVal, isEnterprise, isFetching]); + return (
@@ -257,7 +271,7 @@ function SideNav({ {!collapsed && ( <> -
{!isEnterprise ? 'Free' : 'Enterprise'}
+ {!isFetching &&
{licenseTag}
} Date: Wed, 31 Jan 2024 15:59:40 +0530 Subject: [PATCH 15/16] fix: update only showTotal property by spreading pagination object (#4467) * fix: update only showTotal property by spreading pagination object --- frontend/src/components/ResizeTable/ResizeTable.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/ResizeTable/ResizeTable.tsx b/frontend/src/components/ResizeTable/ResizeTable.tsx index 9cbaee0346..a7d0396a50 100644 --- a/frontend/src/components/ResizeTable/ResizeTable.tsx +++ b/frontend/src/components/ResizeTable/ResizeTable.tsx @@ -77,6 +77,7 @@ function ResizeTable({ hideOnSinglePage: true, showTotal: (total: number, range: number[]): string => `${range[0]}-${range[1]} of ${total} items`, + ...tableParams.pagination, }; return onDragColumn ? ( From 13ced00a35ea64c6266c339a365dda1ec5abf3f1 Mon Sep 17 00:00:00 2001 From: Prashant Shahi Date: Wed, 31 Jan 2024 17:27:41 +0545 Subject: [PATCH 16/16] =?UTF-8?q?chore(signoz):=20=F0=9F=93=8C=20pin=20ver?= =?UTF-8?q?sions:=20SigNoz=200.38,=20SigNoz=20OtelCollector=200.88.9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Prashant Shahi --- deploy/docker-swarm/clickhouse-setup/docker-compose.yaml | 8 ++++---- deploy/docker/clickhouse-setup/docker-compose-core.yaml | 4 ++-- deploy/docker/clickhouse-setup/docker-compose.yaml | 8 ++++---- go.mod | 2 +- go.sum | 4 ++-- pkg/query-service/tests/test-deploy/docker-compose.yaml | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index 238efaf9bc..c8bf9c9037 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -146,7 +146,7 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.37.1 + image: signoz/query-service:0.38.0 command: [ "-config=/root/config/prometheus.yml", @@ -186,7 +186,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:0.37.1 + image: signoz/frontend:0.38.0 deploy: restart_policy: condition: on-failure @@ -199,7 +199,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/signoz-otel-collector:0.88.8 + image: signoz/signoz-otel-collector:0.88.9 command: [ "--config=/etc/otel-collector-config.yaml", @@ -237,7 +237,7 @@ services: - query-service otel-collector-migrator: - image: signoz/signoz-schema-migrator:0.88.8 + image: signoz/signoz-schema-migrator:0.88.9 deploy: restart_policy: condition: on-failure diff --git a/deploy/docker/clickhouse-setup/docker-compose-core.yaml b/deploy/docker/clickhouse-setup/docker-compose-core.yaml index 866029e73d..ba7058ad80 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-core.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-core.yaml @@ -66,7 +66,7 @@ services: - --storage.path=/data otel-collector-migrator: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.8} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.9} container_name: otel-migrator command: - "--dsn=tcp://clickhouse:9000" @@ -81,7 +81,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` otel-collector: container_name: signoz-otel-collector - image: signoz/signoz-otel-collector:0.88.8 + image: signoz/signoz-otel-collector:0.88.9 command: [ "--config=/etc/otel-collector-config.yaml", diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index d2d6f7d0ef..0e04d69cf8 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -164,7 +164,7 @@ services: # Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md` query-service: - image: signoz/query-service:${DOCKER_TAG:-0.37.1} + image: signoz/query-service:${DOCKER_TAG:-0.38.0} container_name: signoz-query-service command: [ @@ -203,7 +203,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.37.1} + image: signoz/frontend:${DOCKER_TAG:-0.38.0} container_name: signoz-frontend restart: on-failure depends_on: @@ -215,7 +215,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector-migrator: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.8} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.9} container_name: otel-migrator command: - "--dsn=tcp://clickhouse:9000" @@ -229,7 +229,7 @@ services: otel-collector: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.8} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.9} container_name: signoz-otel-collector command: [ diff --git a/go.mod b/go.mod index 23505f3f98..eeb6e05ec9 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21 require ( github.com/ClickHouse/clickhouse-go/v2 v2.15.0 github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb - github.com/SigNoz/signoz-otel-collector v0.88.8 + github.com/SigNoz/signoz-otel-collector v0.88.9 github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 github.com/SigNoz/zap_otlp/zap_otlp_sync v0.0.0-20230822164844-1b861a431974 github.com/antonmedv/expr v1.15.3 diff --git a/go.sum b/go.sum index b7c5de4e09..5d28bc873e 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,8 @@ github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb h1:bneLSKPf9YUSFm github.com/SigNoz/govaluate v0.0.0-20220522085550-d19c08c206cb/go.mod h1:JznGDNg9x1cujDKa22RaQOimOvvEfy3nxzDGd8XDgmA= github.com/SigNoz/prometheus v1.9.78 h1:bB3yuDrRzi/Mv00kWayR9DZbyjTuGfendSqISyDcXiY= github.com/SigNoz/prometheus v1.9.78/go.mod h1:MffmFu2qFILQrOHehx3D0XjYtaZMVfI+Ppeiv98x4Ww= -github.com/SigNoz/signoz-otel-collector v0.88.8 h1:oa/0gSfkGhjzXtz1htzWBQx3p4VhBPs5iwMRxqfa2uo= -github.com/SigNoz/signoz-otel-collector v0.88.8/go.mod h1:7I4FWwraVSnDywsPNbo8TdHDsPxShtYkGU5usr6dTtk= +github.com/SigNoz/signoz-otel-collector v0.88.9 h1:7bbJSXrSZcQsdEVVLsjsNXm/bWe9MhKu8qfXp8MlXQM= +github.com/SigNoz/signoz-otel-collector v0.88.9/go.mod h1:7I4FWwraVSnDywsPNbo8TdHDsPxShtYkGU5usr6dTtk= github.com/SigNoz/zap_otlp v0.1.0 h1:T7rRcFN87GavY8lDGZj0Z3Xv6OhJA6Pj3I9dNPmqvRc= github.com/SigNoz/zap_otlp v0.1.0/go.mod h1:lcHvbDbRgvDnPxo9lDlaL1JK2PyOyouP/C3ynnYIvyo= github.com/SigNoz/zap_otlp/zap_otlp_encoder v0.0.0-20230822164844-1b861a431974 h1:PKVgdf83Yw+lZJbFtNGBgqXiXNf3+kOXW2qZ7Ms7OaY= diff --git a/pkg/query-service/tests/test-deploy/docker-compose.yaml b/pkg/query-service/tests/test-deploy/docker-compose.yaml index 9017f9326e..7ce3107dbd 100644 --- a/pkg/query-service/tests/test-deploy/docker-compose.yaml +++ b/pkg/query-service/tests/test-deploy/docker-compose.yaml @@ -192,7 +192,7 @@ services: <<: *db-depend otel-collector-migrator: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.8} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.9} container_name: otel-migrator command: - "--dsn=tcp://clickhouse:9000" @@ -205,7 +205,7 @@ services: # condition: service_healthy otel-collector: - image: signoz/signoz-otel-collector:0.88.8 + image: signoz/signoz-otel-collector:0.88.9 container_name: signoz-otel-collector command: [