mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-19 16:30:31 +01:00
Compare commits
1 Commits
ns/scope
...
chore/quer
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f00d52f8bb |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -231,5 +231,4 @@ cython_debug/
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
# agents
|
||||
*settings.local.json
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
|
||||
|
||||
const handleChangeGroupByKeys = useCallback(
|
||||
(value: IBuilderQuery['groupBy']) => {
|
||||
handleChangeQueryData('groupBy', value);
|
||||
handleChangeQueryData('groupBy', value, { runAfterUpdate: true });
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
@@ -283,14 +283,14 @@ function QueryAddOns({
|
||||
|
||||
const handleChangeGroupByKeys = useCallback(
|
||||
(value: IBuilderQuery['groupBy']) => {
|
||||
handleChangeQueryData('groupBy', value);
|
||||
handleChangeQueryData('groupBy', value, { runAfterUpdate: true });
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
const handleChangeOrderByKeys = useCallback(
|
||||
(value: IBuilderQuery['orderBy']) => {
|
||||
handleChangeQueryData('orderBy', value);
|
||||
handleChangeQueryData('orderBy', value, { runAfterUpdate: true });
|
||||
},
|
||||
[handleChangeQueryData],
|
||||
);
|
||||
|
||||
@@ -73,8 +73,8 @@ export const QueryV2 = forwardRef(function QueryV2(
|
||||
});
|
||||
|
||||
const handleToggleDisableQuery = useCallback(() => {
|
||||
handleChangeQueryData('disabled', !query.disabled);
|
||||
}, [handleChangeQueryData, query]);
|
||||
handleChangeQueryData('disabled', !query.disabled, { runAfterUpdate: true });
|
||||
}, [handleChangeQueryData, query.disabled]);
|
||||
|
||||
const handleToggleCollapsQuery = (): void => {
|
||||
setIsCollapsed(!isCollapsed);
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
BaseAutocompleteData,
|
||||
DataTypes,
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import {
|
||||
DataSource,
|
||||
MetricAggregateOperator,
|
||||
@@ -333,3 +334,196 @@ describe('useQueryBuilderOperations - Empty Aggregate Attribute Type', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useQueryBuilderOperations - handleChangeQueryData runAfterUpdate', () => {
|
||||
const mockHandleSetQueryData = jest.fn();
|
||||
const mockHandleSetTraceOperatorData = jest.fn();
|
||||
const mockHandleRunQuery = jest.fn();
|
||||
|
||||
const baseQuery: IBuilderQuery = {
|
||||
dataSource: DataSource.METRICS,
|
||||
aggregateOperator: MetricAggregateOperator.AVG,
|
||||
aggregateAttribute: {
|
||||
key: 'system.cpu.load',
|
||||
dataType: DataTypes.Float64,
|
||||
type: ATTRIBUTE_TYPES.GAUGE,
|
||||
} as BaseAutocompleteData,
|
||||
timeAggregation: MetricAggregateOperator.AVG,
|
||||
spaceAggregation: '',
|
||||
aggregations: [],
|
||||
having: [],
|
||||
limit: null,
|
||||
queryName: 'A',
|
||||
functions: [],
|
||||
filters: { items: [], op: 'AND' },
|
||||
groupBy: [],
|
||||
orderBy: [],
|
||||
stepInterval: 60,
|
||||
expression: '',
|
||||
disabled: false,
|
||||
reduceTo: ReduceOperators.AVG,
|
||||
legend: '',
|
||||
};
|
||||
|
||||
const otherQuery: IBuilderQuery = { ...baseQuery, queryName: 'B' };
|
||||
|
||||
const buildCurrentQuery = (): Query => ({
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
id: 'q-1',
|
||||
unit: '',
|
||||
builder: {
|
||||
queryData: [baseQuery, otherQuery],
|
||||
queryFormulas: [],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
});
|
||||
|
||||
const setupMock = (overrides: Record<string, unknown> = {}): void => {
|
||||
(useQueryBuilder as jest.Mock).mockReturnValue({
|
||||
handleSetQueryData: mockHandleSetQueryData,
|
||||
handleSetTraceOperatorData: mockHandleSetTraceOperatorData,
|
||||
handleSetFormulaData: jest.fn(),
|
||||
removeQueryBuilderEntityByIndex: jest.fn(),
|
||||
setLastUsedQuery: jest.fn(),
|
||||
redirectWithQueryBuilderData: jest.fn(),
|
||||
handleRunQuery: mockHandleRunQuery,
|
||||
panelType: 'time_series',
|
||||
currentQuery: buildCurrentQuery(),
|
||||
...overrides,
|
||||
});
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setupMock();
|
||||
});
|
||||
|
||||
it('does not call handleRunQuery when options is omitted', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQueryOperations({
|
||||
query: baseQuery,
|
||||
index: 0,
|
||||
entityVersion: ENTITY_VERSION_V4,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.handleChangeQueryData('legend', 'cpu-load');
|
||||
});
|
||||
|
||||
expect(mockHandleSetQueryData).toHaveBeenCalledWith(
|
||||
0,
|
||||
expect.objectContaining({ legend: 'cpu-load' }),
|
||||
);
|
||||
expect(mockHandleRunQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls handleRunQuery with the freshly-changed query when runAfterUpdate is true', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQueryOperations({
|
||||
query: baseQuery,
|
||||
index: 0,
|
||||
entityVersion: ENTITY_VERSION_V4,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.handleChangeQueryData('disabled', true, {
|
||||
runAfterUpdate: true,
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockHandleSetQueryData).toHaveBeenCalledWith(
|
||||
0,
|
||||
expect.objectContaining({ disabled: true }),
|
||||
);
|
||||
expect(mockHandleRunQuery).toHaveBeenCalledTimes(1);
|
||||
const [override] = mockHandleRunQuery.mock.calls[0];
|
||||
// Index 0 reflects the new value...
|
||||
expect(override.builder.queryData[0]).toStrictEqual(
|
||||
expect.objectContaining({ queryName: 'A', disabled: true }),
|
||||
);
|
||||
// ...siblings stay untouched.
|
||||
expect(override.builder.queryData[1]).toStrictEqual(
|
||||
expect.objectContaining({ queryName: 'B', disabled: false }),
|
||||
);
|
||||
});
|
||||
|
||||
it('applies the change at the correct index without disturbing other queries', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQueryOperations({
|
||||
query: otherQuery,
|
||||
index: 1,
|
||||
entityVersion: ENTITY_VERSION_V4,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.handleChangeQueryData(
|
||||
'groupBy',
|
||||
[
|
||||
{
|
||||
key: 'host.name',
|
||||
type: 'tag',
|
||||
dataType: DataTypes.String,
|
||||
} as BaseAutocompleteData,
|
||||
],
|
||||
{ runAfterUpdate: true },
|
||||
);
|
||||
});
|
||||
|
||||
const [override] = mockHandleRunQuery.mock.calls[0];
|
||||
expect(override.builder.queryData[0]).toStrictEqual(
|
||||
expect.objectContaining({ queryName: 'A', groupBy: [] }),
|
||||
);
|
||||
expect(override.builder.queryData[1]).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
queryName: 'B',
|
||||
groupBy: [expect.objectContaining({ key: 'host.name' })],
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps handleSetQueryData and handleRunQuery in sync for legend formatting', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQueryOperations({
|
||||
query: baseQuery,
|
||||
index: 0,
|
||||
entityVersion: ENTITY_VERSION_V4,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.handleChangeQueryData('legend', '{{service.name}}', {
|
||||
runAfterUpdate: true,
|
||||
});
|
||||
});
|
||||
|
||||
const [override] = mockHandleRunQuery.mock.calls[0];
|
||||
const setCallLegend = mockHandleSetQueryData.mock.calls[0][1].legend;
|
||||
expect(override.builder.queryData[0].legend).toBe(setCallLegend);
|
||||
});
|
||||
|
||||
it('does not call handleRunQuery for trace-operator queries (early return)', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useQueryOperations({
|
||||
query: baseQuery,
|
||||
index: 0,
|
||||
entityVersion: ENTITY_VERSION_V4,
|
||||
isForTraceOperator: true,
|
||||
}),
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.handleChangeQueryData('disabled', true, {
|
||||
runAfterUpdate: true,
|
||||
});
|
||||
});
|
||||
|
||||
expect(mockHandleSetTraceOperatorData).toHaveBeenCalledTimes(1);
|
||||
expect(mockHandleSetQueryData).not.toHaveBeenCalled();
|
||||
expect(mockHandleRunQuery).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
import {
|
||||
HandleChangeFormulaData,
|
||||
HandleChangeQueryData,
|
||||
HandleChangeQueryDataOptions,
|
||||
HandleChangeQueryDataV5,
|
||||
UseQueryOperations,
|
||||
} from 'types/common/operations.types';
|
||||
@@ -76,6 +77,7 @@ export const useQueryOperations: UseQueryOperations = ({
|
||||
currentQuery,
|
||||
setLastUsedQuery,
|
||||
redirectWithQueryBuilderData,
|
||||
handleRunQuery,
|
||||
} = useQueryBuilder();
|
||||
|
||||
const [operators, setOperators] = useState<SelectOption<string, string>[]>([]);
|
||||
@@ -530,7 +532,7 @@ export const useQueryOperations: UseQueryOperations = ({
|
||||
|
||||
const handleChangeQueryData: HandleChangeQueryData | HandleChangeQueryDataV5 =
|
||||
useCallback(
|
||||
(key: string, value: any) => {
|
||||
(key: string, value: any, options?: HandleChangeQueryDataOptions) => {
|
||||
const newQuery = {
|
||||
...query,
|
||||
[key]:
|
||||
@@ -541,8 +543,24 @@ export const useQueryOperations: UseQueryOperations = ({
|
||||
|
||||
if (isForTraceOperator) {
|
||||
handleSetTraceOperatorData(index, newQuery);
|
||||
} else {
|
||||
handleSetQueryData(index, newQuery);
|
||||
return;
|
||||
}
|
||||
|
||||
handleSetQueryData(index, newQuery);
|
||||
|
||||
// `runAfterUpdate` lets callers stage-and-run inline. We pass the
|
||||
// freshly-computed query straight to `handleRunQuery` because the
|
||||
// setState above hasn't flushed yet.
|
||||
if (options?.runAfterUpdate) {
|
||||
handleRunQuery({
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item, i) =>
|
||||
i === index ? newQuery : item,
|
||||
),
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
[
|
||||
@@ -551,6 +569,8 @@ export const useQueryOperations: UseQueryOperations = ({
|
||||
handleSetQueryData,
|
||||
handleSetTraceOperatorData,
|
||||
isForTraceOperator,
|
||||
handleRunQuery,
|
||||
currentQuery,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1024,49 +1024,57 @@ export function QueryBuilderProvider({
|
||||
[],
|
||||
);
|
||||
|
||||
const handleRunQuery = useCallback(() => {
|
||||
const isExplorer =
|
||||
location.pathname === ROUTES.LOGS_EXPLORER ||
|
||||
location.pathname === ROUTES.TRACES_EXPLORER;
|
||||
if (isExplorer) {
|
||||
setCalledFromHandleRunQuery(true);
|
||||
}
|
||||
const currentQueryData = {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
queryData: currentQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
filter: {
|
||||
...item.filter,
|
||||
expression:
|
||||
item.filter?.expression.trim() === ''
|
||||
? ''
|
||||
: (item.filter?.expression ?? ''),
|
||||
},
|
||||
filters: {
|
||||
items: [],
|
||||
op: 'AND',
|
||||
},
|
||||
})),
|
||||
},
|
||||
};
|
||||
// `overrideQuery` lets callers run a query value that hasn't been committed
|
||||
// to `currentQuery` state yet — e.g. a click handler that toggles a flag
|
||||
// and wants to stage-and-run in the same tick, without waiting for the
|
||||
// state update to flush.
|
||||
const handleRunQuery = useCallback(
|
||||
(overrideQuery?: Query) => {
|
||||
const isExplorer =
|
||||
location.pathname === ROUTES.LOGS_EXPLORER ||
|
||||
location.pathname === ROUTES.TRACES_EXPLORER;
|
||||
if (isExplorer) {
|
||||
setCalledFromHandleRunQuery(true);
|
||||
}
|
||||
const sourceQuery = overrideQuery ?? currentQuery;
|
||||
const currentQueryData = {
|
||||
...sourceQuery,
|
||||
builder: {
|
||||
...sourceQuery.builder,
|
||||
queryData: sourceQuery.builder.queryData.map((item) => ({
|
||||
...item,
|
||||
filter: {
|
||||
...item.filter,
|
||||
expression:
|
||||
item.filter?.expression.trim() === ''
|
||||
? ''
|
||||
: (item.filter?.expression ?? ''),
|
||||
},
|
||||
filters: {
|
||||
items: [],
|
||||
op: 'AND',
|
||||
},
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
redirectWithQueryBuilderData({
|
||||
...{
|
||||
...currentQueryData,
|
||||
...updateStepInterval({
|
||||
builder: currentQueryData.builder,
|
||||
clickhouse_sql: currentQueryData.clickhouse_sql,
|
||||
promql: currentQueryData.promql,
|
||||
id: currentQueryData.id,
|
||||
queryType,
|
||||
unit: currentQueryData.unit,
|
||||
}),
|
||||
},
|
||||
queryType,
|
||||
});
|
||||
}, [currentQuery, location.pathname, queryType, redirectWithQueryBuilderData]);
|
||||
redirectWithQueryBuilderData({
|
||||
...{
|
||||
...currentQueryData,
|
||||
...updateStepInterval({
|
||||
builder: currentQueryData.builder,
|
||||
clickhouse_sql: currentQueryData.clickhouse_sql,
|
||||
promql: currentQueryData.promql,
|
||||
id: currentQueryData.id,
|
||||
queryType,
|
||||
unit: currentQueryData.unit,
|
||||
}),
|
||||
},
|
||||
queryType,
|
||||
});
|
||||
},
|
||||
[currentQuery, location.pathname, queryType, redirectWithQueryBuilderData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (location.pathname !== currentPathnameRef.current) {
|
||||
|
||||
@@ -26,6 +26,16 @@ type UseQueryOperationsParams = Pick<QueryProps, 'index' | 'query'> &
|
||||
savePreviousQuery?: boolean;
|
||||
};
|
||||
|
||||
export interface HandleChangeQueryDataOptions {
|
||||
/**
|
||||
* When true, stage-and-run the query immediately after the local state
|
||||
* update — no need to wait for the user to click "Stage and Run".
|
||||
* Useful for inline toggles (visibility, disable, etc.) where the panel
|
||||
* should reflect the change without an extra click.
|
||||
*/
|
||||
runAfterUpdate?: boolean;
|
||||
}
|
||||
|
||||
// Generic type that can work with both legacy and V5 query types
|
||||
export type HandleChangeQueryData<T = IBuilderQuery> = <
|
||||
Key extends keyof T,
|
||||
@@ -33,6 +43,7 @@ export type HandleChangeQueryData<T = IBuilderQuery> = <
|
||||
>(
|
||||
key: Key,
|
||||
value: Value,
|
||||
options?: HandleChangeQueryDataOptions,
|
||||
) => void;
|
||||
|
||||
export type HandleChangeTraceOperatorData<T = IBuilderTraceOperator> = <
|
||||
|
||||
@@ -280,7 +280,7 @@ export type QueryBuilderContextType = {
|
||||
shallStringify?: boolean,
|
||||
newTab?: boolean,
|
||||
) => void;
|
||||
handleRunQuery: () => void;
|
||||
handleRunQuery: (overrideQuery?: Query) => void;
|
||||
resetQuery: (newCurrentQuery?: QueryState) => void;
|
||||
handleOnUnitsChange: (units: Format['id']) => void;
|
||||
updateAllQueriesOperators: (
|
||||
|
||||
@@ -56,15 +56,6 @@ func QueryStringToKeysSelectors(query string) []*telemetrytypes.FieldKeySelector
|
||||
FieldDataType: key.FieldDataType,
|
||||
})
|
||||
}
|
||||
|
||||
if key.FieldContext == telemetrytypes.FieldContextScope {
|
||||
keys = append(keys, &telemetrytypes.FieldKeySelector{
|
||||
Name: key.FieldContext.StringValue() + "." + key.Name,
|
||||
Signal: key.Signal,
|
||||
FieldContext: key.FieldContext,
|
||||
FieldDataType: key.FieldDataType,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ func (t *telemetryMetaStore) getTracesKeys(ctx context.Context, fieldKeySelector
|
||||
`CASE
|
||||
// WHEN tagType = 'spanfield' THEN 1
|
||||
WHEN tagType = 'resource' THEN 2
|
||||
WHEN tagType = 'scope' THEN 3
|
||||
// WHEN tagType = 'scope' THEN 3
|
||||
WHEN tagType = 'tag' THEN 4
|
||||
ELSE 5
|
||||
END as priority`,
|
||||
|
||||
@@ -51,7 +51,6 @@ var (
|
||||
ValueType: schema.ColumnTypeString,
|
||||
}},
|
||||
"resource": {Name: "resource", Type: schema.JSONColumnType{}},
|
||||
"scope": {Name: "scope", Type: schema.JSONColumnType{}},
|
||||
|
||||
"events": {Name: "events", Type: schema.ArrayColumnType{
|
||||
ElementType: schema.ColumnTypeString,
|
||||
@@ -177,7 +176,7 @@ func (m *defaultFieldMapper) getColumn(
|
||||
case telemetrytypes.FieldContextResource:
|
||||
return []*schema.Column{indexV3Columns["resource"]}, nil
|
||||
case telemetrytypes.FieldContextScope:
|
||||
return []*schema.Column{indexV3Columns["scope"]}, nil
|
||||
return []*schema.Column{}, qbtypes.ErrColumnNotFound
|
||||
case telemetrytypes.FieldContextAttribute:
|
||||
switch key.FieldDataType {
|
||||
case telemetrytypes.FieldDataTypeString:
|
||||
@@ -262,28 +261,21 @@ func (m *defaultFieldMapper) FieldFor(
|
||||
|
||||
switch column.Type.GetType() {
|
||||
case schema.ColumnTypeEnumJSON:
|
||||
switch key.FieldContext {
|
||||
case telemetrytypes.FieldContextScope:
|
||||
switch key.Name {
|
||||
case "scope.name", "scope.version":
|
||||
return fmt.Sprintf("%s::String", key.Name), nil
|
||||
default:
|
||||
return fmt.Sprintf("%s.attributes.`%s`::String", column.Name, key.Name), nil
|
||||
}
|
||||
case telemetrytypes.FieldContextResource:
|
||||
oldColumn := indexV3Columns["resources_string"]
|
||||
oldKeyName := fmt.Sprintf("%s['%s']", oldColumn.Name, key.Name)
|
||||
// have to add ::string as clickHouse throws an error :- data types Variant/Dynamic are not allowed in GROUP BY
|
||||
// once clickHouse dependency is updated, we need to check if we can remove it.
|
||||
if key.Materialized {
|
||||
oldKeyName = telemetrytypes.FieldKeyToMaterializedColumnName(key)
|
||||
oldKeyNameExists := telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key)
|
||||
return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, %s==true, %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldKeyNameExists, oldKeyName), nil
|
||||
} else {
|
||||
return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, mapContains(%s, '%s'), %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldColumn.Name, key.Name, oldKeyName), nil
|
||||
}
|
||||
// json is only supported for resource context as of now
|
||||
if key.FieldContext != telemetrytypes.FieldContextResource {
|
||||
return "", errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "only resource context fields are supported for json columns, got %s", key.FieldContext.String)
|
||||
}
|
||||
oldColumn := indexV3Columns["resources_string"]
|
||||
oldKeyName := fmt.Sprintf("%s['%s']", oldColumn.Name, key.Name)
|
||||
// have to add ::string as clickHouse throws an error :- data types Variant/Dynamic are not allowed in GROUP BY
|
||||
// once clickHouse dependency is updated, we need to check if we can remove it.
|
||||
if key.Materialized {
|
||||
oldKeyName = telemetrytypes.FieldKeyToMaterializedColumnName(key)
|
||||
oldKeyNameExists := telemetrytypes.FieldKeyToMaterializedColumnNameForExists(key)
|
||||
return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, %s==true, %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldKeyNameExists, oldKeyName), nil
|
||||
} else {
|
||||
return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, mapContains(%s, '%s'), %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldColumn.Name, key.Name, oldKeyName), nil
|
||||
}
|
||||
return "", errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "json column type only supported for resource and scope context, got %s", key.FieldContext.String)
|
||||
case schema.ColumnTypeEnumString,
|
||||
schema.ColumnTypeEnumUInt64,
|
||||
schema.ColumnTypeEnumUInt32,
|
||||
|
||||
@@ -78,51 +78,6 @@ func TestGetFieldKeyName(t *testing.T) {
|
||||
expectedResult: "multiIf(resource.`deployment.environment` IS NOT NULL, resource.`deployment.environment`::String, `resource_string_deployment$$environment_exists`==true, `resource_string_deployment$$environment`, NULL)",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Scope field - name",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "name",
|
||||
FieldContext: telemetrytypes.FieldContextScope,
|
||||
},
|
||||
expectedResult: "scope.name::String",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Scope field - scope.name (denormalized)",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "scope.name",
|
||||
FieldContext: telemetrytypes.FieldContextScope,
|
||||
},
|
||||
expectedResult: "scope.name::String",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Scope field - version",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "version",
|
||||
FieldContext: telemetrytypes.FieldContextScope,
|
||||
},
|
||||
expectedResult: "scope.version::String",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Scope field - scope.version (denormalized)",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "scope.version",
|
||||
FieldContext: telemetrytypes.FieldContextScope,
|
||||
},
|
||||
expectedResult: "scope.version::String",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Scope field - custom attribute",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "custom.attr",
|
||||
FieldContext: telemetrytypes.FieldContextScope,
|
||||
},
|
||||
expectedResult: "scope.attributes.`custom.attr`::String",
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "Non-existent column",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
|
||||
@@ -350,66 +350,6 @@ func TestStatementBuilder(t *testing.T) {
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "scope.name filter and group by",
|
||||
requestType: qbtypes.RequestTypeTimeSeries,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
StepInterval: qbtypes.Step{Duration: 30 * time.Second},
|
||||
Aggregations: []qbtypes.TraceAggregation{
|
||||
{
|
||||
Expression: "count()",
|
||||
},
|
||||
},
|
||||
Filter: &qbtypes.Filter{
|
||||
Expression: "scope.name = 'opentelemetry-io'",
|
||||
},
|
||||
Limit: 10,
|
||||
GroupBy: []qbtypes.GroupByKey{
|
||||
{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "scope.name",
|
||||
FieldContext: telemetrytypes.FieldContextScope,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __limit_cte AS (SELECT toString(multiIf(scope.name::String IS NOT NULL, scope.name::String, NULL)) AS `scope.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE (scope.name::String = ? AND scope.name::String IS NOT NULL) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `scope.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(scope.name::String IS NOT NULL, scope.name::String, NULL)) AS `scope.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE (scope.name::String = ? AND scope.name::String IS NOT NULL) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`scope.name`) GLOBAL IN (SELECT `scope.name` FROM __limit_cte) GROUP BY ts, `scope.name`",
|
||||
Args: []any{"opentelemetry-io", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "opentelemetry-io", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "scope.version filter with scope.name group by",
|
||||
requestType: qbtypes.RequestTypeTimeSeries,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.TraceAggregation]{
|
||||
Signal: telemetrytypes.SignalTraces,
|
||||
StepInterval: qbtypes.Step{Duration: 30 * time.Second},
|
||||
Aggregations: []qbtypes.TraceAggregation{
|
||||
{
|
||||
Expression: "count()",
|
||||
},
|
||||
},
|
||||
Filter: &qbtypes.Filter{
|
||||
Expression: "scope.version = '1.0.0'",
|
||||
},
|
||||
Limit: 10,
|
||||
GroupBy: []qbtypes.GroupByKey{
|
||||
{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "scope.name",
|
||||
FieldContext: telemetrytypes.FieldContextScope,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __limit_cte AS (SELECT toString(multiIf(scope.name::String IS NOT NULL, scope.name::String, NULL)) AS `scope.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE (scope.version::String = ? AND scope.version::String IS NOT NULL) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? GROUP BY `scope.name` ORDER BY __result_0 DESC LIMIT ?) SELECT toStartOfInterval(timestamp, INTERVAL 30 SECOND) AS ts, toString(multiIf(scope.name::String IS NOT NULL, scope.name::String, NULL)) AS `scope.name`, count() AS __result_0 FROM signoz_traces.distributed_signoz_index_v3 WHERE (scope.version::String = ? AND scope.version::String IS NOT NULL) AND timestamp >= ? AND timestamp < ? AND ts_bucket_start >= ? AND ts_bucket_start <= ? AND (`scope.name`) GLOBAL IN (SELECT `scope.name` FROM __limit_cte) GROUP BY ts, `scope.name`",
|
||||
Args: []any{"1.0.0", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448), 10, "1.0.0", "1747947419000000000", "1747983448000000000", uint64(1747945619), uint64(1747983448)},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
|
||||
@@ -111,20 +111,6 @@ func buildCompleteFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey {
|
||||
FieldDataType: telemetrytypes.FieldDataTypeBool,
|
||||
},
|
||||
},
|
||||
"scope.name": {
|
||||
{
|
||||
Name: "scope.name",
|
||||
FieldContext: telemetrytypes.FieldContextScope,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
"scope.version": {
|
||||
{
|
||||
Name: "scope.version",
|
||||
FieldContext: telemetrytypes.FieldContextScope,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, keys := range keysMap {
|
||||
for _, key := range keys {
|
||||
|
||||
Reference in New Issue
Block a user