Revert "3rd party API sem conv fix (Supports >1.26) (#8822)" (#8954)

This reverts commit 396e0cdc2d7cf58814295f34e7efaf9f845931bc.
This commit is contained in:
Ekansh Gupta 2025-09-01 12:50:56 +05:30 committed by GitHub
parent 87ce197631
commit 382d9d4a87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 932 additions and 851 deletions

View File

@ -1,503 +0,0 @@
package thirdpartyapi
import (
"fmt"
"github.com/SigNoz/signoz/pkg/types/thirdpartyapitypes"
"net"
"regexp"
"time"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
)
const (
urlPathKeyLegacy = "http.url"
serverAddressKeyLegacy = "net.peer.name"
urlPathKey = "url.full"
serverAddressKey = "server.address"
)
var defaultStepInterval = 60 * time.Second
type SemconvFieldMapping struct {
LegacyField string
CurrentField string
FieldType telemetrytypes.FieldDataType
Context telemetrytypes.FieldContext
}
var dualSemconvGroupByKeys = map[string][]qbtypes.GroupByKey{
"server": {
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: serverAddressKey,
FieldDataType: telemetrytypes.FieldDataTypeString,
FieldContext: telemetrytypes.FieldContextAttribute,
Signal: telemetrytypes.SignalTraces,
},
},
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: serverAddressKeyLegacy,
FieldDataType: telemetrytypes.FieldDataTypeString,
FieldContext: telemetrytypes.FieldContextAttribute,
Signal: telemetrytypes.SignalTraces,
},
},
},
"url": {
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: urlPathKey,
FieldDataType: telemetrytypes.FieldDataTypeString,
FieldContext: telemetrytypes.FieldContextAttribute,
Signal: telemetrytypes.SignalTraces,
},
},
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: urlPathKeyLegacy,
FieldDataType: telemetrytypes.FieldDataTypeString,
FieldContext: telemetrytypes.FieldContextAttribute,
Signal: telemetrytypes.SignalTraces,
},
},
},
}
func MergeSemconvColumns(result *qbtypes.QueryRangeResponse) *qbtypes.QueryRangeResponse {
if result == nil || result.Data.Results == nil {
return result
}
for _, res := range result.Data.Results {
scalarData, ok := res.(*qbtypes.ScalarData)
if !ok {
continue
}
serverAddressKeyIdx := -1
serverAddressKeyLegacyIdx := -1
for i, col := range scalarData.Columns {
if col.Name == serverAddressKey {
serverAddressKeyIdx = i
} else if col.Name == serverAddressKeyLegacy {
serverAddressKeyLegacyIdx = i
}
}
if serverAddressKeyIdx == -1 || serverAddressKeyLegacyIdx == -1 {
continue
}
var newRows [][]any
for _, row := range scalarData.Data {
if len(row) <= serverAddressKeyIdx || len(row) <= serverAddressKeyLegacyIdx {
continue
}
var serverName any
if isValidValue(row[serverAddressKeyIdx]) {
serverName = row[serverAddressKeyIdx]
} else if isValidValue(row[serverAddressKeyLegacyIdx]) {
serverName = row[serverAddressKeyLegacyIdx]
}
if serverName != nil {
newRow := make([]any, len(row)-1)
newRow[0] = serverName
targetIdx := 1
for i, val := range row {
if i != serverAddressKeyLegacyIdx && i != serverAddressKeyIdx {
if targetIdx < len(newRow) {
newRow[targetIdx] = val
targetIdx++
}
}
}
newRows = append(newRows, newRow)
}
}
newColumns := make([]*qbtypes.ColumnDescriptor, len(scalarData.Columns)-1)
targetIdx := 0
for i, col := range scalarData.Columns {
if i == serverAddressKeyIdx {
newCol := &qbtypes.ColumnDescriptor{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: serverAddressKeyLegacy,
FieldDataType: col.FieldDataType,
FieldContext: col.FieldContext,
Signal: col.Signal,
},
QueryName: col.QueryName,
AggregationIndex: col.AggregationIndex,
Meta: col.Meta,
Type: col.Type,
}
newColumns[targetIdx] = newCol
targetIdx++
} else if i != serverAddressKeyLegacyIdx {
newColumns[targetIdx] = col
targetIdx++
}
}
scalarData.Columns = newColumns
scalarData.Data = newRows
}
return result
}
func isValidValue(val any) bool {
if val == nil {
return false
}
if str, ok := val.(string); ok {
return str != "" && str != "n/a"
}
return true
}
func FilterResponse(results []*qbtypes.QueryRangeResponse) []*qbtypes.QueryRangeResponse {
filteredResults := make([]*qbtypes.QueryRangeResponse, 0, len(results))
for _, res := range results {
if res.Data.Results == nil {
continue
}
filteredData := make([]any, 0, len(res.Data.Results))
for _, result := range res.Data.Results {
if result == nil {
filteredData = append(filteredData, result)
continue
}
switch resultData := result.(type) {
case *qbtypes.TimeSeriesData:
if resultData.Aggregations != nil {
for _, agg := range resultData.Aggregations {
filteredSeries := make([]*qbtypes.TimeSeries, 0, len(agg.Series))
for _, series := range agg.Series {
if shouldIncludeSeries(series) {
filteredSeries = append(filteredSeries, series)
}
}
agg.Series = filteredSeries
}
}
case *qbtypes.RawData:
filteredRows := make([]*qbtypes.RawRow, 0, len(resultData.Rows))
for _, row := range resultData.Rows {
if shouldIncludeRow(row) {
filteredRows = append(filteredRows, row)
}
}
resultData.Rows = filteredRows
}
filteredData = append(filteredData, result)
}
res.Data.Results = filteredData
filteredResults = append(filteredResults, res)
}
return filteredResults
}
func shouldIncludeSeries(series *qbtypes.TimeSeries) bool {
for _, label := range series.Labels {
if label.Key.Name == serverAddressKeyLegacy || label.Key.Name == serverAddressKey {
if strVal, ok := label.Value.(string); ok {
if net.ParseIP(strVal) != nil {
return false
}
}
}
}
return true
}
func shouldIncludeRow(row *qbtypes.RawRow) bool {
if row.Data != nil {
for _, key := range []string{serverAddressKeyLegacy, serverAddressKey} {
if domainVal, ok := row.Data[key]; ok {
if domainStr, ok := domainVal.(string); ok {
if net.ParseIP(domainStr) != nil {
return false
}
}
}
}
}
return true
}
func containsKindStringOverride(expression string) bool {
kindStringPattern := regexp.MustCompile(`kind_string\s*[!=<>]+`)
return kindStringPattern.MatchString(expression)
}
func mergeGroupBy(base, additional []qbtypes.GroupByKey) []qbtypes.GroupByKey {
return append(base, additional...)
}
func BuildDomainList(req *thirdpartyapitypes.ThirdPartyApiRequest) (*qbtypes.QueryRangeRequest, error) {
if err := req.Validate(); err != nil {
return nil, err
}
queries := []qbtypes.QueryEnvelope{
buildEndpointsQuery(req),
buildLastSeenQuery(req),
buildRpsQuery(req),
buildErrorQuery(req),
buildTotalSpanQuery(req),
buildP99Query(req),
buildErrorRateFormula(),
}
return &qbtypes.QueryRangeRequest{
SchemaVersion: "v5",
Start: req.Start,
End: req.End,
RequestType: qbtypes.RequestTypeScalar,
CompositeQuery: qbtypes.CompositeQuery{
Queries: queries,
},
FormatOptions: &qbtypes.FormatOptions{
FormatTableResultForUI: true,
},
}, nil
}
func BuildDomainInfo(req *thirdpartyapitypes.ThirdPartyApiRequest) (*qbtypes.QueryRangeRequest, error) {
if err := req.Validate(); err != nil {
return nil, err
}
queries := []qbtypes.QueryEnvelope{
buildEndpointsInfoQuery(req),
buildP99InfoQuery(req),
buildErrorRateInfoQuery(req),
buildLastSeenInfoQuery(req),
}
return &qbtypes.QueryRangeRequest{
SchemaVersion: "v5",
Start: req.Start,
End: req.End,
RequestType: qbtypes.RequestTypeScalar,
CompositeQuery: qbtypes.CompositeQuery{
Queries: queries,
},
FormatOptions: &qbtypes.FormatOptions{
FormatTableResultForUI: true,
},
}, nil
}
func buildEndpointsQuery(req *thirdpartyapitypes.ThirdPartyApiRequest) qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Name: "endpoints",
Signal: telemetrytypes.SignalTraces,
StepInterval: qbtypes.Step{Duration: defaultStepInterval},
Aggregations: []qbtypes.TraceAggregation{
{Expression: "count_distinct(http.url)"},
},
Filter: buildBaseFilter(req.Filter),
GroupBy: mergeGroupBy(dualSemconvGroupByKeys["server"], req.GroupBy),
},
}
}
func buildLastSeenQuery(req *thirdpartyapitypes.ThirdPartyApiRequest) qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Name: "lastseen",
Signal: telemetrytypes.SignalTraces,
StepInterval: qbtypes.Step{Duration: defaultStepInterval},
Aggregations: []qbtypes.TraceAggregation{
{Expression: "max(timestamp)"},
},
Filter: buildBaseFilter(req.Filter),
GroupBy: mergeGroupBy(dualSemconvGroupByKeys["server"], req.GroupBy),
},
}
}
func buildRpsQuery(req *thirdpartyapitypes.ThirdPartyApiRequest) qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Name: "rps",
Signal: telemetrytypes.SignalTraces,
StepInterval: qbtypes.Step{Duration: defaultStepInterval},
Aggregations: []qbtypes.TraceAggregation{
{Expression: "rate()"},
},
Filter: buildBaseFilter(req.Filter),
GroupBy: mergeGroupBy(dualSemconvGroupByKeys["server"], req.GroupBy),
},
}
}
func buildErrorQuery(req *thirdpartyapitypes.ThirdPartyApiRequest) qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Name: "error",
Signal: telemetrytypes.SignalTraces,
StepInterval: qbtypes.Step{Duration: defaultStepInterval},
Aggregations: []qbtypes.TraceAggregation{
{Expression: "count()"},
},
Filter: buildErrorFilter(req.Filter),
GroupBy: mergeGroupBy(dualSemconvGroupByKeys["server"], req.GroupBy),
},
}
}
func buildTotalSpanQuery(req *thirdpartyapitypes.ThirdPartyApiRequest) qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Name: "total_span",
Signal: telemetrytypes.SignalTraces,
StepInterval: qbtypes.Step{Duration: defaultStepInterval},
Aggregations: []qbtypes.TraceAggregation{
{Expression: "count()"},
},
Filter: buildBaseFilter(req.Filter),
GroupBy: mergeGroupBy(dualSemconvGroupByKeys["server"], req.GroupBy),
},
}
}
func buildP99Query(req *thirdpartyapitypes.ThirdPartyApiRequest) qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Name: "p99",
Signal: telemetrytypes.SignalTraces,
StepInterval: qbtypes.Step{Duration: defaultStepInterval},
Aggregations: []qbtypes.TraceAggregation{
{Expression: "p99(duration_nano)"},
},
Filter: buildBaseFilter(req.Filter),
GroupBy: mergeGroupBy(dualSemconvGroupByKeys["server"], req.GroupBy),
},
}
}
func buildErrorRateFormula() qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeFormula,
Spec: qbtypes.QueryBuilderFormula{
Name: "error_rate",
Expression: "(error/total_span)*100",
},
}
}
func buildEndpointsInfoQuery(req *thirdpartyapitypes.ThirdPartyApiRequest) qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Name: "endpoints",
Signal: telemetrytypes.SignalTraces,
StepInterval: qbtypes.Step{Duration: defaultStepInterval},
Aggregations: []qbtypes.TraceAggregation{
{Expression: "rate(http.url)"},
},
Filter: buildBaseFilter(req.Filter),
GroupBy: mergeGroupBy(dualSemconvGroupByKeys["url"], req.GroupBy),
},
}
}
func buildP99InfoQuery(req *thirdpartyapitypes.ThirdPartyApiRequest) qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Name: "p99",
Signal: telemetrytypes.SignalTraces,
StepInterval: qbtypes.Step{Duration: defaultStepInterval},
Aggregations: []qbtypes.TraceAggregation{
{Expression: "p99(duration_nano)"},
},
Filter: buildBaseFilter(req.Filter),
GroupBy: req.GroupBy,
},
}
}
func buildErrorRateInfoQuery(req *thirdpartyapitypes.ThirdPartyApiRequest) qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Name: "error_rate",
Signal: telemetrytypes.SignalTraces,
StepInterval: qbtypes.Step{Duration: defaultStepInterval},
Aggregations: []qbtypes.TraceAggregation{
{Expression: "rate()"},
},
Filter: buildBaseFilter(req.Filter),
GroupBy: req.GroupBy,
},
}
}
func buildLastSeenInfoQuery(req *thirdpartyapitypes.ThirdPartyApiRequest) qbtypes.QueryEnvelope {
return qbtypes.QueryEnvelope{
Type: qbtypes.QueryTypeBuilder,
Spec: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
Name: "lastseen",
Signal: telemetrytypes.SignalTraces,
StepInterval: qbtypes.Step{Duration: defaultStepInterval},
Aggregations: []qbtypes.TraceAggregation{
{Expression: "max(timestamp)"},
},
Filter: buildBaseFilter(req.Filter),
GroupBy: req.GroupBy,
},
}
}
func buildBaseFilter(additionalFilter *qbtypes.Filter) *qbtypes.Filter {
baseExpression := fmt.Sprintf("(%s EXISTS OR %s EXISTS) AND kind_string = 'Client'",
urlPathKeyLegacy, urlPathKey)
if additionalFilter != nil && additionalFilter.Expression != "" {
if containsKindStringOverride(additionalFilter.Expression) {
return &qbtypes.Filter{Expression: baseExpression}
}
baseExpression = fmt.Sprintf("(%s) AND (%s)", baseExpression, additionalFilter.Expression)
}
return &qbtypes.Filter{Expression: baseExpression}
}
func buildErrorFilter(additionalFilter *qbtypes.Filter) *qbtypes.Filter {
errorExpression := fmt.Sprintf("has_error = true AND (%s EXISTS OR %s EXISTS) AND kind_string = 'Client'",
urlPathKeyLegacy, urlPathKey)
if additionalFilter != nil && additionalFilter.Expression != "" {
if containsKindStringOverride(additionalFilter.Expression) {
return &qbtypes.Filter{Expression: errorExpression}
}
errorExpression = fmt.Sprintf("(%s) AND (%s)", errorExpression, additionalFilter.Expression)
}
return &qbtypes.Filter{Expression: errorExpression}
}

View File

@ -1,233 +0,0 @@
package thirdpartyapi
import (
"github.com/SigNoz/signoz/pkg/types/thirdpartyapitypes"
"testing"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/stretchr/testify/assert"
)
func TestFilterResponse(t *testing.T) {
tests := []struct {
name string
input []*qbtypes.QueryRangeResponse
expected []*qbtypes.QueryRangeResponse
}{
{
name: "should filter out IP addresses from series labels",
input: []*qbtypes.QueryRangeResponse{
{
Data: qbtypes.QueryData{
Results: []any{
&qbtypes.TimeSeriesData{
Aggregations: []*qbtypes.AggregationBucket{
{
Series: []*qbtypes.TimeSeries{
{
Labels: []*qbtypes.Label{
{
Key: telemetrytypes.TelemetryFieldKey{Name: "net.peer.name"},
Value: "192.168.1.1",
},
},
},
{
Labels: []*qbtypes.Label{
{
Key: telemetrytypes.TelemetryFieldKey{Name: "net.peer.name"},
Value: "example.com",
},
},
},
},
},
},
},
},
},
},
},
expected: []*qbtypes.QueryRangeResponse{
{
Data: qbtypes.QueryData{
Results: []any{
&qbtypes.TimeSeriesData{
Aggregations: []*qbtypes.AggregationBucket{
{
Series: []*qbtypes.TimeSeries{
{
Labels: []*qbtypes.Label{
{
Key: telemetrytypes.TelemetryFieldKey{Name: "net.peer.name"},
Value: "example.com",
},
},
},
},
},
},
},
},
},
},
},
},
{
name: "should filter out IP addresses from raw data",
input: []*qbtypes.QueryRangeResponse{
{
Data: qbtypes.QueryData{
Results: []any{
&qbtypes.RawData{
Rows: []*qbtypes.RawRow{
{
Data: map[string]any{
"net.peer.name": "192.168.1.1",
},
},
{
Data: map[string]any{
"net.peer.name": "example.com",
},
},
},
},
},
},
},
},
expected: []*qbtypes.QueryRangeResponse{
{
Data: qbtypes.QueryData{
Results: []any{
&qbtypes.RawData{
Rows: []*qbtypes.RawRow{
{
Data: map[string]any{
"net.peer.name": "example.com",
},
},
},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := FilterResponse(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestBuildDomainList(t *testing.T) {
tests := []struct {
name string
input *thirdpartyapitypes.ThirdPartyApiRequest
wantErr bool
}{
{
name: "basic domain list query",
input: &thirdpartyapitypes.ThirdPartyApiRequest{
Start: 1000,
End: 2000,
},
wantErr: false,
},
{
name: "with filters and group by",
input: &thirdpartyapitypes.ThirdPartyApiRequest{
Start: 1000,
End: 2000,
Filter: &qbtypes.Filter{
Expression: "test = 'value'",
},
GroupBy: []qbtypes.GroupByKey{
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: "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.Len(t, result.CompositeQuery.Queries, 7) // endpoints, lastseen, rps, error, total_span, p99, error_rate
assert.Equal(t, "v5", result.SchemaVersion)
assert.Equal(t, qbtypes.RequestTypeScalar, result.RequestType)
})
}
}
func TestBuildDomainInfo(t *testing.T) {
tests := []struct {
name string
input *thirdpartyapitypes.ThirdPartyApiRequest
wantErr bool
}{
{
name: "basic domain info query",
input: &thirdpartyapitypes.ThirdPartyApiRequest{
Start: 1000,
End: 2000,
},
wantErr: false,
},
{
name: "with filters and group by",
input: &thirdpartyapitypes.ThirdPartyApiRequest{
Start: 1000,
End: 2000,
Filter: &qbtypes.Filter{
Expression: "test = 'value'",
},
GroupBy: []qbtypes.GroupByKey{
{
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
Name: "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.Len(t, result.CompositeQuery.Queries, 4) // endpoints, p99, error_rate, lastseen
assert.Equal(t, "v5", result.SchemaVersion)
assert.Equal(t, qbtypes.RequestTypeScalar, result.RequestType)
})
}
}

View File

@ -8,9 +8,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/SigNoz/signoz/pkg/modules/thirdpartyapi"
//qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"io"
"math"
"net/http"
@ -48,6 +45,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/app/inframetrics"
queues2 "github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations/thirdPartyApi"
"github.com/SigNoz/signoz/pkg/query-service/app/logs"
logsv3 "github.com/SigNoz/signoz/pkg/query-service/app/logs/v3"
logsv4 "github.com/SigNoz/signoz/pkg/query-service/app/logs/v4"
@ -4984,130 +4982,105 @@ func (aH *APIHandler) getQueueOverview(w http.ResponseWriter, r *http.Request) {
}
func (aH *APIHandler) getDomainList(w http.ResponseWriter, r *http.Request) {
// Extract claims from context for organization ID
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
render.Error(w, err)
return
}
orgID, err := valuer.NewUUID(claims.OrgID)
if err != nil {
render.Error(w, err)
return
}
// Parse the request body to get third-party query parameters
thirdPartyQueryRequest, apiErr := ParseRequestBody(r)
if apiErr != nil {
zap.L().Error("Failed to parse request body", zap.Error(apiErr))
render.Error(w, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, apiErr.Error()))
zap.L().Error(apiErr.Err.Error())
RespondError(w, apiErr, nil)
return
}
// Build the v5 query range request for domain listing
queryRangeRequest, err := thirdpartyapi.BuildDomainList(thirdPartyQueryRequest)
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(), orgID, queryRangeParams)
if err != nil {
zap.L().Error("Failed to build domain list query", zap.Error(err))
apiErrObj := errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
render.Error(w, apiErrObj)
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
RespondError(w, apiErrObj, errQuriesByName)
return
}
// Validate the v5 query range request
if err := queryRangeRequest.Validate(); err != nil {
zap.L().Error("Query validation failed", zap.Error(err))
apiErrObj := errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
render.Error(w, apiErrObj)
return
}
// Execute the query using the v5 querier
result, err := aH.Signoz.Querier.QueryRange(r.Context(), orgID, queryRangeRequest)
result, err = postprocess.PostProcessResult(result, queryRangeParams)
if err != nil {
zap.L().Error("Query execution failed", zap.Error(err))
apiErrObj := errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
render.Error(w, apiErrObj)
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
RespondError(w, apiErrObj, errQuriesByName)
return
}
result = thirdpartyapi.MergeSemconvColumns(result)
// Filter IP addresses if ShowIp is false
var finalResult = result
if !thirdPartyQueryRequest.ShowIp {
filteredResults := thirdpartyapi.FilterResponse([]*qbtypes.QueryRangeResponse{result})
if len(filteredResults) > 0 {
finalResult = filteredResults[0]
}
result = thirdPartyApi.FilterResponse(result)
}
// Send the response
aH.Respond(w, finalResult)
resp := v3.QueryRangeResponse{
Result: result,
}
aH.Respond(w, resp)
}
// getDomainInfo handles requests for domain information using v5 query builder
func (aH *APIHandler) getDomainInfo(w http.ResponseWriter, r *http.Request) {
// Extract claims from context for organization ID
claims, err := authtypes.ClaimsFromContext(r.Context())
if err != nil {
render.Error(w, err)
return
}
orgID, err := valuer.NewUUID(claims.OrgID)
if err != nil {
render.Error(w, err)
return
}
// Parse the request body to get third-party query parameters
thirdPartyQueryRequest, apiErr := ParseRequestBody(r)
if apiErr != nil {
zap.L().Error("Failed to parse request body", zap.Error(apiErr))
render.Error(w, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, apiErr.Error()))
zap.L().Error(apiErr.Err.Error())
RespondError(w, apiErr, nil)
return
}
// Build the v5 query range request for domain info
queryRangeRequest, err := thirdpartyapi.BuildDomainInfo(thirdPartyQueryRequest)
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(), orgID, queryRangeParams)
if err != nil {
zap.L().Error("Failed to build domain info query", zap.Error(err))
apiErrObj := errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
render.Error(w, apiErrObj)
apiErrObj := &model.ApiError{Typ: model.ErrorBadData, Err: err}
RespondError(w, apiErrObj, errQuriesByName)
return
}
// Validate the v5 query range request
if err := queryRangeRequest.Validate(); err != nil {
zap.L().Error("Query validation failed", zap.Error(err))
apiErrObj := errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
render.Error(w, apiErrObj)
return
}
result = postprocess.TransformToTableForBuilderQueries(result, queryRangeParams)
// Execute the query using the v5 querier
result, err := aH.Signoz.Querier.QueryRange(r.Context(), orgID, queryRangeRequest)
if err != nil {
zap.L().Error("Query execution failed", zap.Error(err))
apiErrObj := errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, err.Error())
render.Error(w, apiErrObj)
return
}
result = thirdpartyapi.MergeSemconvColumns(result)
// Filter IP addresses if ShowIp is false
var finalResult *qbtypes.QueryRangeResponse = result
if !thirdPartyQueryRequest.ShowIp {
filteredResults := thirdpartyapi.FilterResponse([]*qbtypes.QueryRangeResponse{result})
if len(filteredResults) > 0 {
finalResult = filteredResults[0]
}
result = thirdPartyApi.FilterResponse(result)
}
// Send the response
aH.Respond(w, finalResult)
resp := v3.QueryRangeResponse{
Result: result,
}
aH.Respond(w, resp)
}
// RegisterTraceFunnelsRoutes adds trace funnels routes

View File

@ -0,0 +1,13 @@
package thirdPartyApi
import v3 "github.com/SigNoz/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"`
}

View File

@ -0,0 +1,598 @@
package thirdPartyApi
import (
"net"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
)
const (
urlPathKey = "http.url"
serverNameKey = "net.peer.name"
)
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[serverNameKey]; 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",
Legend: "endpoints",
DataSource: v3.DataSourceTraces,
StepInterval: defaultStepInterval,
AggregateOperator: v3.AggregateOperatorCountDistinct,
AggregateAttribute: v3.AttributeKey{
Key: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
TimeAggregation: v3.TimeAggregationCountDistinct,
SpaceAggregation: v3.SpaceAggregationSum,
Filters: &v3.FilterSet{
Operator: "AND",
Items: getFilterSet([]v3.FilterItem{
{
Key: v3.AttributeKey{
Key: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
IsColumn: false,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorExists,
Value: "",
},
{
Key: v3.AttributeKey{
Key: "kind_string",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
Operator: "=",
Value: "Client",
},
}, thirdPartyApis.Filters),
},
Expression: "endpoints",
GroupBy: getGroupBy([]v3.AttributeKey{
{
Key: serverNameKey,
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
}, thirdPartyApis.GroupBy),
ReduceTo: v3.ReduceToOperatorAvg,
ShiftBy: 0,
IsAnomaly: false,
}
builderQueries["lastseen"] = &v3.BuilderQuery{
QueryName: "lastseen",
Legend: "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{
{
Key: v3.AttributeKey{
Key: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
IsColumn: false,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorExists,
Value: "",
},
{
Key: v3.AttributeKey{
Key: "kind_string",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
Operator: "=",
Value: "Client",
},
}, thirdPartyApis.Filters),
},
Expression: "lastseen",
GroupBy: getGroupBy([]v3.AttributeKey{
{
Key: serverNameKey,
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
}, thirdPartyApis.GroupBy),
ReduceTo: v3.ReduceToOperatorAvg,
ShiftBy: 0,
IsAnomaly: false,
}
builderQueries["rps"] = &v3.BuilderQuery{
QueryName: "rps",
Legend: "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{
{
Key: v3.AttributeKey{
Key: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
IsColumn: false,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorExists,
Value: "",
},
{
Key: v3.AttributeKey{
Key: "kind_string",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
Operator: "=",
Value: "Client",
},
}, thirdPartyApis.Filters),
},
Expression: "rps",
GroupBy: getGroupBy([]v3.AttributeKey{
{
Key: serverNameKey,
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
}, thirdPartyApis.GroupBy),
ReduceTo: v3.ReduceToOperatorAvg,
ShiftBy: 0,
IsAnomaly: false,
}
builderQueries["error"] = &v3.BuilderQuery{
QueryName: "error",
DataSource: v3.DataSourceTraces,
StepInterval: defaultStepInterval,
AggregateOperator: v3.AggregateOperatorCount,
AggregateAttribute: v3.AttributeKey{
Key: "span_id",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
TimeAggregation: v3.TimeAggregationCount,
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",
},
{
Key: v3.AttributeKey{
Key: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
IsColumn: false,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorExists,
Value: "",
},
{
Key: v3.AttributeKey{
Key: "kind_string",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
Operator: "=",
Value: "Client",
},
}, thirdPartyApis.Filters),
},
Expression: "error",
GroupBy: getGroupBy([]v3.AttributeKey{
{
Key: serverNameKey,
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
}, thirdPartyApis.GroupBy),
ReduceTo: v3.ReduceToOperatorAvg,
Disabled: true,
ShiftBy: 0,
IsAnomaly: false,
}
builderQueries["total_span"] = &v3.BuilderQuery{
QueryName: "total_span",
DataSource: v3.DataSourceTraces,
StepInterval: defaultStepInterval,
AggregateOperator: v3.AggregateOperatorCount,
AggregateAttribute: v3.AttributeKey{
Key: "span_id",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
TimeAggregation: v3.TimeAggregationCount,
SpaceAggregation: v3.SpaceAggregationSum,
Filters: &v3.FilterSet{
Operator: "AND",
Items: getFilterSet([]v3.FilterItem{
{
Key: v3.AttributeKey{
Key: "http.url",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: false,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorExists,
Value: "",
},
{
Key: v3.AttributeKey{
Key: "kind_string",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
Operator: "=",
Value: "Client",
},
}, thirdPartyApis.Filters),
},
Expression: "total_span",
GroupBy: getGroupBy([]v3.AttributeKey{
{
Key: "net.peer.name",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
}, thirdPartyApis.GroupBy),
ReduceTo: v3.ReduceToOperatorAvg,
Disabled: true,
ShiftBy: 0,
IsAnomaly: false,
}
builderQueries["p99"] = &v3.BuilderQuery{
QueryName: "p99",
Legend: "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{
{
Key: v3.AttributeKey{
Key: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
IsColumn: false,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorExists,
Value: "",
},
{
Key: v3.AttributeKey{
Key: "kind_string",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
Operator: "=",
Value: "Client",
},
}, thirdPartyApis.Filters),
},
Expression: "p99",
GroupBy: getGroupBy([]v3.AttributeKey{
{
Key: serverNameKey,
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
}, thirdPartyApis.GroupBy),
ReduceTo: v3.ReduceToOperatorAvg,
ShiftBy: 0,
IsAnomaly: false,
}
builderQueries["error_rate"] = &v3.BuilderQuery{
QueryName: "error_rate",
Expression: "(error/total_span)*100",
Legend: "error_rate",
Disabled: false,
ShiftBy: 0,
IsAnomaly: false,
}
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: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeTag,
},
TimeAggregation: v3.TimeAggregationRate,
SpaceAggregation: v3.SpaceAggregationSum,
Filters: &v3.FilterSet{
Operator: "AND",
Items: getFilterSet([]v3.FilterItem{
{
Key: v3.AttributeKey{
Key: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
IsColumn: false,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorExists,
Value: "",
},
{
Key: v3.AttributeKey{
Key: "kind_string",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
Operator: "=",
Value: "Client",
},
}, thirdPartyApis.Filters),
},
Expression: "endpoints",
Disabled: false,
GroupBy: getGroupBy([]v3.AttributeKey{
{
Key: urlPathKey,
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{
{
Key: v3.AttributeKey{
Key: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
IsColumn: false,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorExists,
Value: "",
},
{
Key: v3.AttributeKey{
Key: "kind_string",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
Operator: "=",
Value: "Client",
},
}, 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{
{
Key: v3.AttributeKey{
Key: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
IsColumn: false,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorExists,
Value: "",
},
{
Key: v3.AttributeKey{
Key: "kind_string",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
Operator: "=",
Value: "Client",
},
}, 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{
{
Key: v3.AttributeKey{
Key: urlPathKey,
DataType: v3.AttributeKeyDataTypeString,
IsColumn: false,
Type: v3.AttributeKeyTypeTag,
},
Operator: v3.FilterOperatorExists,
Value: "",
},
{
Key: v3.AttributeKey{
Key: "kind_string",
DataType: v3.AttributeKeyDataTypeString,
IsColumn: true,
},
Operator: "=",
Value: "Client",
},
}, 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
}

View File

@ -0,0 +1,267 @@
package thirdPartyApi
import (
"testing"
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
"github.com/stretchr/testify/assert"
)
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)
})
}
}

View File

@ -6,7 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/SigNoz/signoz/pkg/types/thirdpartyapitypes"
"math"
"net/http"
"sort"
@ -15,15 +14,16 @@ import (
"text/template"
"time"
"github.com/SigNoz/govaluate"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/kafka"
queues2 "github.com/SigNoz/signoz/pkg/query-service/app/integrations/messagingQueues/queues"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations/thirdPartyApi"
"github.com/SigNoz/govaluate"
"github.com/gorilla/mux"
promModel "github.com/prometheus/common/model"
"go.uber.org/multierr"
"go.uber.org/zap"
errorsV2 "github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/query-service/app/metrics"
"github.com/SigNoz/signoz/pkg/query-service/app/queryBuilder"
"github.com/SigNoz/signoz/pkg/query-service/common"
@ -981,15 +981,10 @@ func ParseQueueBody(r *http.Request) (*queues2.QueueListRequest, *model.ApiError
}
// ParseRequestBody for third party APIs
func ParseRequestBody(r *http.Request) (*thirdpartyapitypes.ThirdPartyApiRequest, error) {
req := new(thirdpartyapitypes.ThirdPartyApiRequest)
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
return nil, errorsV2.Newf(errorsV2.TypeInvalidInput, errorsV2.CodeInvalidInput, "cannot parse the request body: %v", err)
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)}
}
if err := req.Validate(); err != nil {
return nil, err
}
return req, nil
return thirdPartApis, nil
}

View File

@ -1,29 +0,0 @@
package thirdpartyapitypes
import (
"github.com/SigNoz/signoz/pkg/errors"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
)
type ThirdPartyApiRequest struct {
Start uint64 `json:"start"`
End uint64 `json:"end"`
ShowIp bool `json:"show_ip,omitempty"`
Domain string `json:"domain,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
Filter *qbtypes.Filter `json:"filters,omitempty"`
GroupBy []qbtypes.GroupByKey `json:"groupBy,omitempty"`
}
// Validate validates the ThirdPartyApiRequest
func (req *ThirdPartyApiRequest) Validate() error {
if req.Start >= req.End {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "start time must be before end time")
}
if req.Filter != nil && req.Filter.Expression == "" {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "filter expression cannot be empty when filter is provided")
}
return nil
}