Compare commits

..

2 Commits

Author SHA1 Message Date
amlannandy
f82d25cbca chore: fix warning in query builder search v2 2025-01-18 16:06:15 +05:30
Nityananda Gohain
16e61b45ac fix: support in operator for json search (#6689)
* fix: support in in json search

* fix: remove else condition and update test cases

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-01-18 01:44:25 +05:30
22 changed files with 300 additions and 664 deletions

View File

@@ -692,7 +692,7 @@ function QueryBuilderSearchV2(
operatorOptions = QUERY_BUILDER_OPERATORS_BY_TYPES[
currentFilterItem.key
.dataType as keyof typeof QUERY_BUILDER_OPERATORS_BY_TYPES
].map((operator) => ({
]?.map((operator) => ({
label: operator,
value: operator,
}));

5
go.mod
View File

@@ -20,7 +20,6 @@ require (
github.com/go-kit/log v0.2.1
github.com/go-redis/redis/v8 v8.11.5
github.com/go-redis/redismock/v8 v8.11.5
github.com/go-viper/mapstructure/v2 v2.1.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.6.0
github.com/gorilla/handlers v1.5.1
@@ -30,7 +29,6 @@ require (
github.com/jmoiron/sqlx v1.3.4
github.com/json-iterator/go v1.1.12
github.com/knadh/koanf v1.5.0
github.com/knadh/koanf/v2 v2.1.1
github.com/mailru/easyjson v0.7.7
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/oklog/oklog v0.3.2
@@ -103,7 +101,6 @@ require (
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.7.1 // indirect
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
@@ -111,6 +108,7 @@ require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
@@ -131,6 +129,7 @@ require (
github.com/jpillora/backoff v1.0.0 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/klauspost/compress v1.17.10 // indirect
github.com/knadh/koanf/v2 v2.1.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/leodido/go-syslog/v4 v4.2.0 // indirect
github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b // indirect

View File

@@ -1,90 +0,0 @@
package config
import (
"github.com/go-viper/mapstructure/v2"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/v2"
)
const (
KoanfDelimiter string = "::"
)
// Conf is a wrapper around the koanf library.
type Conf struct {
*koanf.Koanf
}
// NewConf creates a new Conf instance.
func NewConf() *Conf {
return &Conf{koanf.New(KoanfDelimiter)}
}
// NewConfFromMap creates a new Conf instance from a map.
func NewConfFromMap(m map[string]any) (*Conf, error) {
conf := NewConf()
if err := conf.Koanf.Load(confmap.Provider(m, KoanfDelimiter), nil); err != nil {
return nil, err
}
return conf, nil
}
// MustNewConfFromMap creates a new Conf instance from a map.
// It panics if the conf cannot be created.
func MustNewConfFromMap(m map[string]any) *Conf {
conf, err := NewConfFromMap(m)
if err != nil {
panic(err)
}
return conf
}
// Merge merges the current configuration with the input configuration.
func (conf *Conf) Merge(input *Conf) error {
return conf.Koanf.Merge(input.Koanf)
}
// Merge merges the current configuration with the input configuration.
func (conf *Conf) MergeAt(input *Conf, path string) error {
return conf.Koanf.MergeAt(input.Koanf, path)
}
// Unmarshal unmarshals the configuration at the given path into the input.
// It uses a WeaklyTypedInput to allow for more flexible unmarshalling.
func (conf *Conf) Unmarshal(path string, input any) error {
dc := &mapstructure.DecoderConfig{
TagName: "mapstructure",
WeaklyTypedInput: true,
DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToSliceHookFunc(","),
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.TextUnmarshallerHookFunc(),
),
Result: input,
}
return conf.Koanf.UnmarshalWithConf(path, input, koanf.UnmarshalConf{Tag: "mapstructure", DecoderConfig: dc})
}
// Set sets the configuration at the given key.
// It decodes the input into a map as per mapstructure.Decode and then merges it into the configuration.
func (conf *Conf) Set(key string, input any) error {
m := map[string]any{}
err := mapstructure.Decode(input, &m)
if err != nil {
return err
}
newConf := NewConf()
if err := newConf.Koanf.Load(confmap.Provider(m, KoanfDelimiter), nil); err != nil {
return err
}
if err := conf.Koanf.MergeAt(newConf.Koanf, key); err != nil {
return err
}
return nil
}

View File

@@ -1,38 +0,0 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestConfMerge(t *testing.T) {
testCases := []struct {
name string
conf *Conf
input *Conf
expected *Conf
pass bool
}{
{name: "Empty", conf: NewConf(), input: NewConf(), expected: NewConf(), pass: true},
{name: "Merge", conf: MustNewConfFromMap(map[string]any{"a": "b"}), input: MustNewConfFromMap(map[string]any{"c": "d"}), expected: MustNewConfFromMap(map[string]any{"a": "b", "c": "d"}), pass: true},
{name: "NestedMerge", conf: MustNewConfFromMap(map[string]any{"a": map[string]any{"b": "v1", "c": "v2"}}), input: MustNewConfFromMap(map[string]any{"a": map[string]any{"d": "v1", "e": "v2"}}), expected: MustNewConfFromMap(map[string]any{"a": map[string]any{"b": "v1", "c": "v2", "d": "v1", "e": "v2"}}), pass: true},
{name: "Override", conf: MustNewConfFromMap(map[string]any{"a": "b"}), input: MustNewConfFromMap(map[string]any{"a": "c"}), expected: MustNewConfFromMap(map[string]any{"a": "c"}), pass: true},
}
t.Parallel()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.conf.Merge(tc.input)
if !tc.pass {
assert.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tc.expected.Raw(), tc.conf.Raw())
assert.Equal(t, tc.expected.Raw(), tc.conf.Raw())
})
}
}

View File

@@ -3,34 +3,32 @@ package config
import (
"context"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/cache"
signozconfmap "go.signoz.io/signoz/pkg/confmap"
"go.signoz.io/signoz/pkg/instrumentation"
"go.signoz.io/signoz/pkg/web"
)
func New(ctx context.Context, resolverConfig ResolverConfig, configFactories []factory.ConfigFactory) (*Conf, error) {
// Get the config from the resolver
resolver, err := NewResolver(resolverConfig)
if err != nil {
return nil, err
// This map contains the default values of all config structs
var (
defaults = map[string]signozconfmap.Config{
"web": &web.Config{},
"cache": &cache.Config{},
}
)
resolvedConf, err := resolver.Do(ctx)
if err != nil {
return nil, err
}
conf := NewConf()
// Set the default configs
for _, factory := range configFactories {
c := factory.New()
if err := conf.Set(factory.Name().String(), c); err != nil {
return nil, err
}
}
err = conf.Merge(resolvedConf)
if err != nil {
return nil, err
}
return conf, nil
// Config defines the entire configuration of signoz.
type Config struct {
Instrumentation instrumentation.Config `mapstructure:"instrumentation"`
Web web.Config `mapstructure:"web"`
Cache cache.Config `mapstructure:"cache"`
}
func New(ctx context.Context, settings ProviderSettings) (*Config, error) {
provider, err := NewProvider(settings)
if err != nil {
return nil, err
}
return provider.Get(ctx)
}

54
pkg/config/config_test.go Normal file
View File

@@ -0,0 +1,54 @@
package config
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/confmap/provider/signozenvprovider"
"go.signoz.io/signoz/pkg/web"
)
func TestNewWithSignozEnvProvider(t *testing.T) {
t.Setenv("SIGNOZ__WEB__PREFIX", "/web")
t.Setenv("SIGNOZ__WEB__DIRECTORY", "/build")
t.Setenv("SIGNOZ__CACHE__PROVIDER", "redis")
t.Setenv("SIGNOZ__CACHE__REDIS__HOST", "127.0.0.1")
config, err := New(context.Background(), ProviderSettings{
ResolverSettings: confmap.ResolverSettings{
URIs: []string{"signozenv:"},
ProviderFactories: []confmap.ProviderFactory{
signozenvprovider.NewFactory(),
},
},
})
require.NoError(t, err)
expected := &Config{
Web: web.Config{
Prefix: "/web",
Directory: "/build",
},
Cache: cache.Config{
Provider: "redis",
Memory: cache.Memory{
TTL: time.Duration(-1),
CleanupInterval: 1 * time.Minute,
},
Redis: cache.Redis{
Host: "127.0.0.1",
Port: 6379,
Password: "",
DB: 0,
},
},
}
assert.Equal(t, expected, config)
}

View File

@@ -1,71 +0,0 @@
package envprovider
import (
"context"
"strings"
koanfenv "github.com/knadh/koanf/providers/env"
"go.signoz.io/signoz/pkg/config"
)
const (
prefix string = "SIGNOZ_"
scheme string = "env"
)
type provider struct{}
func NewFactory() config.ProviderFactory {
return config.NewProviderFactory(New)
}
func New(config config.ProviderConfig) config.Provider {
return &provider{}
}
func (provider *provider) Scheme() string {
return scheme
}
func (provider *provider) Get(ctx context.Context, uri config.Uri) (*config.Conf, error) {
conf := config.NewConf()
err := conf.Load(
koanfenv.Provider(
prefix,
// Do not set this to `_`. The correct delimiter is being set by the custom callback provided below.
// Since this had to be passed, using `config.KoanfDelimiter` eliminates any possible side effect.
config.KoanfDelimiter,
func(s string) string {
s = strings.ToLower(strings.TrimPrefix(s, prefix))
return provider.cb(s, config.KoanfDelimiter)
},
),
nil,
)
return conf, err
}
func (provider *provider) cb(s string, delim string) string {
delims := []rune(delim)
runes := []rune(s)
result := make([]rune, 0, len(runes))
for i := 0; i < len(runes); i++ {
// Check for double underscore pattern
if i < len(runes)-1 && runes[i] == '_' && runes[i+1] == '_' {
result = append(result, '_')
i++ // Skip next underscore
continue
}
if runes[i] == '_' {
result = append(result, delims...)
continue
}
result = append(result, runes[i])
}
return string(result)
}

View File

@@ -1,78 +0,0 @@
package envprovider
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/config"
)
func TestGetWithStrings(t *testing.T) {
t.Setenv("SIGNOZ_K1_K2", "string")
t.Setenv("SIGNOZ_K3__K4", "string")
t.Setenv("SIGNOZ_K5__K6_K7__K8", "string")
t.Setenv("SIGNOZ_K9___K10", "string")
t.Setenv("SIGNOZ_K11____K12", "string")
expected := map[string]any{
"k1::k2": "string",
"k3_k4": "string",
"k5_k6::k7_k8": "string",
"k9_::k10": "string",
"k11__k12": "string",
}
provider := New(config.ProviderConfig{})
actual, err := provider.Get(context.Background(), config.MustNewUri("env:"))
require.NoError(t, err)
assert.Equal(t, expected, actual.All())
}
func TestGetWithGoTypes(t *testing.T) {
t.Setenv("SIGNOZ_BOOL", "true")
t.Setenv("SIGNOZ_STRING", "string")
t.Setenv("SIGNOZ_INT", "1")
t.Setenv("SIGNOZ_SLICE", "[1,2]")
expected := map[string]any{
"bool": "true",
"int": "1",
"slice": "[1,2]",
"string": "string",
}
provider := New(config.ProviderConfig{})
actual, err := provider.Get(context.Background(), config.MustNewUri("env:"))
require.NoError(t, err)
assert.Equal(t, expected, actual.All())
}
func TestGetWithGoTypesWithUnmarshal(t *testing.T) {
t.Setenv("SIGNOZ_BOOL", "true")
t.Setenv("SIGNOZ_STRING", "string")
t.Setenv("SIGNOZ_INT", "1")
type test struct {
Bool bool `mapstructure:"bool"`
String string `mapstructure:"string"`
Int int `mapstructure:"int"`
}
expected := test{
Bool: true,
String: "string",
Int: 1,
}
provider := New(config.ProviderConfig{})
conf, err := provider.Get(context.Background(), config.MustNewUri("env:"))
require.NoError(t, err)
actual := test{}
err = conf.Unmarshal("", &actual)
require.NoError(t, err)
assert.Equal(t, expected, actual)
}

View File

@@ -1,34 +0,0 @@
package fileprovider
import (
"context"
koanfyaml "github.com/knadh/koanf/parsers/yaml"
koanffile "github.com/knadh/koanf/providers/file"
"go.signoz.io/signoz/pkg/config"
)
const (
scheme string = "file"
)
type provider struct{}
func NewFactory() config.ProviderFactory {
return config.NewProviderFactory(New)
}
func New(config config.ProviderConfig) config.Provider {
return &provider{}
}
func (provider *provider) Scheme() string {
return scheme
}
func (provider *provider) Get(ctx context.Context, uri config.Uri) (*config.Conf, error) {
conf := config.NewConf()
err := conf.Load(koanffile.Provider(uri.Value()), koanfyaml.Parser())
return conf, err
}

View File

@@ -1,68 +0,0 @@
package fileprovider
import (
"context"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.signoz.io/signoz/pkg/config"
)
func TestGetWithStrings(t *testing.T) {
expected := map[string]any{
"k1::k2": "string",
"k3_k4": "string",
"k5_k6::k7_k8": "string",
"k9_::k10": "string",
"k11__k12": "string",
}
provider := New(config.ProviderConfig{})
actual, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "strings.yaml")))
require.NoError(t, err)
assert.Equal(t, expected, actual.All())
}
func TestGetWithGoTypes(t *testing.T) {
expected := map[string]any{
"bool": true,
"int": 1,
"slice": []any{1, 2},
"string": "string",
}
provider := New(config.ProviderConfig{})
actual, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "gotypes.yaml")))
require.NoError(t, err)
assert.Equal(t, expected, actual.All())
}
func TestGetWithGoTypesWithUnmarshal(t *testing.T) {
type test struct {
Bool bool `mapstructure:"bool"`
String string `mapstructure:"string"`
Int int `mapstructure:"int"`
Slice []any `mapstructure:"slice"`
}
expected := test{
Bool: true,
String: "string",
Int: 1,
Slice: []any{1, 2},
}
provider := New(config.ProviderConfig{})
conf, err := provider.Get(context.Background(), config.MustNewUri("file:"+filepath.Join("testdata", "gotypes.yaml")))
require.NoError(t, err)
actual := test{}
err = conf.Unmarshal("", &actual)
require.NoError(t, err)
assert.Equal(t, expected, actual)
}

View File

@@ -1,6 +0,0 @@
bool: true
string: string
int: 1
slice:
- 1
- 2

View File

@@ -1,8 +0,0 @@
k1:
k2: string
k3_k4: string
k5_k6:
k7_k8: string
k9_:
k10: string
k11__k12: string

View File

@@ -2,38 +2,51 @@ package config
import (
"context"
"fmt"
"go.opentelemetry.io/collector/confmap"
)
// NewProviderFunc is a function that creates a new provider.
type NewProviderFunc = func(ProviderConfig) Provider
// ProviderFactory is a factory that creates a new provider.
type ProviderFactory interface {
New(ProviderConfig) Provider
}
// NewProviderFactory creates a new provider factory.
func NewProviderFactory(f NewProviderFunc) ProviderFactory {
return &providerFactory{f: f}
}
// providerFactory is a factory that implements the ProviderFactory interface.
type providerFactory struct {
f NewProviderFunc
}
// New creates a new provider.
func (factory *providerFactory) New(config ProviderConfig) Provider {
return factory.f(config)
}
// ProviderConfig is the configuration for a provider.
type ProviderConfig struct{}
// Provider is an interface that represents a configuration provider.
// Provides the configuration for signoz.
type Provider interface {
// Get returns the configuration for the given URI.
Get(context.Context, Uri) (*Conf, error)
// Scheme returns the scheme of the provider.
Scheme() string
// Get returns the configuration, or error otherwise.
Get(ctx context.Context) (*Config, error)
}
type provider struct {
resolver *confmap.Resolver
}
// ProviderSettings are the settings to configure the behavior of the Provider.
type ProviderSettings struct {
// ResolverSettings are the settings to configure the behavior of the confmap.Resolver.
ResolverSettings confmap.ResolverSettings
}
// NewProvider returns a new Provider that provides the entire configuration.
// See https://github.com/open-telemetry/opentelemetry-collector/blob/main/otelcol/configprovider.go for
// more details
func NewProvider(settings ProviderSettings) (Provider, error) {
resolver, err := confmap.NewResolver(settings.ResolverSettings)
if err != nil {
return nil, err
}
return &provider{
resolver: resolver,
}, nil
}
func (provider *provider) Get(ctx context.Context) (*Config, error) {
conf, err := provider.resolver.Resolve(ctx)
if err != nil {
return nil, fmt.Errorf("cannot resolve configuration: %w", err)
}
config, err := unmarshal(conf)
if err != nil {
return nil, fmt.Errorf("cannot unmarshal configuration: %w", err)
}
return config, nil
}

View File

@@ -1,87 +0,0 @@
package config
import (
"context"
"errors"
"fmt"
)
type ResolverConfig struct {
// Each string or `uri` must follow "<scheme>:<value>" format. This format is compatible with the URI definition
// defined at https://datatracker.ietf.org/doc/html/rfc3986".
// It is required to have at least one uri.
Uris []string
// ProviderFactories is a slice of Provider factories.
// It is required to have at least one factory.
ProviderFactories []ProviderFactory
}
type Resolver struct {
uris []Uri
providers map[string]Provider
}
func NewResolver(config ResolverConfig) (*Resolver, error) {
if len(config.Uris) == 0 {
return nil, errors.New("cannot build resolver, no uris have been provided")
}
if len(config.ProviderFactories) == 0 {
return nil, errors.New("cannot build resolver, no providers have been provided")
}
uris := make([]Uri, len(config.Uris))
for i, inputUri := range config.Uris {
uri, err := NewUri(inputUri)
if err != nil {
return nil, err
}
uris[i] = uri
}
providers := make(map[string]Provider, len(config.ProviderFactories))
for _, factory := range config.ProviderFactories {
provider := factory.New(ProviderConfig{})
scheme := provider.Scheme()
// Check that the scheme is unique.
if _, ok := providers[scheme]; ok {
return nil, fmt.Errorf("cannot build resolver, duplicate scheme %q found", scheme)
}
providers[provider.Scheme()] = provider
}
return &Resolver{
uris: uris,
providers: providers,
}, nil
}
func (resolver *Resolver) Do(ctx context.Context) (*Conf, error) {
conf := NewConf()
for _, uri := range resolver.uris {
currentConf, err := resolver.get(ctx, uri)
if err != nil {
return nil, err
}
if err = conf.Merge(currentConf); err != nil {
return nil, fmt.Errorf("cannot merge config: %w", err)
}
}
return conf, nil
}
func (resolver *Resolver) get(ctx context.Context, uri Uri) (*Conf, error) {
provider, ok := resolver.providers[uri.scheme]
if !ok {
return nil, fmt.Errorf("cannot find provider with schema %q", uri.scheme)
}
return provider.Get(ctx, uri)
}

49
pkg/config/unmarshaler.go Normal file
View File

@@ -0,0 +1,49 @@
package config
import (
"fmt"
"go.opentelemetry.io/collector/confmap"
)
// unmarshal converts a confmap.Conf into a Config struct.
// It splits the input confmap into a map of key-value pairs, fetches the corresponding
// signozconfmap.Config interface by name, merges it with the default config, validates it,
// and then creates a new confmap from the parsed map to unmarshal into the Config struct.
func unmarshal(conf *confmap.Conf) (*Config, error) {
raw := make(map[string]any)
if err := conf.Unmarshal(&raw); err != nil {
return nil, err
}
parsed := make(map[string]any)
// To help the defaults kick in, we need iterate over the default map instead of the raw values
for k, v := range defaults {
sub, err := conf.Sub(k)
if err != nil {
return nil, fmt.Errorf("cannot read config for %q: %w", k, err)
}
d := v.NewWithDefaults()
if err := sub.Unmarshal(&d); err != nil {
return nil, fmt.Errorf("cannot merge config for %q: %w", k, err)
}
err = d.Validate()
if err != nil {
return nil, fmt.Errorf("failed to validate config for for %q: %w", k, err)
}
parsed[k] = d
}
parsedConf := confmap.NewFromStringMap(parsed)
config := new(Config)
err := parsedConf.Unmarshal(config)
if err != nil {
return nil, fmt.Errorf("cannot unmarshal config: %w", err)
}
return config, nil
}

View File

@@ -1,46 +0,0 @@
package config
import (
"fmt"
"regexp"
)
var (
// uriRegex is a regex that matches the URI format. It complies with the URI definition defined at https://datatracker.ietf.org/doc/html/rfc3986.
// The format is "<scheme>:<value>".
uriRegex = regexp.MustCompile(`(?s:^(?P<Scheme>[A-Za-z][A-Za-z0-9+.-]+):(?P<Value>.*)$)`)
)
type Uri struct {
scheme string
value string
}
func NewUri(input string) (Uri, error) {
submatches := uriRegex.FindStringSubmatch(input)
if len(submatches) != 3 {
return Uri{}, fmt.Errorf("invalid uri: %q", input)
}
return Uri{
scheme: submatches[1],
value: submatches[2],
}, nil
}
func MustNewUri(input string) Uri {
uri, err := NewUri(input)
if err != nil {
panic(err)
}
return uri
}
func (uri Uri) Scheme() string {
return uri.scheme
}
func (uri Uri) Value() string {
return uri.value
}

View File

@@ -1,35 +0,0 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewUri(t *testing.T) {
testCases := []struct {
input string
expected Uri
pass bool
}{
{input: "file:/path/1", expected: Uri{scheme: "file", value: "/path/1"}, pass: true},
{input: "file:", expected: Uri{scheme: "file", value: ""}, pass: true},
{input: "env:", expected: Uri{scheme: "env", value: ""}, pass: true},
{input: "scheme", expected: Uri{}, pass: false},
}
for _, tc := range testCases {
uri, err := NewUri(tc.input)
if !tc.pass {
assert.Error(t, err)
continue
}
require.NoError(t, err)
assert.NotPanics(t, func() { MustNewUri(tc.input) })
assert.Equal(t, tc.expected, uri)
assert.Equal(t, tc.expected.Scheme(), uri.scheme)
assert.Equal(t, tc.expected.Value(), uri.value)
}
}

View File

@@ -167,6 +167,22 @@ func jsonFilterEnrich(filter v3.FilterItem) v3.FilterItem {
// check if the value is a int, float, string, bool
valueType := ""
switch filter.Value.(type) {
// even the filter value is an array the actual type of the value is string.
case []interface{}:
// check first value type in array and use that
if len(filter.Value.([]interface{})) > 0 {
firstVal := filter.Value.([]interface{})[0]
switch firstVal.(type) {
case uint8, uint16, uint32, uint64, int, int8, int16, int32, int64:
valueType = "int64"
case float32, float64:
valueType = "float64"
case bool:
valueType = "bool"
default:
valueType = "string"
}
}
case uint8, uint16, uint32, uint64, int, int8, int16, int32, int64:
valueType = "int64"
case float32, float64:

View File

@@ -563,6 +563,50 @@ var testJSONFilterEnrichData = []struct {
Value: 10.0,
},
},
{
Name: "check IN",
Filter: v3.FilterItem{
Key: v3.AttributeKey{
Key: "body.attr",
DataType: v3.AttributeKeyDataTypeUnspecified,
Type: v3.AttributeKeyTypeUnspecified,
},
Operator: "IN",
Value: []interface{}{"hello", "world"},
},
Result: v3.FilterItem{
Key: v3.AttributeKey{
Key: "body.attr",
DataType: v3.AttributeKeyDataTypeString,
Type: v3.AttributeKeyTypeUnspecified,
IsJSON: true,
},
Operator: "IN",
Value: []interface{}{"hello", "world"},
},
},
{
Name: "check NOT_IN",
Filter: v3.FilterItem{
Key: v3.AttributeKey{
Key: "body.attr",
DataType: v3.AttributeKeyDataTypeUnspecified,
Type: v3.AttributeKeyTypeUnspecified,
},
Operator: "NOT_IN",
Value: []interface{}{10, 20},
},
Result: v3.FilterItem{
Key: v3.AttributeKey{
Key: "body.attr",
DataType: v3.AttributeKeyDataTypeInt64,
Type: v3.AttributeKeyTypeUnspecified,
IsJSON: true,
},
Operator: "NOT_IN",
Value: []interface{}{10, 20},
},
},
}
func TestJsonEnrich(t *testing.T) {

View File

@@ -183,6 +183,71 @@ var testGetJSONFilterData = []struct {
},
Filter: "lower(body) like lower('%message%') AND JSON_EXISTS(body, '$.\"message\"')",
},
{
Name: "test json in array string",
FilterItem: v3.FilterItem{
Key: v3.AttributeKey{
Key: "body.name",
DataType: "string",
IsJSON: true,
},
Operator: "in",
Value: []interface{}{"hello", "world"},
},
Filter: "lower(body) like lower('%name%') AND JSON_EXISTS(body, '$.\"name\"') AND JSON_VALUE(body, '$.\"name\"') IN ['hello','world']",
},
{
Name: "test json in array number",
FilterItem: v3.FilterItem{
Key: v3.AttributeKey{
Key: "body.value",
DataType: "int64",
IsJSON: true,
},
Operator: "in",
Value: []interface{}{10, 11},
},
Filter: "lower(body) like lower('%value%') AND JSON_EXISTS(body, '$.\"value\"') AND JSONExtract(JSON_VALUE(body, '$.\"value\"'), 'Int64') IN [10,11]",
},
{
Name: "test json in array mixed data- allow",
FilterItem: v3.FilterItem{
Key: v3.AttributeKey{
Key: "body.value",
DataType: "int64",
IsJSON: true,
},
Operator: "in",
Value: []interface{}{11, "11"},
},
Filter: "lower(body) like lower('%value%') AND JSON_EXISTS(body, '$.\"value\"') AND JSONExtract(JSON_VALUE(body, '$.\"value\"'), 'Int64') IN [11,11]",
},
{
Name: "test json in array mixed data- fail",
FilterItem: v3.FilterItem{
Key: v3.AttributeKey{
Key: "body.value",
DataType: "int64",
IsJSON: true,
},
Operator: "in",
Value: []interface{}{11, "11", "hello"},
},
Error: true,
},
{
Name: "test json in array mixed data- allow",
FilterItem: v3.FilterItem{
Key: v3.AttributeKey{
Key: "body.value",
DataType: "string",
IsJSON: true,
},
Operator: "in",
Value: []interface{}{"hello", 11},
},
Filter: "lower(body) like lower('%value%') AND JSON_EXISTS(body, '$.\"value\"') AND JSON_VALUE(body, '$.\"value\"') IN ['hello','11']",
},
}
func TestGetJSONFilter(t *testing.T) {

View File

@@ -1,41 +0,0 @@
package signoz
import (
"context"
"go.signoz.io/signoz/pkg/cache"
"go.signoz.io/signoz/pkg/config"
"go.signoz.io/signoz/pkg/factory"
"go.signoz.io/signoz/pkg/instrumentation"
"go.signoz.io/signoz/pkg/sqlstore"
"go.signoz.io/signoz/pkg/web"
)
// Config defines the entire configuration of signoz.
type Config struct {
Instrumentation instrumentation.Config `mapstructure:"instrumentation"`
Web web.Config `mapstructure:"web"`
Cache cache.Config `mapstructure:"cache"`
SQLStore sqlstore.Config `mapstructure:"sqlstore"`
}
func NewConfig(ctx context.Context, resolverConfig config.ResolverConfig) (Config, error) {
configFactories := []factory.ConfigFactory{
instrumentation.NewConfigFactory(),
web.NewConfigFactory(),
sqlstore.NewConfigFactory(),
cache.NewConfigFactory(),
}
conf, err := config.New(ctx, resolverConfig, configFactories)
if err != nil {
return Config{}, err
}
var config Config
if err := conf.Unmarshal("", &config); err != nil {
return Config{}, err
}
return config, nil
}

View File

@@ -15,7 +15,7 @@ type SigNoz struct {
Web web.Web
}
func New(config config.Config, skipWebFrontend bool) (*SigNoz, error) {
func New(config *config.Config, skipWebFrontend bool) (*SigNoz, error) {
var cache cache.Cache
// init for the cache