mirror of
https://github.com/SigNoz/signoz.git
synced 2026-10-20 09:00:40 +01:00
Compare commits
1 Commits
fix/quick-
...
fix/dynami
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de6fbfeb2d |
@@ -3,10 +3,7 @@ import { Button, Skeleton } from 'antd';
|
||||
import { TelemetrytypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { FieldKeysConfig } from 'api/querySuggestions/types';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import {
|
||||
EXCLUDED_QUICK_FILTER_NAMES,
|
||||
SIGNAL_DATA_SOURCE_MAP,
|
||||
} from 'components/QuickFilters/QuickFiltersSettings/constants';
|
||||
import { SIGNAL_DATA_SOURCE_MAP } from 'components/QuickFilters/QuickFiltersSettings/constants';
|
||||
import { SignalType } from 'components/QuickFilters/types';
|
||||
import { buildCompositeKey } from 'container/OptionsMenu/utils';
|
||||
import { useFieldKeysSuggestion } from 'hooks/querySuggestions/useFieldKeysSuggestion';
|
||||
@@ -83,11 +80,7 @@ function OtherFilters({
|
||||
buildCompositeKey(filter.name, filter.fieldContext, filter.fieldDataType),
|
||||
),
|
||||
);
|
||||
return suggestions.filter(
|
||||
(attr) =>
|
||||
!addedKeys.has(attr.key as string) &&
|
||||
!EXCLUDED_QUICK_FILTER_NAMES.has(attr.name),
|
||||
);
|
||||
return suggestions.filter((attr) => !addedKeys.has(attr.key as string));
|
||||
}, [fetchedKeys, addedFilters]);
|
||||
|
||||
const handleAddFilter = (filter: TelemetryFieldKey): void => {
|
||||
|
||||
@@ -9,8 +9,3 @@ export const SIGNAL_DATA_SOURCE_MAP = {
|
||||
[SignalType.METER_EXPLORER]: DataSource.METRICS,
|
||||
[SignalType.AI_OBSERVABILITY]: DataSource.TRACES,
|
||||
};
|
||||
|
||||
export const EXCLUDED_QUICK_FILTER_NAMES: ReadonlySet<string> = new Set([
|
||||
'trace_id',
|
||||
'span_id',
|
||||
]);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useGetQuickFilters } from 'api/generated/services/quick-filter';
|
||||
import { EXCLUDED_QUICK_FILTER_NAMES } from 'components/QuickFilters/QuickFiltersSettings/constants';
|
||||
import { TelemetryFieldKey } from 'types/api/v5/queryRange';
|
||||
|
||||
import { IQuickFiltersConfig, SignalType } from '../types';
|
||||
@@ -32,10 +31,7 @@ const useFilterConfig = ({
|
||||
);
|
||||
|
||||
const customFilters = useMemo<TelemetryFieldKey[]>(
|
||||
() =>
|
||||
((data?.data?.filters ?? []) as TelemetryFieldKey[]).filter(
|
||||
(filter) => !EXCLUDED_QUICK_FILTER_NAMES.has(filter.name),
|
||||
),
|
||||
() => (data?.data?.filters ?? []) as TelemetryFieldKey[],
|
||||
[data],
|
||||
);
|
||||
|
||||
|
||||
@@ -395,70 +395,6 @@ describe('Quick Filters with custom filters', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('hides trace_id and span_id from saved and suggested filters', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
server.use(
|
||||
rest.get(quickFiltersListURL, (_req, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
...quickFiltersListResponse,
|
||||
data: {
|
||||
...quickFiltersListResponse.data,
|
||||
filters: [
|
||||
...quickFiltersListResponse.data.filters,
|
||||
{ name: 'trace_id', fieldDataType: 'string', fieldContext: 'span' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
rest.get(quickFiltersSuggestionsURL, (_req, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
complete: true,
|
||||
keys: {
|
||||
[FILTER_K8S_DEPLOYMENT_NAME]: [
|
||||
{
|
||||
name: FILTER_K8S_DEPLOYMENT_NAME,
|
||||
fieldContext: 'resource',
|
||||
fieldDataType: 'string',
|
||||
},
|
||||
],
|
||||
trace_id: [
|
||||
{ name: 'trace_id', fieldContext: 'log', fieldDataType: 'string' },
|
||||
{
|
||||
name: 'trace_id',
|
||||
fieldContext: 'attribute',
|
||||
fieldDataType: 'string',
|
||||
},
|
||||
],
|
||||
span_id: [
|
||||
{ name: 'span_id', fieldContext: 'log', fieldDataType: 'string' },
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
render(<TestQuickFilters signal={SIGNAL} />);
|
||||
await screen.findByText(FILTER_SERVICE_NAME);
|
||||
expect(screen.queryByText('Trace Id')).not.toBeInTheDocument();
|
||||
|
||||
const icon = await screen.findByTestId(SETTINGS_ICON_TEST_ID);
|
||||
const settingsButton = icon.closest('button') ?? icon;
|
||||
await user.click(settingsButton);
|
||||
|
||||
await screen.findByText(FILTER_K8S_DEPLOYMENT_NAME);
|
||||
expect(screen.queryByText('trace_id')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('span_id')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('adds a filter from OTHER FILTERS to ADDED FILTERS when clicked', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
|
||||
@@ -193,6 +193,40 @@ describe('ValueSelector', () => {
|
||||
|
||||
expect(onSearch).toHaveBeenLastCalledWith('pay');
|
||||
});
|
||||
|
||||
it('commits ALL even when the selection holds a value outside the list', async () => {
|
||||
// A search pick, or one an older time window offered: ALL must still go out as
|
||||
// `__all__`, not as a list that filters the panels to the truncated options.
|
||||
const onChange = jest.fn();
|
||||
const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
|
||||
render(
|
||||
<ValueSelector
|
||||
options={OPTIONS}
|
||||
variableType="dynamic"
|
||||
multiSelect
|
||||
showAllOption
|
||||
selection={{ value: ['searched'], allSelected: false }}
|
||||
onChange={onChange}
|
||||
emptyFallback={{ value: null, allSelected: true }}
|
||||
testId="variable-select-env"
|
||||
dynamic={{
|
||||
values: OPTIONS,
|
||||
relatedValues: [],
|
||||
complete: false,
|
||||
onSearch: jest.fn(),
|
||||
onSearchReset: jest.fn(),
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
await openDropdown();
|
||||
await user.click(screen.getByText('ALL'));
|
||||
await user.keyboard('{Escape}');
|
||||
|
||||
expect(onChange).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ allSelected: true }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearing', () => {
|
||||
|
||||
@@ -12,6 +12,7 @@ function commit(
|
||||
values,
|
||||
options: OPTIONS,
|
||||
showAllOption: true,
|
||||
isDynamic: false,
|
||||
emptyFallback: FALLBACK,
|
||||
...overrides,
|
||||
});
|
||||
@@ -60,6 +61,20 @@ describe('selectionFromCommittedValues', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// A dynamic ALL is sent as `__all__`, so reading it as a list would filter the
|
||||
// panels down to the truncated option set.
|
||||
it('reads every option PLUS a value outside the list as ALL for a dynamic variable', () => {
|
||||
expect(
|
||||
commit([...OPTIONS, 'searched'], { isDynamic: true }).allSelected,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('does not read a dynamic subset PLUS a value outside the list as ALL', () => {
|
||||
expect(
|
||||
commit(['checkout', 'searched'], { isDynamic: true }).allSelected,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
// Derived from the values + options at commit time, never from the old selection.
|
||||
it('recomputes the marker: a typed value the data now offers is a normal pick', () => {
|
||||
expect(
|
||||
|
||||
@@ -102,6 +102,7 @@ function ValueSelector({
|
||||
values,
|
||||
options,
|
||||
showAllOption,
|
||||
isDynamic: !!dynamic,
|
||||
emptyFallback,
|
||||
});
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ interface CommittedValues {
|
||||
values: string[];
|
||||
options: string[];
|
||||
showAllOption: boolean;
|
||||
isDynamic: boolean;
|
||||
emptyFallback: VariableSelection;
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ export function selectionFromCommittedValues({
|
||||
values,
|
||||
options,
|
||||
showAllOption,
|
||||
isDynamic,
|
||||
emptyFallback,
|
||||
}: CommittedValues): VariableSelection {
|
||||
if (values.length === 0) {
|
||||
@@ -71,10 +73,12 @@ export function selectionFromCommittedValues({
|
||||
const customValues = values.filter((value) => !options.includes(value));
|
||||
// ALL re-materializes to the option set, so a set carrying a typed value is not ALL
|
||||
// — the next refetch would expand it back and drop what the user typed.
|
||||
// A dynamic ALL travels as `__all__` and is never re-materialized, so a value outside
|
||||
// its truncated list (a search pick, an older window's pick) loses nothing.
|
||||
const allSelected =
|
||||
showAllOption &&
|
||||
options.length > 0 &&
|
||||
customValues.length === 0 &&
|
||||
(isDynamic || customValues.length === 0) &&
|
||||
options.every((option) => values.includes(option));
|
||||
|
||||
return {
|
||||
|
||||
@@ -184,6 +184,7 @@ func NewDefaultQuickFilter(orgID valuer.UUID) ([]*StorableQuickFilter, error) {
|
||||
{Name: "http.method", FieldContext: telemetrytypes.FieldContextAttribute, FieldDataType: telemetrytypes.FieldDataTypeString},
|
||||
{Name: "http.route", FieldContext: telemetrytypes.FieldContextAttribute, FieldDataType: telemetrytypes.FieldDataTypeString},
|
||||
{Name: "http_url", FieldContext: telemetrytypes.FieldContextSpan, FieldDataType: telemetrytypes.FieldDataTypeString},
|
||||
{Name: "trace_id", FieldContext: telemetrytypes.FieldContextSpan, FieldDataType: telemetrytypes.FieldDataTypeString},
|
||||
}
|
||||
|
||||
logsFilters := []telemetrytypes.TelemetryFieldKey{
|
||||
|
||||
Reference in New Issue
Block a user