Compare commits

...

2 Commits

Author SHA1 Message Date
Tushar Vats
219e6ac54c chore(openapi): regenerate specs
Assisted-by: Claude Opus 5.5
2026-11-05 00:28:04 +05:30
Tushar Vats
a04f65a4f2 fix(fields): resolve field value names like the query builder
A name with a context prefix looks up both readings, exact name first,
since the prefix may be part of the stored name. An explicit fieldContext
takes the name as is so every key stays addressable.

Assisted-by: Claude Opus 5.5
2026-11-05 00:28:03 +05:30
7 changed files with 388 additions and 22 deletions

View File

@@ -15826,13 +15826,22 @@ paths:
where it is case-sensitive and % and _ act as wildcards. For keys, a context
prefix such as resource.service.name sets fieldContext when it is empty.'
type: string
- description: The key whose values to return, such as service.name. A context
prefix such as resource.service.name sets fieldContext when it is empty.
- description: 'The key whose values to return, such as service.name. With fieldContext
set, it is the stored name as is. Otherwise a context prefix such as resource.service.name
sets the context and a type suffix such as service.name:string sets fieldDataType
when that is empty; as in filter expressions, a prefixed name also matches
a key stored under the full name, whose values come first: log.level returns
values of a key named log.level, then of the log field level.'
in: query
name: name
schema:
description: The key whose values to return, such as service.name. A context
prefix such as resource.service.name sets fieldContext when it is empty.
description: 'The key whose values to return, such as service.name. With
fieldContext set, it is the stored name as is. Otherwise a context prefix
such as resource.service.name sets the context and a type suffix such
as service.name:string sets fieldDataType when that is empty; as in filter
expressions, a prefixed name also matches a key stored under the full
name, whose values come first: log.level returns values of a key named
log.level, then of the log field level.'
type: string
- description: Filter expression already applied in the explorer. When set,
relatedValues lists the values seen on data matching it.
@@ -28411,13 +28420,22 @@ paths:
where it is case-sensitive and % and _ act as wildcards. For keys, a context
prefix such as resource.service.name sets fieldContext when it is empty.'
type: string
- description: The key whose values to return, such as service.name. A context
prefix such as resource.service.name sets fieldContext when it is empty.
- description: 'The key whose values to return, such as service.name. With fieldContext
set, it is the stored name as is. Otherwise a context prefix such as resource.service.name
sets the context and a type suffix such as service.name:string sets fieldDataType
when that is empty; as in filter expressions, a prefixed name also matches
a key stored under the full name, whose values come first: log.level returns
values of a key named log.level, then of the log field level.'
in: query
name: name
schema:
description: The key whose values to return, such as service.name. A context
prefix such as resource.service.name sets fieldContext when it is empty.
description: 'The key whose values to return, such as service.name. With
fieldContext set, it is the stored name as is. Otherwise a context prefix
such as resource.service.name sets the context and a type suffix such
as service.name:string sets fieldDataType when that is empty; as in filter
expressions, a prefixed name also matches a key stored under the full
name, whose values come first: log.level returns values of a key named
log.level, then of the log field level.'
type: string
- description: Filter expression already applied in the explorer. When set,
relatedValues lists the values seen on data matching it.

View File

@@ -0,0 +1,50 @@
package telemetrymetadata
import (
"context"
"testing"
cmock "github.com/SigNoz/clickhouse-go-mock"
"github.com/SigNoz/signoz/pkg/flagger/flaggertest"
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/telemetrystore/telemetrystoretest"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetAllValuesExactNameFirst(t *testing.T) {
mockTelemetryStore := telemetrystoretest.New(telemetrystore.Config{}, &regexMatcher{})
mock := mockTelemetryStore.Mock()
metadata := NewTelemetryMetaStore(instrumentationtest.New().ToProviderSettings(), mockTelemetryStore, flaggertest.New(t))
boolColumns := []cmock.ColumnType{{Name: "1", Type: "UInt8"}}
valueColumns := []cmock.ColumnType{{Name: "string_value", Type: "String"}, {Name: "number_value", Type: "Float64"}}
mock.ExpectQuery(`SELECT 1 FROM .*tag_key = \? AND tag_data_type = \? LIMIT`).
WithArgs("log.level", "bool", 1).
WillReturnRows(cmock.NewRows(boolColumns, [][]any{}))
mock.ExpectQuery(`SELECT DISTINCT string_value, number_value FROM .*tag_key = \? AND tag_data_type <> \? LIMIT`).
WithArgs("log.level", "bool", 3).
WillReturnRows(cmock.NewRows(valueColumns, [][]any{{"warn", float64(0)}, {"info", float64(0)}}))
mock.ExpectQuery(`SELECT 1 FROM .*tag_key = \? AND tag_data_type = \? AND tag_type = \?`).
WithArgs("level", "bool", "logfield", 1).
WillReturnRows(cmock.NewRows(boolColumns, [][]any{}))
mock.ExpectQuery(`SELECT DISTINCT string_value, number_value FROM .*tag_key = \? AND tag_data_type <> \? AND tag_type = \?`).
WithArgs("level", "bool", "logfield", 3).
WillReturnRows(cmock.NewRows(valueColumns, [][]any{{"info", float64(0)}, {"debug", float64(0)}}))
selector := telemetrytypes.NewFieldValueSelectorFromPostableFieldValueParams(telemetrytypes.PostableFieldValueParams{
Name: "log.level",
PostableFieldKeysParams: telemetrytypes.PostableFieldKeysParams{Signal: telemetrytypes.SignalLogs, Limit: 2},
})
values, complete, err := metadata.GetAllValues(context.Background(), valuer.GenerateUUID(), selector)
require.NoError(t, err)
assert.Equal(t, []string{"warn", "info"}, values.StringValues)
assert.False(t, complete)
assert.NoError(t, mock.ExpectationsWereMet())
}

View File

@@ -1517,7 +1517,24 @@ func (t *telemetryMetaStore) getRelatedValues(ctx context.Context, orgID valuer.
}
func (t *telemetryMetaStore) GetRelatedValues(ctx context.Context, orgID valuer.UUID, fieldValueSelector *telemetrytypes.FieldValueSelector) ([]string, bool, error) {
return t.getRelatedValues(ctx, orgID, fieldValueSelector)
selectors := fieldValueSelector.NameMatchSelectors()
if len(selectors) == 1 {
return t.getRelatedValues(ctx, orgID, fieldValueSelector)
}
all := make([]*telemetrytypes.TelemetryFieldValues, 0, len(selectors))
complete := true
for _, selector := range selectors {
values, selectorComplete, err := t.getRelatedValues(ctx, orgID, selector)
if err != nil {
return nil, false, err
}
all = append(all, &telemetrytypes.TelemetryFieldValues{RelatedValues: values})
complete = complete && selectorComplete
}
merged, mergeComplete := mergeFieldValues(fieldValueSelector.Limit, all...)
return merged.RelatedValues, complete && mergeComplete, nil
}
func (t *telemetryMetaStore) getSpanFieldValues(ctx context.Context, orgID valuer.UUID, fieldValueSelector *telemetrytypes.FieldValueSelector) (*telemetrytypes.TelemetryFieldValues, bool, error) {
@@ -1826,7 +1843,7 @@ func (t *telemetryMetaStore) getMetricFieldValues(ctx context.Context, orgID val
}
if fieldValueSelector.FieldContext != telemetrytypes.FieldContextUnspecified {
sb.Where(sb.E("attr_type", fieldValueSelector.FieldContext.TagType()))
sb.Where(sb.E("attr_type", fieldValueSelector.FieldContext.MetricAttrType()))
}
if fieldValueSelector.FieldDataType != telemetrytypes.FieldDataTypeUnspecified {
@@ -2092,6 +2109,23 @@ func (t *telemetryMetaStore) getMeterSourceMetricFieldValues(ctx context.Context
return values, complete, nil
}
// mergeFieldValues dedupes the values into one result capped at limit, and reports
// false when the cap dropped any.
func mergeFieldValues(limit int, values ...*telemetrytypes.TelemetryFieldValues) (*telemetrytypes.TelemetryFieldValues, bool) {
if limit == 0 {
limit = 50
}
merged := &telemetrytypes.TelemetryFieldValues{}
mapOfValues := make(map[any]bool)
mapOfRelatedValues := make(map[any]bool)
complete := true
for _, v := range values {
complete = populateAllUnspecifiedValues(merged, mapOfValues, mapOfRelatedValues, v, limit) && complete
}
return merged, complete
}
func populateAllUnspecifiedValues(allUnspecifiedValues *telemetrytypes.TelemetryFieldValues, mapOfValues map[any]bool, mapOfRelatedValues map[any]bool, values *telemetrytypes.TelemetryFieldValues, limit int) bool {
complete := true
totalCount := len(mapOfValues) + len(mapOfRelatedValues)
@@ -2149,6 +2183,27 @@ func populateAllUnspecifiedValues(allUnspecifiedValues *telemetrytypes.Telemetry
// GetAllValues returns all values and whether the result is complete.
func (t *telemetryMetaStore) GetAllValues(ctx context.Context, orgID valuer.UUID, fieldValueSelector *telemetrytypes.FieldValueSelector) (*telemetrytypes.TelemetryFieldValues, bool, error) {
selectors := fieldValueSelector.NameMatchSelectors()
if len(selectors) == 1 {
return t.getAllValues(ctx, orgID, fieldValueSelector)
}
all := make([]*telemetrytypes.TelemetryFieldValues, 0, len(selectors))
complete := true
for _, selector := range selectors {
values, selectorComplete, err := t.getAllValues(ctx, orgID, selector)
if err != nil {
return nil, false, err
}
all = append(all, values)
complete = complete && selectorComplete
}
merged, mergeComplete := mergeFieldValues(fieldValueSelector.Limit, all...)
return merged, complete && mergeComplete, nil
}
func (t *telemetryMetaStore) getAllValues(ctx context.Context, orgID valuer.UUID, fieldValueSelector *telemetrytypes.FieldValueSelector) (*telemetrytypes.TelemetryFieldValues, bool, error) {
values := &telemetrytypes.TelemetryFieldValues{}
var complete = true
var err error

View File

@@ -147,7 +147,11 @@ func (f *TelemetryFieldKey) Equal(key *TelemetryFieldKey) bool {
// key.Normalize()
// // Result: Name: "service.name", FieldContext: FieldContextResource, FieldDataType: FieldDataTypeString
func (f *TelemetryFieldKey) Normalize() {
f.normalizeDataType()
f.normalizeContext()
}
func (f *TelemetryFieldKey) normalizeDataType() {
// Step 1: Parse data type from the right (after the last ":") if not already specified
if f.FieldDataType == FieldDataTypeUnspecified {
if colonIdx := strings.LastIndex(f.Name, ":"); colonIdx != -1 {
@@ -158,7 +162,9 @@ func (f *TelemetryFieldKey) Normalize() {
}
}
}
}
func (f *TelemetryFieldKey) normalizeContext() {
// Step 2: Parse field context from the left if not already specified
if f.FieldContext == FieldContextUnspecified {
if dotIdx := strings.Index(f.Name, "."); dotIdx != -1 {
@@ -279,6 +285,9 @@ type FieldValueSelector struct {
ExistingQuery string `json:"existingQuery" description:"Filter expression already applied in the explorer. Used to compute relatedValues; empty disables them."`
Value string `json:"value" description:"Substring the returned values must contain, case-insensitive except for the meter source, where it is case-sensitive and % and _ act as wildcards. Empty returns any value."`
Limit int `json:"limit" description:"Maximum number of values to return."`
// nameAsWritten is set when FieldContext was parsed from the name's prefix.
nameAsWritten string
}
type GettableFieldKeys struct {
@@ -306,7 +315,7 @@ type GettableFieldValues struct {
type PostableFieldValueParams struct {
PostableFieldKeysParams
Name string `query:"name" description:"The key whose values to return, such as service.name. A context prefix such as resource.service.name sets fieldContext when it is empty."`
Name string `query:"name" description:"The key whose values to return, such as service.name. With fieldContext set, it is the stored name as is. Otherwise a context prefix such as resource.service.name sets the context and a type suffix such as service.name:string sets fieldDataType when that is empty; as in filter expressions, a prefixed name also matches a key stored under the full name, whose values come first: log.level returns values of a key named log.level, then of the log field level."`
ExistingQuery string `query:"existingQuery" description:"Filter expression already applied in the explorer. When set, relatedValues lists the values seen on data matching it."`
}
@@ -358,26 +367,35 @@ func NewFieldKeySelectorFromPostableFieldKeysParams(params PostableFieldKeysPara
}
func NewFieldValueSelectorFromPostableFieldValueParams(params PostableFieldValueParams) *FieldValueSelector {
keySelector := NewFieldKeySelectorFromPostableFieldKeysParams(params.PostableFieldKeysParams)
// SearchText filters values here, so it must not be parsed as a key.
keyParams := params.PostableFieldKeysParams
keyParams.SearchText = ""
keySelector := NewFieldKeySelectorFromPostableFieldKeysParams(keyParams)
fieldValueSelector := &FieldValueSelector{
FieldKeySelector: keySelector,
}
fieldValueSelector.Name = params.Name
if params.Name != "" && fieldValueSelector.FieldContext == FieldContextUnspecified {
parsedFieldKey := GetFieldKeyFromKeyText(params.Name)
if parsedFieldKey.FieldContext != FieldContextUnspecified {
// Only apply inferred context if it is valid for the current signal
if isContextValidForSignal(parsedFieldKey.FieldContext, fieldValueSelector.Signal) {
fieldValueSelector.Name = parsedFieldKey.Name
fieldValueSelector.FieldContext = parsedFieldKey.FieldContext
// An explicit context means the name is the stored name as is.
key := TelemetryFieldKey{Name: params.Name, FieldContext: keySelector.FieldContext, FieldDataType: keySelector.FieldDataType}
if keySelector.FieldContext == FieldContextUnspecified {
key.normalizeDataType()
nameWithContext := key.Name
key.normalizeContext()
// Only apply inferred context if it is valid for the current signal
if isContextValidForSignal(key.FieldContext, keySelector.Signal) {
if key.FieldContext != FieldContextUnspecified {
fieldValueSelector.nameAsWritten = nameWithContext
}
} else {
key.Name = nameWithContext
key.FieldContext = FieldContextUnspecified
}
}
fieldValueSelector.Name = key.Name
fieldValueSelector.FieldContext = key.FieldContext
fieldValueSelector.FieldDataType = key.FieldDataType
fieldValueSelector.ExistingQuery = params.ExistingQuery
fieldValueSelector.Value = params.SearchText
@@ -390,6 +408,29 @@ func NewFieldValueSelectorFromPostableFieldValueParams(params PostableFieldValue
return fieldValueSelector
}
// NameMatchSelectors returns the lookups for the requested key, highest priority first.
// A context parsed from the name may instead be part of the stored name (attribute.key
// may be the attribute named attribute.key), so like the query builder both readings are
// looked up: the name as written, in any context, before the name with its prefix stripped.
func (s *FieldValueSelector) NameMatchSelectors() []*FieldValueSelector {
if s.nameAsWritten != "" {
return []*FieldValueSelector{s.withName(s.nameAsWritten), s}
}
return []*FieldValueSelector{s}
}
// withName returns a copy looking up name in any context.
func (s *FieldValueSelector) withName(name string) *FieldValueSelector {
keySelector := *s.FieldKeySelector
keySelector.Name = name
keySelector.FieldContext = FieldContextUnspecified
selector := *s
selector.FieldKeySelector = &keySelector
selector.nameAsWritten = ""
return &selector
}
func NewTelemetryFieldKey(name string, fieldContext FieldContext, fieldDataType FieldDataType) *TelemetryFieldKey {
return &TelemetryFieldKey{
Name: name,

View File

@@ -164,6 +164,15 @@ func (f FieldContext) TagType() string {
return ""
}
// MetricAttrType returns the attr_type the metrics metadata table stores for the context;
// metric attributes are stored as point.
func (f FieldContext) MetricAttrType() string {
if f == FieldContextAttribute {
return "point"
}
return f.TagType()
}
func isContextValidForSignal(ctx FieldContext, signal Signal) bool {
if ctx == FieldContextResource ||
ctx == FieldContextAttribute ||

View File

@@ -0,0 +1,25 @@
package telemetrytypes
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestFieldContextMetricAttrType(t *testing.T) {
testCases := []struct {
name string
context FieldContext
expected string
}{
{name: "Attribute_Point", context: FieldContextAttribute, expected: "point"},
{name: "Resource_Resource", context: FieldContextResource, expected: "resource"},
{name: "Scope_Scope", context: FieldContextScope, expected: "scope"},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
assert.Equal(t, testCase.expected, testCase.context.MetricAttrType())
})
}
}

View File

@@ -3,6 +3,9 @@ package telemetrytypes
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetFieldKeyFromKeyText(t *testing.T) {
@@ -422,3 +425,168 @@ func TestNewFieldKeySelectorFromPostableFieldKeysParamsMetricNamespace(t *testin
t.Fatalf("expected metric name to remain empty, got %q", selector.MetricContext.MetricName)
}
}
func TestNewFieldValueSelectorFromPostableFieldValueParams(t *testing.T) {
testCases := []struct {
name string
params PostableFieldValueParams
expectedName string
expectedContext FieldContext
expectedDataType FieldDataType
expectedValue string
expectedLookups []TelemetryFieldKey
}{
{
name: "ResourcePrefix_Parsed",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs}, Name: "resource.service.name"},
expectedName: "service.name",
expectedContext: FieldContextResource,
expectedLookups: []TelemetryFieldKey{{Name: "resource.service.name", FieldContext: FieldContextUnspecified}, {Name: "service.name", FieldContext: FieldContextResource}},
},
{
name: "AttributePrefix_Parsed",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs}, Name: "attribute.key"},
expectedName: "key",
expectedContext: FieldContextAttribute,
expectedLookups: []TelemetryFieldKey{{Name: "attribute.key", FieldContext: FieldContextUnspecified}, {Name: "key", FieldContext: FieldContextAttribute}},
},
{
name: "DoubleAttributePrefix_OnlyFirstStripped",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs}, Name: "attribute.attribute.key"},
expectedName: "attribute.key",
expectedContext: FieldContextAttribute,
expectedLookups: []TelemetryFieldKey{{Name: "attribute.attribute.key", FieldContext: FieldContextUnspecified}, {Name: "attribute.key", FieldContext: FieldContextAttribute}},
},
{
name: "TagAlias_ExactMatchAsWritten",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs}, Name: "tag.region"},
expectedName: "region",
expectedContext: FieldContextAttribute,
expectedLookups: []TelemetryFieldKey{{Name: "tag.region", FieldContext: FieldContextUnspecified}, {Name: "region", FieldContext: FieldContextAttribute}},
},
{
name: "LogPrefix_Parsed",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs}, Name: "log.level"},
expectedName: "level",
expectedContext: FieldContextLog,
expectedLookups: []TelemetryFieldKey{{Name: "log.level", FieldContext: FieldContextUnspecified}, {Name: "level", FieldContext: FieldContextLog}},
},
{
name: "SpanPrefix_Parsed",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalTraces}, Name: "span.kind"},
expectedName: "kind",
expectedContext: FieldContextSpan,
expectedLookups: []TelemetryFieldKey{{Name: "span.kind", FieldContext: FieldContextUnspecified}, {Name: "kind", FieldContext: FieldContextSpan}},
},
{
name: "ContextInvalidForSignal_KeptAsWritten",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs}, Name: "span.kind"},
expectedName: "span.kind",
expectedContext: FieldContextUnspecified,
expectedLookups: []TelemetryFieldKey{{Name: "span.kind", FieldContext: FieldContextUnspecified}},
},
{
name: "ExplicitContext_NameNotParsed",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs, FieldContext: FieldContextAttribute}, Name: "log.level"},
expectedName: "log.level",
expectedContext: FieldContextAttribute,
expectedLookups: []TelemetryFieldKey{{Name: "log.level", FieldContext: FieldContextAttribute}},
},
{
name: "ExplicitBodyContext_SingleLookup",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs, FieldContext: FieldContextBody}, Name: "key"},
expectedName: "key",
expectedContext: FieldContextBody,
expectedLookups: []TelemetryFieldKey{{Name: "key", FieldContext: FieldContextBody}},
},
{
name: "ExplicitAttributeContext_SingleLookup",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs, FieldContext: FieldContextAttribute}, Name: "key"},
expectedName: "key",
expectedContext: FieldContextAttribute,
expectedLookups: []TelemetryFieldKey{{Name: "key", FieldContext: FieldContextAttribute}},
},
{
name: "ExplicitAttributeContext_PrefixedStoredName",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs, FieldContext: FieldContextAttribute}, Name: "attribute.key"},
expectedName: "attribute.key",
expectedContext: FieldContextAttribute,
expectedLookups: []TelemetryFieldKey{{Name: "attribute.key", FieldContext: FieldContextAttribute}},
},
{
name: "ExplicitContext_TypeSuffixNotParsed",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs, FieldContext: FieldContextAttribute}, Name: "http.method:string"},
expectedName: "http.method:string",
expectedContext: FieldContextAttribute,
expectedLookups: []TelemetryFieldKey{{Name: "http.method:string", FieldContext: FieldContextAttribute}},
},
{
name: "DataTypeSuffix_WithoutContext",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs}, Name: "service.name:string"},
expectedName: "service.name",
expectedContext: FieldContextUnspecified,
expectedDataType: FieldDataTypeString,
expectedLookups: []TelemetryFieldKey{{Name: "service.name", FieldContext: FieldContextUnspecified}},
},
{
name: "DataTypeSuffix_WithContext",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs}, Name: "resource.host.name:string"},
expectedName: "host.name",
expectedContext: FieldContextResource,
expectedDataType: FieldDataTypeString,
expectedLookups: []TelemetryFieldKey{{Name: "resource.host.name", FieldContext: FieldContextUnspecified}, {Name: "host.name", FieldContext: FieldContextResource}},
},
{
name: "DataTypeSuffix_WithInvalidContext",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs}, Name: "span.kind:int64"},
expectedName: "span.kind",
expectedContext: FieldContextUnspecified,
expectedDataType: FieldDataTypeInt64,
expectedLookups: []TelemetryFieldKey{{Name: "span.kind", FieldContext: FieldContextUnspecified}},
},
{
name: "ExplicitDataType_SuffixKept",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs, FieldDataType: FieldDataTypeBool}, Name: "http.method:string"},
expectedName: "http.method:string",
expectedContext: FieldContextUnspecified,
expectedDataType: FieldDataTypeBool,
expectedLookups: []TelemetryFieldKey{{Name: "http.method:string", FieldContext: FieldContextUnspecified}},
},
{
name: "SearchTextWithContextPrefix_NotParsedAsKey",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs, SearchText: "log.x"}, Name: "service.name"},
expectedName: "service.name",
expectedContext: FieldContextUnspecified,
expectedValue: "log.x",
expectedLookups: []TelemetryFieldKey{{Name: "service.name", FieldContext: FieldContextUnspecified}},
},
{
name: "SearchTextWithContextPrefix_NameStillParsed",
params: PostableFieldValueParams{PostableFieldKeysParams: PostableFieldKeysParams{Signal: SignalLogs, SearchText: "attribute.x"}, Name: "resource.k8s.pod.name"},
expectedName: "k8s.pod.name",
expectedContext: FieldContextResource,
expectedValue: "attribute.x",
expectedLookups: []TelemetryFieldKey{{Name: "resource.k8s.pod.name", FieldContext: FieldContextUnspecified}, {Name: "k8s.pod.name", FieldContext: FieldContextResource}},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
selector := NewFieldValueSelectorFromPostableFieldValueParams(testCase.params)
assert.Equal(t, testCase.expectedName, selector.Name)
assert.Equal(t, testCase.expectedContext, selector.FieldContext)
assert.Equal(t, testCase.expectedDataType, selector.FieldDataType)
assert.Equal(t, testCase.expectedValue, selector.Value)
lookups := selector.NameMatchSelectors()
require.Len(t, lookups, len(testCase.expectedLookups))
for i, expected := range testCase.expectedLookups {
assert.Equal(t, expected.Name, lookups[i].Name)
assert.Equal(t, expected.FieldContext, lookups[i].FieldContext)
assert.Equal(t, selector.FieldDataType, lookups[i].FieldDataType)
}
assert.Equal(t, testCase.expectedName, selector.Name, "lookups must not mutate the selector")
})
}
}