mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 18:39:04 +08:00
feat: add APIs for third party api feat (#7005)
* feat: add APIs for third party api feat Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com> * fix: minor fixes Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com> * test: add unit tests Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com> * chore: minor changes Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com> * fix: review comments Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com> * test: add unit tests Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com> * chore: cleanup Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com> * chore: review comments Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com> * chore: review comments Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com> --------- Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
This commit is contained in:
parent
3b6952abf2
commit
407654e68e
@ -374,6 +374,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
|||||||
apiHandler.RegisterQueryRangeV4Routes(r, am)
|
apiHandler.RegisterQueryRangeV4Routes(r, am)
|
||||||
apiHandler.RegisterWebSocketPaths(r, am)
|
apiHandler.RegisterWebSocketPaths(r, am)
|
||||||
apiHandler.RegisterMessagingQueuesRoutes(r, am)
|
apiHandler.RegisterMessagingQueuesRoutes(r, am)
|
||||||
|
apiHandler.RegisterThirdPartyApiRoutes(r, am)
|
||||||
|
|
||||||
c := cors.New(cors.Options{
|
c := cors.New(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"go.signoz.io/signoz/pkg/query-service/app/inframetrics"
|
"go.signoz.io/signoz/pkg/query-service/app/inframetrics"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/integrations"
|
"go.signoz.io/signoz/pkg/query-service/app/integrations"
|
||||||
queues2 "go.signoz.io/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
|
queues2 "go.signoz.io/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/integrations/thirdPartyApi"
|
||||||
"go.signoz.io/signoz/pkg/query-service/app/logs"
|
"go.signoz.io/signoz/pkg/query-service/app/logs"
|
||||||
logsv3 "go.signoz.io/signoz/pkg/query-service/app/logs/v3"
|
logsv3 "go.signoz.io/signoz/pkg/query-service/app/logs/v3"
|
||||||
logsv4 "go.signoz.io/signoz/pkg/query-service/app/logs/v4"
|
logsv4 "go.signoz.io/signoz/pkg/query-service/app/logs/v4"
|
||||||
@ -2600,21 +2601,19 @@ func (aH *APIHandler) RegisterMessagingQueuesRoutes(router *mux.Router, am *Auth
|
|||||||
spanEvaluation := kafkaRouter.PathPrefix("/span").Subrouter()
|
spanEvaluation := kafkaRouter.PathPrefix("/span").Subrouter()
|
||||||
|
|
||||||
spanEvaluation.HandleFunc("/evaluation", am.ViewAccess(aH.getProducerConsumerEval)).Methods(http.MethodPost)
|
spanEvaluation.HandleFunc("/evaluation", am.ViewAccess(aH.getProducerConsumerEval)).Methods(http.MethodPost)
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------------------------------
|
// RegisterThirdPartyApiRoutes adds third-party-api integration routes
|
||||||
// Celery-specific routes
|
func (aH *APIHandler) RegisterThirdPartyApiRoutes(router *mux.Router, am *AuthMiddleware) {
|
||||||
celeryRouter := messagingQueuesRouter.PathPrefix("/celery").Subrouter()
|
|
||||||
|
|
||||||
// Celery overview routes
|
// Main messaging queues router
|
||||||
celeryRouter.HandleFunc("/overview", am.ViewAccess(aH.getCeleryOverview)).Methods(http.MethodPost)
|
thirdPartyApiRouter := router.PathPrefix("/api/v1/third-party-apis").Subrouter()
|
||||||
|
|
||||||
// Celery tasks routes
|
// Domain Overview route
|
||||||
celeryRouter.HandleFunc("/tasks", am.ViewAccess(aH.getCeleryTasks)).Methods(http.MethodPost)
|
overviewRouter := thirdPartyApiRouter.PathPrefix("/overview").Subrouter()
|
||||||
|
|
||||||
// Celery performance routes
|
overviewRouter.HandleFunc("/list", am.ViewAccess(aH.getDomainList)).Methods(http.MethodPost)
|
||||||
celeryRouter.HandleFunc("/performance", am.ViewAccess(aH.getCeleryPerformance)).Methods(http.MethodPost)
|
overviewRouter.HandleFunc("/domain", am.ViewAccess(aH.getDomainInfo)).Methods(http.MethodPost)
|
||||||
|
|
||||||
// for other messaging queues, add SubRouters here
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// not using md5 hashing as the plain string would work
|
// not using md5 hashing as the plain string would work
|
||||||
@ -3493,24 +3492,6 @@ func (aH *APIHandler) getProducerConsumerEval(
|
|||||||
aH.Respond(w, resp)
|
aH.Respond(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseKafkaQueueBody parse for messaging queue params
|
|
||||||
func ParseKafkaQueueBody(r *http.Request) (*kafka.MessagingQueue, *model.ApiError) {
|
|
||||||
messagingQueue := new(kafka.MessagingQueue)
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(messagingQueue); err != nil {
|
|
||||||
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("cannot parse the request body: %v", err)}
|
|
||||||
}
|
|
||||||
return messagingQueue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseQueueBody parses for any queue
|
|
||||||
func ParseQueueBody(r *http.Request) (*queues2.QueueListRequest, *model.ApiError) {
|
|
||||||
queue := new(queues2.QueueListRequest)
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(queue); err != nil {
|
|
||||||
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("cannot parse the request body: %v", err)}
|
|
||||||
}
|
|
||||||
return queue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
|
|
||||||
func (aH *APIHandler) getUserPreference(
|
func (aH *APIHandler) getUserPreference(
|
||||||
@ -5515,13 +5496,78 @@ func (aH *APIHandler) getQueueOverview(w http.ResponseWriter, r *http.Request) {
|
|||||||
aH.Respond(w, results)
|
aH.Respond(w, results)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) getCeleryOverview(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) getDomainList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
thirdPartyQueryRequest, apiErr := ParseRequestBody(r)
|
||||||
|
if apiErr != nil {
|
||||||
|
zap.L().Error(apiErr.Err.Error())
|
||||||
|
RespondError(w, apiErr, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
queryRangeParams, err := thirdPartyApi.BuildDomainList(thirdPartyQueryRequest)
|
||||||
|
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||||
|
zap.L().Error(err.Error())
|
||||||
|
RespondError(w, apiErr, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []*v3.Result
|
||||||
|
var errQuriesByName map[string]error
|
||||||
|
|
||||||
|
result, errQuriesByName, err = aH.querierV2.QueryRange(r.Context(), queryRangeParams)
|
||||||
|
if err != nil {
|
||||||
|
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||||
|
RespondError(w, apiErrObj, errQuriesByName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result = postprocess.TransformToTableForBuilderQueries(result, queryRangeParams)
|
||||||
|
|
||||||
|
if !thirdPartyQueryRequest.ShowIp {
|
||||||
|
result = thirdPartyApi.FilterResponse(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := v3.QueryRangeResponse{
|
||||||
|
Result: result,
|
||||||
|
}
|
||||||
|
aH.Respond(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) getCeleryTasks(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) getDomainInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Implement celery tasks logic for both state and list types
|
thirdPartyQueryRequest, apiErr := ParseRequestBody(r)
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) getCeleryPerformance(w http.ResponseWriter, r *http.Request) {
|
if apiErr != nil {
|
||||||
// TODO: Implement celery performance logic for error, rate, and latency types
|
zap.L().Error(apiErr.Err.Error())
|
||||||
|
RespondError(w, apiErr, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
queryRangeParams, err := thirdPartyApi.BuildDomainInfo(thirdPartyQueryRequest)
|
||||||
|
|
||||||
|
if err := validateQueryRangeParamsV3(queryRangeParams); err != nil {
|
||||||
|
zap.L().Error(err.Error())
|
||||||
|
RespondError(w, apiErr, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []*v3.Result
|
||||||
|
var errQuriesByName map[string]error
|
||||||
|
|
||||||
|
result, errQuriesByName, err = aH.querierV2.QueryRange(r.Context(), queryRangeParams)
|
||||||
|
if err != nil {
|
||||||
|
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||||
|
RespondError(w, apiErrObj, errQuriesByName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result = postprocess.TransformToTableForBuilderQueries(result, queryRangeParams)
|
||||||
|
|
||||||
|
if !thirdPartyQueryRequest.ShowIp {
|
||||||
|
result = thirdPartyApi.FilterResponse(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := v3.QueryRangeResponse{
|
||||||
|
Result: result,
|
||||||
|
}
|
||||||
|
aH.Respond(w, resp)
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
package celery
|
|
13
pkg/query-service/app/integrations/thirdPartyApi/model.go
Normal file
13
pkg/query-service/app/integrations/thirdPartyApi/model.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package thirdPartyApi
|
||||||
|
|
||||||
|
import v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
|
||||||
|
type ThirdPartyApis struct {
|
||||||
|
Start int64 `json:"start"`
|
||||||
|
End int64 `json:"end"`
|
||||||
|
ShowIp bool `json:"show_ip,omitempty"`
|
||||||
|
Domain int64 `json:"domain,omitempty"`
|
||||||
|
Endpoint string `json:"endpoint,omitempty"`
|
||||||
|
Filters v3.FilterSet `json:"filters,omitempty"`
|
||||||
|
GroupBy []v3.AttributeKey `json:"groupBy,omitempty"`
|
||||||
|
}
|
337
pkg/query-service/app/integrations/thirdPartyApi/translator.go
Normal file
337
pkg/query-service/app/integrations/thirdPartyApi/translator.go
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
package thirdPartyApi
|
||||||
|
|
||||||
|
import (
|
||||||
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultStepInterval int64 = 60
|
||||||
|
|
||||||
|
func FilterResponse(results []*v3.Result) []*v3.Result {
|
||||||
|
filteredResults := make([]*v3.Result, 0, len(results))
|
||||||
|
|
||||||
|
for _, res := range results {
|
||||||
|
if res.Table == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filteredRows := make([]*v3.TableRow, 0, len(res.Table.Rows))
|
||||||
|
for _, row := range res.Table.Rows {
|
||||||
|
if row.Data != nil {
|
||||||
|
if domainVal, ok := row.Data["net.peer.name"]; ok {
|
||||||
|
if domainStr, ok := domainVal.(string); ok {
|
||||||
|
if net.ParseIP(domainStr) != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filteredRows = append(filteredRows, row)
|
||||||
|
}
|
||||||
|
res.Table.Rows = filteredRows
|
||||||
|
|
||||||
|
filteredResults = append(filteredResults, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredResults
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFilterSet(existingFilters []v3.FilterItem, apiFilters v3.FilterSet) []v3.FilterItem {
|
||||||
|
if len(apiFilters.Items) != 0 {
|
||||||
|
existingFilters = append(existingFilters, apiFilters.Items...)
|
||||||
|
}
|
||||||
|
return existingFilters
|
||||||
|
}
|
||||||
|
|
||||||
|
func getGroupBy(existingGroupBy []v3.AttributeKey, apiGroupBy []v3.AttributeKey) []v3.AttributeKey {
|
||||||
|
if len(apiGroupBy) != 0 {
|
||||||
|
existingGroupBy = append(existingGroupBy, apiGroupBy...)
|
||||||
|
}
|
||||||
|
return existingGroupBy
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildDomainList(thirdPartyApis *ThirdPartyApis) (*v3.QueryRangeParamsV3, error) {
|
||||||
|
|
||||||
|
unixMilliStart := thirdPartyApis.Start
|
||||||
|
unixMilliEnd := thirdPartyApis.End
|
||||||
|
|
||||||
|
builderQueries := make(map[string]*v3.BuilderQuery)
|
||||||
|
|
||||||
|
builderQueries["endpoints"] = &v3.BuilderQuery{
|
||||||
|
QueryName: "endpoints",
|
||||||
|
DataSource: v3.DataSourceTraces,
|
||||||
|
StepInterval: defaultStepInterval,
|
||||||
|
AggregateOperator: v3.AggregateOperatorCountDistinct,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "http.url",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
TimeAggregation: v3.TimeAggregationCountDistinct,
|
||||||
|
SpaceAggregation: v3.SpaceAggregationSum,
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: getFilterSet([]v3.FilterItem{}, thirdPartyApis.Filters),
|
||||||
|
},
|
||||||
|
Expression: "endpoints",
|
||||||
|
GroupBy: getGroupBy([]v3.AttributeKey{
|
||||||
|
{
|
||||||
|
Key: "net.peer.name",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
}, thirdPartyApis.GroupBy),
|
||||||
|
ReduceTo: v3.ReduceToOperatorAvg,
|
||||||
|
}
|
||||||
|
|
||||||
|
builderQueries["lastseen"] = &v3.BuilderQuery{
|
||||||
|
QueryName: "lastseen",
|
||||||
|
DataSource: v3.DataSourceTraces,
|
||||||
|
StepInterval: defaultStepInterval,
|
||||||
|
AggregateOperator: v3.AggregateOperatorMax,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "timestamp",
|
||||||
|
},
|
||||||
|
TimeAggregation: v3.TimeAggregationMax,
|
||||||
|
SpaceAggregation: v3.SpaceAggregationSum,
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: getFilterSet([]v3.FilterItem{}, thirdPartyApis.Filters),
|
||||||
|
},
|
||||||
|
Expression: "lastseen",
|
||||||
|
GroupBy: getGroupBy([]v3.AttributeKey{
|
||||||
|
{
|
||||||
|
Key: "net.peer.name",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
}, thirdPartyApis.GroupBy),
|
||||||
|
ReduceTo: v3.ReduceToOperatorAvg,
|
||||||
|
}
|
||||||
|
|
||||||
|
builderQueries["rps"] = &v3.BuilderQuery{
|
||||||
|
QueryName: "rps",
|
||||||
|
DataSource: v3.DataSourceTraces,
|
||||||
|
StepInterval: defaultStepInterval,
|
||||||
|
AggregateOperator: v3.AggregateOperatorRate,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "",
|
||||||
|
},
|
||||||
|
TimeAggregation: v3.TimeAggregationRate,
|
||||||
|
SpaceAggregation: v3.SpaceAggregationSum,
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: getFilterSet([]v3.FilterItem{}, thirdPartyApis.Filters),
|
||||||
|
},
|
||||||
|
Expression: "rps",
|
||||||
|
GroupBy: getGroupBy([]v3.AttributeKey{
|
||||||
|
{
|
||||||
|
Key: "net.peer.name",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
}, thirdPartyApis.GroupBy),
|
||||||
|
ReduceTo: v3.ReduceToOperatorAvg,
|
||||||
|
}
|
||||||
|
|
||||||
|
builderQueries["error_rate"] = &v3.BuilderQuery{
|
||||||
|
QueryName: "error_rate",
|
||||||
|
DataSource: v3.DataSourceTraces,
|
||||||
|
StepInterval: defaultStepInterval,
|
||||||
|
AggregateOperator: v3.AggregateOperatorRate,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "",
|
||||||
|
},
|
||||||
|
TimeAggregation: v3.TimeAggregationRate,
|
||||||
|
SpaceAggregation: v3.SpaceAggregationSum,
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: getFilterSet([]v3.FilterItem{
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{
|
||||||
|
Key: "has_error",
|
||||||
|
DataType: v3.AttributeKeyDataTypeBool,
|
||||||
|
IsColumn: true,
|
||||||
|
},
|
||||||
|
Operator: "=",
|
||||||
|
Value: "true",
|
||||||
|
},
|
||||||
|
}, thirdPartyApis.Filters),
|
||||||
|
},
|
||||||
|
Expression: "error_rate",
|
||||||
|
GroupBy: getGroupBy([]v3.AttributeKey{
|
||||||
|
{
|
||||||
|
Key: "net.peer.name",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
}, thirdPartyApis.GroupBy),
|
||||||
|
ReduceTo: v3.ReduceToOperatorAvg,
|
||||||
|
}
|
||||||
|
|
||||||
|
builderQueries["p99"] = &v3.BuilderQuery{
|
||||||
|
QueryName: "p99",
|
||||||
|
DataSource: v3.DataSourceTraces,
|
||||||
|
StepInterval: defaultStepInterval,
|
||||||
|
AggregateOperator: v3.AggregateOperatorP99,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "duration_nano",
|
||||||
|
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||||
|
IsColumn: true,
|
||||||
|
},
|
||||||
|
TimeAggregation: "p99",
|
||||||
|
SpaceAggregation: v3.SpaceAggregationSum,
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: getFilterSet([]v3.FilterItem{}, thirdPartyApis.Filters),
|
||||||
|
},
|
||||||
|
Expression: "p99",
|
||||||
|
GroupBy: getGroupBy([]v3.AttributeKey{
|
||||||
|
{
|
||||||
|
Key: "net.peer.name",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
}, thirdPartyApis.GroupBy),
|
||||||
|
ReduceTo: v3.ReduceToOperatorAvg,
|
||||||
|
}
|
||||||
|
|
||||||
|
compositeQuery := &v3.CompositeQuery{
|
||||||
|
QueryType: v3.QueryTypeBuilder,
|
||||||
|
PanelType: v3.PanelTypeTable,
|
||||||
|
FillGaps: false,
|
||||||
|
BuilderQueries: builderQueries,
|
||||||
|
}
|
||||||
|
|
||||||
|
queryRangeParams := &v3.QueryRangeParamsV3{
|
||||||
|
Start: unixMilliStart,
|
||||||
|
End: unixMilliEnd,
|
||||||
|
Step: defaultStepInterval,
|
||||||
|
CompositeQuery: compositeQuery,
|
||||||
|
Version: "v4",
|
||||||
|
FormatForWeb: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryRangeParams, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildDomainInfo(thirdPartyApis *ThirdPartyApis) (*v3.QueryRangeParamsV3, error) {
|
||||||
|
unixMilliStart := thirdPartyApis.Start
|
||||||
|
unixMilliEnd := thirdPartyApis.End
|
||||||
|
|
||||||
|
builderQueries := make(map[string]*v3.BuilderQuery)
|
||||||
|
|
||||||
|
builderQueries["endpoints"] = &v3.BuilderQuery{
|
||||||
|
QueryName: "endpoints",
|
||||||
|
DataSource: v3.DataSourceTraces,
|
||||||
|
StepInterval: defaultStepInterval,
|
||||||
|
AggregateOperator: v3.AggregateOperatorCount,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "http.url",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
TimeAggregation: v3.TimeAggregationRate,
|
||||||
|
SpaceAggregation: v3.SpaceAggregationSum,
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: getFilterSet([]v3.FilterItem{}, thirdPartyApis.Filters),
|
||||||
|
},
|
||||||
|
Expression: "endpoints",
|
||||||
|
Disabled: false,
|
||||||
|
GroupBy: getGroupBy([]v3.AttributeKey{
|
||||||
|
{
|
||||||
|
Key: "http.url",
|
||||||
|
DataType: v3.AttributeKeyDataTypeString,
|
||||||
|
Type: v3.AttributeKeyTypeTag,
|
||||||
|
},
|
||||||
|
}, thirdPartyApis.GroupBy),
|
||||||
|
Legend: "",
|
||||||
|
ReduceTo: v3.ReduceToOperatorAvg,
|
||||||
|
}
|
||||||
|
|
||||||
|
builderQueries["p99"] = &v3.BuilderQuery{
|
||||||
|
QueryName: "p99",
|
||||||
|
DataSource: v3.DataSourceTraces,
|
||||||
|
StepInterval: defaultStepInterval,
|
||||||
|
AggregateOperator: v3.AggregateOperatorP99,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "duration_nano",
|
||||||
|
DataType: v3.AttributeKeyDataTypeFloat64,
|
||||||
|
IsColumn: true,
|
||||||
|
},
|
||||||
|
TimeAggregation: "p99",
|
||||||
|
SpaceAggregation: v3.SpaceAggregationSum,
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: getFilterSet([]v3.FilterItem{}, thirdPartyApis.Filters),
|
||||||
|
},
|
||||||
|
Expression: "p99",
|
||||||
|
Disabled: false,
|
||||||
|
Having: nil,
|
||||||
|
GroupBy: getGroupBy([]v3.AttributeKey{}, thirdPartyApis.GroupBy),
|
||||||
|
Legend: "",
|
||||||
|
ReduceTo: v3.ReduceToOperatorAvg,
|
||||||
|
}
|
||||||
|
|
||||||
|
builderQueries["error_rate"] = &v3.BuilderQuery{
|
||||||
|
QueryName: "error_rate",
|
||||||
|
DataSource: v3.DataSourceTraces,
|
||||||
|
StepInterval: defaultStepInterval,
|
||||||
|
AggregateOperator: v3.AggregateOperatorRate,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "",
|
||||||
|
},
|
||||||
|
TimeAggregation: v3.TimeAggregationRate,
|
||||||
|
SpaceAggregation: v3.SpaceAggregationSum,
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: getFilterSet([]v3.FilterItem{}, thirdPartyApis.Filters),
|
||||||
|
},
|
||||||
|
Expression: "error_rate",
|
||||||
|
Disabled: false,
|
||||||
|
GroupBy: getGroupBy([]v3.AttributeKey{}, thirdPartyApis.GroupBy),
|
||||||
|
Legend: "",
|
||||||
|
ReduceTo: v3.ReduceToOperatorAvg,
|
||||||
|
}
|
||||||
|
|
||||||
|
builderQueries["lastseen"] = &v3.BuilderQuery{
|
||||||
|
QueryName: "lastseen",
|
||||||
|
DataSource: v3.DataSourceTraces,
|
||||||
|
StepInterval: defaultStepInterval,
|
||||||
|
AggregateOperator: v3.AggregateOperatorMax,
|
||||||
|
AggregateAttribute: v3.AttributeKey{
|
||||||
|
Key: "timestamp",
|
||||||
|
},
|
||||||
|
TimeAggregation: v3.TimeAggregationMax,
|
||||||
|
SpaceAggregation: v3.SpaceAggregationSum,
|
||||||
|
Filters: &v3.FilterSet{
|
||||||
|
Operator: "AND",
|
||||||
|
Items: getFilterSet([]v3.FilterItem{}, thirdPartyApis.Filters),
|
||||||
|
},
|
||||||
|
Expression: "lastseen",
|
||||||
|
Disabled: false,
|
||||||
|
Having: nil,
|
||||||
|
OrderBy: nil,
|
||||||
|
GroupBy: getGroupBy([]v3.AttributeKey{}, thirdPartyApis.GroupBy),
|
||||||
|
Legend: "",
|
||||||
|
ReduceTo: v3.ReduceToOperatorAvg,
|
||||||
|
}
|
||||||
|
|
||||||
|
compositeQuery := &v3.CompositeQuery{
|
||||||
|
QueryType: v3.QueryTypeBuilder,
|
||||||
|
PanelType: v3.PanelTypeTable,
|
||||||
|
FillGaps: false,
|
||||||
|
BuilderQueries: builderQueries,
|
||||||
|
}
|
||||||
|
|
||||||
|
queryRangeParams := &v3.QueryRangeParamsV3{
|
||||||
|
Start: unixMilliStart,
|
||||||
|
End: unixMilliEnd,
|
||||||
|
Step: defaultStepInterval,
|
||||||
|
CompositeQuery: compositeQuery,
|
||||||
|
Version: "v4",
|
||||||
|
FormatForWeb: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryRangeParams, nil
|
||||||
|
}
|
@ -0,0 +1,267 @@
|
|||||||
|
package thirdPartyApi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilterResponse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []*v3.Result
|
||||||
|
expected []*v3.Result
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should filter out IP addresses from net.peer.name",
|
||||||
|
input: []*v3.Result{
|
||||||
|
{
|
||||||
|
Table: &v3.Table{
|
||||||
|
Rows: []*v3.TableRow{
|
||||||
|
{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"net.peer.name": "192.168.1.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"net.peer.name": "example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []*v3.Result{
|
||||||
|
{
|
||||||
|
Table: &v3.Table{
|
||||||
|
Rows: []*v3.TableRow{
|
||||||
|
{
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"net.peer.name": "example.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should handle nil data",
|
||||||
|
input: []*v3.Result{
|
||||||
|
{
|
||||||
|
Table: &v3.Table{
|
||||||
|
Rows: []*v3.TableRow{
|
||||||
|
{
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []*v3.Result{
|
||||||
|
{
|
||||||
|
Table: &v3.Table{
|
||||||
|
Rows: []*v3.TableRow{
|
||||||
|
{
|
||||||
|
Data: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := FilterResponse(tt.input)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFilterSet(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
existingFilters []v3.FilterItem
|
||||||
|
apiFilters v3.FilterSet
|
||||||
|
expected []v3.FilterItem
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should append new filters",
|
||||||
|
existingFilters: []v3.FilterItem{
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{Key: "existing"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
apiFilters: v3.FilterSet{
|
||||||
|
Items: []v3.FilterItem{
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{Key: "new"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: []v3.FilterItem{
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{Key: "existing"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{Key: "new"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should handle empty api filters",
|
||||||
|
existingFilters: []v3.FilterItem{{Key: v3.AttributeKey{Key: "existing"}}},
|
||||||
|
apiFilters: v3.FilterSet{},
|
||||||
|
expected: []v3.FilterItem{{Key: v3.AttributeKey{Key: "existing"}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := getFilterSet(tt.existingFilters, tt.apiFilters)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGroupBy(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
existingGroup []v3.AttributeKey
|
||||||
|
apiGroup []v3.AttributeKey
|
||||||
|
expected []v3.AttributeKey
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should append new group by attributes",
|
||||||
|
existingGroup: []v3.AttributeKey{
|
||||||
|
{Key: "existing"},
|
||||||
|
},
|
||||||
|
apiGroup: []v3.AttributeKey{
|
||||||
|
{Key: "new"},
|
||||||
|
},
|
||||||
|
expected: []v3.AttributeKey{
|
||||||
|
{Key: "existing"},
|
||||||
|
{Key: "new"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should handle empty api group",
|
||||||
|
existingGroup: []v3.AttributeKey{{Key: "existing"}},
|
||||||
|
apiGroup: []v3.AttributeKey{},
|
||||||
|
expected: []v3.AttributeKey{{Key: "existing"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := getGroupBy(tt.existingGroup, tt.apiGroup)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDomainList(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input *ThirdPartyApis
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic domain list query",
|
||||||
|
input: &ThirdPartyApis{
|
||||||
|
Start: 1000,
|
||||||
|
End: 2000,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with filters and group by",
|
||||||
|
input: &ThirdPartyApis{
|
||||||
|
Start: 1000,
|
||||||
|
End: 2000,
|
||||||
|
Filters: v3.FilterSet{
|
||||||
|
Items: []v3.FilterItem{
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{Key: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
GroupBy: []v3.AttributeKey{
|
||||||
|
{Key: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := BuildDomainList(tt.input)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
assert.Equal(t, tt.input.Start, result.Start)
|
||||||
|
assert.Equal(t, tt.input.End, result.End)
|
||||||
|
assert.NotNil(t, result.CompositeQuery)
|
||||||
|
assert.NotNil(t, result.CompositeQuery.BuilderQueries)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildDomainInfo(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input *ThirdPartyApis
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic domain info query",
|
||||||
|
input: &ThirdPartyApis{
|
||||||
|
Start: 1000,
|
||||||
|
End: 2000,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with filters and group by",
|
||||||
|
input: &ThirdPartyApis{
|
||||||
|
Start: 1000,
|
||||||
|
End: 2000,
|
||||||
|
Filters: v3.FilterSet{
|
||||||
|
Items: []v3.FilterItem{
|
||||||
|
{
|
||||||
|
Key: v3.AttributeKey{Key: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
GroupBy: []v3.AttributeKey{
|
||||||
|
{Key: "test"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result, err := BuildDomainInfo(tt.input)
|
||||||
|
if tt.wantErr {
|
||||||
|
assert.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
assert.Equal(t, tt.input.Start, result.Start)
|
||||||
|
assert.Equal(t, tt.input.End, result.End)
|
||||||
|
assert.NotNil(t, result.CompositeQuery)
|
||||||
|
assert.NotNil(t, result.CompositeQuery.BuilderQueries)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,9 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/integrations/messagingQueues/kafka"
|
||||||
|
queues2 "go.signoz.io/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
|
||||||
|
"go.signoz.io/signoz/pkg/query-service/app/integrations/thirdPartyApi"
|
||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -1007,3 +1010,30 @@ func ParseQueryRangeParams(r *http.Request) (*v3.QueryRangeParamsV3, *model.ApiE
|
|||||||
|
|
||||||
return queryRangeParams, nil
|
return queryRangeParams, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseKafkaQueueBody parse for messaging queue params
|
||||||
|
func ParseKafkaQueueBody(r *http.Request) (*kafka.MessagingQueue, *model.ApiError) {
|
||||||
|
messagingQueue := new(kafka.MessagingQueue)
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(messagingQueue); err != nil {
|
||||||
|
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("cannot parse the request body: %v", err)}
|
||||||
|
}
|
||||||
|
return messagingQueue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseQueueBody parses for any queue
|
||||||
|
func ParseQueueBody(r *http.Request) (*queues2.QueueListRequest, *model.ApiError) {
|
||||||
|
queue := new(queues2.QueueListRequest)
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(queue); err != nil {
|
||||||
|
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("cannot parse the request body: %v", err)}
|
||||||
|
}
|
||||||
|
return queue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRequestBody for third party APIs
|
||||||
|
func ParseRequestBody(r *http.Request) (*thirdPartyApi.ThirdPartyApis, *model.ApiError) {
|
||||||
|
thirdPartApis := new(thirdPartyApi.ThirdPartyApis)
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(thirdPartApis); err != nil {
|
||||||
|
return nil, &model.ApiError{Typ: model.ErrorBadData, Err: fmt.Errorf("cannot parse the request body: %v", err)}
|
||||||
|
}
|
||||||
|
return thirdPartApis, nil
|
||||||
|
}
|
||||||
|
@ -315,6 +315,7 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
|
|||||||
api.RegisterWebSocketPaths(r, am)
|
api.RegisterWebSocketPaths(r, am)
|
||||||
api.RegisterQueryRangeV4Routes(r, am)
|
api.RegisterQueryRangeV4Routes(r, am)
|
||||||
api.RegisterMessagingQueuesRoutes(r, am)
|
api.RegisterMessagingQueuesRoutes(r, am)
|
||||||
|
api.RegisterThirdPartyApiRoutes(r, am)
|
||||||
|
|
||||||
c := cors.New(cors.Options{
|
c := cors.New(cors.Options{
|
||||||
AllowedOrigins: []string{"*"},
|
AllowedOrigins: []string{"*"},
|
||||||
|
@ -297,6 +297,9 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, _ string, pan
|
|||||||
aggregationKey := ""
|
aggregationKey := ""
|
||||||
if mq.AggregateAttribute.Key != "" {
|
if mq.AggregateAttribute.Key != "" {
|
||||||
aggregationKey = getColumnName(mq.AggregateAttribute)
|
aggregationKey = getColumnName(mq.AggregateAttribute)
|
||||||
|
if mq.AggregateAttribute.Key == "timestamp" {
|
||||||
|
aggregationKey = "toUnixTimestamp64Nano(timestamp)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mq.AggregateOperator {
|
switch mq.AggregateOperator {
|
||||||
|
@ -328,6 +328,9 @@ func buildTracesQuery(start, end, step int64, mq *v3.BuilderQuery, panelType v3.
|
|||||||
aggregationKey := ""
|
aggregationKey := ""
|
||||||
if mq.AggregateAttribute.Key != "" {
|
if mq.AggregateAttribute.Key != "" {
|
||||||
aggregationKey = getColumnName(mq.AggregateAttribute)
|
aggregationKey = getColumnName(mq.AggregateAttribute)
|
||||||
|
if mq.AggregateAttribute.Key == "timestamp" {
|
||||||
|
aggregationKey = "toUnixTimestamp64Nano(timestamp)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryTmpl string
|
var queryTmpl string
|
||||||
|
@ -678,6 +678,21 @@ func Test_buildTracesQuery(t *testing.T) {
|
|||||||
want: "SELECT toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " +
|
want: "SELECT toFloat64(count(distinct(name))) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " +
|
||||||
"(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) having value > 10 order by value ASC",
|
"(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) having value > 10 order by value ASC",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Test timestamp as aggregate attribute key",
|
||||||
|
args: args{
|
||||||
|
panelType: v3.PanelTypeTable,
|
||||||
|
start: 1680066360726210000,
|
||||||
|
end: 1680066458000000000,
|
||||||
|
mq: &v3.BuilderQuery{
|
||||||
|
AggregateOperator: v3.AggregateOperatorMax,
|
||||||
|
Filters: &v3.FilterSet{},
|
||||||
|
AggregateAttribute: v3.AttributeKey{Key: "timestamp", IsColumn: false, Type: v3.AttributeKeyTypeTag},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "SELECT max(toUnixTimestamp64Nano(timestamp)) as value from signoz_traces.distributed_signoz_index_v3 where (timestamp >= '1680066360726210000' AND timestamp <= '1680066458000000000') AND " +
|
||||||
|
"(ts_bucket_start >= 1680064560 AND ts_bucket_start <= 1680066458) order by value DESC",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user