mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-08 18:59:56 +00:00
Compare commits
3 Commits
fix/8403
...
fix/data-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18fa83ebfa | ||
|
|
9daefeb881 | ||
|
|
526cf01cb7 |
@@ -98,6 +98,7 @@ interface QueryBuilderSearchV2Props {
|
||||
hideSpanScopeSelector?: boolean;
|
||||
// Determines whether to call onChange when a tag is closed
|
||||
triggerOnChangeOnClose?: boolean;
|
||||
skipQueryBuilderRedirect?: boolean;
|
||||
}
|
||||
|
||||
export interface Option {
|
||||
@@ -137,6 +138,7 @@ function QueryBuilderSearchV2(
|
||||
operatorConfigKey,
|
||||
hideSpanScopeSelector,
|
||||
triggerOnChangeOnClose,
|
||||
skipQueryBuilderRedirect,
|
||||
} = props;
|
||||
|
||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
||||
@@ -1038,7 +1040,11 @@ function QueryBuilderSearchV2(
|
||||
})}
|
||||
</Select>
|
||||
{!hideSpanScopeSelector && (
|
||||
<SpanScopeSelector query={query} onChange={onChange} />
|
||||
<SpanScopeSelector
|
||||
query={query}
|
||||
onChange={onChange}
|
||||
skipQueryBuilderRedirect={skipQueryBuilderRedirect}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -1056,6 +1062,7 @@ QueryBuilderSearchV2.defaultProps = {
|
||||
operatorConfigKey: undefined,
|
||||
hideSpanScopeSelector: true,
|
||||
triggerOnChangeOnClose: false,
|
||||
skipQueryBuilderRedirect: false,
|
||||
};
|
||||
|
||||
export default QueryBuilderSearchV2;
|
||||
|
||||
@@ -23,6 +23,7 @@ interface SpanFilterConfig {
|
||||
interface SpanScopeSelectorProps {
|
||||
onChange?: (value: TagFilter) => void;
|
||||
query?: IBuilderQuery;
|
||||
skipQueryBuilderRedirect?: boolean;
|
||||
}
|
||||
|
||||
const SPAN_FILTER_CONFIG: Record<SpanScope, SpanFilterConfig | null> = {
|
||||
@@ -58,6 +59,7 @@ const SELECT_OPTIONS = [
|
||||
function SpanScopeSelector({
|
||||
onChange,
|
||||
query,
|
||||
skipQueryBuilderRedirect,
|
||||
}: SpanScopeSelectorProps): JSX.Element {
|
||||
const { currentQuery, redirectWithQueryBuilderData } = useQueryBuilder();
|
||||
const [selectedScope, setSelectedScope] = useState<SpanScope>(
|
||||
@@ -79,6 +81,7 @@ function SpanScopeSelector({
|
||||
if (hasFilter('isEntryPoint')) return SpanScope.ENTRYPOINT_SPANS;
|
||||
return SpanScope.ALL_SPANS;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let queryData = (currentQuery?.builder?.queryData || [])?.find(
|
||||
(item) => item.queryName === query?.queryName,
|
||||
@@ -127,13 +130,10 @@ function SpanScopeSelector({
|
||||
},
|
||||
}));
|
||||
|
||||
if (onChange && query) {
|
||||
if (skipQueryBuilderRedirect && onChange && query) {
|
||||
onChange({
|
||||
...query.filters,
|
||||
items: getUpdatedFilters(
|
||||
[...query.filters.items, ...newQuery.builder.queryData[0].filters.items],
|
||||
true,
|
||||
),
|
||||
items: getUpdatedFilters([...query.filters.items], true),
|
||||
});
|
||||
|
||||
setSelectedScope(newScope);
|
||||
@@ -156,6 +156,7 @@ function SpanScopeSelector({
|
||||
SpanScopeSelector.defaultProps = {
|
||||
onChange: undefined,
|
||||
query: undefined,
|
||||
skipQueryBuilderRedirect: false,
|
||||
};
|
||||
|
||||
export default SpanScopeSelector;
|
||||
|
||||
@@ -3,9 +3,11 @@ import {
|
||||
render,
|
||||
RenderResult,
|
||||
screen,
|
||||
within,
|
||||
} from '@testing-library/react';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import { QueryBuilderContext } from 'providers/QueryBuilder';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
Query,
|
||||
@@ -13,6 +15,7 @@ import {
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import QueryBuilderSearchV2 from '../QueryBuilderSearchV2';
|
||||
import SpanScopeSelector from '../SpanScopeSelector';
|
||||
|
||||
const mockRedirectWithQueryBuilderData = jest.fn();
|
||||
@@ -48,6 +51,14 @@ const defaultQuery = {
|
||||
},
|
||||
};
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const defaultQueryBuilderQuery: IBuilderQuery = {
|
||||
...initialQueriesMap.traces.builder.queryData[0],
|
||||
queryName: 'A',
|
||||
@@ -76,6 +87,7 @@ const renderWithContext = (
|
||||
initialQuery = defaultQuery,
|
||||
onChangeProp?: (value: TagFilter) => void,
|
||||
queryProp?: IBuilderQuery,
|
||||
skipQueryBuilderRedirect = false,
|
||||
): RenderResult =>
|
||||
render(
|
||||
<QueryBuilderContext.Provider
|
||||
@@ -87,12 +99,19 @@ const renderWithContext = (
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<SpanScopeSelector onChange={onChangeProp} query={queryProp} />
|
||||
<SpanScopeSelector
|
||||
onChange={onChangeProp}
|
||||
query={queryProp}
|
||||
skipQueryBuilderRedirect={skipQueryBuilderRedirect}
|
||||
/>
|
||||
</QueryBuilderContext.Provider>,
|
||||
);
|
||||
|
||||
const selectOption = async (optionText: string): Promise<void> => {
|
||||
const selector = screen.getByRole('combobox');
|
||||
const selector = within(screen.getByTestId('span-scope-selector')).getByRole(
|
||||
'combobox',
|
||||
);
|
||||
|
||||
fireEvent.mouseDown(selector);
|
||||
|
||||
// Wait for dropdown to appear
|
||||
@@ -264,6 +283,7 @@ describe('SpanScopeSelector', () => {
|
||||
defaultQuery,
|
||||
mockOnChange,
|
||||
localQuery,
|
||||
true,
|
||||
);
|
||||
expect(await screen.findByText('All Spans')).toBeInTheDocument();
|
||||
|
||||
@@ -283,6 +303,7 @@ describe('SpanScopeSelector', () => {
|
||||
defaultQuery,
|
||||
mockOnChange,
|
||||
localQuery,
|
||||
true,
|
||||
);
|
||||
expect(await screen.findByText('Root Spans')).toBeInTheDocument();
|
||||
|
||||
@@ -303,6 +324,7 @@ describe('SpanScopeSelector', () => {
|
||||
defaultQuery,
|
||||
mockOnChange,
|
||||
localQuery,
|
||||
true,
|
||||
);
|
||||
expect(await screen.findByText('Root Spans')).toBeInTheDocument();
|
||||
|
||||
@@ -324,6 +346,7 @@ describe('SpanScopeSelector', () => {
|
||||
defaultQuery,
|
||||
mockOnChange,
|
||||
localQuery,
|
||||
true,
|
||||
);
|
||||
expect(await screen.findByText('Root Spans')).toBeInTheDocument();
|
||||
|
||||
@@ -350,6 +373,7 @@ describe('SpanScopeSelector', () => {
|
||||
defaultQuery,
|
||||
mockOnChange,
|
||||
localQuery,
|
||||
true,
|
||||
);
|
||||
expect(await screen.findByText('Entrypoint Spans')).toBeInTheDocument();
|
||||
|
||||
@@ -361,5 +385,60 @@ describe('SpanScopeSelector', () => {
|
||||
container.querySelector('span[title="All Spans"]'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not duplicate non-scope filters when changing span scope', async () => {
|
||||
const query = {
|
||||
...defaultQuery,
|
||||
builder: {
|
||||
...defaultQuery.builder,
|
||||
queryData: [
|
||||
{
|
||||
...defaultQuery.builder.queryData[0],
|
||||
filters: {
|
||||
items: [createNonScopeFilter('service', 'checkout')],
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<QueryBuilderContext.Provider
|
||||
value={
|
||||
{
|
||||
currentQuery: query,
|
||||
redirectWithQueryBuilderData: mockRedirectWithQueryBuilderData,
|
||||
} as any
|
||||
}
|
||||
>
|
||||
<QueryBuilderSearchV2
|
||||
query={query.builder.queryData[0] as any}
|
||||
onChange={mockOnChange}
|
||||
hideSpanScopeSelector={false}
|
||||
/>
|
||||
</QueryBuilderContext.Provider>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
expect(await screen.findByText('All Spans')).toBeInTheDocument();
|
||||
|
||||
await selectOption('Entrypoint Spans');
|
||||
|
||||
expect(mockRedirectWithQueryBuilderData).toHaveBeenCalled();
|
||||
|
||||
const redirectQueryArg = mockRedirectWithQueryBuilderData.mock
|
||||
.calls[0][0] as Query;
|
||||
const { items } = redirectQueryArg.builder.queryData[0].filters;
|
||||
// Count non-scope filters
|
||||
const nonScopeFilters = items.filter(
|
||||
(filter) => filter.key?.type !== 'spanSearchScope',
|
||||
);
|
||||
expect(nonScopeFilters).toHaveLength(1);
|
||||
|
||||
expect(nonScopeFilters).toContainEqual(
|
||||
createNonScopeFilter('service', 'checkout'),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -142,6 +142,7 @@ function Filters({
|
||||
}}
|
||||
onChange={handleFilterChange}
|
||||
hideSpanScopeSelector={false}
|
||||
skipQueryBuilderRedirect
|
||||
/>
|
||||
{filteredSpanIds.length > 0 && (
|
||||
<div className="pre-next-toggle">
|
||||
|
||||
@@ -17,6 +17,7 @@ import { AppState } from 'store/reducers';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import DOCLINKS from 'utils/docLinks';
|
||||
import { transformBuilderQueryFields } from 'utils/queryTransformers';
|
||||
|
||||
import TraceExplorerControls from '../Controls';
|
||||
import { TracesLoading } from '../TraceLoading/TraceLoading';
|
||||
@@ -39,9 +40,22 @@ function TracesView({ isFilterApplied }: TracesViewProps): JSX.Element {
|
||||
QueryParams.pagination,
|
||||
);
|
||||
|
||||
const transformedQuery = useMemo(
|
||||
() =>
|
||||
transformBuilderQueryFields(stagedQuery || initialQueriesMap.traces, {
|
||||
orderBy: [
|
||||
{
|
||||
columnName: 'timestamp',
|
||||
order: 'desc',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[stagedQuery],
|
||||
);
|
||||
|
||||
const { data, isLoading, isFetching, isError } = useGetQueryRange(
|
||||
{
|
||||
query: stagedQuery || initialQueriesMap.traces,
|
||||
query: transformedQuery,
|
||||
graphType: panelType || PANEL_TYPES.TRACE,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
|
||||
@@ -594,6 +594,53 @@ describe('TracesExplorer - ', () => {
|
||||
'http://localhost/trace/5765b60ba7cc4ddafe8bdaa9c1b4b246',
|
||||
);
|
||||
});
|
||||
it('trace explorer - trace view should only send order by timestamp in the query', async () => {
|
||||
let capturedPayload: QueryRangePayload;
|
||||
const orderBy = [
|
||||
{ columnName: 'id', order: 'desc' },
|
||||
{ columnName: 'serviceName', order: 'desc' },
|
||||
];
|
||||
const defaultOrderBy = [{ columnName: 'timestamp', order: 'desc' }];
|
||||
server.use(
|
||||
rest.post(`${BASE_URL}/api/v4/query_range`, async (req, res, ctx) => {
|
||||
const payload = await req.json();
|
||||
capturedPayload = payload;
|
||||
return res(ctx.status(200), ctx.json(queryRangeForTraceView));
|
||||
}),
|
||||
);
|
||||
render(
|
||||
<QueryBuilderContext.Provider
|
||||
value={{
|
||||
...qbProviderValue,
|
||||
panelType: PANEL_TYPES.TRACE,
|
||||
stagedQuery: {
|
||||
...qbProviderValue.stagedQuery,
|
||||
builder: {
|
||||
...qbProviderValue.stagedQuery.builder,
|
||||
queryData: [
|
||||
{
|
||||
...qbProviderValue.stagedQuery.builder.queryData[0],
|
||||
orderBy,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TracesExplorer />
|
||||
</QueryBuilderContext.Provider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayload).toBeDefined();
|
||||
expect(capturedPayload?.compositeQuery?.builderQueries?.A.orderBy).toEqual(
|
||||
defaultOrderBy,
|
||||
);
|
||||
expect(
|
||||
capturedPayload?.compositeQuery?.builderQueries?.A.orderBy,
|
||||
).not.toEqual(orderBy);
|
||||
});
|
||||
});
|
||||
|
||||
it('test for explorer options', async () => {
|
||||
const { getByText, getByTestId } = render(
|
||||
|
||||
28
frontend/src/utils/queryTransformers.ts
Normal file
28
frontend/src/utils/queryTransformers.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
/**
|
||||
* Transforms a query by modifying specific fields in the builder queries
|
||||
* @param query - The original query object
|
||||
* @param fieldOverrides - Partial object containing fields to override in each builder query
|
||||
* @returns A new query object with the modified fields
|
||||
*/
|
||||
export const transformBuilderQueryFields = (
|
||||
query: Query,
|
||||
fieldOverrides: Partial<IBuilderQuery>,
|
||||
): Query => {
|
||||
// Create a deep copy of the query
|
||||
const transformedQuery: Query = cloneDeep(query);
|
||||
|
||||
// Update the specified fields for each query in the builder
|
||||
if (transformedQuery.builder?.queryData) {
|
||||
transformedQuery.builder.queryData = transformedQuery.builder.queryData.map(
|
||||
(queryItem) => ({
|
||||
...queryItem,
|
||||
...fieldOverrides,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return transformedQuery;
|
||||
};
|
||||
@@ -236,7 +236,14 @@ func (q *queryCache) FindMissingTimeRanges(orgID valuer.UUID, start, end, step i
|
||||
|
||||
func (q *queryCache) getCachedSeriesData(orgID valuer.UUID, cacheKey string) []*CachedSeriesData {
|
||||
cacheableSeriesData := new(CacheableSeriesData)
|
||||
err := q.cache.Get(context.TODO(), orgID, cacheKey, cacheableSeriesData, true)
|
||||
tmpcacheableSeriesData := new(CacheableSeriesData)
|
||||
err := q.cache.Get(context.TODO(), orgID, cacheKey, tmpcacheableSeriesData, true)
|
||||
data, err := tmpcacheableSeriesData.MarshalBinary()
|
||||
if err != nil {
|
||||
zap.L().Error("error marshalling cacheable series data", zap.Error(err))
|
||||
}
|
||||
cacheableSeriesData.UnmarshalBinary(data)
|
||||
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil
|
||||
}
|
||||
@@ -300,11 +307,18 @@ func (q *queryCache) MergeWithCachedSeriesDataV2(orgID valuer.UUID, cacheKey str
|
||||
return newData
|
||||
}
|
||||
|
||||
tmpcacheableSeriesData := new(CacheableSeriesData)
|
||||
cacheableSeriesData := new(CacheableSeriesData)
|
||||
err := q.cache.Get(context.TODO(), orgID, cacheKey, cacheableSeriesData, true)
|
||||
err := q.cache.Get(context.TODO(), orgID, cacheKey, tmpcacheableSeriesData, true)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil
|
||||
}
|
||||
data, err := tmpcacheableSeriesData.MarshalBinary()
|
||||
if err != nil {
|
||||
zap.L().Error("error marshalling cacheable series data", zap.Error(err))
|
||||
}
|
||||
cacheableSeriesData.UnmarshalBinary(data)
|
||||
|
||||
allData := append(cacheableSeriesData.Series, newData...)
|
||||
|
||||
sort.Slice(allData, func(i, j int) bool {
|
||||
|
||||
@@ -353,10 +353,6 @@ func (m *Manager) EditRule(ctx context.Context, ruleStr string, id valuer.UUID)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(channels) == 0 {
|
||||
return errors.New("no channels found for this org, please set channels first")
|
||||
}
|
||||
|
||||
for _, channel := range channels {
|
||||
preferredChannels = append(preferredChannels, channel.Name)
|
||||
}
|
||||
@@ -540,9 +536,6 @@ func (m *Manager) CreateRule(ctx context.Context, ruleStr string) (*ruletypes.Ge
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(channels) == 0 {
|
||||
return errors.New("no channels found for this org, please set channels first")
|
||||
}
|
||||
|
||||
for _, channel := range channels {
|
||||
preferredChannels = append(preferredChannels, channel.Name)
|
||||
|
||||
Reference in New Issue
Block a user