From c4ce057d7ac3501eb0599b281abeb267d62065ed Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Thu, 20 Jul 2023 17:53:55 +0530 Subject: [PATCH 01/21] feat: live tail API with query range support (#3170) * feat: live tail API with query range support * fix: minor fixes * feat: minor fixes * feat: send error event back to client --------- Co-authored-by: Palash Gupta --- .../app/clickhouseReader/options.go | 2 +- .../app/clickhouseReader/reader.go | 43 ++++++++++ pkg/query-service/app/http_handler.go | 79 +++++++++++++++++++ .../app/logs/v3/query_builder.go | 43 ++++++++-- .../app/logs/v3/query_builder_test.go | 50 ++++++++++-- pkg/query-service/app/querier/querier.go | 2 +- .../app/queryBuilder/query_builder.go | 32 +++++++- pkg/query-service/interfaces/interface.go | 1 + pkg/query-service/model/v3/v3.go | 8 ++ 9 files changed, 241 insertions(+), 19 deletions(-) diff --git a/pkg/query-service/app/clickhouseReader/options.go b/pkg/query-service/app/clickhouseReader/options.go index 35e5e1732c..1f45e47115 100644 --- a/pkg/query-service/app/clickhouseReader/options.go +++ b/pkg/query-service/app/clickhouseReader/options.go @@ -36,7 +36,7 @@ const ( defaultLogAttributeKeysTable string = "distributed_logs_attribute_keys" defaultLogResourceKeysTable string = "distributed_logs_resource_keys" defaultLogTagAttributeTable string = "distributed_tag_attributes" - defaultLiveTailRefreshSeconds int = 10 + defaultLiveTailRefreshSeconds int = 5 defaultWriteBatchDelay time.Duration = 5 * time.Second defaultWriteBatchSize int = 10000 defaultEncoding Encoding = EncodingJSON diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index 2bf15b90cd..ac763be193 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -4504,3 +4504,46 @@ func (r *ClickHouseReader) GetSpanAttributeKeys(ctx context.Context) (map[string } return response, nil } + +func (r *ClickHouseReader) LiveTailLogsV3(ctx context.Context, query string, timestampStart uint64, idStart string, client *v3.LogsLiveTailClient) { + if timestampStart == 0 { + timestampStart = uint64(time.Now().UnixNano()) + } + + ticker := time.NewTicker(time.Duration(r.liveTailRefreshSeconds) * time.Second) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + done := true + client.Done <- &done + zap.S().Debug("closing go routine : " + client.Name) + return + case <-ticker.C: + // get the new 100 logs as anything more older won't make sense + tmpQuery := fmt.Sprintf("timestamp >='%d'", timestampStart) + if idStart != "" { + tmpQuery = fmt.Sprintf("%s AND id > '%s'", tmpQuery, idStart) + } + tmpQuery = fmt.Sprintf(query, tmpQuery) + // the reason we are doing desc is that we need the latest logs first + tmpQuery = fmt.Sprintf("%s order by timestamp desc, id desc limit 100", tmpQuery) + + // using the old structure since we can directly read it to the struct as use it. + response := []model.GetLogsResponse{} + err := r.db.Select(ctx, &response, tmpQuery) + if err != nil { + zap.S().Error(err) + client.Error <- err + return + } + for i := len(response) - 1; i >= 0; i-- { + client.Logs <- &response[i] + if i == 0 { + timestampStart = response[i].Timestamp + idStart = response[i].ID + } + } + } + } +} diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index c32ae0d426..e53e9af91a 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -272,6 +272,9 @@ func (aH *APIHandler) RegisterQueryRangeV3Routes(router *mux.Router, am *AuthMid subRouter.HandleFunc("/autocomplete/attribute_values", am.ViewAccess( withCacheControl(AutoCompleteCacheControlAge, aH.autoCompleteAttributeValues))).Methods(http.MethodGet) subRouter.HandleFunc("/query_range", am.ViewAccess(aH.QueryRangeV3)).Methods(http.MethodPost) + + // live logs + subRouter.HandleFunc("/logs/livetail", am.ViewAccess(aH.liveTailLogs)).Methods(http.MethodPost) } func (aH *APIHandler) Respond(w http.ResponseWriter, data interface{}) { @@ -2903,3 +2906,79 @@ func applyMetricLimit(results []*v3.Result, queryRangeParams *v3.QueryRangeParam } } } + +func (aH *APIHandler) liveTailLogs(w http.ResponseWriter, r *http.Request) { + + queryRangeParams, apiErrorObj := ParseQueryRangeParams(r) + if apiErrorObj != nil { + zap.S().Errorf(apiErrorObj.Err.Error()) + RespondError(w, apiErrorObj, nil) + return + } + + var err error + var queryString string + switch queryRangeParams.CompositeQuery.QueryType { + case v3.QueryTypeBuilder: + // check if any enrichment is required for logs if yes then enrich them + if logsv3.EnrichmentRequired(queryRangeParams) { + // get the fields if any logs query is present + var fields map[string]v3.AttributeKey + fields, err = aH.getLogFieldsV3(r.Context(), queryRangeParams) + if err != nil { + apiErrObj := &model.ApiError{Typ: model.ErrorInternal, Err: err} + RespondError(w, apiErrObj, nil) + return + } + logsv3.Enrich(queryRangeParams, fields) + } + + queryString, err = aH.queryBuilder.PrepareLiveTailQuery(queryRangeParams) + if err != nil { + RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + + default: + err = fmt.Errorf("invalid query type") + RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil) + return + } + + // create the client + client := &v3.LogsLiveTailClient{Name: r.RemoteAddr, Logs: make(chan *model.GetLogsResponse, 1000), Done: make(chan *bool), Error: make(chan error)} + go aH.reader.LiveTailLogsV3(r.Context(), queryString, uint64(queryRangeParams.Start), "", client) + + w.Header().Set("Connection", "keep-alive") + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.WriteHeader(200) + + flusher, ok := w.(http.Flusher) + if !ok { + err := model.ApiError{Typ: model.ErrorStreamingNotSupported, Err: nil} + RespondError(w, &err, "streaming is not supported") + return + } + // flush the headers + flusher.Flush() + for { + select { + case log := <-client.Logs: + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + enc.Encode(log) + fmt.Fprintf(w, "event: log\ndata: %v\n\n", buf.String()) + flusher.Flush() + case <-client.Done: + zap.S().Debug("done!") + return + case err := <-client.Error: + zap.S().Error("error occured!", err) + fmt.Fprintf(w, "event: error\ndata: %v\n\n", err.Error()) + flusher.Flush() + return + } + } +} diff --git a/pkg/query-service/app/logs/v3/query_builder.go b/pkg/query-service/app/logs/v3/query_builder.go index 79c92e3810..b592f684e9 100644 --- a/pkg/query-service/app/logs/v3/query_builder.go +++ b/pkg/query-service/app/logs/v3/query_builder.go @@ -289,6 +289,27 @@ func buildLogsQuery(panelType v3.PanelType, start, end, step int64, mq *v3.Build } } +func buildLogsLiveTailQuery(mq *v3.BuilderQuery) (string, error) { + filterSubQuery, err := buildLogsTimeSeriesFilterQuery(mq.Filters, mq.GroupBy) + if err != nil { + return "", err + } + + switch mq.AggregateOperator { + case v3.AggregateOperatorNoOp: + queryTmpl := constants.LogsSQLSelect + "from signoz_logs.distributed_logs where %s" + if len(filterSubQuery) == 0 { + filterSubQuery = "%s" + } else { + filterSubQuery = "%s " + filterSubQuery + } + query := fmt.Sprintf(queryTmpl, filterSubQuery) + return query, nil + default: + return "", fmt.Errorf("unsupported aggregate operator in live tail") + } +} + // groupBy returns a string of comma separated tags for group by clause // `ts` is always added to the group by clause func groupBy(panelType v3.PanelType, graphLimitQtype string, tags ...string) string { @@ -384,26 +405,36 @@ func addOffsetToQuery(query string, offset uint64) string { return fmt.Sprintf("%s OFFSET %d", query, offset) } -func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.PanelType, mq *v3.BuilderQuery, graphLimitQtype string) (string, error) { +type Options struct { + GraphLimitQtype string + IsLivetailQuery bool +} - if graphLimitQtype == constants.FirstQueryGraphLimit { +func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.PanelType, mq *v3.BuilderQuery, options Options) (string, error) { + if options.IsLivetailQuery { + query, err := buildLogsLiveTailQuery(mq) + if err != nil { + return "", err + } + return query, nil + } else if options.GraphLimitQtype == constants.FirstQueryGraphLimit { // give me just the groupby names - query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, graphLimitQtype) + query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype) if err != nil { return "", err } query = addLimitToQuery(query, mq.Limit) return query, nil - } else if graphLimitQtype == constants.SecondQueryGraphLimit { - query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, graphLimitQtype) + } else if options.GraphLimitQtype == constants.SecondQueryGraphLimit { + query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype) if err != nil { return "", err } return query, nil } - query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, graphLimitQtype) + query, err := buildLogsQuery(panelType, start, end, mq.StepInterval, mq, options.GraphLimitQtype) if err != nil { return "", err } diff --git a/pkg/query-service/app/logs/v3/query_builder_test.go b/pkg/query-service/app/logs/v3/query_builder_test.go index 0253a0b21f..6c47c5421d 100644 --- a/pkg/query-service/app/logs/v3/query_builder_test.go +++ b/pkg/query-service/app/logs/v3/query_builder_test.go @@ -989,7 +989,7 @@ var testPrepLogsQueryData = []struct { TableName string AggregateOperator v3.AggregateOperator ExpectedQuery string - Type string + Options Options }{ { Name: "Test TS with limit- first", @@ -1011,7 +1011,7 @@ var testPrepLogsQueryData = []struct { }, TableName: "logs", ExpectedQuery: "SELECT method from (SELECT attributes_string_value[indexOf(attributes_string_key, 'method')] as method, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND indexOf(attributes_string_key, 'method') > 0 group by method order by value DESC) LIMIT 10", - Type: constants.FirstQueryGraphLimit, + Options: Options{GraphLimitQtype: constants.FirstQueryGraphLimit}, }, { Name: "Test TS with limit- first - with order by value", @@ -1034,7 +1034,7 @@ var testPrepLogsQueryData = []struct { }, TableName: "logs", ExpectedQuery: "SELECT method from (SELECT attributes_string_value[indexOf(attributes_string_key, 'method')] as method, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND indexOf(attributes_string_key, 'method') > 0 group by method order by value ASC) LIMIT 10", - Type: constants.FirstQueryGraphLimit, + Options: Options{GraphLimitQtype: constants.FirstQueryGraphLimit}, }, { Name: "Test TS with limit- first - with order by attribute", @@ -1057,7 +1057,7 @@ var testPrepLogsQueryData = []struct { }, TableName: "logs", ExpectedQuery: "SELECT method from (SELECT attributes_string_value[indexOf(attributes_string_key, 'method')] as method, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND indexOf(attributes_string_key, 'method') > 0 group by method order by method ASC) LIMIT 10", - Type: constants.FirstQueryGraphLimit, + Options: Options{GraphLimitQtype: constants.FirstQueryGraphLimit}, }, { Name: "Test TS with limit- second", @@ -1079,7 +1079,7 @@ var testPrepLogsQueryData = []struct { }, TableName: "logs", ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 0 SECOND) AS ts, attributes_string_value[indexOf(attributes_string_key, 'method')] as method, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND indexOf(attributes_string_key, 'method') > 0 AND (method) IN (%s) group by method,ts order by value DESC", - Type: constants.SecondQueryGraphLimit, + Options: Options{GraphLimitQtype: constants.SecondQueryGraphLimit}, }, { Name: "Test TS with limit- second - with order by", @@ -1102,14 +1102,50 @@ var testPrepLogsQueryData = []struct { }, TableName: "logs", ExpectedQuery: "SELECT toStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 0 SECOND) AS ts, attributes_string_value[indexOf(attributes_string_key, 'method')] as method, toFloat64(count(distinct(attributes_string_value[indexOf(attributes_string_key, 'name')]))) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET' AND indexOf(attributes_string_key, 'method') > 0 AND (method) IN (%s) group by method,ts order by method ASC", - Type: constants.SecondQueryGraphLimit, + Options: Options{GraphLimitQtype: constants.SecondQueryGraphLimit}, + }, + // Live tail + { + Name: "Live Tail Query", + PanelType: v3.PanelTypeList, + Start: 1680066360726210000, + End: 1680066458000000000, + Step: 60, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateOperator: v3.AggregateOperatorNoOp, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{ + {Key: v3.AttributeKey{Key: "method", DataType: v3.AttributeKeyDataTypeString, Type: v3.AttributeKeyTypeTag}, Value: "GET", Operator: "="}, + }, + }, + }, + TableName: "logs", + ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where %s AND attributes_string_value[indexOf(attributes_string_key, 'method')] = 'GET'", + Options: Options{IsLivetailQuery: true}, + }, + { + Name: "Live Tail Query W/O filter", + PanelType: v3.PanelTypeList, + Start: 1680066360726210000, + End: 1680066458000000000, + Step: 60, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateOperator: v3.AggregateOperatorNoOp, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}, + }, + TableName: "logs", + ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where %s", + Options: Options{IsLivetailQuery: true}, }, } func TestPrepareLogsQuery(t *testing.T) { for _, tt := range testPrepLogsQueryData { Convey("TestBuildLogsQuery", t, func() { - query, err := PrepareLogsQuery(tt.Start, tt.End, "", tt.PanelType, tt.BuilderQuery, tt.Type) + query, err := PrepareLogsQuery(tt.Start, tt.End, "", tt.PanelType, tt.BuilderQuery, tt.Options) So(err, ShouldBeNil) So(query, ShouldEqual, tt.ExpectedQuery) diff --git a/pkg/query-service/app/querier/querier.go b/pkg/query-service/app/querier/querier.go index 7284bddc9f..ea77fca4f3 100644 --- a/pkg/query-service/app/querier/querier.go +++ b/pkg/query-service/app/querier/querier.go @@ -235,7 +235,7 @@ func (q *querier) runBuilderQueries(ctx context.Context, params *v3.QueryRangePa // TODO: add support for logs and traces if builderQuery.DataSource == v3.DataSourceLogs { - query, err := logsV3.PrepareLogsQuery(params.Start, params.End, params.CompositeQuery.QueryType, params.CompositeQuery.PanelType, builderQuery, "") + query, err := logsV3.PrepareLogsQuery(params.Start, params.End, params.CompositeQuery.QueryType, params.CompositeQuery.PanelType, builderQuery, logsV3.Options{}) if err != nil { errQueriesByName[queryName] = err.Error() continue diff --git a/pkg/query-service/app/queryBuilder/query_builder.go b/pkg/query-service/app/queryBuilder/query_builder.go index c3f8b0d4d0..fb6c8211bc 100644 --- a/pkg/query-service/app/queryBuilder/query_builder.go +++ b/pkg/query-service/app/queryBuilder/query_builder.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/SigNoz/govaluate" + logsV3 "go.signoz.io/signoz/pkg/query-service/app/logs/v3" "go.signoz.io/signoz/pkg/query-service/cache" "go.signoz.io/signoz/pkg/query-service/constants" v3 "go.signoz.io/signoz/pkg/query-service/model/v3" @@ -40,7 +41,7 @@ var SupportedFunctions = []string{ var EvalFuncs = map[string]govaluate.ExpressionFunction{} type prepareTracesQueryFunc func(start, end int64, panelType v3.PanelType, bq *v3.BuilderQuery, keys map[string]v3.AttributeKey, graphLimitQtype string) (string, error) -type prepareLogsQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery, graphLimitQtype string) (string, error) +type prepareLogsQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery, options logsV3.Options) (string, error) type prepareMetricQueryFunc func(start, end int64, queryType v3.QueryType, panelType v3.PanelType, bq *v3.BuilderQuery) (string, error) type QueryBuilder struct { @@ -131,6 +132,29 @@ func expressionToQuery(qp *v3.QueryRangeParamsV3, varToQuery map[string]string, return formulaQuery, nil } +func (qb *QueryBuilder) PrepareLiveTailQuery(params *v3.QueryRangeParamsV3) (string, error) { + var queryStr string + var err error + compositeQuery := params.CompositeQuery + + if compositeQuery != nil { + // There can only be a signle query and there is no concept of disabling queries + if len(compositeQuery.BuilderQueries) != 1 { + return "", fmt.Errorf("live tail is only supported for single query") + } + for queryName, query := range compositeQuery.BuilderQueries { + if query.Expression == queryName { + queryStr, err = qb.options.BuildLogQuery(params.Start, params.End, compositeQuery.QueryType, compositeQuery.PanelType, query, logsV3.Options{IsLivetailQuery: true}) + if err != nil { + return "", err + } + } + } + + } + return queryStr, nil +} + func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3, args ...interface{}) (map[string]string, error) { queries := make(map[string]string) @@ -169,18 +193,18 @@ func (qb *QueryBuilder) PrepareQueries(params *v3.QueryRangeParamsV3, args ...in case v3.DataSourceLogs: // for ts query with limit replace it as it is already formed if compositeQuery.PanelType == v3.PanelTypeGraph && query.Limit > 0 && len(query.GroupBy) > 0 { - limitQuery, err := qb.options.BuildLogQuery(params.Start, params.End, compositeQuery.QueryType, compositeQuery.PanelType, query, constants.FirstQueryGraphLimit) + limitQuery, err := qb.options.BuildLogQuery(params.Start, params.End, compositeQuery.QueryType, compositeQuery.PanelType, query, logsV3.Options{GraphLimitQtype: constants.FirstQueryGraphLimit}) if err != nil { return nil, err } - placeholderQuery, err := qb.options.BuildLogQuery(params.Start, params.End, compositeQuery.QueryType, compositeQuery.PanelType, query, constants.SecondQueryGraphLimit) + placeholderQuery, err := qb.options.BuildLogQuery(params.Start, params.End, compositeQuery.QueryType, compositeQuery.PanelType, query, logsV3.Options{GraphLimitQtype: constants.SecondQueryGraphLimit}) if err != nil { return nil, err } query := fmt.Sprintf(placeholderQuery, limitQuery) queries[queryName] = query } else { - queryString, err := qb.options.BuildLogQuery(params.Start, params.End, compositeQuery.QueryType, compositeQuery.PanelType, query, "") + queryString, err := qb.options.BuildLogQuery(params.Start, params.End, compositeQuery.QueryType, compositeQuery.PanelType, query, logsV3.Options{}) if err != nil { return nil, err } diff --git a/pkg/query-service/interfaces/interface.go b/pkg/query-service/interfaces/interface.go index 638de6e4d8..f53a3a1fe6 100644 --- a/pkg/query-service/interfaces/interface.go +++ b/pkg/query-service/interfaces/interface.go @@ -69,6 +69,7 @@ type Reader interface { // QB V3 metrics/traces/logs GetTimeSeriesResultV3(ctx context.Context, query string) ([]*v3.Series, error) GetListResultV3(ctx context.Context, query string) ([]*v3.Row, error) + LiveTailLogsV3(ctx context.Context, query string, timestampStart uint64, idStart string, client *v3.LogsLiveTailClient) GetTotalSpans(ctx context.Context) (uint64, error) GetSpansInLastHeartBeatInterval(ctx context.Context) (uint64, error) diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index 7647e5a75d..d1dab19c0d 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -8,6 +8,7 @@ import ( "time" "github.com/google/uuid" + "go.signoz.io/signoz/pkg/query-service/model" ) type DataSource string @@ -584,6 +585,13 @@ type Result struct { List []*Row `json:"list"` } +type LogsLiveTailClient struct { + Name string + Logs chan *model.GetLogsResponse + Done chan *bool + Error chan error +} + type Series struct { Labels map[string]string `json:"labels"` Points []Point `json:"values"` From ace2d8a3b35c0c7e2cf90212fb52f170ea67ab80 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 21 Jul 2023 16:17:31 +0530 Subject: [PATCH 02/21] Failing query range request due to top level operation dependency (#3182) * fix: dependency check for top level operation * refactor: one on one mapping * fix: removed the unwanted if checks --------- Co-authored-by: Palash Gupta --- frontend/src/container/GridGraphLayout/Graph/index.tsx | 7 +++++-- .../MetricsApplication/Tabs/Overview/ServiceOverview.tsx | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/container/GridGraphLayout/Graph/index.tsx b/frontend/src/container/GridGraphLayout/Graph/index.tsx index c0f6e9a5a9..401530329c 100644 --- a/frontend/src/container/GridGraphLayout/Graph/index.tsx +++ b/frontend/src/container/GridGraphLayout/Graph/index.tsx @@ -60,6 +60,7 @@ function GridCardGraph({ allowDelete, allowClone, allowEdit, + isQueryEnabled, }: GridCardGraphProps): JSX.Element { const { ref: graphRef, inView: isGraphVisible } = useInView({ threshold: 0, @@ -115,7 +116,7 @@ function GridCardGraph({ variables, ], keepPreviousData: true, - enabled: isGraphVisible && !isEmptyWidget, + enabled: isGraphVisible && !isEmptyWidget && isQueryEnabled, refetchOnMount: false, onError: (error) => { setErrorMessage(error.message); @@ -287,7 +288,7 @@ function GridCardGraph({ ); } - if (prevChartDataSetRef?.labels === undefined && queryResponse.isLoading) { + if (queryResponse.status === 'loading' || queryResponse.status === 'idle') { return ( {!isEmpty(widget) && prevChartDataSetRef?.labels ? ( @@ -399,6 +400,7 @@ interface GridCardGraphProps extends DispatchProps { allowDelete?: boolean; allowClone?: boolean; allowEdit?: boolean; + isQueryEnabled?: boolean; } GridCardGraph.defaultProps = { @@ -407,6 +409,7 @@ GridCardGraph.defaultProps = { allowDelete: true, allowClone: true, allowEdit: true, + isQueryEnabled: true, }; const mapDispatchToProps = ( diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx index 3b1ad2b7d9..4fb5d4f024 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx @@ -49,6 +49,8 @@ function ServiceOverview({ [servicename, tagFilterItems, isSpanMetricEnable, topLevelOperationsRoute], ); + const isQueryEnabled = topLevelOperationsRoute.length > 0; + return ( <> - - - ), - [], - ); - - type DataIndex = keyof ServicesList; - - const getColumnSearchProps = useCallback( - (dataIndex: DataIndex): ColumnType => ({ - filterDropdown, - filterIcon: FilterIcon, - onFilter: (value: string | number | boolean, record: DataProps): boolean => - record[dataIndex] - .toString() - .toLowerCase() - .includes(value.toString().toLowerCase()), - render: (metrics: string): JSX.Element => { - const urlParams = new URLSearchParams(search); - const avialableParams = routeConfig[ROUTES.SERVICE_METRICS]; - const queryString = getQueryString(avialableParams, urlParams); - - return ( - - {metrics} - - ); - }, - }), - [filterDropdown, FilterIcon, search], - ); - - const columns: ColumnsType = useMemo( - () => [ - { - title: 'Application', - dataIndex: 'serviceName', - width: 200, - key: 'serviceName', - ...getColumnSearchProps('serviceName'), - }, - { - title: 'P99 latency (in ms)', - dataIndex: 'p99', - key: 'p99', - width: 150, - defaultSortOrder: 'descend', - sorter: (a: DataProps, b: DataProps): number => a.p99 - b.p99, - render: (value: number): string => (value / 1000000).toFixed(2), - }, - { - title: 'Error Rate (% of total)', - dataIndex: 'errorRate', - key: 'errorRate', - width: 150, - sorter: (a: DataProps, b: DataProps): number => a.errorRate - b.errorRate, - render: (value: number): string => value.toFixed(2), - }, - { - title: 'Operations Per Second', - dataIndex: 'callRate', - key: 'callRate', - width: 150, - sorter: (a: DataProps, b: DataProps): number => a.callRate - b.callRate, - render: (value: number): string => value.toFixed(2), - }, - ], - [getColumnSearchProps], - ); - - if ( - services.length === 0 && - loading === false && - !skipOnboarding && - error === true - ) { - return ; - } - - return ( - - - - ); -} - -type DataProps = ServicesList; - -export default Metrics; diff --git a/frontend/src/container/ServiceTable/Columns/ColumnContants.ts b/frontend/src/container/ServiceTable/Columns/ColumnContants.ts new file mode 100644 index 0000000000..69e96a1ae3 --- /dev/null +++ b/frontend/src/container/ServiceTable/Columns/ColumnContants.ts @@ -0,0 +1,26 @@ +export enum ColumnKey { + Application = 'serviceName', + P99 = 'p99', + ErrorRate = 'errorRate', + Operations = 'callRate', +} + +export const ColumnTitle: { + [key in ColumnKey]: string; +} = { + [ColumnKey.Application]: 'Application', + [ColumnKey.P99]: 'P99 latency (in ms)', + [ColumnKey.ErrorRate]: 'Error Rate (% of total)', + [ColumnKey.Operations]: 'Operations Per Second', +}; + +export enum ColumnWidth { + Application = 200, + P99 = 150, + ErrorRate = 150, + Operations = 150, +} + +export const SORTING_ORDER = 'descend'; + +export const SEARCH_PLACEHOLDER = 'Search by service'; diff --git a/frontend/src/container/ServiceTable/Columns/GetColumnSearchProps.tsx b/frontend/src/container/ServiceTable/Columns/GetColumnSearchProps.tsx new file mode 100644 index 0000000000..4257dc57ec --- /dev/null +++ b/frontend/src/container/ServiceTable/Columns/GetColumnSearchProps.tsx @@ -0,0 +1,34 @@ +import { SearchOutlined } from '@ant-design/icons'; +import type { ColumnType } from 'antd/es/table'; +import ROUTES from 'constants/routes'; +import { routeConfig } from 'container/SideNav/config'; +import { getQueryString } from 'container/SideNav/helper'; +import { Link } from 'react-router-dom'; +import { ServicesList } from 'types/api/metrics/getService'; + +import { filterDropdown } from '../Filter/FilterDropdown'; +import { Name } from '../styles'; + +export const getColumnSearchProps = ( + dataIndex: keyof ServicesList, + search: string, +): ColumnType => ({ + filterDropdown, + filterIcon: , + onFilter: (value: string | number | boolean, record: ServicesList): boolean => + record[dataIndex] + .toString() + .toLowerCase() + .includes(value.toString().toLowerCase()), + render: (metrics: string): JSX.Element => { + const urlParams = new URLSearchParams(search); + const avialableParams = routeConfig[ROUTES.SERVICE_METRICS]; + const queryString = getQueryString(avialableParams, urlParams); + + return ( + + {metrics} + + ); + }, +}); diff --git a/frontend/src/container/ServiceTable/Columns/ServiceColumn.ts b/frontend/src/container/ServiceTable/Columns/ServiceColumn.ts new file mode 100644 index 0000000000..f613a9dab4 --- /dev/null +++ b/frontend/src/container/ServiceTable/Columns/ServiceColumn.ts @@ -0,0 +1,46 @@ +import type { ColumnsType } from 'antd/es/table'; +import { ServicesList } from 'types/api/metrics/getService'; + +import { + ColumnKey, + ColumnTitle, + ColumnWidth, + SORTING_ORDER, +} from './ColumnContants'; +import { getColumnSearchProps } from './GetColumnSearchProps'; + +export const getColumns = (search: string): ColumnsType => [ + { + title: ColumnTitle[ColumnKey.Application], + dataIndex: ColumnKey.Application, + width: ColumnWidth.Application, + key: ColumnKey.Application, + ...getColumnSearchProps('serviceName', search), + }, + { + title: ColumnTitle[ColumnKey.P99], + dataIndex: ColumnKey.P99, + key: ColumnKey.P99, + width: ColumnWidth.P99, + defaultSortOrder: SORTING_ORDER, + sorter: (a: ServicesList, b: ServicesList): number => a.p99 - b.p99, + render: (value: number): string => (value / 1000000).toFixed(2), + }, + { + title: ColumnTitle[ColumnKey.ErrorRate], + dataIndex: ColumnKey.ErrorRate, + key: ColumnKey.ErrorRate, + width: 150, + sorter: (a: ServicesList, b: ServicesList): number => + a.errorRate - b.errorRate, + render: (value: number): string => value.toFixed(2), + }, + { + title: ColumnTitle[ColumnKey.Operations], + dataIndex: ColumnKey.Operations, + key: ColumnKey.Operations, + width: ColumnWidth.Operations, + sorter: (a: ServicesList, b: ServicesList): number => a.callRate - b.callRate, + render: (value: number): string => value.toFixed(2), + }, +]; diff --git a/frontend/src/container/ServiceTable/Filter/FilterDropdown.tsx b/frontend/src/container/ServiceTable/Filter/FilterDropdown.tsx new file mode 100644 index 0000000000..1dc4a12d89 --- /dev/null +++ b/frontend/src/container/ServiceTable/Filter/FilterDropdown.tsx @@ -0,0 +1,41 @@ +import { SearchOutlined } from '@ant-design/icons'; +import { Button, Card, Input, Space } from 'antd'; +import type { FilterDropdownProps } from 'antd/es/table/interface'; + +import { SEARCH_PLACEHOLDER } from '../Columns/ColumnContants'; + +export const filterDropdown = ({ + setSelectedKeys, + selectedKeys, + confirm, +}: FilterDropdownProps): JSX.Element => { + const handleSearch = (): void => { + confirm(); + }; + + const selectedKeysHandler = (e: React.ChangeEvent): void => { + setSelectedKeys(e.target.value ? [e.target.value] : []); + }; + + return ( + + + + + + + ); +}; diff --git a/frontend/src/container/ServiceTable/Service.test.tsx b/frontend/src/container/ServiceTable/Service.test.tsx new file mode 100644 index 0000000000..4fc9231a78 --- /dev/null +++ b/frontend/src/container/ServiceTable/Service.test.tsx @@ -0,0 +1,50 @@ +import { render, screen, waitFor } from '@testing-library/react'; +import ROUTES from 'constants/routes'; +import { BrowserRouter } from 'react-router-dom'; + +import { Services } from './__mock__/servicesListMock'; +import Metrics from './index'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useLocation: (): { pathname: string } => ({ + pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.APPLICATION}/`, + }), +})); + +describe('Metrics Component', () => { + it('renders without errors', async () => { + render( + + + , + ); + + await waitFor(() => { + expect(screen.getByText(/application/i)).toBeInTheDocument(); + expect(screen.getByText(/p99 latency \(in ms\)/i)).toBeInTheDocument(); + expect(screen.getByText(/error rate \(% of total\)/i)).toBeInTheDocument(); + expect(screen.getByText(/operations per second/i)).toBeInTheDocument(); + }); + }); + + it('renders if the data is loaded in the table', async () => { + render( + + + , + ); + + expect(screen.getByText('frontend')).toBeInTheDocument(); + }); + + it('renders no data when required conditions are met', async () => { + render( + + + , + ); + + expect(screen.getByText('No data')).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx b/frontend/src/container/ServiceTable/SkipOnBoardModal/index.tsx similarity index 100% rename from frontend/src/container/MetricsTable/SkipOnBoardModal/index.tsx rename to frontend/src/container/ServiceTable/SkipOnBoardModal/index.tsx diff --git a/frontend/src/container/ServiceTable/__mock__/servicesListMock.ts b/frontend/src/container/ServiceTable/__mock__/servicesListMock.ts new file mode 100644 index 0000000000..283ba7718b --- /dev/null +++ b/frontend/src/container/ServiceTable/__mock__/servicesListMock.ts @@ -0,0 +1,22 @@ +import { ServicesList } from 'types/api/metrics/getService'; + +export const Services: ServicesList[] = [ + { + serviceName: 'frontend', + p99: 1261498140, + avgDuration: 768497850.9803921, + numCalls: 255, + callRate: 0.9444444444444444, + numErrors: 0, + errorRate: 0, + }, + { + serviceName: 'customer', + p99: 890150740.0000001, + avgDuration: 369612035.2941176, + numCalls: 255, + callRate: 0.9444444444444444, + numErrors: 0, + errorRate: 0, + }, +]; diff --git a/frontend/src/container/ServiceTable/index.tsx b/frontend/src/container/ServiceTable/index.tsx new file mode 100644 index 0000000000..6c9fa1aede --- /dev/null +++ b/frontend/src/container/ServiceTable/index.tsx @@ -0,0 +1,26 @@ +import { ResizeTable } from 'components/ResizeTable'; +import { useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; + +import { getColumns } from './Columns/ServiceColumn'; +import { Container } from './styles'; +import ServiceTableProp from './types'; + +function Services({ services, isLoading }: ServiceTableProp): JSX.Element { + const { search } = useLocation(); + + const tableColumns = useMemo(() => getColumns(search), [search]); + + return ( + + + + ); +} + +export default Services; diff --git a/frontend/src/container/MetricsTable/styles.ts b/frontend/src/container/ServiceTable/styles.ts similarity index 100% rename from frontend/src/container/MetricsTable/styles.ts rename to frontend/src/container/ServiceTable/styles.ts diff --git a/frontend/src/container/ServiceTable/types.ts b/frontend/src/container/ServiceTable/types.ts new file mode 100644 index 0000000000..7118bfa2fe --- /dev/null +++ b/frontend/src/container/ServiceTable/types.ts @@ -0,0 +1,6 @@ +import { ServicesList } from 'types/api/metrics/getService'; + +export default interface ServiceTableProp { + services: ServicesList[]; + isLoading: boolean; +} diff --git a/frontend/src/hooks/useErrorNotification.ts b/frontend/src/hooks/useErrorNotification.ts new file mode 100644 index 0000000000..b2961f66cc --- /dev/null +++ b/frontend/src/hooks/useErrorNotification.ts @@ -0,0 +1,17 @@ +import { AxiosError } from 'axios'; +import { useEffect } from 'react'; + +import { useNotifications } from './useNotifications'; + +const useErrorNotification = (error: AxiosError | null): void => { + const { notifications } = useNotifications(); + useEffect(() => { + if (error) { + notifications.error({ + message: error.message, + }); + } + }, [error, notifications]); +}; + +export default useErrorNotification; diff --git a/frontend/src/hooks/useQueryService.ts b/frontend/src/hooks/useQueryService.ts new file mode 100644 index 0000000000..0307460ccc --- /dev/null +++ b/frontend/src/hooks/useQueryService.ts @@ -0,0 +1,29 @@ +import getService from 'api/metrics/getService'; +import { AxiosError } from 'axios'; +import { Time } from 'container/TopNav/DateTimeSelection/config'; +import { useQuery, UseQueryResult } from 'react-query'; +import { PayloadProps } from 'types/api/metrics/getService'; +import { Tags } from 'types/reducer/trace'; + +export const useQueryService = ({ + minTime, + maxTime, + selectedTime, + selectedTags, +}: UseQueryServiceProps): UseQueryResult => { + const queryKey = [minTime, maxTime, selectedTime, selectedTags]; + return useQuery(queryKey, () => + getService({ + end: maxTime, + start: minTime, + selectedTags, + }), + ); +}; + +interface UseQueryServiceProps { + minTime: number; + maxTime: number; + selectedTime: Time; + selectedTags: Tags[]; +} diff --git a/frontend/src/pages/Metrics/index.tsx b/frontend/src/pages/Metrics/index.tsx deleted file mode 100644 index 1d83d28b33..0000000000 --- a/frontend/src/pages/Metrics/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Space } from 'antd'; -import getLocalStorageKey from 'api/browser/localstorage/get'; -import ReleaseNote from 'components/ReleaseNote'; -import Spinner from 'components/Spinner'; -import { SKIP_ONBOARDING } from 'constants/onboarding'; -import MetricTable from 'container/MetricsTable'; -import ResourceAttributesFilter from 'container/ResourceAttributesFilter'; -import { useNotifications } from 'hooks/useNotifications'; -import useResourceAttribute from 'hooks/useResourceAttribute'; -import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; -import { useEffect, useMemo } from 'react'; -import { connect, useSelector } from 'react-redux'; -import { useLocation } from 'react-router-dom'; -import { bindActionCreators, Dispatch } from 'redux'; -import { ThunkDispatch } from 'redux-thunk'; -import { GetService, GetServiceProps } from 'store/actions/metrics'; -import { AppState } from 'store/reducers'; -import AppActions from 'types/actions'; -import { GlobalReducer } from 'types/reducer/globalTime'; -import MetricReducer from 'types/reducer/metrics'; -import { Tags } from 'types/reducer/trace'; - -function Metrics({ getService }: MetricsProps): JSX.Element { - const { minTime, maxTime, loading, selectedTime } = useSelector< - AppState, - GlobalReducer - >((state) => state.globalTime); - const location = useLocation(); - const { services, error, errorMessage } = useSelector( - (state) => state.metrics, - ); - const { notifications } = useNotifications(); - - useEffect(() => { - if (error) { - notifications.error({ - message: errorMessage, - }); - } - }, [error, errorMessage, notifications]); - - const { queries } = useResourceAttribute(); - - const selectedTags = useMemo( - () => (convertRawQueriesToTraceSelectedTags(queries, '') as Tags[]) || [], - [queries], - ); - - const isSkipped = getLocalStorageKey(SKIP_ONBOARDING) === 'true'; - - useEffect(() => { - if (loading === false) { - getService({ - maxTime, - minTime, - selectedTags, - }); - } - }, [getService, loading, maxTime, minTime, selectedTags]); - - useEffect(() => { - let timeInterval: NodeJS.Timeout; - - if (loading === false && !isSkipped && services.length === 0) { - timeInterval = setInterval(() => { - getService({ - maxTime, - minTime, - selectedTags, - }); - }, 50000); - } - - return (): void => { - clearInterval(timeInterval); - }; - }, [ - getService, - isSkipped, - loading, - maxTime, - minTime, - services, - selectedTime, - selectedTags, - ]); - - if (loading) { - return ; - } - - return ( - - - - - - - ); -} - -interface DispatchProps { - getService: ( - props: GetServiceProps, - ) => (dispatch: Dispatch, getState: () => AppState) => void; -} - -const mapDispatchToProps = ( - dispatch: ThunkDispatch, -): DispatchProps => ({ - getService: bindActionCreators(GetService, dispatch), -}); - -type MetricsProps = DispatchProps; - -export default connect(null, mapDispatchToProps)(Metrics); diff --git a/frontend/src/pages/Services/index.tsx b/frontend/src/pages/Services/index.tsx new file mode 100644 index 0000000000..87c0720efb --- /dev/null +++ b/frontend/src/pages/Services/index.tsx @@ -0,0 +1,70 @@ +import { Space } from 'antd'; +import localStorageGet from 'api/browser/localstorage/get'; +import localStorageSet from 'api/browser/localstorage/set'; +import ReleaseNote from 'components/ReleaseNote'; +import { SKIP_ONBOARDING } from 'constants/onboarding'; +import ResourceAttributesFilter from 'container/ResourceAttributesFilter'; +import ServicesTable from 'container/ServiceTable'; +import SkipOnBoardingModal from 'container/ServiceTable/SkipOnBoardModal'; +import useErrorNotification from 'hooks/useErrorNotification'; +import { useQueryService } from 'hooks/useQueryService'; +import useResourceAttribute from 'hooks/useResourceAttribute'; +import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; +import { useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; +import { AppState } from 'store/reducers'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { Tags } from 'types/reducer/trace'; + +function Metrics(): JSX.Element { + const { minTime, maxTime, selectedTime } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + + const location = useLocation(); + const { queries } = useResourceAttribute(); + const [skipOnboarding, setSkipOnboarding] = useState( + localStorageGet(SKIP_ONBOARDING) === 'true', + ); + + const onContinueClick = (): void => { + localStorageSet(SKIP_ONBOARDING, 'true'); + setSkipOnboarding(true); + }; + + const selectedTags = useMemo( + () => (convertRawQueriesToTraceSelectedTags(queries, '') as Tags[]) || [], + [queries], + ); + + const { data, error, isLoading, isError } = useQueryService({ + minTime, + maxTime, + selectedTime, + selectedTags, + }); + + useErrorNotification(error); + + if ( + data?.length === 0 && + isLoading === false && + !skipOnboarding && + isError === true + ) { + return ; + } + + return ( + + + + + + + ); +} + +export default Metrics; diff --git a/frontend/src/store/actions/metrics/getService.ts b/frontend/src/store/actions/metrics/getService.ts index 90d65c0c43..8de8f3c134 100644 --- a/frontend/src/store/actions/metrics/getService.ts +++ b/frontend/src/store/actions/metrics/getService.ts @@ -1,5 +1,6 @@ import getService from 'api/metrics/getService'; import { AxiosError } from 'axios'; +import { SOMETHING_WENT_WRONG } from 'constants/api'; import GetMinMax from 'lib/getMinMax'; import { Dispatch } from 'redux'; import { AppState } from 'store/reducers'; @@ -38,16 +39,16 @@ export const GetService = ( selectedTags: props.selectedTags, }); - if (response.statusCode === 200) { + if (response.length > 0) { dispatch({ type: 'GET_SERVICE_LIST_SUCCESS', - payload: response.payload, + payload: response, }); } else { dispatch({ type: 'GET_SERVICE_LIST_ERROR', payload: { - errorMessage: response.error || 'Something went wrong', + errorMessage: SOMETHING_WENT_WRONG, }, }); } @@ -55,7 +56,7 @@ export const GetService = ( dispatch({ type: 'GET_SERVICE_LIST_ERROR', payload: { - errorMessage: (error as AxiosError).toString() || 'Something went wrong', + errorMessage: (error as AxiosError).toString() || SOMETHING_WENT_WRONG, }, }); } From cac637ac88a2672c2df647c8a1c7308cbe90daf5 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Wed, 26 Jul 2023 12:27:46 +0530 Subject: [PATCH 08/21] feat: add support for apdex settings (#3186) --- pkg/query-service/app/apdex.go | 46 ++++++++++++ .../app/clickhouseReader/reader.go | 47 ++++++++++++ pkg/query-service/app/http_handler.go | 4 + pkg/query-service/app/parser.go | 8 ++ pkg/query-service/dao/interface.go | 4 + pkg/query-service/dao/sqlite/apdex.go | 74 +++++++++++++++++++ pkg/query-service/dao/sqlite/connection.go | 5 ++ pkg/query-service/interfaces/interface.go | 2 + pkg/query-service/model/db.go | 6 ++ pkg/query-service/model/v3/v3.go | 5 ++ 10 files changed, 201 insertions(+) create mode 100644 pkg/query-service/app/apdex.go create mode 100644 pkg/query-service/dao/sqlite/apdex.go diff --git a/pkg/query-service/app/apdex.go b/pkg/query-service/app/apdex.go new file mode 100644 index 0000000000..6854a91367 --- /dev/null +++ b/pkg/query-service/app/apdex.go @@ -0,0 +1,46 @@ +package app + +import ( + "context" + "net/http" + "strings" + + "go.signoz.io/signoz/pkg/query-service/dao" + "go.signoz.io/signoz/pkg/query-service/model" +) + +func (aH *APIHandler) setApdexSettings(w http.ResponseWriter, r *http.Request) { + req, err := parseSetApdexScoreRequest(r) + if aH.HandleError(w, err, http.StatusBadRequest) { + return + } + + if err := dao.DB().SetApdexSettings(context.Background(), req); err != nil { + RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil) + return + } + + aH.WriteJSON(w, r, map[string]string{"data": "apdex score updated successfully"}) +} + +func (aH *APIHandler) getApdexSettings(w http.ResponseWriter, r *http.Request) { + services := r.URL.Query().Get("services") + apdexSet, err := dao.DB().GetApdexSettings(context.Background(), strings.Split(strings.TrimSpace(services), ",")) + if err != nil { + RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil) + return + } + + aH.WriteJSON(w, r, apdexSet) +} + +func (aH *APIHandler) getLatencyMetricMetadata(w http.ResponseWriter, r *http.Request) { + metricName := r.URL.Query().Get("metricName") + metricMetadata, err := aH.reader.GetLatencyMetricMetadata(r.Context(), metricName, aH.preferDelta) + if err != nil { + RespondError(w, &model.ApiError{Err: err, Typ: model.ErrorInternal}, nil) + return + } + + aH.WriteJSON(w, r, metricMetadata) +} diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index ac763be193..5d4faae2e6 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -5,6 +5,7 @@ import ( "context" "database/sql" "encoding/json" + "math" "fmt" "io/ioutil" @@ -3829,6 +3830,52 @@ func (r *ClickHouseReader) GetMetricAttributeValues(ctx context.Context, req *v3 return &attributeValues, nil } +func (r *ClickHouseReader) GetLatencyMetricMetadata(ctx context.Context, metricName string, preferDelta bool) (*v3.LatencyMetricMetadataResponse, error) { + query := fmt.Sprintf("SELECT DISTINCT(temporality) from %s.%s WHERE metric_name='%s'", signozMetricDBName, signozTSTableName, metricName) + rows, err := r.db.Query(ctx, query, metricName) + if err != nil { + zap.S().Error(err) + return nil, fmt.Errorf("error while executing query: %s", err.Error()) + } + defer rows.Close() + + var deltaExists bool + for rows.Next() { + var temporality string + if err := rows.Scan(&temporality); err != nil { + return nil, fmt.Errorf("error while scanning rows: %s", err.Error()) + } + if temporality == string(v3.Delta) { + deltaExists = true + } + } + + query = fmt.Sprintf("SELECT DISTINCT(toFloat64(JSONExtractString(labels, 'le'))) as le from %s.%s WHERE metric_name='%s' ORDER BY le", signozMetricDBName, signozTSTableName, metricName) + rows, err = r.db.Query(ctx, query, metricName) + if err != nil { + zap.S().Error(err) + return nil, fmt.Errorf("error while executing query: %s", err.Error()) + } + defer rows.Close() + + var leFloat64 []float64 + for rows.Next() { + var le float64 + if err := rows.Scan(&le); err != nil { + return nil, fmt.Errorf("error while scanning rows: %s", err.Error()) + } + if math.IsInf(le, 0) { + continue + } + leFloat64 = append(leFloat64, le) + } + + return &v3.LatencyMetricMetadataResponse{ + Delta: deltaExists && preferDelta, + Le: leFloat64, + }, nil +} + func isColumn(tableStatement, field string) bool { return strings.Contains(tableStatement, fmt.Sprintf("`%s` ", field)) } diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index e53e9af91a..5008d6edae 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -332,6 +332,10 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *AuthMiddleware) { router.HandleFunc("/api/v1/dependency_graph", am.ViewAccess(aH.dependencyGraph)).Methods(http.MethodPost) router.HandleFunc("/api/v1/settings/ttl", am.AdminAccess(aH.setTTL)).Methods(http.MethodPost) router.HandleFunc("/api/v1/settings/ttl", am.ViewAccess(aH.getTTL)).Methods(http.MethodGet) + router.HandleFunc("/api/v1/settings/apdex", am.AdminAccess(aH.setApdexSettings)).Methods(http.MethodPost) + router.HandleFunc("/api/v1/settings/apdex", am.ViewAccess(aH.getApdexSettings)).Methods(http.MethodGet) + + router.HandleFunc("/api/v1/metric_meta", am.ViewAccess(aH.getLatencyMetricMetadata)).Methods(http.MethodGet) router.HandleFunc("/api/v1/version", am.OpenAccess(aH.getVersion)).Methods(http.MethodGet) router.HandleFunc("/api/v1/featureFlags", am.OpenAccess(aH.getFeatureFlags)).Methods(http.MethodGet) diff --git a/pkg/query-service/app/parser.go b/pkg/query-service/app/parser.go index 4804847ea2..5c44ba19f4 100644 --- a/pkg/query-service/app/parser.go +++ b/pkg/query-service/app/parser.go @@ -726,6 +726,14 @@ func parseInviteRequest(r *http.Request) (*model.InviteRequest, error) { return &req, nil } +func parseSetApdexScoreRequest(r *http.Request) (*model.ApdexSettings, error) { + var req model.ApdexSettings + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + return &req, nil +} + func parseRegisterRequest(r *http.Request) (*auth.RegisterRequest, error) { var req auth.RegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { diff --git a/pkg/query-service/dao/interface.go b/pkg/query-service/dao/interface.go index ceece7faef..c1bb852e20 100644 --- a/pkg/query-service/dao/interface.go +++ b/pkg/query-service/dao/interface.go @@ -32,6 +32,8 @@ type Queries interface { GetResetPasswordEntry(ctx context.Context, token string) (*model.ResetPasswordEntry, *model.ApiError) GetUsersByOrg(ctx context.Context, orgId string) ([]model.UserPayload, *model.ApiError) GetUsersByGroup(ctx context.Context, groupId string) ([]model.UserPayload, *model.ApiError) + + GetApdexSettings(ctx context.Context, services []string) ([]model.ApdexSettings, *model.ApiError) } type Mutations interface { @@ -56,4 +58,6 @@ type Mutations interface { UpdateUserPassword(ctx context.Context, hash, userId string) *model.ApiError UpdateUserGroup(ctx context.Context, userId, groupId string) *model.ApiError + + SetApdexSettings(ctx context.Context, set *model.ApdexSettings) *model.ApiError } diff --git a/pkg/query-service/dao/sqlite/apdex.go b/pkg/query-service/dao/sqlite/apdex.go new file mode 100644 index 0000000000..8c74553fb8 --- /dev/null +++ b/pkg/query-service/dao/sqlite/apdex.go @@ -0,0 +1,74 @@ +package sqlite + +import ( + "context" + "fmt" + + "go.signoz.io/signoz/pkg/query-service/model" +) + +const defaultApdexThreshold = 0.5 + +func (mds *ModelDaoSqlite) GetApdexSettings(ctx context.Context, services []string) ([]model.ApdexSettings, *model.ApiError) { + var apdexSettings []model.ApdexSettings + var serviceName string + + for i, service := range services { + if i == 0 { + serviceName = fmt.Sprintf("'%s'", service) + } else { + serviceName = fmt.Sprintf("%s, '%s'", serviceName, service) + } + } + + query := fmt.Sprintf("SELECT * FROM apdex_settings WHERE service_name IN (%s)", serviceName) + + err := mds.db.Select(&apdexSettings, query) + if err != nil { + return nil, &model.ApiError{ + Err: err, + } + } + + // add default apdex settings for services that don't have any + for _, service := range services { + var found bool + for _, apdexSetting := range apdexSettings { + if apdexSetting.ServiceName == service { + found = true + break + } + } + + if !found { + apdexSettings = append(apdexSettings, model.ApdexSettings{ + ServiceName: service, + Threshold: defaultApdexThreshold, + }) + } + } + + return apdexSettings, nil +} + +func (mds *ModelDaoSqlite) SetApdexSettings(ctx context.Context, apdexSettings *model.ApdexSettings) *model.ApiError { + + fmt.Println("apdexSettings:", apdexSettings) + _, err := mds.db.NamedExec(` + INSERT OR REPLACE INTO apdex_settings ( + service_name, + threshold, + exclude_status_codes + ) VALUES ( + :service_name, + :threshold, + :exclude_status_codes + )`, apdexSettings) + if err != nil { + return &model.ApiError{ + Err: err, + } + } + + return nil +} diff --git a/pkg/query-service/dao/sqlite/connection.go b/pkg/query-service/dao/sqlite/connection.go index a1b7842bb3..dd113a2863 100644 --- a/pkg/query-service/dao/sqlite/connection.go +++ b/pkg/query-service/dao/sqlite/connection.go @@ -73,6 +73,11 @@ func InitDB(dataSourceName string) (*ModelDaoSqlite, error) { flags TEXT, FOREIGN KEY(user_id) REFERENCES users(id) ); + CREATE TABLE IF NOT EXISTS apdex_settings ( + service_name TEXT PRIMARY KEY, + threshold FLOAT NOT NULL, + exclude_status_codes TEXT NOT NULL + ); ` _, err = db.Exec(table_schema) diff --git a/pkg/query-service/interfaces/interface.go b/pkg/query-service/interfaces/interface.go index f53a3a1fe6..8b3697f89c 100644 --- a/pkg/query-service/interfaces/interface.go +++ b/pkg/query-service/interfaces/interface.go @@ -95,6 +95,8 @@ type Reader interface { QueryDashboardVars(ctx context.Context, query string) (*model.DashboardVar, error) CheckClickHouse(ctx context.Context) error + + GetLatencyMetricMetadata(context.Context, string, bool) (*v3.LatencyMetricMetadataResponse, error) } type Querier interface { diff --git a/pkg/query-service/model/db.go b/pkg/query-service/model/db.go index e043dd2ddd..9ae7270232 100644 --- a/pkg/query-service/model/db.go +++ b/pkg/query-service/model/db.go @@ -36,6 +36,12 @@ type User struct { GroupId string `json:"groupId,omitempty" db:"group_id"` } +type ApdexSettings struct { + ServiceName string `json:"serviceName" db:"service_name"` + Threshold float64 `json:"threshold" db:"threshold"` + ExcludeStatusCodes string `json:"excludeStatusCodes" db:"exclude_status_codes"` // sqlite doesn't support array type +} + type UserFlag map[string]string func (uf UserFlag) Value() (driver.Value, error) { diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index d1dab19c0d..4e4fd2c0a1 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -663,3 +663,8 @@ func (eq *ExplorerQuery) Validate() error { } return eq.CompositeQuery.Validate() } + +type LatencyMetricMetadataResponse struct { + Delta bool `json:"delta"` + Le []float64 `json:"le"` +} From 127ccdacb42fa68f0a386c73d61a6d43fa295505 Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Wed, 26 Jul 2023 15:06:07 +0530 Subject: [PATCH 09/21] feat: duplicate types are removed for panel and replaced by panel_types and icons is added (#3198) --- frontend/src/assets/Dashboard/Table.tsx | 18 +++++++++++ frontend/src/assets/Dashboard/TimeSeries.tsx | 31 +++++-------------- frontend/src/assets/Dashboard/Value.tsx | 24 ++++++-------- .../FormAlertRules/ChartPreview/index.tsx | 3 +- .../GridGraphLayout/Graph/FullView/styles.ts | 3 +- .../src/container/GridGraphLayout/index.tsx | 3 +- .../src/container/GridGraphLayout/styles.ts | 3 +- .../src/container/GridGraphLayout/utils.ts | 5 ++- .../src/container/GridPanelSwitch/types.ts | 3 +- .../src/container/LogsExplorerViews/index.tsx | 5 ++- .../NewDashboard/ComponentsSlider/index.tsx | 19 +++--------- .../ComponentsSlider/menuItems.ts | 17 +++------- .../NewDashboard/ComponentsSlider/styles.ts | 1 + .../LeftContainer/QuerySection/index.tsx | 4 +-- .../LeftContainer/WidgetGraph/PlotTag.tsx | 4 +-- .../LeftContainer/WidgetGraph/styles.ts | 3 +- .../NewWidget/RightContainer/index.tsx | 10 +++--- frontend/src/container/NewWidget/index.tsx | 4 +-- frontend/src/container/NewWidget/types.ts | 4 +-- .../QueryBuilder/QueryBuilder.interfaces.ts | 4 +-- .../queryBuilder/useGetExplorerQueryRange.ts | 3 +- .../useGetPanelTypesQueryParam.ts | 6 ++-- frontend/src/lib/getStartEndRangeTime.ts | 4 +-- .../getOperatorsBySourceAndPanelType.ts | 3 +- frontend/src/pages/DashboardWidget/index.tsx | 6 ++-- frontend/src/pages/TracesExplorer/index.tsx | 5 ++- frontend/src/providers/QueryBuilder.tsx | 9 +++--- .../store/actions/dashboard/getDashboard.ts | 3 +- .../store/actions/dashboard/saveDashboard.ts | 4 +-- .../src/types/api/alerts/compositeQuery.ts | 4 +-- frontend/src/types/api/dashboard/getAll.ts | 4 +-- frontend/src/types/common/queryBuilder.ts | 8 ++--- 32 files changed, 97 insertions(+), 130 deletions(-) create mode 100644 frontend/src/assets/Dashboard/Table.tsx diff --git a/frontend/src/assets/Dashboard/Table.tsx b/frontend/src/assets/Dashboard/Table.tsx new file mode 100644 index 0000000000..e1ade19994 --- /dev/null +++ b/frontend/src/assets/Dashboard/Table.tsx @@ -0,0 +1,18 @@ +function Table(): JSX.Element { + return ( + + + + ); +} + +export default Table; diff --git a/frontend/src/assets/Dashboard/TimeSeries.tsx b/frontend/src/assets/Dashboard/TimeSeries.tsx index 54d8100a63..a9ddbc5717 100644 --- a/frontend/src/assets/Dashboard/TimeSeries.tsx +++ b/frontend/src/assets/Dashboard/TimeSeries.tsx @@ -1,33 +1,16 @@ function TimeSeries(): JSX.Element { return ( - - - - - - - + ); } diff --git a/frontend/src/assets/Dashboard/Value.tsx b/frontend/src/assets/Dashboard/Value.tsx index c43b63ac13..708e16d6b9 100644 --- a/frontend/src/assets/Dashboard/Value.tsx +++ b/frontend/src/assets/Dashboard/Value.tsx @@ -1,26 +1,22 @@ -import { CSSProperties } from 'react'; - -function Value(props: ValueProps): JSX.Element { - const { fillColor } = props; - +function Value(): JSX.Element { return ( + ); } -interface ValueProps { - fillColor: CSSProperties['color']; -} - export default Value; diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index c27d69f8ba..b8d66c4334 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -3,7 +3,6 @@ import { StaticLineProps } from 'components/Graph'; import Spinner from 'components/Spinner'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import GridPanelSwitch from 'container/GridPanelSwitch'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems'; import { Time } from 'container/TopNav/DateTimeSelection/config'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; @@ -18,7 +17,7 @@ import { ChartContainer, FailedMessageContainer } from './styles'; export interface ChartPreviewProps { name: string; query: Query | null; - graphType?: GRAPH_TYPES; + graphType?: PANEL_TYPES; selectedTime?: timePreferenceType; selectedInterval?: Time; headline?: JSX.Element; diff --git a/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts b/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts index e2b3384af3..377e891a35 100644 --- a/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts +++ b/frontend/src/container/GridGraphLayout/Graph/FullView/styles.ts @@ -1,9 +1,8 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; -import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; import styled, { css, FlattenSimpleInterpolation } from 'styled-components'; interface Props { - $panelType: ITEMS; + $panelType: PANEL_TYPES; } export const NotFoundContainer = styled.div` diff --git a/frontend/src/container/GridGraphLayout/index.tsx b/frontend/src/container/GridGraphLayout/index.tsx index 6b6ec7d883..f728781a28 100644 --- a/frontend/src/container/GridGraphLayout/index.tsx +++ b/frontend/src/container/GridGraphLayout/index.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/no-unstable-nested-components */ import updateDashboardApi from 'api/dashboard/update'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import useComponentPermission from 'hooks/useComponentPermission'; import { useNotifications } from 'hooks/useNotifications'; import { @@ -260,7 +261,7 @@ function GridGraph(props: Props): JSX.Element { { data, generateWidgetId: id, - graphType: 'EMPTY_WIDGET', + graphType: PANEL_TYPES.EMPTY_WIDGET, selectedDashboard, layout, isRedirected: false, diff --git a/frontend/src/container/GridGraphLayout/styles.ts b/frontend/src/container/GridGraphLayout/styles.ts index ca54bd6140..a08ffdc361 100644 --- a/frontend/src/container/GridGraphLayout/styles.ts +++ b/frontend/src/container/GridGraphLayout/styles.ts @@ -1,14 +1,13 @@ import { Button as ButtonComponent, Card as CardComponent, Space } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { StyledCSS } from 'container/GantChart/Trace/styles'; -import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; import RGL, { WidthProvider } from 'react-grid-layout'; import styled, { css, FlattenSimpleInterpolation } from 'styled-components'; const ReactGridLayoutComponent = WidthProvider(RGL); interface CardProps { - $panelType: ITEMS; + $panelType: PANEL_TYPES; } export const Card = styled(CardComponent)` diff --git a/frontend/src/container/GridGraphLayout/utils.ts b/frontend/src/container/GridGraphLayout/utils.ts index 6f6910b3b5..2caa48b705 100644 --- a/frontend/src/container/GridGraphLayout/utils.ts +++ b/frontend/src/container/GridGraphLayout/utils.ts @@ -1,7 +1,6 @@ import { NotificationInstance } from 'antd/es/notification/interface'; import updateDashboardApi from 'api/dashboard/update'; -import { initialQueriesMap } from 'constants/queryBuilder'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { Layout } from 'react-grid-layout'; import store from 'store'; import { Dashboard, Widgets } from 'types/api/dashboard/getAll'; @@ -69,7 +68,7 @@ export const UpdateDashboard = async ( interface UpdateDashboardProps { data: Dashboard['data']; - graphType: GRAPH_TYPES; + graphType: PANEL_TYPES; generateWidgetId: string; layout: Layout[]; selectedDashboard: Dashboard; diff --git a/frontend/src/container/GridPanelSwitch/types.ts b/frontend/src/container/GridPanelSwitch/types.ts index 26dca8be31..5f16fc5b2d 100644 --- a/frontend/src/container/GridPanelSwitch/types.ts +++ b/frontend/src/container/GridPanelSwitch/types.ts @@ -6,14 +6,13 @@ import { } from 'components/Graph'; import { GridTableComponentProps } from 'container/GridTableComponent/types'; import { GridValueComponentProps } from 'container/GridValueComponent/types'; -import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { QueryDataV3 } from 'types/api/widgets/getQuery'; import { PANEL_TYPES } from '../../constants/queryBuilder'; export type GridPanelSwitchProps = { - panelType: ITEMS; + panelType: PANEL_TYPES; data: ChartData; title?: string; opacity?: string; diff --git a/frontend/src/container/LogsExplorerViews/index.tsx b/frontend/src/container/LogsExplorerViews/index.tsx index a2fadefc53..0523b4d89e 100644 --- a/frontend/src/container/LogsExplorerViews/index.tsx +++ b/frontend/src/container/LogsExplorerViews/index.tsx @@ -17,7 +17,6 @@ import GoToTop from 'container/GoToTop'; import LogsExplorerChart from 'container/LogsExplorerChart'; import LogsExplorerList from 'container/LogsExplorerList'; import LogsExplorerTable from 'container/LogsExplorerTable'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { SIGNOZ_VALUE } from 'container/QueryBuilder/filters/OrderByFilter/constants'; import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; @@ -179,7 +178,7 @@ function LogsExplorerViews(): JSX.Element { }, []); const getUpdateQuery = useCallback( - (newPanelType: GRAPH_TYPES): Query => { + (newPanelType: PANEL_TYPES): Query => { let query = updateAllQueriesOperators( currentQuery, newPanelType, @@ -201,7 +200,7 @@ function LogsExplorerViews(): JSX.Element { const handleChangeView = useCallback( (type: string) => { - const newPanelType = type as GRAPH_TYPES; + const newPanelType = type as PANEL_TYPES; if (newPanelType === panelType) return; diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx index 8ede8520b7..a5c48ed307 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx +++ b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx @@ -1,6 +1,4 @@ -/* eslint-disable @typescript-eslint/naming-convention */ - -import { initialQueriesMap } from 'constants/queryBuilder'; +import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { queryParamNamesMap } from 'constants/queryBuilderQueryNames'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { useNotifications } from 'hooks/useNotifications'; @@ -17,7 +15,7 @@ import { AppState } from 'store/reducers'; import AppActions from 'types/actions'; import DashboardReducer from 'types/reducer/dashboards'; -import menuItems, { ITEMS } from './menuItems'; +import menuItems from './menuItems'; import { Card, Container, Text } from './styles'; function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element { @@ -31,7 +29,7 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element { const { data } = selectedDashboard; const onClickHandler = useCallback( - async (name: ITEMS) => { + (name: PANEL_TYPES) => (): void => { try { const emptyLayout = data.layout?.find((e) => e.i === 'empty'); @@ -65,14 +63,7 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element { return ( {menuItems.map(({ name, Icon, display }) => ( - { - event.preventDefault(); - onClickHandler(name); - }} - id={name} - key={name} - > + {display} @@ -81,8 +72,6 @@ function DashboardGraphSlider({ toggleAddWidget }: Props): JSX.Element { ); } -export type GRAPH_TYPES = ITEMS; - interface DispatchProps { toggleAddWidget: ( props: ToggleAddWidgetProps, diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts b/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts index baadf921fe..560da8deef 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts +++ b/frontend/src/container/NewDashboard/ComponentsSlider/menuItems.ts @@ -1,4 +1,5 @@ -import TimeSeries from 'assets/Dashboard/TimeSeries'; +import TableIcon from 'assets/Dashboard/Table'; +import TimeSeriesIcon from 'assets/Dashboard/TimeSeries'; import ValueIcon from 'assets/Dashboard/Value'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { CSSProperties } from 'react'; @@ -6,7 +7,7 @@ import { CSSProperties } from 'react'; const Items: ItemsProps[] = [ { name: PANEL_TYPES.TIME_SERIES, - Icon: TimeSeries, + Icon: TimeSeriesIcon, display: 'Time Series', }, { @@ -14,19 +15,11 @@ const Items: ItemsProps[] = [ Icon: ValueIcon, display: 'Value', }, - { name: PANEL_TYPES.TABLE, Icon: TimeSeries, display: 'Table' }, + { name: PANEL_TYPES.TABLE, Icon: TableIcon, display: 'Table' }, ]; -export type ITEMS = - | 'graph' - | 'value' - | 'list' - | 'table' - | 'EMPTY_WIDGET' - | 'trace'; - interface ItemsProps { - name: ITEMS; + name: PANEL_TYPES; Icon: (props: IconProps) => JSX.Element; display: string; } diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/styles.ts b/frontend/src/container/NewDashboard/ComponentsSlider/styles.ts index ad59f774a8..d7d3c6a7f2 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/styles.ts +++ b/frontend/src/container/NewDashboard/ComponentsSlider/styles.ts @@ -16,6 +16,7 @@ export const Card = styled(CardComponent)` display: flex; flex-direction: column; justify-content: space-between; + align-items: center; } `; diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx index 51501c8b39..52f4bbc92c 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/index.tsx @@ -1,6 +1,6 @@ import { Button, Tabs, Typography } from 'antd'; import TextToolTip from 'components/TextToolTip'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { WidgetGraphProps } from 'container/NewWidget/types'; import { QueryBuilder } from 'container/QueryBuilder'; import { QueryBuilderProps } from 'container/QueryBuilder/QueryBuilder.interfaces'; @@ -170,7 +170,7 @@ const mapDispatchToProps = ( }); interface QueryProps extends DispatchProps { - selectedGraph: GRAPH_TYPES; + selectedGraph: PANEL_TYPES; selectedTime: WidgetGraphProps['selectedTime']; } diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx index 6359295fae..219d4e011b 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/PlotTag.tsx @@ -1,4 +1,4 @@ -import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { EQueryType } from 'types/common/dashboard'; import QueryTypeTag from '../QueryTypeTag'; @@ -6,7 +6,7 @@ import { PlotTagWrapperStyled } from './styles'; interface IPlotTagProps { queryType: EQueryType; - panelType: ITEMS; + panelType: PANEL_TYPES; } function PlotTag({ queryType, panelType }: IPlotTagProps): JSX.Element | null { diff --git a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts index b3fd9fcb2d..58239ad942 100644 --- a/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts +++ b/frontend/src/container/NewWidget/LeftContainer/WidgetGraph/styles.ts @@ -1,10 +1,9 @@ import { Card, Tooltip } from 'antd'; import { PANEL_TYPES } from 'constants/queryBuilder'; -import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; import styled from 'styled-components'; interface Props { - $panelType: ITEMS; + $panelType: PANEL_TYPES; } export const Container = styled(Card)` diff --git a/frontend/src/container/NewWidget/RightContainer/index.tsx b/frontend/src/container/NewWidget/RightContainer/index.tsx index 0f9ab1c597..b2fff041f9 100644 --- a/frontend/src/container/NewWidget/RightContainer/index.tsx +++ b/frontend/src/container/NewWidget/RightContainer/index.tsx @@ -1,10 +1,8 @@ import { Input, Select } from 'antd'; import InputComponent from 'components/Input'; import TimePreference from 'components/TimePreferenceDropDown'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; -import GraphTypes, { - ITEMS, -} from 'container/NewDashboard/ComponentsSlider/menuItems'; +import { PANEL_TYPES } from 'constants/queryBuilder'; +import GraphTypes from 'container/NewDashboard/ComponentsSlider/menuItems'; import { Dispatch, SetStateAction, useCallback } from 'react'; import { Container, Title } from './styles'; @@ -144,12 +142,12 @@ interface RightContainerProps { setOpacity: Dispatch>; selectedNullZeroValue: string; setSelectedNullZeroValue: Dispatch>; - selectedGraph: GRAPH_TYPES; + selectedGraph: PANEL_TYPES; setSelectedTime: Dispatch>; selectedTime: timePreferance; yAxisUnit: string; setYAxisUnit: Dispatch>; - setGraphHandler: (type: ITEMS) => void; + setGraphHandler: (type: PANEL_TYPES) => void; } export default RightContainer; diff --git a/frontend/src/container/NewWidget/index.tsx b/frontend/src/container/NewWidget/index.tsx index 17cb4ea884..eb49ec8bd0 100644 --- a/frontend/src/container/NewWidget/index.tsx +++ b/frontend/src/container/NewWidget/index.tsx @@ -1,8 +1,8 @@ import { LockFilled } from '@ant-design/icons'; import { Button, Modal, Tooltip, Typography } from 'antd'; import { FeatureKeys } from 'constants/features'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; -import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag'; import { useNotifications } from 'hooks/useNotifications'; @@ -148,7 +148,7 @@ function NewWidget({ selectedGraph, saveSettingOfPanel }: Props): JSX.Element { history.push(generatePath(ROUTES.DASHBOARD, { dashboardId })); }, [dashboardId, dispatch]); - const setGraphHandler = (type: ITEMS): void => { + const setGraphHandler = (type: PANEL_TYPES): void => { const params = new URLSearchParams(search); params.set('graphType', type); history.push({ search: params.toString() }); diff --git a/frontend/src/container/NewWidget/types.ts b/frontend/src/container/NewWidget/types.ts index ca6a121382..e4fce1ba89 100644 --- a/frontend/src/container/NewWidget/types.ts +++ b/frontend/src/container/NewWidget/types.ts @@ -1,10 +1,10 @@ -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { Widgets } from 'types/api/dashboard/getAll'; import { timePreferance } from './RightContainer/timeItems'; export interface NewWidgetProps { - selectedGraph: GRAPH_TYPES; + selectedGraph: PANEL_TYPES; yAxisUnit: Widgets['yAxisUnit']; } diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts index fec93c7106..4758ad882b 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts +++ b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts @@ -1,4 +1,4 @@ -import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { ReactNode } from 'react'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; @@ -14,7 +14,7 @@ export type QueryBuilderConfig = export type QueryBuilderProps = { config?: QueryBuilderConfig; - panelType: ITEMS; + panelType: PANEL_TYPES; actions?: ReactNode; filterConfigs?: Partial< Record diff --git a/frontend/src/hooks/queryBuilder/useGetExplorerQueryRange.ts b/frontend/src/hooks/queryBuilder/useGetExplorerQueryRange.ts index 92789b960e..408e598727 100644 --- a/frontend/src/hooks/queryBuilder/useGetExplorerQueryRange.ts +++ b/frontend/src/hooks/queryBuilder/useGetExplorerQueryRange.ts @@ -1,6 +1,5 @@ import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { useMemo } from 'react'; import { UseQueryOptions, UseQueryResult } from 'react-query'; import { useSelector } from 'react-redux'; @@ -15,7 +14,7 @@ import { useQueryBuilder } from './useQueryBuilder'; export const useGetExplorerQueryRange = ( requestData: Query | null, - panelType: GRAPH_TYPES | null, + panelType: PANEL_TYPES | null, options?: UseQueryOptions, Error>, ): UseQueryResult, Error> => { const { isEnabledQuery } = useQueryBuilder(); diff --git a/frontend/src/hooks/queryBuilder/useGetPanelTypesQueryParam.ts b/frontend/src/hooks/queryBuilder/useGetPanelTypesQueryParam.ts index 560790e574..8b5aed7a44 100644 --- a/frontend/src/hooks/queryBuilder/useGetPanelTypesQueryParam.ts +++ b/frontend/src/hooks/queryBuilder/useGetPanelTypesQueryParam.ts @@ -1,11 +1,11 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; import { queryParamNamesMap } from 'constants/queryBuilderQueryNames'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import useUrlQuery from 'hooks/useUrlQuery'; import { useMemo } from 'react'; -export const useGetPanelTypesQueryParam = ( +export const useGetPanelTypesQueryParam = ( defaultPanelType?: T, -): T extends undefined ? GRAPH_TYPES | null : GRAPH_TYPES => { +): T extends undefined ? PANEL_TYPES | null : PANEL_TYPES => { const urlQuery = useUrlQuery(); return useMemo(() => { diff --git a/frontend/src/lib/getStartEndRangeTime.ts b/frontend/src/lib/getStartEndRangeTime.ts index 486b6d0784..13a2d40031 100644 --- a/frontend/src/lib/getStartEndRangeTime.ts +++ b/frontend/src/lib/getStartEndRangeTime.ts @@ -1,4 +1,4 @@ -import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems'; import { Time } from 'container/TopNav/DateTimeSelection/config'; import store from 'store'; @@ -36,7 +36,7 @@ const getStartEndRangeTime = ({ interface GetStartEndRangeTimesProps { type?: timePreferenceType; - graphType?: ITEMS | null; + graphType?: PANEL_TYPES | null; interval?: Time; } diff --git a/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts b/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts index dd6869847b..e415d8f7f4 100644 --- a/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts +++ b/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts @@ -1,11 +1,10 @@ import { mapOfOperators, PANEL_TYPES } from 'constants/queryBuilder'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { DataSource, StringOperators } from 'types/common/queryBuilder'; import { SelectOption } from 'types/common/select'; type GetQueryOperatorsParams = { dataSource: DataSource; - panelType: GRAPH_TYPES; + panelType: PANEL_TYPES; }; // Modify this function if need special conditions for filtering of the operators diff --git a/frontend/src/pages/DashboardWidget/index.tsx b/frontend/src/pages/DashboardWidget/index.tsx index a4ed88e264..fc8a712b63 100644 --- a/frontend/src/pages/DashboardWidget/index.tsx +++ b/frontend/src/pages/DashboardWidget/index.tsx @@ -1,7 +1,7 @@ import { Card, Typography } from 'antd'; import Spinner from 'components/Spinner'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import NewWidget from 'container/NewWidget'; import history from 'lib/history'; import { useEffect, useRef, useState } from 'react'; @@ -18,7 +18,7 @@ function DashboardWidget({ getDashboard }: NewDashboardProps): JSX.Element { const { search } = useLocation(); const { dashboardId } = useParams(); - const [selectedGraph, setSelectedGraph] = useState(); + const [selectedGraph, setSelectedGraph] = useState(); const { loading, dashboards, error, errorMessage } = useSelector< AppState, DashboardReducer @@ -34,7 +34,7 @@ function DashboardWidget({ getDashboard }: NewDashboardProps): JSX.Element { useEffect(() => { const params = new URLSearchParams(search); - const graphType = params.get('graphType') as GRAPH_TYPES | null; + const graphType = params.get('graphType') as PANEL_TYPES | null; if (graphType === null) { history.push(generatePath(ROUTES.DASHBOARD, { dashboardId })); diff --git a/frontend/src/pages/TracesExplorer/index.tsx b/frontend/src/pages/TracesExplorer/index.tsx index 855f82ba40..6cb81093f3 100644 --- a/frontend/src/pages/TracesExplorer/index.tsx +++ b/frontend/src/pages/TracesExplorer/index.tsx @@ -10,7 +10,6 @@ import { import { queryParamNamesMap } from 'constants/queryBuilderQueryNames'; import ROUTES from 'constants/routes'; import ExportPanel from 'container/ExportPanel'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { SIGNOZ_VALUE } from 'container/QueryBuilder/filters/OrderByFilter/constants'; import QuerySection from 'container/TracesExplorer/QuerySection'; import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; @@ -149,7 +148,7 @@ function TracesExplorer(): JSX.Element { ); const getUpdateQuery = useCallback( - (newPanelType: GRAPH_TYPES): Query => { + (newPanelType: PANEL_TYPES): Query => { let query = updateAllQueriesOperators( currentQuery, newPanelType, @@ -174,7 +173,7 @@ function TracesExplorer(): JSX.Element { const handleTabChange = useCallback( (type: string): void => { - const newPanelType = type as GRAPH_TYPES; + const newPanelType = type as PANEL_TYPES; if (panelType === newPanelType) return; const query = getUpdateQuery(newPanelType); diff --git a/frontend/src/providers/QueryBuilder.tsx b/frontend/src/providers/QueryBuilder.tsx index 04d74f2f19..4de8de7d4a 100644 --- a/frontend/src/providers/QueryBuilder.tsx +++ b/frontend/src/providers/QueryBuilder.tsx @@ -14,7 +14,6 @@ import { PANEL_TYPES, } from 'constants/queryBuilder'; import { queryParamNamesMap } from 'constants/queryBuilderQueryNames'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam'; import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval'; import useUrlQuery from 'hooks/useUrlQuery'; @@ -92,7 +91,7 @@ export function QueryBuilderProvider({ null, ); - const [panelType, setPanelType] = useState(null); + const [panelType, setPanelType] = useState(null); const [currentQuery, setCurrentQuery] = useState( queryState || initialQueryState, @@ -105,7 +104,7 @@ export function QueryBuilderProvider({ ( queryData: IBuilderQuery, dataSource: DataSource, - currentPanelType: GRAPH_TYPES, + currentPanelType: PANEL_TYPES, ): IBuilderQuery => { const initialOperators = getOperatorsBySourceAndPanelType({ dataSource, @@ -212,7 +211,7 @@ export function QueryBuilderProvider({ ); const updateAllQueriesOperators = useCallback( - (query: Query, panelType: GRAPH_TYPES, dataSource: DataSource): Query => { + (query: Query, panelType: PANEL_TYPES, dataSource: DataSource): Query => { const queryData = query.builder.queryData.map((item) => getElementWithActualOperator(item, dataSource, panelType), ); @@ -496,7 +495,7 @@ export function QueryBuilderProvider({ ); const handleSetConfig = useCallback( - (newPanelType: GRAPH_TYPES, dataSource: DataSource | null) => { + (newPanelType: PANEL_TYPES, dataSource: DataSource | null) => { setPanelType(newPanelType); setInitialDataSource(dataSource); }, diff --git a/frontend/src/store/actions/dashboard/getDashboard.ts b/frontend/src/store/actions/dashboard/getDashboard.ts index f84461c655..76305bae3b 100644 --- a/frontend/src/store/actions/dashboard/getDashboard.ts +++ b/frontend/src/store/actions/dashboard/getDashboard.ts @@ -1,6 +1,5 @@ import getDashboard from 'api/dashboard/get'; import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; import { Dispatch } from 'redux'; import AppActions from 'types/actions'; import { Props } from 'types/api/dashboard/get'; @@ -65,5 +64,5 @@ export const GetDashboard = ({ export interface GetDashboardProps { uuid: Props['uuid']; widgetId?: string; - graphType?: GRAPH_TYPES; + graphType?: PANEL_TYPES; } diff --git a/frontend/src/store/actions/dashboard/saveDashboard.ts b/frontend/src/store/actions/dashboard/saveDashboard.ts index 827a11fc64..1339a3a7b8 100644 --- a/frontend/src/store/actions/dashboard/saveDashboard.ts +++ b/frontend/src/store/actions/dashboard/saveDashboard.ts @@ -1,9 +1,9 @@ import { notification } from 'antd'; import updateDashboardApi from 'api/dashboard/update'; import { AxiosError } from 'axios'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { queryParamNamesMap } from 'constants/queryBuilderQueryNames'; import ROUTES from 'constants/routes'; -import { ITEMS } from 'container/NewDashboard/ComponentsSlider/menuItems'; import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval'; import history from 'lib/history'; import { Layout } from 'react-grid-layout'; @@ -170,5 +170,5 @@ export interface SaveDashboardProps { widgetId: Widgets['id']; dashboardId: string; yAxisUnit: Widgets['yAxisUnit']; - graphType: ITEMS; + graphType: PANEL_TYPES; } diff --git a/frontend/src/types/api/alerts/compositeQuery.ts b/frontend/src/types/api/alerts/compositeQuery.ts index 2bb0288400..1f25718367 100644 --- a/frontend/src/types/api/alerts/compositeQuery.ts +++ b/frontend/src/types/api/alerts/compositeQuery.ts @@ -1,4 +1,4 @@ -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { BuilderClickHouseResource, BuilderPromQLResource, @@ -11,5 +11,5 @@ export interface ICompositeMetricQuery { promQueries: BuilderPromQLResource; chQueries: BuilderClickHouseResource; queryType: EQueryType; - panelType: GRAPH_TYPES; + panelType: PANEL_TYPES; } diff --git a/frontend/src/types/api/dashboard/getAll.ts b/frontend/src/types/api/dashboard/getAll.ts index 20e2a07702..69a3dc6401 100644 --- a/frontend/src/types/api/dashboard/getAll.ts +++ b/frontend/src/types/api/dashboard/getAll.ts @@ -1,4 +1,4 @@ -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { timePreferenceType } from 'container/NewWidget/RightContainer/timeItems'; import { Layout } from 'react-grid-layout'; import { Query } from 'types/api/queryBuilder/queryBuilderData'; @@ -57,7 +57,7 @@ export interface DashboardData { export interface IBaseWidget { isStacked: boolean; id: string; - panelTypes: GRAPH_TYPES; + panelTypes: PANEL_TYPES; title: string; description: string; opacity: string; diff --git a/frontend/src/types/common/queryBuilder.ts b/frontend/src/types/common/queryBuilder.ts index f607cac9f7..5e7cf57b02 100644 --- a/frontend/src/types/common/queryBuilder.ts +++ b/frontend/src/types/common/queryBuilder.ts @@ -1,4 +1,4 @@ -import { GRAPH_TYPES } from 'container/NewDashboard/ComponentsSlider'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import { IBuilderFormula, IBuilderQuery, @@ -157,7 +157,7 @@ export type QueryBuilderContextType = { currentQuery: Query; stagedQuery: Query | null; initialDataSource: DataSource | null; - panelType: GRAPH_TYPES | null; + panelType: PANEL_TYPES | null; isEnabledQuery: boolean; handleSetQueryData: (index: number, queryData: IBuilderQuery) => void; handleSetFormulaData: (index: number, formulaData: IBuilderFormula) => void; @@ -167,7 +167,7 @@ export type QueryBuilderContextType = { newQueryData: IPromQLQuery | IClickHouseQuery, ) => void; handleSetConfig: ( - newPanelType: GRAPH_TYPES, + newPanelType: PANEL_TYPES, dataSource: DataSource | null, ) => void; removeQueryBuilderEntityByIndex: ( @@ -189,7 +189,7 @@ export type QueryBuilderContextType = { resetStagedQuery: () => void; updateAllQueriesOperators: ( queryData: Query, - panelType: GRAPH_TYPES, + panelType: PANEL_TYPES, dataSource: DataSource, ) => Query; updateQueriesData: ( From 7b85ece7960fd8d5fa755566edec5108e2d8ee0b Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Thu, 27 Jul 2023 09:38:19 +0530 Subject: [PATCH 10/21] fix: update how limit is added to table queries (#3207) --- pkg/query-service/app/logs/v3/query_builder.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/query-service/app/logs/v3/query_builder.go b/pkg/query-service/app/logs/v3/query_builder.go index 7047273497..1b84f71fcc 100644 --- a/pkg/query-service/app/logs/v3/query_builder.go +++ b/pkg/query-service/app/logs/v3/query_builder.go @@ -442,7 +442,7 @@ func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.Pan query, err = reduceQuery(query, mq.ReduceTo, mq.AggregateOperator) } - if panelType == v3.PanelTypeList || panelType == v3.PanelTypeTable { + if panelType == v3.PanelTypeList { if mq.PageSize > 0 { if mq.Limit > 0 && mq.Offset > mq.Limit { return "", fmt.Errorf("max limit exceeded") @@ -452,8 +452,9 @@ func PrepareLogsQuery(start, end int64, queryType v3.QueryType, panelType v3.Pan } else { query = addLimitToQuery(query, mq.Limit) } + } else if panelType == v3.PanelTypeTable { + query = addLimitToQuery(query, mq.Limit) } return query, err - } From e86e045a287dc1e1174c71a12c9b3454792a1b03 Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Thu, 27 Jul 2023 09:49:34 +0530 Subject: [PATCH 11/21] fix: ignore autocomplete request for body (#3208) --- pkg/query-service/app/clickhouseReader/reader.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index 5d4faae2e6..f40f48c922 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -4030,13 +4030,18 @@ func (r *ClickHouseReader) GetLogAttributeValues(ctx context.Context, req *v3.Fi var attributeValues v3.FilterAttributeValueResponse // if dataType or tagType is not present return empty response - if len(req.FilterAttributeKeyDataType) == 0 || len(req.TagType) == 0 || req.FilterAttributeKey == "body" { + if len(req.FilterAttributeKeyDataType) == 0 || len(req.TagType) == 0 { // also check if it is not a top level key if _, ok := constants.StaticFieldsLogsV3[req.FilterAttributeKey]; !ok { return &v3.FilterAttributeValueResponse{}, nil } } + // ignore autocomplete request for body + if req.FilterAttributeKey == "body" { + return &v3.FilterAttributeValueResponse{}, nil + } + // if data type is bool, return true and false if req.FilterAttributeKeyDataType == v3.AttributeKeyDataTypeBool { return &v3.FilterAttributeValueResponse{ From 872759f579b427e2c6561608658dda5ddbcc100e Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 27 Jul 2023 12:12:15 +0530 Subject: [PATCH 12/21] refactor: updated the legend div size (#3131) Co-authored-by: Palash Gupta --- frontend/src/components/Graph/Plugin/Legend.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/Graph/Plugin/Legend.ts b/frontend/src/components/Graph/Plugin/Legend.ts index 787933f442..8880657855 100644 --- a/frontend/src/components/Graph/Plugin/Legend.ts +++ b/frontend/src/components/Graph/Plugin/Legend.ts @@ -19,6 +19,7 @@ const getOrCreateLegendList = ( listContainer.style.overflowY = 'scroll'; listContainer.style.justifyContent = isLonger ? 'start' : 'center'; listContainer.style.alignItems = isLonger ? 'start' : 'center'; + listContainer.style.minHeight = '2rem'; listContainer.style.height = '100%'; listContainer.style.flexWrap = 'wrap'; listContainer.style.justifyContent = 'center'; From 2c5c97280196edd00d5e70043ed1a86311bb5669 Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Thu, 27 Jul 2023 10:22:44 +0300 Subject: [PATCH 13/21] fix: custom where clause value (#3209) * fix: custom where clause value * fix: operations * fix: return suggestions for body --------- Co-authored-by: Palash Gupta --- .../LogExplorerQuerySection/index.tsx | 10 ++- .../QueryBuilder/QueryBuilder.interfaces.ts | 12 +++- .../QueryBuilder/components/Query/Query.tsx | 6 +- .../filters/QueryBuilderSearch/index.tsx | 15 ++++- .../src/hooks/queryBuilder/useAutoComplete.ts | 19 ++++-- frontend/src/hooks/queryBuilder/useOptions.ts | 66 ++++++++++++++++--- .../hooks/queryBuilder/useQueryOperations.ts | 11 +++- .../useSetCurrentKeyAndOperator.ts | 19 +++--- frontend/src/hooks/queryBuilder/useTag.ts | 23 ++++++- 9 files changed, 143 insertions(+), 38 deletions(-) diff --git a/frontend/src/container/LogExplorerQuerySection/index.tsx b/frontend/src/container/LogExplorerQuerySection/index.tsx index 3cd972c2fa..234d2b589f 100644 --- a/frontend/src/container/LogExplorerQuerySection/index.tsx +++ b/frontend/src/container/LogExplorerQuerySection/index.tsx @@ -1,5 +1,9 @@ import { Button } from 'antd'; -import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder'; +import { + initialQueriesMap, + OPERATORS, + PANEL_TYPES, +} from 'constants/queryBuilder'; import ExplorerOrderBy from 'container/ExplorerOrderBy'; import { QueryBuilder } from 'container/QueryBuilder'; import { OrderByFilterProps } from 'container/QueryBuilder/filters/OrderByFilter/OrderByFilter.interfaces'; @@ -30,6 +34,10 @@ function LogExplorerQuerySection(): JSX.Element { const isTable = panelTypes === PANEL_TYPES.TABLE; const config: QueryBuilderProps['filterConfigs'] = { stepInterval: { isHidden: isTable, isDisabled: true }, + filters: { + customKey: 'body', + customOp: OPERATORS.CONTAINS, + }, }; return config; diff --git a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts index 4758ad882b..0923395296 100644 --- a/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts +++ b/frontend/src/container/QueryBuilder/QueryBuilder.interfaces.ts @@ -1,10 +1,18 @@ import { PANEL_TYPES } from 'constants/queryBuilder'; +import { WhereClauseConfig } from 'hooks/queryBuilder/useAutoComplete'; import { ReactNode } from 'react'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource } from 'types/common/queryBuilder'; import { OrderByFilterProps } from './filters/OrderByFilter/OrderByFilter.interfaces'; +type FilterConfigs = { + [Key in keyof Omit]: { + isHidden: boolean; + isDisabled: boolean; + }; +} & { filters: WhereClauseConfig }; + export type QueryBuilderConfig = | { queryVariant: 'static'; @@ -16,8 +24,6 @@ export type QueryBuilderProps = { config?: QueryBuilderConfig; panelType: PANEL_TYPES; actions?: ReactNode; - filterConfigs?: Partial< - Record - >; + filterConfigs?: Partial; queryComponents?: { renderOrderBy?: (props: OrderByFilterProps) => ReactNode }; }; diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index e60c562200..4418daec5e 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -305,7 +305,11 @@ export const Query = memo(function Query({ )} - + diff --git a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx index 8b3e8e54ea..7111c1b0e8 100644 --- a/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx +++ b/frontend/src/container/QueryBuilder/filters/QueryBuilderSearch/index.tsx @@ -1,5 +1,8 @@ import { Select, Spin, Tag, Tooltip } from 'antd'; -import { useAutoComplete } from 'hooks/queryBuilder/useAutoComplete'; +import { + useAutoComplete, + WhereClauseConfig, +} from 'hooks/queryBuilder/useAutoComplete'; import { useFetchKeysAndValues } from 'hooks/queryBuilder/useFetchKeysAndValues'; import { KeyboardEvent, @@ -31,6 +34,7 @@ import { function QueryBuilderSearch({ query, onChange, + whereClauseConfig, }: QueryBuilderSearchProps): JSX.Element { const { updateTag, @@ -45,7 +49,7 @@ function QueryBuilderSearch({ isFetching, setSearchKey, searchKey, - } = useAutoComplete(query); + } = useAutoComplete(query, whereClauseConfig); const { sourceKeys, handleRemoveSourceKey } = useFetchKeysAndValues( searchValue, @@ -169,7 +173,7 @@ function QueryBuilderSearch({ notFoundContent={isFetching ? : null} > {options.map((option) => ( - + {option.label} {option.selected && } @@ -181,8 +185,13 @@ function QueryBuilderSearch({ interface QueryBuilderSearchProps { query: IBuilderQuery; onChange: (value: TagFilter) => void; + whereClauseConfig?: WhereClauseConfig; } +QueryBuilderSearch.defaultProps = { + whereClauseConfig: undefined, +}; + export interface CustomTagProps { label: ReactNode; value: string; diff --git a/frontend/src/hooks/queryBuilder/useAutoComplete.ts b/frontend/src/hooks/queryBuilder/useAutoComplete.ts index c7519761ec..e326a0e33f 100644 --- a/frontend/src/hooks/queryBuilder/useAutoComplete.ts +++ b/frontend/src/hooks/queryBuilder/useAutoComplete.ts @@ -1,7 +1,6 @@ import { getRemovePrefixFromKey, getTagToken, - isExistsNotExistsOperator, replaceStringWithMaxLength, tagRegexp, } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; @@ -16,7 +15,15 @@ import { useSetCurrentKeyAndOperator } from './useSetCurrentKeyAndOperator'; import { useTag } from './useTag'; import { useTagValidation } from './useTagValidation'; -export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => { +export type WhereClauseConfig = { + customKey: string; + customOp: string; +}; + +export const useAutoComplete = ( + query: IBuilderQuery, + whereClauseConfig?: WhereClauseConfig, +): IAutoComplete => { const [searchValue, setSearchValue] = useState(''); const [searchKey, setSearchKey] = useState(''); @@ -40,11 +47,11 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => { ); const { handleAddTag, handleClearTag, tags, updateTag } = useTag( - key, isValidTag, handleSearch, query, setSearchKey, + whereClauseConfig, ); const handleSelect = useCallback( @@ -59,11 +66,10 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => { }); } if (!isMulti) { - if (isExistsNotExistsOperator(value)) handleAddTag(value); - if (isValidTag && !isExistsNotExistsOperator(value)) handleAddTag(value); + handleAddTag(value); } }, - [handleAddTag, isMulti, isValidTag], + [handleAddTag, isMulti], ); const handleKeyDown = useCallback( @@ -102,6 +108,7 @@ export const useAutoComplete = (query: IBuilderQuery): IAutoComplete => { isExist, results, result, + whereClauseConfig, ); return { diff --git a/frontend/src/hooks/queryBuilder/useOptions.ts b/frontend/src/hooks/queryBuilder/useOptions.ts index 07a326852f..82dc2c1e24 100644 --- a/frontend/src/hooks/queryBuilder/useOptions.ts +++ b/frontend/src/hooks/queryBuilder/useOptions.ts @@ -7,8 +7,11 @@ import { transformStringWithPrefix } from 'lib/query/transformStringWithPrefix'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { WhereClauseConfig } from './useAutoComplete'; import { useOperators } from './useOperators'; +export const WHERE_CLAUSE_CUSTOM_SUFFIX = '-custom'; + export const useOptions = ( key: string, keys: BaseAutocompleteData[], @@ -19,6 +22,7 @@ export const useOptions = ( isExist: boolean, results: string[], result: string[], + whereClauseConfig?: WhereClauseConfig, ): Option[] => { const [options, setOptions] = useState([]); const operators = useOperators(key, keys); @@ -51,21 +55,64 @@ export const useOptions = ( [key, operator], ); + const getOptionsWithValidOperator = useCallback( + (key: string, results: string[], searchValue: string) => { + const hasAllResults = results.every((value) => result.includes(value)); + const values = getKeyOpValue(results); + + return hasAllResults + ? [ + { + label: searchValue, + value: searchValue, + }, + ] + : [ + { + label: searchValue, + value: searchValue, + }, + ...values, + ]; + }, + [getKeyOpValue, result], + ); + + const getKeyOperatorOptions = useCallback( + (key: string) => { + const operatorsOptions = operators?.map((operator) => ({ + value: `${key} ${operator} `, + label: `${key} ${operator} `, + })); + if (whereClauseConfig) { + return [ + { + label: `${searchValue} `, + value: `${searchValue}${WHERE_CLAUSE_CUSTOM_SUFFIX}`, + }, + ...operatorsOptions, + ]; + } + return operatorsOptions; + }, + [operators, searchValue, whereClauseConfig], + ); + useEffect(() => { let newOptions: Option[] = []; if (!key) { newOptions = searchValue ? [ - { label: `${searchValue} `, value: `${searchValue} ` }, + { + label: `${searchValue} `, + value: `${searchValue} `, + }, ...getOptionsFromKeys(keys), ] : getOptionsFromKeys(keys); } else if (key && !operator) { - newOptions = operators?.map((operator) => ({ - value: `${key} ${operator} `, - label: `${key} ${operator} `, - })); + newOptions = getKeyOperatorOptions(key); } else if (key && operator) { if (isMulti) { newOptions = results.map((item) => ({ @@ -75,17 +122,14 @@ export const useOptions = ( } else if (isExist) { newOptions = []; } else if (isValidOperator) { - const hasAllResults = results.every((value) => result.includes(value)); - const values = getKeyOpValue(results); - newOptions = hasAllResults - ? [{ label: searchValue, value: searchValue }] - : [{ label: searchValue, value: searchValue }, ...values]; + newOptions = getOptionsWithValidOperator(key, results, searchValue); } } if (newOptions.length > 0) { setOptions(newOptions); } }, [ + whereClauseConfig, getKeyOpValue, getOptionsFromKeys, isExist, @@ -98,6 +142,8 @@ export const useOptions = ( result, results, searchValue, + getKeyOperatorOptions, + getOptionsWithValidOperator, ]); return useMemo( diff --git a/frontend/src/hooks/queryBuilder/useQueryOperations.ts b/frontend/src/hooks/queryBuilder/useQueryOperations.ts index 04c2002d30..8e0251d0b5 100644 --- a/frontend/src/hooks/queryBuilder/useQueryOperations.ts +++ b/frontend/src/hooks/queryBuilder/useQueryOperations.ts @@ -63,9 +63,18 @@ export const useQueryOperations: UseQueryOperations = ({ const getNewListOfAdditionalFilters = useCallback( (dataSource: DataSource): string[] => { + const additionalFiltersKeys: (keyof Pick< + IBuilderQuery, + 'orderBy' | 'limit' | 'having' | 'stepInterval' + >)[] = ['having', 'limit', 'orderBy', 'stepInterval']; + const result: string[] = mapOfFilters[dataSource].reduce( (acc, item) => { - if (filterConfigs && filterConfigs[item.field]?.isHidden) { + if ( + filterConfigs && + filterConfigs[item.field as typeof additionalFiltersKeys[number]] + ?.isHidden + ) { return acc; } diff --git a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts index c848373b6c..2da205b349 100644 --- a/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts +++ b/frontend/src/hooks/queryBuilder/useSetCurrentKeyAndOperator.ts @@ -15,17 +15,14 @@ export const useSetCurrentKeyAndOperator = ( let key = ''; let operator = ''; let result: string[] = []; - - if (value) { - const { tagKey, tagOperator, tagValue } = getTagToken(value); - const isSuggestKey = keys?.some( - (el) => el?.key === getRemovePrefixFromKey(tagKey), - ); - if (isSuggestKey || keys.length === 0) { - key = tagKey || ''; - operator = tagOperator || ''; - result = tagValue || []; - } + const { tagKey, tagOperator, tagValue } = getTagToken(value); + const isSuggestKey = keys?.some( + (el) => el?.key === getRemovePrefixFromKey(tagKey), + ); + if (isSuggestKey || keys.length === 0) { + key = tagKey || ''; + operator = tagOperator || ''; + result = tagValue || []; } return [key, operator, result]; diff --git a/frontend/src/hooks/queryBuilder/useTag.ts b/frontend/src/hooks/queryBuilder/useTag.ts index 3bc272fc86..704d8796c1 100644 --- a/frontend/src/hooks/queryBuilder/useTag.ts +++ b/frontend/src/hooks/queryBuilder/useTag.ts @@ -1,5 +1,6 @@ import { getOperatorFromValue, + getTagToken, isExistsNotExistsOperator, isInNInOperator, } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils'; @@ -8,6 +9,8 @@ import * as Papa from 'papaparse'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; +import { WhereClauseConfig } from './useAutoComplete'; + type IUseTag = { handleAddTag: (value: string) => void; handleClearTag: (value: string) => void; @@ -24,11 +27,11 @@ type IUseTag = { */ export const useTag = ( - key: string, isValidTag: boolean, handleSearch: (value: string) => void, query: IBuilderQuery, setSearchKey: (value: string) => void, + whereClauseConfig?: WhereClauseConfig, ): IUseTag => { const initTagsData = useMemo( () => @@ -57,15 +60,31 @@ export const useTag = ( * Adds a new tag to the tag list. * @param {string} value - The tag value to be added. */ + const handleAddTag = useCallback( (value: string): void => { + const { tagKey } = getTagToken(value); + const [key, id] = tagKey.split('-'); + + if (id === 'custom') { + const customValue = whereClauseConfig + ? `${whereClauseConfig.customKey} ${whereClauseConfig.customOp} ${key}` + : ''; + setTags((prevTags) => + prevTags.includes(customValue) ? prevTags : [...prevTags, customValue], + ); + handleSearch(''); + setSearchKey(''); + return; + } + if ((value && key && isValidTag) || isExistsNotExistsOperator(value)) { setTags((prevTags) => [...prevTags, value]); handleSearch(''); setSearchKey(''); } }, - [key, isValidTag, handleSearch, setSearchKey], + [whereClauseConfig, isValidTag, handleSearch, setSearchKey], ); /** From b915f9ef7b6fac9e6c79cf95e9b5e48ebc8e5308 Mon Sep 17 00:00:00 2001 From: Vishal Sharma Date: Fri, 28 Jul 2023 10:00:16 +0530 Subject: [PATCH 14/21] feat: add new params labelsArray to series (#3214) --- .../app/clickhouseReader/reader.go | 31 +++++++++++++++---- pkg/query-service/model/v3/v3.go | 7 +++-- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/pkg/query-service/app/clickhouseReader/reader.go b/pkg/query-service/app/clickhouseReader/reader.go index f40f48c922..bb3072df12 100644 --- a/pkg/query-service/app/clickhouseReader/reader.go +++ b/pkg/query-service/app/clickhouseReader/reader.go @@ -4128,7 +4128,7 @@ func (r *ClickHouseReader) GetLogAttributeValues(ctx context.Context, req *v3.Fi } -func readRow(vars []interface{}, columnNames []string) ([]string, map[string]string, v3.Point) { +func readRow(vars []interface{}, columnNames []string) ([]string, map[string]string, []map[string]string, v3.Point) { // Each row will have a value and a timestamp, and an optional list of label values // example: {Timestamp: ..., Value: ...} // The timestamp may also not present in some cases where the time series is reduced to single value @@ -4138,6 +4138,7 @@ func readRow(vars []interface{}, columnNames []string) ([]string, map[string]str // example: ["frontend", "/fetch"] var groupBy []string + var groupAttributesArray []map[string]string // groupAttributes is a container to hold the key-value pairs for the current // metric point. // example: {"serviceName": "frontend", "operation": "/fetch"} @@ -4156,10 +4157,16 @@ func readRow(vars []interface{}, columnNames []string) ([]string, map[string]str } for key, val := range metric { groupBy = append(groupBy, val) + if _, ok := groupAttributes[key]; !ok { + groupAttributesArray = append(groupAttributesArray, map[string]string{key: val}) + } groupAttributes[key] = val } } else { groupBy = append(groupBy, *v) + if _, ok := groupAttributes[colName]; !ok { + groupAttributesArray = append(groupAttributesArray, map[string]string{colName: *v}) + } groupAttributes[colName] = *v } case *time.Time: @@ -4169,6 +4176,9 @@ func readRow(vars []interface{}, columnNames []string) ([]string, map[string]str point.Value = float64(reflect.ValueOf(v).Elem().Float()) } else { groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Float())) + if _, ok := groupAttributes[colName]; !ok { + groupAttributesArray = append(groupAttributesArray, map[string]string{colName: fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Float())}) + } groupAttributes[colName] = fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Float()) } case *uint8, *uint64, *uint16, *uint32: @@ -4176,6 +4186,9 @@ func readRow(vars []interface{}, columnNames []string) ([]string, map[string]str point.Value = float64(reflect.ValueOf(v).Elem().Uint()) } else { groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Uint())) + if _, ok := groupAttributes[colName]; !ok { + groupAttributesArray = append(groupAttributesArray, map[string]string{colName: fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Uint())}) + } groupAttributes[colName] = fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Uint()) } case *int8, *int16, *int32, *int64: @@ -4183,17 +4196,23 @@ func readRow(vars []interface{}, columnNames []string) ([]string, map[string]str point.Value = float64(reflect.ValueOf(v).Elem().Int()) } else { groupBy = append(groupBy, fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Int())) + if _, ok := groupAttributes[colName]; !ok { + groupAttributesArray = append(groupAttributesArray, map[string]string{colName: fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Int())}) + } groupAttributes[colName] = fmt.Sprintf("%v", reflect.ValueOf(v).Elem().Int()) } case *bool: groupBy = append(groupBy, fmt.Sprintf("%v", *v)) + if _, ok := groupAttributes[colName]; !ok { + groupAttributesArray = append(groupAttributesArray, map[string]string{colName: fmt.Sprintf("%v", *v)}) + } groupAttributes[colName] = fmt.Sprintf("%v", *v) default: zap.S().Errorf("unsupported var type %v found in query builder query result for column %s", v, colName) } } - return groupBy, groupAttributes, point + return groupBy, groupAttributes, groupAttributesArray, point } func readRowsForTimeSeriesResult(rows driver.Rows, vars []interface{}, columnNames []string) ([]*v3.Series, error) { @@ -4226,25 +4245,25 @@ func readRowsForTimeSeriesResult(rows driver.Rows, vars []interface{}, columnNam // "order,/order": {"serviceName": "order", "operation": "/order"}, // } seriesToAttrs := make(map[string]map[string]string) - + labelsArray := make(map[string][]map[string]string) for rows.Next() { if err := rows.Scan(vars...); err != nil { return nil, err } - groupBy, groupAttributes, metricPoint := readRow(vars, columnNames) + groupBy, groupAttributes, groupAttributesArray, metricPoint := readRow(vars, columnNames) sort.Strings(groupBy) key := strings.Join(groupBy, "") if _, exists := seriesToAttrs[key]; !exists { keys = append(keys, key) } seriesToAttrs[key] = groupAttributes + labelsArray[key] = groupAttributesArray seriesToPoints[key] = append(seriesToPoints[key], metricPoint) } var seriesList []*v3.Series for _, key := range keys { points := seriesToPoints[key] - // find the grouping sets point for the series // this is the point with the zero timestamp // if there is no such point, then the series is not grouped @@ -4258,7 +4277,7 @@ func readRowsForTimeSeriesResult(rows driver.Rows, vars []interface{}, columnNam break } } - series := v3.Series{Labels: seriesToAttrs[key], Points: points, GroupingSetsPoint: groupingSetsPoint} + series := v3.Series{Labels: seriesToAttrs[key], Points: points, GroupingSetsPoint: groupingSetsPoint, LabelsArray: labelsArray[key]} seriesList = append(seriesList, &series) } return seriesList, nil diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index 4e4fd2c0a1..d371273017 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -593,9 +593,10 @@ type LogsLiveTailClient struct { } type Series struct { - Labels map[string]string `json:"labels"` - Points []Point `json:"values"` - GroupingSetsPoint *Point `json:"-"` + Labels map[string]string `json:"labels"` + LabelsArray []map[string]string `json:"labelsArray"` + Points []Point `json:"values"` + GroupingSetsPoint *Point `json:"-"` } func (s *Series) SortPoints() { From 203eef8cde5e6eb5bdc279ab47b66a28af3997f7 Mon Sep 17 00:00:00 2001 From: Nityananda Gohain Date: Fri, 28 Jul 2023 13:56:36 +0530 Subject: [PATCH 15/21] feat: hotfix check if limit is not zero (#3221) * feat: hotfix check if limit is not zero * fix: move to common function --- .../app/logs/v3/query_builder.go | 3 ++ .../app/logs/v3/query_builder_test.go | 33 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/pkg/query-service/app/logs/v3/query_builder.go b/pkg/query-service/app/logs/v3/query_builder.go index 1b84f71fcc..4c6ee73bad 100644 --- a/pkg/query-service/app/logs/v3/query_builder.go +++ b/pkg/query-service/app/logs/v3/query_builder.go @@ -398,6 +398,9 @@ func reduceQuery(query string, reduceTo v3.ReduceToOperator, aggregateOperator v } func addLimitToQuery(query string, limit uint64) string { + if limit == 0 { + return query + } return fmt.Sprintf("%s LIMIT %d", query, limit) } diff --git a/pkg/query-service/app/logs/v3/query_builder_test.go b/pkg/query-service/app/logs/v3/query_builder_test.go index 2e5f240dc6..361c1cefa7 100644 --- a/pkg/query-service/app/logs/v3/query_builder_test.go +++ b/pkg/query-service/app/logs/v3/query_builder_test.go @@ -1140,6 +1140,39 @@ var testPrepLogsQueryData = []struct { ExpectedQuery: "SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, body,CAST((attributes_string_key, attributes_string_value), 'Map(String, String)') as attributes_string,CAST((attributes_int64_key, attributes_int64_value), 'Map(String, Int64)') as attributes_int64,CAST((attributes_float64_key, attributes_float64_value), 'Map(String, Float64)') as attributes_float64,CAST((resources_string_key, resources_string_value), 'Map(String, String)') as resources_string from signoz_logs.distributed_logs where %s", Options: Options{IsLivetailQuery: true}, }, + { + Name: "Table query w/o limit", + PanelType: v3.PanelTypeTable, + Start: 1680066360726210000, + End: 1680066458000000000, + Step: 60, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateOperator: v3.AggregateOperatorCount, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}, + }, + TableName: "logs", + ExpectedQuery: "SELECT now() as ts, toFloat64(count(*)) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) order by value DESC", + Options: Options{}, + }, + { + Name: "Table query with limit", + PanelType: v3.PanelTypeTable, + Start: 1680066360726210000, + End: 1680066458000000000, + Step: 60, + BuilderQuery: &v3.BuilderQuery{ + QueryName: "A", + AggregateOperator: v3.AggregateOperatorCount, + Expression: "A", + Filters: &v3.FilterSet{Operator: "AND", Items: []v3.FilterItem{}}, + Limit: 10, + }, + TableName: "logs", + ExpectedQuery: "SELECT now() as ts, toFloat64(count(*)) as value from signoz_logs.distributed_logs where (timestamp >= 1680066360726210000 AND timestamp <= 1680066458000000000) order by value DESC LIMIT 10", + Options: Options{}, + }, } func TestPrepareLogsQuery(t *testing.T) { From 4753868298b9103fe41cfb9dfd4a13740cb5852c Mon Sep 17 00:00:00 2001 From: vasukapil Date: Fri, 28 Jul 2023 14:33:07 +0530 Subject: [PATCH 16/21] Remove having filter (#3219) --- .../LogExplorerQuerySection/index.tsx | 2 ++ .../QueryBuilder/components/Query/Query.tsx | 24 +++++++++++-------- .../TracesExplorer/QuerySection/index.tsx | 4 +++- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/frontend/src/container/LogExplorerQuerySection/index.tsx b/frontend/src/container/LogExplorerQuerySection/index.tsx index 234d2b589f..165bc8c20f 100644 --- a/frontend/src/container/LogExplorerQuerySection/index.tsx +++ b/frontend/src/container/LogExplorerQuerySection/index.tsx @@ -32,8 +32,10 @@ function LogExplorerQuerySection(): JSX.Element { const filterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => { const isTable = panelTypes === PANEL_TYPES.TABLE; + const isList = panelTypes === PANEL_TYPES.LIST; const config: QueryBuilderProps['filterConfigs'] = { stepInterval: { isHidden: isTable, isDisabled: true }, + having: { isHidden: isList, isDisabled: true }, filters: { customKey: 'body', customOp: OPERATORS.CONTAINS, diff --git a/frontend/src/container/QueryBuilder/components/Query/Query.tsx b/frontend/src/container/QueryBuilder/components/Query/Query.tsx index 4418daec5e..7f8b4c1cdc 100644 --- a/frontend/src/container/QueryBuilder/components/Query/Query.tsx +++ b/frontend/src/container/QueryBuilder/components/Query/Query.tsx @@ -220,16 +220,19 @@ export const Query = memo(function Query({ - - - - - - - - - - + {!filterConfigs?.having?.isHidden && ( + + + + + + + + + + + )} + @@ -248,6 +251,7 @@ export const Query = memo(function Query({ panelType, isMetricsDataSource, query, + filterConfigs?.having?.isHidden, handleChangeLimit, handleChangeHavingFilter, renderOrderByFilter, diff --git a/frontend/src/container/TracesExplorer/QuerySection/index.tsx b/frontend/src/container/TracesExplorer/QuerySection/index.tsx index 81514b496d..80f2925b37 100644 --- a/frontend/src/container/TracesExplorer/QuerySection/index.tsx +++ b/frontend/src/container/TracesExplorer/QuerySection/index.tsx @@ -17,12 +17,14 @@ function QuerySection(): JSX.Element { const panelTypes = useGetPanelTypesQueryParam(PANEL_TYPES.LIST); const filterConfigs: QueryBuilderProps['filterConfigs'] = useMemo(() => { + const isList = panelTypes === PANEL_TYPES.LIST; const config: QueryBuilderProps['filterConfigs'] = { stepInterval: { isHidden: false, isDisabled: true }, + having: { isHidden: isList, isDisabled: true }, }; return config; - }, []); + }, [panelTypes]); const renderOrderBy = useCallback( ({ query, onChange }: OrderByFilterProps) => ( From a2c03243cb36643893864bb5277d8749653be7f1 Mon Sep 17 00:00:00 2001 From: Palash Gupta Date: Fri, 28 Jul 2023 17:39:45 +0530 Subject: [PATCH 17/21] chore: import_grafana_json is disabled (#3224) --- frontend/src/container/ListOfDashboard/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/container/ListOfDashboard/index.tsx b/frontend/src/container/ListOfDashboard/index.tsx index 60da406603..bcd5d81fca 100644 --- a/frontend/src/container/ListOfDashboard/index.tsx +++ b/frontend/src/container/ListOfDashboard/index.tsx @@ -224,6 +224,7 @@ function ListOfAllDashboard(): JSX.Element { key: t('import_grafana_json').toString(), label: t('import_grafana_json'), onClick: (): void => onModalHandler(true), + disabled: true, }); return menuItems; From 5469cd34fad6a688cc49c73da4f639dd47e60b6a Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Fri, 28 Jul 2023 18:44:46 +0300 Subject: [PATCH 18/21] fix: column name with legend (#3200) * fix: column name with legend * fix: render columns with data * fix: index * fix: render rows * fix: remove log * fix: return operators for entiry metric * fix: remove noop and rate for table metric * fix: for request * chore: allow count attribute to be empty --------- Co-authored-by: Srikanth Chekuri --- frontend/src/constants/queryBuilder.ts | 2 +- .../getOperatorsBySourceAndPanelType.ts | 13 +- .../lib/query/createTableColumnsFromQuery.ts | 176 ++++++++++++------ pkg/query-service/model/v3/v3.go | 3 +- 4 files changed, 130 insertions(+), 64 deletions(-) diff --git a/frontend/src/constants/queryBuilder.ts b/frontend/src/constants/queryBuilder.ts index 509b97340c..515be4d080 100644 --- a/frontend/src/constants/queryBuilder.ts +++ b/frontend/src/constants/queryBuilder.ts @@ -125,7 +125,7 @@ export const initialFilters: TagFilter = { export const initialQueryBuilderFormValues: IBuilderQuery = { dataSource: DataSource.METRICS, queryName: createNewBuilderItemName({ existNames: [], sourceNames: alphabet }), - aggregateOperator: MetricAggregateOperator.NOOP, + aggregateOperator: MetricAggregateOperator.COUNT, aggregateAttribute: initialAutocompleteData, filters: { items: [], op: 'AND' }, expression: createNewBuilderItemName({ diff --git a/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts b/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts index e415d8f7f4..e189275802 100644 --- a/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts +++ b/frontend/src/lib/newQueryBuilder/getOperatorsBySourceAndPanelType.ts @@ -1,5 +1,9 @@ import { mapOfOperators, PANEL_TYPES } from 'constants/queryBuilder'; -import { DataSource, StringOperators } from 'types/common/queryBuilder'; +import { + DataSource, + MetricAggregateOperator, + StringOperators, +} from 'types/common/queryBuilder'; import { SelectOption } from 'types/common/select'; type GetQueryOperatorsParams = { @@ -19,6 +23,13 @@ export const getOperatorsBySourceAndPanelType = ({ (operator) => operator.value === StringOperators.NOOP, ); } + if (panelType === PANEL_TYPES.TABLE && dataSource === DataSource.METRICS) { + operatorsByDataSource = operatorsByDataSource.filter( + (operator) => + operator.value !== MetricAggregateOperator.NOOP && + operator.value !== MetricAggregateOperator.RATE, + ); + } if ( dataSource !== DataSource.METRICS && panelType !== PANEL_TYPES.LIST && diff --git a/frontend/src/lib/query/createTableColumnsFromQuery.ts b/frontend/src/lib/query/createTableColumnsFromQuery.ts index 2241c166f1..bf95335016 100644 --- a/frontend/src/lib/query/createTableColumnsFromQuery.ts +++ b/frontend/src/lib/query/createTableColumnsFromQuery.ts @@ -6,7 +6,6 @@ import { } from 'constants/queryBuilder'; import { FORMULA_REGEXP } from 'constants/regExp'; import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces'; -import { toCapitalize } from 'lib/toCapitalize'; import { ReactNode } from 'react'; import { IBuilderFormula, @@ -145,7 +144,7 @@ const addOperatorFormulaColumns = ( let formulaLabel = `${formulaQuery.queryName}(${formulaQuery.expression})`; if (formulaQuery.legend) { - formulaLabel += ` - ${formulaQuery.legend}`; + formulaLabel = formulaQuery.legend; } const formulaColumn: DynamicColumn = { @@ -171,18 +170,14 @@ const addOperatorFormulaColumns = ( } if (currentQueryData.legend) { - operatorLabel += ` - ${currentQueryData.legend}`; - } else { - operatorLabel += ` - ${currentQueryData.queryName}`; + operatorLabel = currentQueryData.legend; } - const resultValue = `${toCapitalize(operatorLabel)}`; - const operatorColumn: DynamicColumn = { query, field: currentQueryData.queryName, dataIndex: currentQueryData.queryName, - title: customLabel || resultValue, + title: customLabel || operatorLabel, data: [], type: 'operator', // sortable: isNumber, @@ -218,26 +213,24 @@ const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => { const dynamicColumns: DynamicColumns = []; queryTableData.forEach((currentQuery) => { + const { series, queryName, list } = currentQuery; + const currentStagedQuery = getQueryByName( query.builder, - currentQuery.queryName, - isFormula(currentQuery.queryName) ? 'queryFormulas' : 'queryData', + queryName, + isFormula(queryName) ? 'queryFormulas' : 'queryData', ); - if (currentQuery.list) { - currentQuery.list.forEach((listItem) => { + if (list) { + list.forEach((listItem) => { Object.keys(listItem.data).forEach((label) => { addListLabels(currentStagedQuery, label as ListItemKey, dynamicColumns); }); }); } - if (currentQuery.series) { - const isValuesColumnExist = currentQuery.series.some( - (item) => item.values.length > 0, - ); - const isEveryValuesExist = currentQuery.series.every( - (item) => item.values.length > 0, - ); + if (series) { + const isValuesColumnExist = series.some((item) => item.values.length > 0); + const isEveryValuesExist = series.every((item) => item.values.length > 0); if (isValuesColumnExist) { addOperatorFormulaColumns( @@ -247,7 +240,7 @@ const getDynamicColumns: GetDynamicColumns = (queryTableData, query) => { ); } - currentQuery.series.forEach((seria) => { + series.forEach((seria) => { Object.keys(seria.labels).forEach((label) => { if (label === currentQuery?.queryName) return; @@ -277,49 +270,105 @@ const fillEmptyRowCells = ( }); }; -const fillData = ( - seria: SeriesItem, - columns: DynamicColumns, - queryName: string, - value?: SeriesItem['values'][number], -): void => { - const labelEntries = Object.entries(seria.labels); +const findSeriaValueFromAnotherQuery = ( + currentLabels: Record, + nextQuery: QueryDataV3 | null, +): SeriesItem | null => { + if (!nextQuery || !nextQuery.series) return null; - const unusedColumnsKeys = new Set( - columns.map((item) => item.field), - ); + let value = null; - columns.forEach((column) => { - if (queryName === column.field && value) { - column.data.push(parseFloat(value.value).toFixed(2)); - unusedColumnsKeys.delete(column.field); - return; + const labelEntries = Object.entries(currentLabels); + + nextQuery.series.forEach((seria) => { + const localLabelEntries = Object.entries(seria.labels); + if (localLabelEntries.length !== labelEntries.length) return; + + const isExistLabels = localLabelEntries.find(([key, value]) => + labelEntries.find( + ([currentKey, currentValue]) => + currentKey === key && currentValue === value, + ), + ); + + if (isExistLabels) { + value = seria; } - - labelEntries.forEach(([key, currentValue]) => { - if (column.field === key) { - column.data.push(currentValue); - unusedColumnsKeys.delete(key); - } - }); - - fillEmptyRowCells(unusedColumnsKeys, columns, column); }); + + return value; }; -const fillDataFromSeria = ( - seria: SeriesItem, - columns: DynamicColumns, +const isEqualQueriesByLabel = ( + equalQueries: string[], queryName: string, +): boolean => equalQueries.includes(queryName); + +const fillDataFromSeries = ( + currentQuery: QueryDataV3, + queryTableData: QueryDataV3[], + columns: DynamicColumns, + equalQueriesByLabels: string[], + // TODO: fix it + // eslint-disable-next-line sonarjs/cognitive-complexity ): void => { - if (seria.values.length === 0) { - fillData(seria, columns, queryName); + const { series, queryName } = currentQuery; + const isEqualQuery = isEqualQueriesByLabel(equalQueriesByLabels, queryName); - return; - } + if (!series) return; - seria.values.forEach((value) => { - fillData(seria, columns, queryName, value); + series.forEach((seria) => { + const labelEntries = Object.entries(seria.labels); + + const unusedColumnsKeys = new Set( + columns.map((item) => item.field), + ); + + columns.forEach((column) => { + if (queryName === column.field) { + if (seria.values.length === 0) return; + + column.data.push(parseFloat(seria.values[0].value).toFixed(2)); + unusedColumnsKeys.delete(column.field); + return; + } + + if (column.type !== 'field' && column.field !== queryName) { + const nextQueryData = + queryTableData.find((q) => q.queryName === column.field) || null; + + const targetSeria = findSeriaValueFromAnotherQuery( + seria.labels, + nextQueryData, + ); + + if (targetSeria) { + const isEqual = isEqualQueriesByLabel(equalQueriesByLabels, column.field); + if (!isEqual) { + equalQueriesByLabels.push(column.field); + } + + column.data.push(parseFloat(targetSeria.values[0].value).toFixed(2)); + } else { + column.data.push('N/A'); + } + + unusedColumnsKeys.delete(column.field); + + return; + } + + if (isEqualQuery) return; + + labelEntries.forEach(([key, currentValue]) => { + if (column.field === key) { + column.data.push(currentValue); + unusedColumnsKeys.delete(key); + } + }); + + fillEmptyRowCells(unusedColumnsKeys, columns, column); + }); }); }; @@ -348,15 +397,20 @@ const fillColumnsData: FillColumnData = (queryTableData, cols) => { const formulas = cols.filter((item) => item.type === 'formula'); const resultColumns = [...fields, ...operators, ...formulas]; - queryTableData.forEach((currentQuery) => { - if (currentQuery.series) { - currentQuery.series.forEach((seria) => { - fillDataFromSeria(seria, resultColumns, currentQuery.queryName); - }); - } + const equalQueriesByLabels: string[] = []; - if (currentQuery.list) { - currentQuery.list.forEach((listItem) => { + queryTableData.forEach((currentQuery) => { + const { list } = currentQuery; + + fillDataFromSeries( + currentQuery, + queryTableData, + resultColumns, + equalQueriesByLabels, + ); + + if (list) { + list.forEach((listItem) => { fillDataFromList(listItem, resultColumns); }); } diff --git a/pkg/query-service/model/v3/v3.go b/pkg/query-service/model/v3/v3.go index d371273017..1fb4cf8e60 100644 --- a/pkg/query-service/model/v3/v3.go +++ b/pkg/query-service/model/v3/v3.go @@ -107,7 +107,8 @@ func (a AggregateOperator) RequireAttribute(dataSource DataSource) bool { switch dataSource { case DataSourceMetrics: switch a { - case AggregateOperatorNoOp: + case AggregateOperatorNoOp, + AggregateOperatorCount: return false default: return true From 5c83d133a2fbee3b7ee8df96a2674a275f547509 Mon Sep 17 00:00:00 2001 From: Yevhen Shevchenko <90138953+yeshev@users.noreply.github.com> Date: Fri, 28 Jul 2023 19:14:07 +0300 Subject: [PATCH 19/21] fix: table resize (#3227) --- frontend/src/container/QueryTable/config.ts | 3 +++ frontend/src/lib/query/createTableColumnsFromQuery.ts | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 frontend/src/container/QueryTable/config.ts diff --git a/frontend/src/container/QueryTable/config.ts b/frontend/src/container/QueryTable/config.ts new file mode 100644 index 0000000000..6fd3312147 --- /dev/null +++ b/frontend/src/container/QueryTable/config.ts @@ -0,0 +1,3 @@ +import { CSSProperties } from 'styled-components'; + +export const QUERY_TABLE_CONFIG: CSSProperties = { width: 145 }; diff --git a/frontend/src/lib/query/createTableColumnsFromQuery.ts b/frontend/src/lib/query/createTableColumnsFromQuery.ts index bf95335016..4e830ce641 100644 --- a/frontend/src/lib/query/createTableColumnsFromQuery.ts +++ b/frontend/src/lib/query/createTableColumnsFromQuery.ts @@ -5,6 +5,7 @@ import { initialQueryBuilderFormValues, } from 'constants/queryBuilder'; import { FORMULA_REGEXP } from 'constants/regExp'; +import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config'; import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces'; import { ReactNode } from 'react'; import { @@ -453,6 +454,7 @@ const generateTableColumns = ( const column: ColumnType = { dataIndex: item.dataIndex, title: item.title, + width: QUERY_TABLE_CONFIG.width, render: renderColumnCell && renderColumnCell[item.dataIndex], // sorter: item.sortable // ? (a: RowData, b: RowData): number => From bc4a4edc7f8ac5d2b1bbcca642927df1cc37c9af Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Fri, 28 Jul 2023 21:54:36 +0530 Subject: [PATCH 20/21] Key Operation to Metrics using USE_SPAN_METRIC feature flag (#3188) * refactor: generic querybuilderWithFormula * refactor: added generic datasource * refactor: dynamic disabled in getQueryBuilderQueriesWithFormula * refactor: generic legend for building query with formulas * feat: added new TopOperationMetrics component for key operation * refactor: added feature flag for key operation * refactor: shifted types and fixed typos * refactor: separated types and renamed file * refactor: one on one mapping * refactor: added clickable link for navigating to traces * chore: separated types * chore: removed unnecessary comments * refactor: one on one mapping for DBCallQueries * refactor: seperated types and one on one mapping for externalQueries * refactor: separate type from metricsPagesQueriesFactory * refactor: separated types and one on one mapping for overviewQueries * refactor: remove the type inforcement from TopOperationQueries.ts * refactor: one on one mapping in TopOperationQueries.ts * refactor: one on one mapping and remove the unwanted code * refactor: shifted logic of navigating to traces to utils * refactor: separated renderColumnCell from the TopOperationMetric component * refactor: generic tableRenderer * refactor: made getTableColumnRenderer more generic * chore: title is updated --------- Co-authored-by: Palash Gupta --- .../MetricsApplication.factory.ts | 12 +- .../MetricsPageQueries/DBCallQueries.ts | 42 ++++-- .../MetricsPageQueries/ExternalQueries.ts | 96 ++++++++---- .../MetricsPageQueriesFactory.ts | 104 ++++--------- .../MetricsPageQueries/OverviewQueries.ts | 80 +++++----- .../MetricsPageQueries/TopOperationQueries.ts | 142 ++++++++++++++++++ .../MetricsApplication/Tabs/DBCall.tsx | 19 ++- .../MetricsApplication/Tabs/External.tsx | 37 +++-- .../MetricsApplication/Tabs/Overview.tsx | 30 ++-- .../Tabs/Overview/ServiceOverview.tsx | 10 +- .../Overview/TableRenderer/ColumnWithLink.tsx | 39 +++++ .../TableRenderer/TableColumnRenderer.tsx | 11 ++ .../Tabs/Overview/TopOperation.tsx | 9 +- .../Tabs/Overview/TopOperationMetrics.tsx | 122 +++++++++++++++ .../Tabs/Overview/config.ts | 1 + .../MetricsApplication/Tabs/types.ts | 59 ++++++++ .../MetricsApplication/TopOperationsTable.tsx | 20 +-- .../container/MetricsApplication/constant.ts | 8 + .../src/container/MetricsApplication/types.ts | 15 ++ .../src/container/MetricsApplication/utils.ts | 22 +++ 20 files changed, 676 insertions(+), 202 deletions(-) create mode 100644 frontend/src/container/MetricsApplication/MetricsPageQueries/TopOperationQueries.ts create mode 100644 frontend/src/container/MetricsApplication/Tabs/Overview/TableRenderer/ColumnWithLink.tsx create mode 100644 frontend/src/container/MetricsApplication/Tabs/Overview/TableRenderer/TableColumnRenderer.tsx create mode 100644 frontend/src/container/MetricsApplication/Tabs/Overview/TopOperationMetrics.tsx create mode 100644 frontend/src/container/MetricsApplication/Tabs/Overview/config.ts create mode 100644 frontend/src/container/MetricsApplication/types.ts diff --git a/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts b/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts index 880d9edba9..26400a7541 100644 --- a/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts +++ b/frontend/src/container/MetricsApplication/MetricsApplication.factory.ts @@ -1,17 +1,19 @@ -import { PANEL_TYPES } from 'constants/queryBuilder'; import { Widgets } from 'types/api/dashboard/getAll'; import { v4 } from 'uuid'; -export const getWidgetQueryBuilder = ( - query: Widgets['query'], +import { GetWidgetQueryBuilderProps } from './types'; + +export const getWidgetQueryBuilder = ({ + query, title = '', -): Widgets => ({ + panelTypes, +}: GetWidgetQueryBuilderProps): Widgets => ({ description: '', id: v4(), isStacked: false, nullZeroValues: '', opacity: '0', - panelTypes: PANEL_TYPES.TIME_SERIES, + panelTypes, query, timePreferance: 'GLOBAL_TIME', title, diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts index c1896b6884..72b9703cdd 100644 --- a/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/DBCallQueries.ts @@ -1,7 +1,11 @@ import { OPERATORS } from 'constants/queryBuilder'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource, QueryBuilderData } from 'types/common/queryBuilder'; +import { + DataSource, + MetricAggregateOperator, + QueryBuilderData, +} from 'types/common/queryBuilder'; import { DataType, FORMULA, MetricsType, WidgetKeys } from '../constant'; import { IServiceName } from '../Tabs/types'; @@ -44,13 +48,14 @@ export const databaseCallsRPS = ({ ]; const legends = [legend]; + const dataSource = DataSource.METRICS; return getQueryBuilderQueries({ autocompleteData, groupBy, legends, filterItems, - dataSource: DataSource.METRICS, + dataSource, }); }; @@ -85,17 +90,36 @@ export const databaseCallsAvgDuration = ({ }, ...tagFilterItems, ]; - const additionalItemsB = additionalItemsA; - return getQueryBuilderQuerieswithFormula({ + const autocompleteData: BaseAutocompleteData[] = [ autocompleteDataA, autocompleteDataB, + ]; + + const additionalItems: TagFilterItem[][] = [ additionalItemsA, - additionalItemsB, - legend: '', - disabled: true, - expression: FORMULA.DATABASE_CALLS_AVG_DURATION, - legendFormula: 'Average Duration', + additionalItemsA, + ]; + + const legends = ['', '']; + const disabled = [true, true]; + const legendFormula = 'Average Duration'; + const expression = FORMULA.DATABASE_CALLS_AVG_DURATION; + const aggregateOperators = [ + MetricAggregateOperator.SUM, + MetricAggregateOperator.SUM, + ]; + const dataSource = DataSource.METRICS; + + return getQueryBuilderQuerieswithFormula({ + autocompleteData, + additionalItems, + legends, + disabled, + expression, + legendFormula, + aggregateOperators, + dataSource, }); }; diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts index 1d375f2e53..b140882c46 100644 --- a/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/ExternalQueries.ts @@ -1,10 +1,17 @@ import { OPERATORS } from 'constants/queryBuilder'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource, QueryBuilderData } from 'types/common/queryBuilder'; +import { + DataSource, + MetricAggregateOperator, + QueryBuilderData, +} from 'types/common/queryBuilder'; import { DataType, FORMULA, MetricsType, WidgetKeys } from '../constant'; -import { IServiceName } from '../Tabs/types'; +import { + ExternalCallDurationByAddressProps, + ExternalCallProps, +} from '../Tabs/types'; import { getQueryBuilderQueries, getQueryBuilderQuerieswithFormula, @@ -36,6 +43,7 @@ export const externalCallErrorPercent = ({ isColumn: true, type: null, }; + const additionalItemsA: TagFilterItem[] = [ { id: '', @@ -71,23 +79,38 @@ export const externalCallErrorPercent = ({ type: MetricsType.Resource, }, op: OPERATORS.IN, - value: [`${servicename}`], + value: [servicename], }, ...tagFilterItems, ]; + const legendFormula = legend; const expression = FORMULA.ERROR_PERCENTAGE; - const disabled = true; - return getQueryBuilderQuerieswithFormula({ + const autocompleteData: BaseAutocompleteData[] = [ autocompleteDataA, autocompleteDataB, + ]; + + const additionalItems: TagFilterItem[][] = [ additionalItemsA, additionalItemsB, - legend, + ]; + + const legends = Array(2).fill(legend); + const aggregateOperators = Array(2).fill(MetricAggregateOperator.SUM); + const disabled = Array(2).fill(true); + const dataSource = DataSource.METRICS; + + return getQueryBuilderQuerieswithFormula({ + autocompleteData, + additionalItems, + legends, groupBy, disabled, expression, legendFormula, + aggregateOperators, + dataSource, }); }; @@ -107,10 +130,11 @@ export const externalCallDuration = ({ key: WidgetKeys.SignozExternalCallLatencyCount, type: null, }; + const expression = FORMULA.DATABASE_CALLS_AVG_DURATION; const legendFormula = 'Average Duration'; const legend = ''; - const disabled = true; + const disabled = Array(2).fill(true); const additionalItemsA: TagFilterItem[] = [ { id: '', @@ -125,17 +149,29 @@ export const externalCallDuration = ({ }, ...tagFilterItems, ]; - const additionalItemsB = additionalItemsA; - return getQueryBuilderQuerieswithFormula({ + const autocompleteData: BaseAutocompleteData[] = [ autocompleteDataA, autocompleteDataB, + ]; + + const additionalItems: TagFilterItem[][] = [ additionalItemsA, - additionalItemsB, - legend, + additionalItemsA, + ]; + + const legends = Array(2).fill(legend); + const aggregateOperators = Array(2).fill(MetricAggregateOperator.SUM); + + return getQueryBuilderQuerieswithFormula({ + autocompleteData, + additionalItems, + legends, disabled, expression, legendFormula, + aggregateOperators, + dataSource: DataSource.METRICS, }); }; @@ -169,13 +205,15 @@ export const externalCallRpsByAddress = ({ ], ]; - const legends: string[] = [legend]; + const legends = [legend]; + const dataSource = DataSource.METRICS; + return getQueryBuilderQueries({ autocompleteData, groupBy, legends, filterItems, - dataSource: DataSource.METRICS, + dataSource, }); }; @@ -198,7 +236,7 @@ export const externalCallDurationByAddress = ({ }; const expression = FORMULA.DATABASE_CALLS_AVG_DURATION; const legendFormula = legend; - const disabled = true; + const disabled = [true, true]; const additionalItemsA: TagFilterItem[] = [ { id: '', @@ -213,26 +251,30 @@ export const externalCallDurationByAddress = ({ }, ...tagFilterItems, ]; - const additionalItemsB = additionalItemsA; - return getQueryBuilderQuerieswithFormula({ + const autocompleteData: BaseAutocompleteData[] = [ autocompleteDataA, autocompleteDataB, + ]; + + const additionalItems: TagFilterItem[][] = [ additionalItemsA, - additionalItemsB, - legend, + additionalItemsA, + ]; + + const legends = Array(2).fill(legend); + const aggregateOperators = Array(2).fill(MetricAggregateOperator.SUM_RATE); + const dataSource = DataSource.METRICS; + + return getQueryBuilderQuerieswithFormula({ + autocompleteData, + additionalItems, + legends, groupBy, disabled, expression, legendFormula, + aggregateOperators, + dataSource, }); }; - -interface ExternalCallDurationByAddressProps extends ExternalCallProps { - legend: string; -} - -export interface ExternalCallProps { - servicename: IServiceName['servicename']; - tagFilterItems: TagFilterItem[]; -} diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts index 58f535cd04..2412dfce47 100644 --- a/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/MetricsPageQueriesFactory.ts @@ -1,20 +1,21 @@ import { + alphabet, initialFormulaBuilderFormValues, initialQueryBuilderFormValuesMap, } from 'constants/queryBuilder'; import getStep from 'lib/getStep'; import store from 'store'; -import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import { - IBuilderQuery, - TagFilterItem, -} from 'types/api/queryBuilder/queryBuilderData'; -import { - DataSource, MetricAggregateOperator, QueryBuilderData, } from 'types/common/queryBuilder'; +import { + BuilderQueriesProps, + BuilderQuerieswithFormulaProps, +} from '../Tabs/types'; + export const getQueryBuilderQueries = ({ autocompleteData, groupBy = [], @@ -61,15 +62,15 @@ export const getQueryBuilderQueries = ({ }); export const getQueryBuilderQuerieswithFormula = ({ - autocompleteDataA, - autocompleteDataB, - additionalItemsA, - additionalItemsB, - legend, + autocompleteData, + additionalItems, + legends, groupBy = [], disabled, expression, legendFormula, + aggregateOperators, + dataSource, }: BuilderQuerieswithFormulaProps): QueryBuilderData => ({ queryFormulas: [ { @@ -78,66 +79,25 @@ export const getQueryBuilderQuerieswithFormula = ({ legend: legendFormula, }, ], - queryData: [ - { - ...initialQueryBuilderFormValuesMap.metrics, - aggregateOperator: MetricAggregateOperator.SUM_RATE, - disabled, - groupBy, - legend, - aggregateAttribute: autocompleteDataA, - reduceTo: 'sum', - filters: { - items: additionalItemsA, - op: 'AND', - }, - stepInterval: getStep({ - end: store.getState().globalTime.maxTime, - inputFormat: 'ns', - start: store.getState().globalTime.minTime, - }), + queryData: autocompleteData.map((_, index) => ({ + ...initialQueryBuilderFormValuesMap.metrics, + aggregateOperator: aggregateOperators[index], + disabled: disabled[index], + groupBy, + legend: legends[index], + aggregateAttribute: autocompleteData[index], + queryName: alphabet[index], + expression: alphabet[index], + reduceTo: 'sum', + filters: { + items: additionalItems[index], + op: 'AND', }, - { - ...initialQueryBuilderFormValuesMap.metrics, - aggregateOperator: MetricAggregateOperator.SUM_RATE, - disabled, - groupBy, - legend, - aggregateAttribute: autocompleteDataB, - queryName: 'B', - expression: 'B', - reduceTo: 'sum', - filters: { - items: additionalItemsB, - op: 'AND', - }, - stepInterval: getStep({ - end: store.getState().globalTime.maxTime, - inputFormat: 'ns', - start: store.getState().globalTime.minTime, - }), - }, - ], + stepInterval: getStep({ + end: store.getState().globalTime.maxTime, + inputFormat: 'ns', + start: store.getState().globalTime.minTime, + }), + dataSource, + })), }); - -interface BuilderQueriesProps { - autocompleteData: BaseAutocompleteData[]; - groupBy?: BaseAutocompleteData[]; - legends: string[]; - filterItems: TagFilterItem[][]; - aggregateOperator?: string[]; - dataSource: DataSource; - queryNameAndExpression?: string[]; -} - -interface BuilderQuerieswithFormulaProps { - autocompleteDataA: BaseAutocompleteData; - autocompleteDataB: BaseAutocompleteData; - legend: string; - disabled: boolean; - groupBy?: BaseAutocompleteData[]; - expression: string; - legendFormula: string; - additionalItemsA: TagFilterItem[]; - additionalItemsB: TagFilterItem[]; -} diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts index ec2d7b9272..e1137f4cfc 100644 --- a/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/OverviewQueries.ts @@ -1,7 +1,11 @@ import { OPERATORS } from 'constants/queryBuilder'; import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; -import { DataSource, QueryBuilderData } from 'types/common/queryBuilder'; +import { + DataSource, + MetricAggregateOperator, + QueryBuilderData, +} from 'types/common/queryBuilder'; import { DataType, @@ -14,7 +18,7 @@ import { QUERYNAME_AND_EXPRESSION, WidgetKeys, } from '../constant'; -import { IServiceName } from '../Tabs/types'; +import { LatencyProps, OperationPerSecProps } from '../Tabs/types'; import { getQueryBuilderQueries, getQueryBuilderQuerieswithFormula, @@ -35,9 +39,7 @@ export const latency = ({ type: isSpanMetricEnable ? null : MetricsType.Tag, }; - const autocompleteData: BaseAutocompleteData[] = Array(3).fill( - newAutoCompleteData, - ); + const autocompleteData = Array(3).fill(newAutoCompleteData); const filterItem: TagFilterItem[] = [ { @@ -65,17 +67,21 @@ export const latency = ({ ...tagFilterItems, ]; - const filterItems: TagFilterItem[][] = Array(3).fill([...filterItem]); + const filterItems = Array(3).fill([...filterItem]); + const legends = LATENCY_AGGREGATEOPERATOR; + const aggregateOperator = isSpanMetricEnable + ? LATENCY_AGGREGATEOPERATOR_SPAN_METRICS + : LATENCY_AGGREGATEOPERATOR; + const dataSource = isSpanMetricEnable ? DataSource.METRICS : DataSource.TRACES; + const queryNameAndExpression = QUERYNAME_AND_EXPRESSION; return getQueryBuilderQueries({ autocompleteData, - legends: LATENCY_AGGREGATEOPERATOR, + legends, filterItems, - aggregateOperator: isSpanMetricEnable - ? LATENCY_AGGREGATEOPERATOR_SPAN_METRICS - : LATENCY_AGGREGATEOPERATOR, - dataSource: isSpanMetricEnable ? DataSource.METRICS : DataSource.TRACES, - queryNameAndExpression: QUERYNAME_AND_EXPRESSION, + aggregateOperator, + dataSource, + queryNameAndExpression, }); }; @@ -121,11 +127,14 @@ export const operationPerSec = ({ ], ]; + const legends = OPERATION_LEGENDS; + const dataSource = DataSource.METRICS; + return getQueryBuilderQueries({ autocompleteData, - legends: OPERATION_LEGENDS, + legends, filterItems, - dataSource: DataSource.METRICS, + dataSource, }); }; @@ -146,6 +155,9 @@ export const errorPercentage = ({ isColumn: true, type: null, }; + + const autocompleteData = [autocompleteDataA, autocompleteDataB]; + const additionalItemsA: TagFilterItem[] = [ { id: '', @@ -209,27 +221,25 @@ export const errorPercentage = ({ ...tagFilterItems, ]; + const additionalItems = [additionalItemsA, additionalItemsB]; + const legends = [GraphTitle.ERROR_PERCENTAGE]; + const disabled = [true, true]; + const expression = FORMULA.ERROR_PERCENTAGE; + const legendFormula = GraphTitle.ERROR_PERCENTAGE; + const aggregateOperators = [ + MetricAggregateOperator.SUM_RATE, + MetricAggregateOperator.SUM_RATE, + ]; + const dataSource = DataSource.METRICS; + return getQueryBuilderQuerieswithFormula({ - autocompleteDataA, - autocompleteDataB, - additionalItemsA, - additionalItemsB, - legend: GraphTitle.ERROR_PERCENTAGE, - disabled: true, - expression: FORMULA.ERROR_PERCENTAGE, - legendFormula: GraphTitle.ERROR_PERCENTAGE, + autocompleteData, + additionalItems, + legends, + disabled, + expression, + legendFormula, + aggregateOperators, + dataSource, }); }; - -export interface OperationPerSecProps { - servicename: IServiceName['servicename']; - tagFilterItems: TagFilterItem[]; - topLevelOperations: string[]; -} - -export interface LatencyProps { - servicename: IServiceName['servicename']; - tagFilterItems: TagFilterItem[]; - isSpanMetricEnable?: boolean; - topLevelOperationsRoute: string[]; -} diff --git a/frontend/src/container/MetricsApplication/MetricsPageQueries/TopOperationQueries.ts b/frontend/src/container/MetricsApplication/MetricsPageQueries/TopOperationQueries.ts new file mode 100644 index 0000000000..6f75d9666d --- /dev/null +++ b/frontend/src/container/MetricsApplication/MetricsPageQueries/TopOperationQueries.ts @@ -0,0 +1,142 @@ +import { OPERATORS } from 'constants/queryBuilder'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; +import { + DataSource, + MetricAggregateOperator, + QueryBuilderData, +} from 'types/common/queryBuilder'; + +import { + DataType, + GraphTitle, + KeyOperationTableHeader, + MetricsType, + WidgetKeys, +} from '../constant'; +import { TopOperationQueryFactoryProps } from '../Tabs/types'; +import { getQueryBuilderQuerieswithFormula } from './MetricsPageQueriesFactory'; + +export const topOperationQueries = ({ + servicename, +}: TopOperationQueryFactoryProps): QueryBuilderData => { + const latencyAutoCompleteData: BaseAutocompleteData = { + key: WidgetKeys.Signoz_latency_bucket, + dataType: DataType.FLOAT64, + isColumn: true, + type: null, + }; + + const errorRateAutoCompleteData: BaseAutocompleteData = { + key: WidgetKeys.SignozCallsTotal, + dataType: DataType.FLOAT64, + isColumn: true, + type: null, + }; + + const numOfCallAutoCompleteData: BaseAutocompleteData = { + key: WidgetKeys.SignozLatencyCount, + dataType: DataType.FLOAT64, + isColumn: true, + type: null, + }; + + const latencyAndNumberOfCallAdditionalItems: TagFilterItem[] = [ + { + id: '', + key: { + key: WidgetKeys.Service_name, + dataType: DataType.STRING, + isColumn: false, + type: MetricsType.Resource, + }, + value: [servicename], + op: OPERATORS.IN, + }, + ]; + + const errorRateAdditionalItemsA: TagFilterItem[] = [ + { + id: '', + key: { + dataType: DataType.STRING, + isColumn: false, + key: WidgetKeys.Service_name, + type: MetricsType.Resource, + }, + op: OPERATORS.IN, + value: [servicename], + }, + { + id: '', + key: { + dataType: DataType.INT64, + isColumn: false, + key: WidgetKeys.StatusCode, + type: MetricsType.Tag, + }, + op: OPERATORS.IN, + value: ['STATUS_CODE_ERROR'], + }, + ]; + + const errorRateAdditionalItemsB = latencyAndNumberOfCallAdditionalItems; + + const groupBy: BaseAutocompleteData[] = [ + { + dataType: DataType.STRING, + isColumn: false, + key: WidgetKeys.Operation, + type: MetricsType.Tag, + }, + ]; + + const autocompleteData = [ + latencyAutoCompleteData, + latencyAutoCompleteData, + latencyAutoCompleteData, + errorRateAutoCompleteData, + errorRateAutoCompleteData, + numOfCallAutoCompleteData, + ]; + const additionalItems = [ + latencyAndNumberOfCallAdditionalItems, + latencyAndNumberOfCallAdditionalItems, + latencyAndNumberOfCallAdditionalItems, + errorRateAdditionalItemsA, + errorRateAdditionalItemsB, + latencyAndNumberOfCallAdditionalItems, + ]; + const disabled = [false, false, false, true, true, false]; + const legends = [ + KeyOperationTableHeader.P50, + KeyOperationTableHeader.P90, + KeyOperationTableHeader.P99, + KeyOperationTableHeader.ERROR_RATE, + KeyOperationTableHeader.ERROR_RATE, + KeyOperationTableHeader.NUM_OF_CALLS, + ]; + const aggregateOperators = [ + MetricAggregateOperator.HIST_QUANTILE_50, + MetricAggregateOperator.HIST_QUANTILE_90, + MetricAggregateOperator.HIST_QUANTILE_99, + MetricAggregateOperator.SUM_RATE, + MetricAggregateOperator.SUM_RATE, + MetricAggregateOperator.SUM_RATE, + ]; + const expression = 'D*100/E'; + const legendFormula = GraphTitle.ERROR_PERCENTAGE; + const dataSource = DataSource.METRICS; + + return getQueryBuilderQuerieswithFormula({ + autocompleteData, + additionalItems, + disabled, + legends, + aggregateOperators, + expression, + legendFormula, + dataSource, + groupBy, + }); +}; diff --git a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx index e2fd6d240f..678d271d11 100644 --- a/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/DBCall.tsx @@ -1,4 +1,5 @@ import { Col } from 'antd'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import Graph from 'container/GridGraphLayout/Graph/'; import { databaseCallsAvgDuration, @@ -50,8 +51,8 @@ function DBCall(): JSX.Element { const databaseCallsRPSWidget = useMemo( () => - getWidgetQueryBuilder( - { + getWidgetQueryBuilder({ + query: { queryType: EQueryType.QUERY_BUILDER, promql: [], builder: databaseCallsRPS({ @@ -62,14 +63,15 @@ function DBCall(): JSX.Element { clickhouse_sql: [], id: uuid(), }, - GraphTitle.DATABASE_CALLS_RPS, - ), + title: GraphTitle.DATABASE_CALLS_RPS, + panelTypes: PANEL_TYPES.TIME_SERIES, + }), [servicename, tagFilterItems], ); const databaseCallsAverageDurationWidget = useMemo( () => - getWidgetQueryBuilder( - { + getWidgetQueryBuilder({ + query: { queryType: EQueryType.QUERY_BUILDER, promql: [], builder: databaseCallsAvgDuration({ @@ -79,8 +81,9 @@ function DBCall(): JSX.Element { clickhouse_sql: [], id: uuid(), }, - GraphTitle.DATABASE_CALLS_AVG_DURATION, - ), + title: GraphTitle.DATABASE_CALLS_AVG_DURATION, + panelTypes: PANEL_TYPES.TIME_SERIES, + }), [servicename, tagFilterItems], ); diff --git a/frontend/src/container/MetricsApplication/Tabs/External.tsx b/frontend/src/container/MetricsApplication/Tabs/External.tsx index ab1e99f430..6595a11808 100644 --- a/frontend/src/container/MetricsApplication/Tabs/External.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/External.tsx @@ -1,4 +1,5 @@ import { Col } from 'antd'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import Graph from 'container/GridGraphLayout/Graph/'; import { externalCallDuration, @@ -41,8 +42,8 @@ function External(): JSX.Element { const externalCallErrorWidget = useMemo( () => - getWidgetQueryBuilder( - { + getWidgetQueryBuilder({ + query: { queryType: EQueryType.QUERY_BUILDER, promql: [], builder: externalCallErrorPercent({ @@ -53,8 +54,9 @@ function External(): JSX.Element { clickhouse_sql: [], id: uuid(), }, - GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE, - ), + title: GraphTitle.EXTERNAL_CALL_ERROR_PERCENTAGE, + panelTypes: PANEL_TYPES.TIME_SERIES, + }), [servicename, tagFilterItems], ); @@ -65,8 +67,8 @@ function External(): JSX.Element { const externalCallDurationWidget = useMemo( () => - getWidgetQueryBuilder( - { + getWidgetQueryBuilder({ + query: { queryType: EQueryType.QUERY_BUILDER, promql: [], builder: externalCallDuration({ @@ -76,15 +78,16 @@ function External(): JSX.Element { clickhouse_sql: [], id: uuid(), }, - GraphTitle.EXTERNAL_CALL_DURATION, - ), + title: GraphTitle.EXTERNAL_CALL_DURATION, + panelTypes: PANEL_TYPES.TIME_SERIES, + }), [servicename, tagFilterItems], ); const externalCallRPSWidget = useMemo( () => - getWidgetQueryBuilder( - { + getWidgetQueryBuilder({ + query: { queryType: EQueryType.QUERY_BUILDER, promql: [], builder: externalCallRpsByAddress({ @@ -95,15 +98,16 @@ function External(): JSX.Element { clickhouse_sql: [], id: uuid(), }, - GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS, - ), + title: GraphTitle.EXTERNAL_CALL_RPS_BY_ADDRESS, + panelTypes: PANEL_TYPES.TIME_SERIES, + }), [servicename, tagFilterItems], ); const externalCallDurationAddressWidget = useMemo( () => - getWidgetQueryBuilder( - { + getWidgetQueryBuilder({ + query: { queryType: EQueryType.QUERY_BUILDER, promql: [], builder: externalCallDurationByAddress({ @@ -114,8 +118,9 @@ function External(): JSX.Element { clickhouse_sql: [], id: uuid(), }, - GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS, - ), + title: GraphTitle.EXTERNAL_CALL_DURATION_BY_ADDRESS, + panelTypes: PANEL_TYPES.TIME_SERIES, + }), [servicename, tagFilterItems], ); diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx index 9d30b624f2..bb34eb78d7 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview.tsx @@ -2,10 +2,13 @@ import getTopLevelOperations, { ServiceDataProps, } from 'api/metrics/getTopLevelOperations'; import { ActiveElement, Chart, ChartData, ChartEvent } from 'chart.js'; +import { FeatureKeys } from 'constants/features'; import { QueryParams } from 'constants/query'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; import { routeConfig } from 'container/SideNav/config'; import { getQueryString } from 'container/SideNav/helper'; +import useFeatureFlag from 'hooks/useFeatureFlag'; import useResourceAttribute from 'hooks/useResourceAttribute'; import { convertRawQueriesToTraceSelectedTags, @@ -29,10 +32,11 @@ import { errorPercentage, operationPerSec, } from '../MetricsPageQueries/OverviewQueries'; -import { Col, Row } from '../styles'; +import { Card, Col, Row } from '../styles'; import ServiceOverview from './Overview/ServiceOverview'; import TopLevelOperation from './Overview/TopLevelOperations'; import TopOperation from './Overview/TopOperation'; +import TopOperationMetrics from './Overview/TopOperationMetrics'; import { Button } from './styles'; import { IServiceName } from './types'; import { @@ -53,6 +57,8 @@ function Application(): JSX.Element { () => (convertRawQueriesToTraceSelectedTags(queries) as Tags[]) || [], [queries], ); + const isSpanMetricEnabled = useFeatureFlag(FeatureKeys.USE_SPAN_METRICS) + ?.active; const handleSetTimeStamp = useCallback((selectTime: number) => { setSelectedTimeStamp(selectTime); @@ -104,8 +110,8 @@ function Application(): JSX.Element { const operationPerSecWidget = useMemo( () => - getWidgetQueryBuilder( - { + getWidgetQueryBuilder({ + query: { queryType: EQueryType.QUERY_BUILDER, promql: [], builder: operationPerSec({ @@ -116,15 +122,16 @@ function Application(): JSX.Element { clickhouse_sql: [], id: uuid(), }, - GraphTitle.RATE_PER_OPS, - ), + title: GraphTitle.RATE_PER_OPS, + panelTypes: PANEL_TYPES.TIME_SERIES, + }), [servicename, tagFilterItems, topLevelOperationsRoute], ); const errorPercentageWidget = useMemo( () => - getWidgetQueryBuilder( - { + getWidgetQueryBuilder({ + query: { queryType: EQueryType.QUERY_BUILDER, promql: [], builder: errorPercentage({ @@ -135,8 +142,9 @@ function Application(): JSX.Element { clickhouse_sql: [], id: uuid(), }, - GraphTitle.ERROR_PERCENTAGE, - ), + title: GraphTitle.ERROR_PERCENTAGE, + panelTypes: PANEL_TYPES.TIME_SERIES, + }), [servicename, tagFilterItems, topLevelOperationsRoute], ); @@ -239,7 +247,9 @@ function Application(): JSX.Element { - + + {isSpanMetricEnabled ? : } + diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx index 4fb5d4f024..28895d2909 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/ServiceOverview.tsx @@ -1,4 +1,5 @@ import { FeatureKeys } from 'constants/features'; +import { PANEL_TYPES } from 'constants/queryBuilder'; import Graph from 'container/GridGraphLayout/Graph/'; import { GraphTitle } from 'container/MetricsApplication/constant'; import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; @@ -31,8 +32,8 @@ function ServiceOverview({ const latencyWidget = useMemo( () => - getWidgetQueryBuilder( - { + getWidgetQueryBuilder({ + query: { queryType: EQueryType.QUERY_BUILDER, promql: [], builder: latency({ @@ -44,8 +45,9 @@ function ServiceOverview({ clickhouse_sql: [], id: uuid(), }, - GraphTitle.LATENCY, - ), + title: GraphTitle.LATENCY, + panelTypes: PANEL_TYPES.TIME_SERIES, + }), [servicename, tagFilterItems, isSpanMetricEnable, topLevelOperationsRoute], ); diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/TableRenderer/ColumnWithLink.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/TableRenderer/ColumnWithLink.tsx new file mode 100644 index 0000000000..99230f3a75 --- /dev/null +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/TableRenderer/ColumnWithLink.tsx @@ -0,0 +1,39 @@ +import { Tooltip, Typography } from 'antd'; +import { navigateToTrace } from 'container/MetricsApplication/utils'; +import { RowData } from 'lib/query/createTableColumnsFromQuery'; + +function ColumnWithLink({ + servicename, + minTime, + maxTime, + selectedTraceTags, + record, +}: LinkColumnProps): JSX.Element { + const text = record.toString(); + + const handleOnClick = (operation: string) => (): void => { + navigateToTrace({ + servicename, + operation, + minTime, + maxTime, + selectedTraceTags, + }); + }; + + return ( + + {text} + + ); +} + +interface LinkColumnProps { + servicename: string; + minTime: number; + maxTime: number; + selectedTraceTags: string; + record: RowData; +} + +export default ColumnWithLink; diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/TableRenderer/TableColumnRenderer.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/TableRenderer/TableColumnRenderer.tsx new file mode 100644 index 0000000000..1720ce9be0 --- /dev/null +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/TableRenderer/TableColumnRenderer.tsx @@ -0,0 +1,11 @@ +import { RowData } from 'lib/query/createTableColumnsFromQuery'; +import { ReactNode } from 'react'; + +import { TableRendererProps } from '../../types'; + +export const getTableColumnRenderer = ({ + columnName, + renderFunction, +}: TableRendererProps): Record ReactNode> => ({ + [columnName]: renderFunction, +}); diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperation.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperation.tsx index 183ec20e7a..8acd032065 100644 --- a/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperation.tsx +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperation.tsx @@ -1,6 +1,5 @@ import getTopOperations from 'api/metrics/getTopOperations'; import Spinner from 'components/Spinner'; -import { Card } from 'container/MetricsApplication/styles'; import TopOperationsTable from 'container/MetricsApplication/TopOperationsTable'; import useResourceAttribute from 'hooks/useResourceAttribute'; import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; @@ -35,11 +34,13 @@ function TopOperation(): JSX.Element { }), }); + const topOperationData = data || []; + return ( - + <> {isLoading && } - {!isLoading && } - + {!isLoading && } + ); } diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperationMetrics.tsx b/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperationMetrics.tsx new file mode 100644 index 0000000000..d205bfbd83 --- /dev/null +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/TopOperationMetrics.tsx @@ -0,0 +1,122 @@ +import { PANEL_TYPES } from 'constants/queryBuilder'; +import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory'; +import { topOperationQueries } from 'container/MetricsApplication/MetricsPageQueries/TopOperationQueries'; +import { QueryTable } from 'container/QueryTable'; +import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; +import { useStepInterval } from 'hooks/queryBuilder/useStepInterval'; +import useResourceAttribute from 'hooks/useResourceAttribute'; +import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; +import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; +import { RowData } from 'lib/query/createTableColumnsFromQuery'; +import { isEmpty } from 'lodash-es'; +import { ReactNode, useMemo, useState } from 'react'; +import { useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { AppState } from 'store/reducers'; +import { EQueryType } from 'types/common/dashboard'; +import { GlobalReducer } from 'types/reducer/globalTime'; +import { v4 as uuid } from 'uuid'; + +import { IServiceName } from '../types'; +import { title } from './config'; +import ColumnWithLink from './TableRenderer/ColumnWithLink'; +import { getTableColumnRenderer } from './TableRenderer/TableColumnRenderer'; + +function TopOperationMetrics(): JSX.Element { + const { servicename } = useParams(); + + const [errorMessage, setErrorMessage] = useState(''); + const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); + const { queries } = useResourceAttribute(); + + const selectedTraceTags = JSON.stringify( + convertRawQueriesToTraceSelectedTags(queries) || [], + ); + + const keyOperationWidget = useMemo( + () => + getWidgetQueryBuilder({ + query: { + queryType: EQueryType.QUERY_BUILDER, + promql: [], + builder: topOperationQueries({ + servicename, + }), + clickhouse_sql: [], + id: uuid(), + }, + panelTypes: PANEL_TYPES.TABLE, + }), + [servicename], + ); + + const updatedQuery = useStepInterval(keyOperationWidget.query); + + const isEmptyWidget = useMemo( + () => keyOperationWidget.id === 'empty' || isEmpty(keyOperationWidget), + [keyOperationWidget], + ); + + const { data, isLoading } = useGetQueryRange( + { + selectedTime: keyOperationWidget?.timePreferance, + graphType: keyOperationWidget?.panelTypes, + query: updatedQuery, + globalSelectedInterval, + variables: getDashboardVariables(), + }, + { + queryKey: [ + `GetMetricsQueryRange-${keyOperationWidget?.timePreferance}-${globalSelectedInterval}-${keyOperationWidget?.id}`, + keyOperationWidget, + maxTime, + minTime, + globalSelectedInterval, + ], + keepPreviousData: true, + enabled: !isEmptyWidget, + refetchOnMount: false, + onError: (error) => { + setErrorMessage(error.message); + }, + }, + ); + + const queryTableData = data?.payload.data.newResult.data.result || []; + + const renderColumnCell = useMemo( + () => + getTableColumnRenderer({ + columnName: 'operation', + renderFunction: (record: RowData): ReactNode => ( + + ), + }), + [servicename, minTime, maxTime, selectedTraceTags], + ); + + if (errorMessage) { + return
{errorMessage}
; + } + + return ( + + ); +} + +export default TopOperationMetrics; diff --git a/frontend/src/container/MetricsApplication/Tabs/Overview/config.ts b/frontend/src/container/MetricsApplication/Tabs/Overview/config.ts new file mode 100644 index 0000000000..03bd1e7d2c --- /dev/null +++ b/frontend/src/container/MetricsApplication/Tabs/Overview/config.ts @@ -0,0 +1 @@ +export const title = (): string => 'Key Operations'; diff --git a/frontend/src/container/MetricsApplication/Tabs/types.ts b/frontend/src/container/MetricsApplication/Tabs/types.ts index 2d60b132ee..971df2c705 100644 --- a/frontend/src/container/MetricsApplication/Tabs/types.ts +++ b/frontend/src/container/MetricsApplication/Tabs/types.ts @@ -1,3 +1,62 @@ +import { RowData } from 'lib/query/createTableColumnsFromQuery'; +import { ReactNode } from 'react'; +import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData'; +import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder'; + export interface IServiceName { servicename: string; } + +export interface TopOperationQueryFactoryProps { + servicename: IServiceName['servicename']; +} + +export interface ExternalCallDurationByAddressProps extends ExternalCallProps { + legend: string; +} + +export interface ExternalCallProps { + servicename: IServiceName['servicename']; + tagFilterItems: TagFilterItem[]; +} + +export interface BuilderQueriesProps { + autocompleteData: BaseAutocompleteData[]; + groupBy?: BaseAutocompleteData[]; + legends: string[]; + filterItems: TagFilterItem[][]; + aggregateOperator?: string[]; + dataSource: DataSource; + queryNameAndExpression?: string[]; +} + +export interface BuilderQuerieswithFormulaProps { + autocompleteData: BaseAutocompleteData[]; + legends: string[]; + disabled: boolean[]; + groupBy?: BaseAutocompleteData[]; + expression: string; + legendFormula: string; + additionalItems: TagFilterItem[][]; + aggregateOperators: MetricAggregateOperator[]; + dataSource: DataSource; +} + +export interface OperationPerSecProps { + servicename: IServiceName['servicename']; + tagFilterItems: TagFilterItem[]; + topLevelOperations: string[]; +} + +export interface LatencyProps { + servicename: IServiceName['servicename']; + tagFilterItems: TagFilterItem[]; + isSpanMetricEnable?: boolean; + topLevelOperationsRoute: string[]; +} + +export interface TableRendererProps { + columnName: string; + renderFunction: (record: RowData) => ReactNode; +} diff --git a/frontend/src/container/MetricsApplication/TopOperationsTable.tsx b/frontend/src/container/MetricsApplication/TopOperationsTable.tsx index 739ef8af0e..e753a965a5 100644 --- a/frontend/src/container/MetricsApplication/TopOperationsTable.tsx +++ b/frontend/src/container/MetricsApplication/TopOperationsTable.tsx @@ -1,17 +1,14 @@ import { Tooltip, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { ResizeTable } from 'components/ResizeTable'; -import { QueryParams } from 'constants/query'; -import ROUTES from 'constants/routes'; import useResourceAttribute from 'hooks/useResourceAttribute'; import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils'; -import history from 'lib/history'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import { AppState } from 'store/reducers'; import { GlobalReducer } from 'types/reducer/globalTime'; -import { getErrorRate } from './utils'; +import { getErrorRate, navigateToTrace } from './utils'; function TopOperationsTable(props: TopOperationsTableProps): JSX.Element { const { minTime, maxTime } = useSelector( @@ -28,16 +25,15 @@ function TopOperationsTable(props: TopOperationsTableProps): JSX.Element { const params = useParams<{ servicename: string }>(); const handleOnClick = (operation: string): void => { - const urlParams = new URLSearchParams(); const { servicename } = params; - urlParams.set(QueryParams.startTime, (minTime / 1000000).toString()); - urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString()); - history.push( - `${ - ROUTES.TRACE - }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1`, - ); + navigateToTrace({ + servicename, + operation, + minTime, + maxTime, + selectedTraceTags, + }); }; const columns: ColumnsType = [ diff --git a/frontend/src/container/MetricsApplication/constant.ts b/frontend/src/container/MetricsApplication/constant.ts index 0e917cce47..8b1564df93 100644 --- a/frontend/src/container/MetricsApplication/constant.ts +++ b/frontend/src/container/MetricsApplication/constant.ts @@ -28,6 +28,14 @@ export enum GraphTitle { EXTERNAL_CALL_DURATION_BY_ADDRESS = 'External Call duration(by Address)', } +export enum KeyOperationTableHeader { + P50 = 'P50', + P90 = 'P90', + P99 = 'P99', + NUM_OF_CALLS = 'Number of Calls', + ERROR_RATE = 'Error Rate', +} + export enum DataType { STRING = 'string', FLOAT64 = 'float64', diff --git a/frontend/src/container/MetricsApplication/types.ts b/frontend/src/container/MetricsApplication/types.ts new file mode 100644 index 0000000000..86acab2cf0 --- /dev/null +++ b/frontend/src/container/MetricsApplication/types.ts @@ -0,0 +1,15 @@ +import { Widgets } from 'types/api/dashboard/getAll'; + +export interface GetWidgetQueryBuilderProps { + query: Widgets['query']; + title?: string; + panelTypes: Widgets['panelTypes']; +} + +export interface NavigateToTraceProps { + servicename: string; + operation: string; + minTime: number; + maxTime: number; + selectedTraceTags: string; +} diff --git a/frontend/src/container/MetricsApplication/utils.ts b/frontend/src/container/MetricsApplication/utils.ts index a27242718c..543e792d77 100644 --- a/frontend/src/container/MetricsApplication/utils.ts +++ b/frontend/src/container/MetricsApplication/utils.ts @@ -1,4 +1,26 @@ +import { QueryParams } from 'constants/query'; +import ROUTES from 'constants/routes'; +import history from 'lib/history'; + import { TopOperationList } from './TopOperationsTable'; +import { NavigateToTraceProps } from './types'; export const getErrorRate = (list: TopOperationList): number => (list.errorCount / list.numCalls) * 100; + +export const navigateToTrace = ({ + servicename, + operation, + minTime, + maxTime, + selectedTraceTags, +}: NavigateToTraceProps): void => { + const urlParams = new URLSearchParams(); + urlParams.set(QueryParams.startTime, (minTime / 1000000).toString()); + urlParams.set(QueryParams.endTime, (maxTime / 1000000).toString()); + history.push( + `${ + ROUTES.TRACE + }?${urlParams.toString()}&selected={"serviceName":["${servicename}"],"operation":["${operation}"]}&filterToFetchData=["duration","status","serviceName","operation"]&spanAggregateCurrentPage=1&selectedTags=${selectedTraceTags}&&isFilterExclude={"serviceName":false,"operation":false}&userSelectedFilter={"status":["error","ok"],"serviceName":["${servicename}"],"operation":["${operation}"]}&spanAggregateCurrentPage=1`, + ); +}; From 378fdb522eaf99e23ab4ca88518b0ec160e64123 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Sat, 29 Jul 2023 16:01:21 +0530 Subject: [PATCH 21/21] chore: bump version to v0.25.0 --- deploy/docker-swarm/clickhouse-setup/docker-compose.yaml | 4 ++-- deploy/docker/clickhouse-setup/docker-compose.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index aa5cbb5ff3..34c4073389 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -137,7 +137,7 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.24.0 + image: signoz/query-service:0.25.0 command: ["-config=/root/config/prometheus.yml"] # ports: # - "6060:6060" # pprof port @@ -166,7 +166,7 @@ services: <<: *clickhouse-depend frontend: - image: signoz/frontend:0.24.0 + image: signoz/frontend:0.25.0 deploy: restart_policy: condition: on-failure diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index 5f16d26489..6998e59236 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -153,7 +153,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.24.0} + image: signoz/query-service:${DOCKER_TAG:-0.25.0} container_name: query-service command: ["-config=/root/config/prometheus.yml"] # ports: @@ -181,7 +181,7 @@ services: <<: *clickhouse-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.24.0} + image: signoz/frontend:${DOCKER_TAG:-0.25.0} container_name: frontend restart: on-failure depends_on: