Skip to content

Commit

Permalink
Feat: QS: query builder suggestions api v0 (SigNoz#5634)
Browse files Browse the repository at this point in the history
* chore: stash initial work with API signature

* chore: put together setup for integration testing filter suggestions

* feat: filter suggestions: suggest attribs using existing autocomplete logic

* chore: filter suggestions test: add expectation for example queries

* feat: filter suggestions: default suggestions when data yet to be received

* feat: finish plumbing basic example queries

* chore: add test for filter suggestions with an existing query

* feat: filter suggestions: don't suggest attribs already included in existing filter

* chore: generate example queries by including existing filter first

* chore: upgrade ClickHouse-go-mock

* chore: some cleanup of reader.GetQBFilterSuggestionsForLogs

* chore: some cleanup of filter suggestion tests

* chore: some cleanup to http handler and request parsing logic for filter suggestions

* chore: remove expectation that attrib suggestions won't contain attribs already used in filter
  • Loading branch information
raj-k-singh committed Aug 8, 2024
1 parent ae325ec commit eb14649
Show file tree
Hide file tree
Showing 12 changed files with 541 additions and 34 deletions.
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module go.signoz.io/signoz
go 1.21.3

require (
github.com/ClickHouse/clickhouse-go/v2 v2.20.0
github.com/ClickHouse/clickhouse-go/v2 v2.23.2
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/SigNoz/govaluate v0.0.0-20240203125216-988004ccc7fd
github.com/SigNoz/signoz-otel-collector v0.102.2
Expand Down Expand Up @@ -46,7 +46,7 @@ require (
github.com/sethvargo/go-password v0.2.0
github.com/smartystreets/goconvey v1.8.1
github.com/soheilhy/cmux v0.1.5
github.com/srikanthccv/ClickHouse-go-mock v0.7.0
github.com/srikanthccv/ClickHouse-go-mock v0.8.0
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/collector/component v0.102.1
go.opentelemetry.io/collector/confmap v0.102.1
Expand Down Expand Up @@ -83,7 +83,7 @@ require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
github.com/ClickHouse/ch-go v0.61.3 // indirect
github.com/ClickHouse/ch-go v0.61.5 // indirect
github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/aws/aws-sdk-go v1.53.16 // indirect
Expand Down Expand Up @@ -156,7 +156,7 @@ require (
github.com/segmentio/backo-go v1.0.1 // indirect
github.com/shirou/gopsutil/v3 v3.24.4 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smarty/assertions v1.15.0 // indirect
github.com/spf13/cobra v1.8.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ClickHouse/ch-go v0.61.3 h1:MmBwUhXrAOBZK7n/sWBzq6FdIQ01cuF2SaaO8KlDRzI=
github.com/ClickHouse/ch-go v0.61.3/go.mod h1:1PqXjMz/7S1ZUaKvwPA3i35W2bz2mAMFeCi6DIXgGwQ=
github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4=
github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg=
github.com/ClickHouse/clickhouse-go/v2 v2.20.0 h1:bvlLQ31XJfl7MxIqAq2l1G6JhHYzqEXdvfpMeU6bkKc=
github.com/ClickHouse/clickhouse-go/v2 v2.20.0/go.mod h1:VQfyA+tCwCRw2G7ogfY8V0fq/r0yJWzy8UDrjiP/Lbs=
github.com/ClickHouse/clickhouse-go/v2 v2.23.2 h1:+DAKPMnxLS7pduQZsrJc8OhdLS2L9MfDEJ2TS+hpYDM=
github.com/ClickHouse/clickhouse-go/v2 v2.23.2/go.mod h1:aNap51J1OM3yxQJRgM+AlP/MPkGBCL8A74uQThoQhR0=
github.com/Code-Hex/go-generics-cache v1.5.1 h1:6vhZGc5M7Y/YD8cIUcY8kcuQLB4cHR7U+0KMqAA0KcU=
github.com/Code-Hex/go-generics-cache v1.5.1/go.mod h1:qxcC9kRVrct9rHeiYpFWSoW1vxyillCVzX13KZG8dl4=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
Expand Down Expand Up @@ -693,6 +697,8 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
Expand All @@ -718,6 +724,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/srikanthccv/ClickHouse-go-mock v0.7.0 h1:XhRMX2663xkDGq3DYavw8m75O94s9u76hOIjo9QBl8c=
github.com/srikanthccv/ClickHouse-go-mock v0.7.0/go.mod h1:IJZ/eL1h4cOy/Jo3PzNKXSPmqRus15BC2MbduYPpA/g=
github.com/srikanthccv/ClickHouse-go-mock v0.8.0 h1:DeeM8XLbTFl6sjYPPwazPEXx7kmRV8TgPFVkt1SqT0Y=
github.com/srikanthccv/ClickHouse-go-mock v0.8.0/go.mod h1:pgJm+apjvi7FHxEdgw1Bt4MRbUYpVxyhKQ/59Wkig24=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
Expand Down
123 changes: 123 additions & 0 deletions pkg/query-service/app/clickhouseReader/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"os"
"reflect"
"regexp"
"slices"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -4357,6 +4358,128 @@ func (r *ClickHouseReader) GetLogAttributeValues(ctx context.Context, req *v3.Fi

}

func (r *ClickHouseReader) GetQBFilterSuggestionsForLogs(
ctx context.Context,
req *v3.QBFilterSuggestionsRequest,
) (*v3.QBFilterSuggestionsResponse, *model.ApiError) {
suggestions := v3.QBFilterSuggestionsResponse{
AttributeKeys: []v3.AttributeKey{},
ExampleQueries: []v3.FilterSet{},
}

// Use existing autocomplete logic for generating attribute suggestions
attribKeysResp, err := r.GetLogAttributeKeys(
ctx, &v3.FilterAttributeKeyRequest{
SearchText: req.SearchText,
DataSource: v3.DataSourceLogs,
Limit: req.Limit,
})
if err != nil {
return nil, model.InternalError(fmt.Errorf("couldn't get attribute keys: %w", err))
}

suggestions.AttributeKeys = attribKeysResp.AttributeKeys

// Rank suggested attributes
slices.SortFunc(suggestions.AttributeKeys, func(a v3.AttributeKey, b v3.AttributeKey) int {

// Higher score => higher rank
attribKeyScore := func(a v3.AttributeKey) int {

// Scoring criteria is expected to get more sophisticated in follow up changes
if a.Type == v3.AttributeKeyTypeResource {
return 2
}

if a.Type == v3.AttributeKeyTypeTag {
return 1
}

return 0
}

// To sort in descending order of score the return value must be negative when a > b
return attribKeyScore(b) - attribKeyScore(a)
})

// Put together suggested example queries.

newExampleQuery := func() v3.FilterSet {
// Include existing filter in example query if specified.
if req.ExistingFilter != nil {
return *req.ExistingFilter
}

return v3.FilterSet{
Operator: "AND",
Items: []v3.FilterItem{},
}
}

// Suggest example query for top suggested attribute using existing
// autocomplete logic for recommending attrib values
//
// Example queries for multiple top attributes using a batch version of
// GetLogAttributeValues is expected to come in a follow up change
if len(suggestions.AttributeKeys) > 0 {
topAttrib := suggestions.AttributeKeys[0]

resp, err := r.GetLogAttributeValues(ctx, &v3.FilterAttributeValueRequest{
DataSource: v3.DataSourceLogs,
FilterAttributeKey: topAttrib.Key,
FilterAttributeKeyDataType: topAttrib.DataType,
TagType: v3.TagType(topAttrib.Type),
Limit: 1,
})

if err != nil {
// Do not fail the entire request if only example query generation fails
zap.L().Error("could not find attribute values for creating example query", zap.Error(err))

} else {
addExampleQuerySuggestion := func(value any) {
exampleQuery := newExampleQuery()

exampleQuery.Items = append(exampleQuery.Items, v3.FilterItem{
Key: topAttrib,
Operator: "=",
Value: value,
})

suggestions.ExampleQueries = append(
suggestions.ExampleQueries, exampleQuery,
)
}

if len(resp.StringAttributeValues) > 0 {
addExampleQuerySuggestion(resp.StringAttributeValues[0])
} else if len(resp.NumberAttributeValues) > 0 {
addExampleQuerySuggestion(resp.NumberAttributeValues[0])
} else if len(resp.BoolAttributeValues) > 0 {
addExampleQuerySuggestion(resp.BoolAttributeValues[0])
}
}
}

// Suggest static example queries for standard log attributes if needed.
if len(suggestions.ExampleQueries) < req.Limit {
exampleQuery := newExampleQuery()
exampleQuery.Items = append(exampleQuery.Items, v3.FilterItem{
Key: v3.AttributeKey{
Key: "body",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeUnspecified,
IsColumn: true,
},
Operator: "contains",
Value: "error",
})
suggestions.ExampleQueries = append(suggestions.ExampleQueries, exampleQuery)
}

return &suggestions, nil
}

func readRow(vars []interface{}, columnNames []string, countOfNumberCols int) ([]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: ...}
Expand Down
26 changes: 26 additions & 0 deletions pkg/query-service/app/http_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ func (aH *APIHandler) RegisterQueryRangeV3Routes(router *mux.Router, am *AuthMid
subRouter.HandleFunc("/query_range", am.ViewAccess(aH.QueryRangeV3)).Methods(http.MethodPost)
subRouter.HandleFunc("/query_range/format", am.ViewAccess(aH.QueryRangeV3Format)).Methods(http.MethodPost)

subRouter.HandleFunc("/filter_suggestions", am.ViewAccess(aH.getQueryBuilderSuggestions)).Methods(http.MethodGet)

// live logs
subRouter.HandleFunc("/logs/livetail", am.ViewAccess(aH.liveTailLogs)).Methods(http.MethodGet)
}
Expand Down Expand Up @@ -3150,6 +3152,30 @@ func (aH *APIHandler) autocompleteAggregateAttributes(w http.ResponseWriter, r *
aH.Respond(w, response)
}

func (aH *APIHandler) getQueryBuilderSuggestions(w http.ResponseWriter, r *http.Request) {
req, err := parseQBFilterSuggestionsRequest(r)
if err != nil {
RespondError(w, err, nil)
return
}

if req.DataSource != v3.DataSourceLogs {
// Support for traces and metrics might come later
RespondError(w, model.BadRequest(
fmt.Errorf("suggestions not supported for %s", req.DataSource),
), nil)
return
}

response, err := aH.reader.GetQBFilterSuggestionsForLogs(r.Context(), req)
if err != nil {
RespondError(w, err, nil)
return
}

aH.Respond(w, response)
}

func (aH *APIHandler) autoCompleteAttributeKeys(w http.ResponseWriter, r *http.Request) {
var response *v3.FilterAttributeKeyResponse
req, err := parseFilterAttributeKeyRequest(r)
Expand Down
45 changes: 45 additions & 0 deletions pkg/query-service/app/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package app

import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -837,6 +838,50 @@ func parseAggregateAttributeRequest(r *http.Request) (*v3.AggregateAttributeRequ
return &req, nil
}

func parseQBFilterSuggestionsRequest(r *http.Request) (
*v3.QBFilterSuggestionsRequest, *model.ApiError,
) {
dataSource := v3.DataSource(r.URL.Query().Get("dataSource"))
if err := dataSource.Validate(); err != nil {
return nil, model.BadRequest(err)
}

limit := baseconstants.DefaultFilterSuggestionsLimit
limitStr := r.URL.Query().Get("limit")
if len(limitStr) > 0 {
limit, err := strconv.Atoi(limitStr)
if err != nil || limit < 1 {
return nil, model.BadRequest(fmt.Errorf(
"invalid limit: %s", limitStr,
))
}
}

var existingFilter *v3.FilterSet
existingFilterB64 := r.URL.Query().Get("existingFilter")
if len(existingFilterB64) > 0 {
decodedFilterJson, err := base64.RawURLEncoding.DecodeString(existingFilterB64)
if err != nil {
return nil, model.BadRequest(fmt.Errorf("couldn't base64 decode existingFilter: %w", err))
}

existingFilter = &v3.FilterSet{}
err = json.Unmarshal(decodedFilterJson, existingFilter)
if err != nil {
return nil, model.BadRequest(fmt.Errorf("couldn't JSON decode existingFilter: %w", err))
}
}

searchText := r.URL.Query().Get("searchText")

return &v3.QBFilterSuggestionsRequest{
DataSource: dataSource,
Limit: limit,
SearchText: searchText,
ExistingFilter: existingFilter,
}, nil
}

func parseFilterAttributeKeyRequest(r *http.Request) (*v3.FilterAttributeKeyRequest, error) {
var req v3.FilterAttributeKeyRequest

Expand Down
2 changes: 2 additions & 0 deletions pkg/query-service/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,3 +407,5 @@ var TracesListViewDefaultSelectedColumns = []v3.AttributeKey{
IsColumn: true,
},
}

const DefaultFilterSuggestionsLimit = 100
4 changes: 4 additions & 0 deletions pkg/query-service/interfaces/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ type Reader interface {
GetLogAttributeValues(ctx context.Context, req *v3.FilterAttributeValueRequest) (*v3.FilterAttributeValueResponse, error)
GetLogAggregateAttributes(ctx context.Context, req *v3.AggregateAttributeRequest) (*v3.AggregateAttributeResponse, error)
GetUsers(ctx context.Context) ([]model.UserPayload, error)
GetQBFilterSuggestionsForLogs(
ctx context.Context,
req *v3.QBFilterSuggestionsRequest,
) (*v3.QBFilterSuggestionsResponse, *model.ApiError)

// Connection needed for rules, not ideal but required
GetConn() clickhouse.Conn
Expand Down
12 changes: 12 additions & 0 deletions pkg/query-service/model/v3/v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,18 @@ type FilterAttributeKeyRequest struct {
Limit int `json:"limit"`
}

type QBFilterSuggestionsRequest struct {
DataSource DataSource `json:"dataSource"`
SearchText string `json:"searchText"`
Limit int `json:"limit"`
ExistingFilter *FilterSet `json:"existing_filter"`
}

type QBFilterSuggestionsResponse struct {
AttributeKeys []AttributeKey `json:"attributes"`
ExampleQueries []FilterSet `json:"example_queries"`
}

type AttributeKeyDataType string

const (
Expand Down
Loading

0 comments on commit eb14649

Please sign in to comment.