Compare commits

...

7 Commits

Author SHA1 Message Date
aks07
daa3025e46 fix(saved-views): invalidate the v2 list after v1 writes
usePreferenceSync reads the v2 list while the explorer bar and management pages still write
through v1, so a save, update or rename left the v2 cache stale. Goes away with the v1 client.
2026-09-23 21:40:22 +05:30
aks07
7f7483bbdd chore(storybook): mock saved views on the v2 api in the home story 2026-09-23 21:07:38 +05:30
aks07
7c65ccb631 feat(preferences): read saved view columns and formatting from the v2 spec
Traces views without columns now fall back to the typed defaultTraceSelectedColumns the loader
uses, instead of the string list from the ListView configs.
2026-09-23 21:07:38 +05:30
aks07
45c84b427f feat(ai-assistant): open saved views through the v2 api 2026-09-23 21:07:38 +05:30
aks07
89dc2d419e feat(home): read saved views from the v2 api
Rows show spec.displayName; the tags block is dropped since no view ever carried tags.
2026-09-23 21:07:38 +05:30
aks07
1d05650aa3 feat(saved-views): add v2 spec helpers for the saved views revamp
getSavedViewQuery shapes a v2 spec for the existing v5 mapper; findSavedView and toSavedViewSource
are the two lookups every v2 consumer needs. Consumers use the generated client and DTOs directly.
2026-09-23 21:07:38 +05:30
Nityananda Gohain
5a1be60745 fix(ai-o11y): scope overview dashboard to gen_ai spans and move message attributes (#12967)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
<!--A few plain bullets saying what changed and why, for a reviewer
skimming it - not a wall of text, not a restatement of the diff, not
generated boilerplate.-->
#### Description
- Top span names and Cost by service use `builder_ai_query`, so non-AI
spans no longer show up.
- LLM cost/token panels filter on `gen_ai.request.model EXISTS`; with
variables on "All" they scanned every span.
- Default span mappers now move (not copy) vendor message keys into
`gen_ai.input.messages` / `gen_ai.output.messages`, as documented.
- Bumped versions: dashboard to 3, `gen_ai.llm` mapper to 3,
`gen_ai.agent` mapper to 2.

<!--Reference issues using `Closes #issue-number` to enable automatic
closure on merge. -->
#### Issues closed by this PR
Part of https://github.com/SigNoz/engineering-pod/issues/6107
2026-09-23 14:56:04 +00:00
15 changed files with 669 additions and 197 deletions

View File

@@ -3,15 +3,22 @@ import {
MessageActionKindDTO,
SavedViewEntityDTO,
} from 'api/ai-assistant/sigNozAIAssistantAPI.schemas';
import { getAllViews } from 'api/saveView/getAllViews';
import { getViewById } from 'api/saveView/getViewById';
import {
getSavedView,
listSavedViews,
} from 'api/generated/services/saved-view';
import {
GetSavedView200,
ListSavedViews200,
SavedviewtypesPanelTypeDTO,
SavedviewtypesSavedViewDTO,
SavedviewtypesSchemaVersionDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import ROUTES from 'constants/routes';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
import { AllViewsProps, ViewProps } from 'types/api/saveViews/types';
import { DataSource } from 'types/common/queryBuilder';
import { AxiosResponse } from 'axios';
import type { History } from 'history';
import {
@@ -31,8 +38,7 @@ import {
} from '../resolveOpenResource';
import { resourceRoute, ResourceType } from '../resourceRoute';
jest.mock('api/saveView/getAllViews');
jest.mock('api/saveView/getViewById');
jest.mock('api/generated/services/saved-view');
jest.mock(
'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi',
@@ -48,43 +54,45 @@ jest.mock(
}),
);
const mockedGetAllViews = getAllViews as jest.MockedFunction<
typeof getAllViews
const mockedListSavedViews = listSavedViews as jest.MockedFunction<
typeof listSavedViews
>;
const mockedGetViewById = getViewById as jest.MockedFunction<
typeof getViewById
const mockedGetSavedView = getSavedView as jest.MockedFunction<
typeof getSavedView
>;
function makeView(id: string, sourcePage: DataSource): ViewProps {
function makeView(
id: string,
source: SavedviewtypesSourceDTO,
): SavedviewtypesSavedViewDTO {
return {
id,
name: `View ${id}`,
category: 'test',
name: `view-${id}`,
source,
schemaVersion: SavedviewtypesSchemaVersionDTO.v2,
createdAt: '2021-07-07T06:31:00.000Z',
createdBy: 'user',
updatedAt: '2021-07-07T06:33:00.000Z',
updatedBy: 'user',
sourcePage,
tags: [],
extraData: '',
compositeQuery: {
panelType: PANEL_TYPES.LIST,
} as ICompositeMetricQuery,
};
spec: {
displayName: `View ${id}`,
panelType: SavedviewtypesPanelTypeDTO.list,
requestType: 'raw',
queries: [{ type: 'builder_query', spec: { name: 'A', signal: source } }],
},
} as unknown as SavedviewtypesSavedViewDTO;
}
function mockViewsResponse(views: ViewProps[]): AxiosResponse<AllViewsProps> {
return {
data: { status: 'success', data: views },
} as AxiosResponse<AllViewsProps>;
function mockViewsResponse(
views: SavedviewtypesSavedViewDTO[],
): ListSavedViews200 {
return { status: 'success', data: views };
}
function mockViewByIdResponse(
view: ViewProps,
): AxiosResponse<{ status: string; data: ViewProps }> {
return {
data: { status: 'success', data: view },
} as AxiosResponse<{ status: string; data: ViewProps }>;
view: SavedviewtypesSavedViewDTO,
): GetSavedView200 {
return { status: 'success', data: view };
}
describe('resourceRoute', () => {
@@ -190,18 +198,33 @@ describe('resolveOpenResource', () => {
describe('findSavedViewInLists', () => {
beforeEach(() => {
mockedGetAllViews.mockReset();
mockedListSavedViews.mockReset();
});
it('loads only the hinted source when entity is provided', async () => {
const tracesView = makeView('view-traces', DataSource.TRACES);
mockedGetAllViews.mockResolvedValueOnce(mockViewsResponse([tracesView]));
const tracesView = makeView('view-traces', SavedviewtypesSourceDTO.traces);
mockedListSavedViews.mockResolvedValueOnce(mockViewsResponse([tracesView]));
const result = await findSavedViewInLists('view-traces', DataSource.TRACES);
expect(result).toStrictEqual(tracesView);
expect(mockedGetAllViews).toHaveBeenCalledTimes(1);
expect(mockedGetAllViews).toHaveBeenCalledWith(DataSource.TRACES);
expect(mockedListSavedViews).toHaveBeenCalledTimes(1);
expect(mockedListSavedViews).toHaveBeenCalledWith({
source: SavedviewtypesSourceDTO.traces,
});
});
it('treats a null list as empty and probes the next source', async () => {
const metricsView = makeView('view-metrics', SavedviewtypesSourceDTO.metrics);
mockedListSavedViews
.mockResolvedValueOnce({ status: 'success', data: null })
.mockResolvedValueOnce(mockViewsResponse([]))
.mockResolvedValueOnce(mockViewsResponse([metricsView]));
const result = await findSavedViewInLists('view-metrics');
expect(result).toStrictEqual(metricsView);
expect(mockedListSavedViews).toHaveBeenCalledTimes(3);
});
});
@@ -227,52 +250,75 @@ describe('openSavedView', () => {
it('navigates with history.push and view query params', () => {
const push = jest.fn();
const history = { push } as unknown as History;
const view = makeView('view-logs', DataSource.LOGS);
const view = makeView('view-logs', SavedviewtypesSourceDTO.logs);
openSavedView(view, history);
expect(push).toHaveBeenCalledTimes(1);
const pushedUrl = push.mock.calls[0][0] as string;
expect(pushedUrl).toContain(ROUTES.LOGS_EXPLORER);
expect(pushedUrl).toContain(QueryParams.viewKey);
const params = new URLSearchParams(pushedUrl.split('?')[1]);
expect(params.get(QueryParams.viewKey)).toBe('"view-logs"');
expect(params.get(QueryParams.viewName)).toBe('"View view-logs"');
expect(params.get(QueryParams.panelTypes)).toBe('"list"');
});
it('throws when the view has no source', () => {
const view = makeView('view-logs', SavedviewtypesSourceDTO.logs);
delete view.source;
expect(() =>
openSavedView(view, { push: jest.fn() } as unknown as History),
).toThrow('Unsupported saved view source');
});
it('throws when the view has no queries', () => {
const view = makeView('view-logs', SavedviewtypesSourceDTO.logs);
view.spec.queries = [];
expect(() =>
openSavedView(view, { push: jest.fn() } as unknown as History),
).toThrow('Saved view is missing query data');
});
});
describe('openSavedViewByKey', () => {
beforeEach(() => {
mockedGetAllViews.mockReset();
mockedGetViewById.mockReset();
mockedListSavedViews.mockReset();
mockedGetSavedView.mockReset();
});
it('prefers the direct view lookup endpoint', async () => {
const view = makeView('view-logs', DataSource.LOGS);
mockedGetViewById.mockResolvedValueOnce(mockViewByIdResponse(view));
const view = makeView('view-logs', SavedviewtypesSourceDTO.logs);
mockedGetSavedView.mockResolvedValueOnce(mockViewByIdResponse(view));
const push = jest.fn();
const history = { push } as unknown as History;
await openSavedViewByKey('view-logs', DataSource.LOGS, history);
expect(mockedGetViewById).toHaveBeenCalledWith('view-logs');
expect(mockedGetAllViews).not.toHaveBeenCalled();
expect(mockedGetSavedView).toHaveBeenCalledWith({ id: 'view-logs' });
expect(mockedListSavedViews).not.toHaveBeenCalled();
expect(push).toHaveBeenCalled();
});
it('falls back to list probing when direct lookup fails', async () => {
const view = makeView('view-traces', DataSource.TRACES);
mockedGetViewById.mockRejectedValueOnce(new Error('not found'));
mockedGetAllViews.mockResolvedValueOnce(mockViewsResponse([view]));
const view = makeView('view-traces', SavedviewtypesSourceDTO.traces);
mockedGetSavedView.mockRejectedValueOnce(new Error('not found'));
mockedListSavedViews.mockResolvedValueOnce(mockViewsResponse([view]));
const push = jest.fn();
const history = { push } as unknown as History;
await openSavedViewByKey('view-traces', DataSource.TRACES, history);
expect(mockedGetAllViews).toHaveBeenCalledWith(DataSource.TRACES);
expect(mockedListSavedViews).toHaveBeenCalledWith({
source: SavedviewtypesSourceDTO.traces,
});
expect(push).toHaveBeenCalled();
});
it('throws when the saved view does not exist', async () => {
mockedGetViewById.mockRejectedValueOnce(new Error('not found'));
mockedGetAllViews.mockResolvedValue(mockViewsResponse([]));
mockedGetSavedView.mockRejectedValueOnce(new Error('not found'));
mockedListSavedViews.mockResolvedValue(mockViewsResponse([]));
await expect(
openSavedViewByKey('missing', DataSource.LOGS, {

View File

@@ -1,15 +1,22 @@
import { getAllViews } from 'api/saveView/getAllViews';
import { getViewById } from 'api/saveView/getViewById';
import {
getSavedView,
listSavedViews,
} from 'api/generated/services/saved-view';
import { SavedviewtypesSavedViewDTO } from 'api/generated/services/sigNoz.schemas';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import {
findSavedView,
getSavedViewQuery,
SavedViewSourcePage,
toSavedViewSource,
} from 'container/SavedViews/utils';
import { SOURCEPAGE_VS_ROUTES } from 'pages/SaveView/constants';
import { ViewProps } from 'types/api/saveViews/types';
import { DataSource } from 'types/common/queryBuilder';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { History } from 'history';
type SavedViewSourceHint = DataSource | 'meter';
type SavedViewSourceHint = SavedViewSourcePage;
const DEFAULT_PROBE_SOURCES: SavedViewSourceHint[] = [
DataSource.LOGS,
@@ -20,13 +27,15 @@ const DEFAULT_PROBE_SOURCES: SavedViewSourceHint[] = [
export async function findSavedViewInLists(
viewKey: string,
sourceHint?: SavedViewSourceHint | null,
): Promise<ViewProps | null> {
): Promise<SavedviewtypesSavedViewDTO | null> {
const sources = sourceHint ? [sourceHint] : DEFAULT_PROBE_SOURCES;
for (const source of sources) {
try {
const response = await getAllViews(source);
const match = response.data.data.find((view) => view.id === viewKey);
const response = await listSavedViews({
source: toSavedViewSource(source),
});
const match = findSavedView(response.data, viewKey);
if (match) {
return match;
}
@@ -41,11 +50,11 @@ export async function findSavedViewInLists(
async function loadSavedView(
viewKey: string,
sourceHint?: SavedViewSourceHint | null,
): Promise<ViewProps> {
): Promise<SavedviewtypesSavedViewDTO> {
try {
const response = await getViewById(viewKey);
if (response.data?.data) {
return response.data.data;
const response = await getSavedView({ id: viewKey });
if (response.data) {
return response.data;
}
} catch {
// Fall back to list probing when the direct lookup fails.
@@ -85,20 +94,23 @@ export function buildExplorerNavigationUrl(
return `${route}?${params.toString()}`;
}
export function openSavedView(view: ViewProps, history: History): void {
const route = explorerRouteForSourcePage(view.sourcePage);
export function openSavedView(
view: SavedviewtypesSavedViewDTO,
history: History,
): void {
const route = view.source ? explorerRouteForSourcePage(view.source) : null;
if (!route) {
throw new Error('Unsupported saved view source');
}
if (!view.compositeQuery) {
if (!view.spec.queries?.length) {
throw new Error('Saved view is missing query data');
}
const query = mapQueryDataFromApi(view.compositeQuery);
const query = getSavedViewQuery(view);
const url = buildExplorerNavigationUrl(route, query, {
[QueryParams.panelTypes]: view.compositeQuery.panelType as PANEL_TYPES,
[QueryParams.viewName]: view.name,
[QueryParams.panelTypes]: view.spec.panelType as unknown as PANEL_TYPES,
[QueryParams.viewName]: view.spec.displayName,
[QueryParams.viewKey]: view.id,
});
history.push(url);
@@ -112,6 +124,3 @@ export async function openSavedViewByKey(
const view = await loadSavedView(viewKey, sourceHint);
openSavedView(view, history);
}
/** @deprecated Use findSavedViewInLists — kept for tests. */
export const findSavedView = findSavedViewInLists;

View File

@@ -1,17 +1,18 @@
import { useEffect, useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { Button, Skeleton } from 'antd';
import { Badge } from '@signozhq/ui/badge';
import logEvent from 'api/common/logEvent';
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
import { useListSavedViews } from 'api/generated/services/saved-view';
import {
SavedviewtypesSavedViewDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import ROUTES from 'constants/routes';
import { useGetAllViews } from 'hooks/saveViews/useGetAllViews';
import { getSavedViewQuery } from 'container/SavedViews/utils';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
import { SOURCEPAGE_VS_ROUTES } from 'pages/SaveView/constants';
import Card from 'periscope/components/Card/Card';
import { useAppContext } from 'providers/App/App';
import { ViewProps } from 'types/api/saveViews/types';
import { DataSource } from 'types/common/queryBuilder';
import { USER_ROLES } from 'types/roles';
import floppyDiscUrl from '@/assets/Icons/floppy-disc.svg';
@@ -35,38 +36,40 @@ export default function SavedViews({
}): JSX.Element {
const { user } = useAppContext();
const [selectedEntity, setSelectedEntity] = useState<string>('logs');
const [selectedEntityViews, setSelectedEntityViews] = useState<any[]>([]);
const [selectedEntityViews, setSelectedEntityViews] = useState<
SavedviewtypesSavedViewDTO[]
>([]);
const {
data: logsViewsData,
isLoading: logsViewsLoading,
isError: logsViewsError,
} = useGetAllViews(DataSource.LOGS);
} = useListSavedViews({ source: SavedviewtypesSourceDTO.logs });
const {
data: tracesViewsData,
isLoading: tracesViewsLoading,
isError: tracesViewsError,
} = useGetAllViews(DataSource.TRACES);
} = useListSavedViews({ source: SavedviewtypesSourceDTO.traces });
const {
data: metricsViewsData,
isLoading: metricsViewsLoading,
isError: metricsViewsError,
} = useGetAllViews(DataSource.METRICS);
} = useListSavedViews({ source: SavedviewtypesSourceDTO.metrics });
const logsViews = useMemo(
() => [...(logsViewsData?.data.data || [])],
() => [...(logsViewsData?.data || [])],
[logsViewsData],
);
const tracesViews = useMemo(
() => [...(tracesViewsData?.data.data || [])],
() => [...(tracesViewsData?.data || [])],
[tracesViewsData],
);
const metricsViews = useMemo(
() => [...(metricsViewsData?.data.data || [])],
() => [...(metricsViewsData?.data || [])],
[metricsViewsData],
);
@@ -88,39 +91,22 @@ export default function SavedViews({
const { handleExplorerTabChange } = useHandleExplorerTabChange();
const handleRedirectQuery = (view: ViewProps): void => {
const handleRedirectQuery = (view: SavedviewtypesSavedViewDTO): void => {
logEvent('Homepage: Saved view clicked', {
viewId: view.id,
viewName: view.name,
viewName: view.spec.displayName,
entity: selectedEntity,
});
let currentViews: ViewProps[] = [];
if (selectedEntity === 'logs') {
currentViews = logsViews;
} else if (selectedEntity === 'traces') {
currentViews = tracesViews;
} else if (selectedEntity === 'metrics') {
currentViews = metricsViews;
}
const currentViewDetails = getViewDetailsUsingViewKey(view.id, currentViews);
if (!currentViewDetails) {
return;
}
const { query, name, id, panelType: currentPanelType } = currentViewDetails;
if (selectedEntity) {
handleExplorerTabChange(
currentPanelType,
{
query,
viewName: name,
viewKey: id,
},
SOURCEPAGE_VS_ROUTES[selectedEntity],
);
}
handleExplorerTabChange(
view.spec.panelType,
{
query: getSavedViewQuery(view),
viewName: view.spec.displayName,
viewKey: view.id,
},
SOURCEPAGE_VS_ROUTES[selectedEntity],
);
};
useEffect(() => {
@@ -239,24 +225,10 @@ export default function SavedViews({
/>
<div className="saved-view-item-name home-data-item-name">
{view.name}
{view.spec.displayName}
</div>
</div>
<div className="saved-view-item-description home-data-item-tag">
{view.tags?.map((tag: string) => {
if (tag === '') {
return null;
}
return (
<Badge color="sienna" key={tag}>
{tag}
</Badge>
);
})}
</div>
<Button
type="link"
size="small"
@@ -307,7 +279,7 @@ export default function SavedViews({
logEvent('Homepage: Saved views switched', {
tab,
});
let currentViews: ViewProps[] = [];
let currentViews: SavedviewtypesSavedViewDTO[] = [];
if (tab === 'logs') {
currentViews = logsViews;
} else if (tab === 'traces') {

View File

@@ -0,0 +1,126 @@
import {
SavedviewtypesPanelTypeDTO,
SavedviewtypesSavedViewDTO,
SavedviewtypesSchemaVersionDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
import { findSavedView, getSavedViewQuery, toSavedViewSource } from '../utils';
jest.mock('uuid', () => ({
v4: (): string => 'test-id',
}));
function makeView(): SavedviewtypesSavedViewDTO {
return {
id: 'view-1',
name: 'errors-by-service-abc123',
source: SavedviewtypesSourceDTO.traces,
schemaVersion: SavedviewtypesSchemaVersionDTO.v2,
createdBy: 'a@b.c',
updatedBy: 'a@b.c',
spec: {
displayName: 'Errors by service',
panelType: SavedviewtypesPanelTypeDTO.list,
requestType: 'raw',
queries: [
{
type: 'builder_query',
spec: {
name: 'A',
signal: 'traces',
stepInterval: 60,
filter: { expression: 'has_error = true' },
// v2 reads back fully defaulted envelopes; nulls must not break the mapper
groupBy: null,
order: null,
selectFields: null,
functions: null,
legend: '',
disabled: false,
},
},
],
selectedFields: [{ name: 'service.name' }],
display: { color: 'red' },
},
} as SavedviewtypesSavedViewDTO;
}
describe('getSavedViewQuery', () => {
it('maps the v2 spec through the v5 branch of mapQueryDataFromApi', () => {
const query = getSavedViewQuery(makeView());
expect(query.queryType).toBe(EQueryType.QUERY_BUILDER);
expect(query.promql).toStrictEqual([]);
expect(query.clickhouse_sql).toStrictEqual([]);
expect(query.builder.queryData).toHaveLength(1);
const [queryData] = query.builder.queryData;
expect(queryData.queryName).toBe('A');
expect(queryData.dataSource).toBe(DataSource.TRACES);
expect(queryData.filter).toStrictEqual({ expression: 'has_error = true' });
expect(queryData.groupBy).toStrictEqual([]);
expect(queryData.orderBy).toStrictEqual([]);
});
it('keeps formulas alongside builder queries', () => {
const view = makeView();
view.spec.queries.push({
type: 'builder_formula',
spec: { name: 'F1', expression: 'A / 2' },
} as SavedviewtypesSavedViewDTO['spec']['queries'][number]);
const query = getSavedViewQuery(view);
expect(query.builder.queryData).toHaveLength(1);
expect(query.builder.queryFormulas).toHaveLength(1);
expect(query.builder.queryFormulas[0].queryName).toBe('F1');
});
it('does not read the panel type into the query', () => {
const view = makeView();
view.spec.panelType = SavedviewtypesPanelTypeDTO.graph;
const query = getSavedViewQuery(view);
// panelType travels separately (url param), the Query itself has no such field
expect(query).not.toHaveProperty('panelType', PANEL_TYPES.TIME_SERIES);
});
});
describe('toSavedViewSource', () => {
it('maps every explorer source page to the v2 source', () => {
expect(toSavedViewSource(DataSource.LOGS)).toBe(SavedviewtypesSourceDTO.logs);
expect(toSavedViewSource(DataSource.TRACES)).toBe(
SavedviewtypesSourceDTO.traces,
);
expect(toSavedViewSource(DataSource.METRICS)).toBe(
SavedviewtypesSourceDTO.metrics,
);
expect(toSavedViewSource('meter')).toBe(SavedviewtypesSourceDTO.meter);
});
});
describe('findSavedView', () => {
const views = [
{ ...makeView(), id: 'a' },
{ ...makeView(), id: 'b' },
];
it('returns the view with the matching id', () => {
expect(findSavedView(views, 'b')?.id).toBe('b');
});
it('returns undefined when the id is not in the list', () => {
expect(findSavedView(views, 'c')).toBeUndefined();
});
it('returns undefined for a null or not yet loaded list', () => {
expect(findSavedView(null, 'a')).toBeUndefined();
expect(findSavedView(undefined, 'a')).toBeUndefined();
});
});

View File

@@ -0,0 +1,49 @@
import {
SavedviewtypesSavedViewDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { QueryEnvelope } from 'types/api/v5/queryRange';
import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
export type SavedViewSourcePage = DataSource | 'meter';
// Explorers and the preferences module are keyed by DataSource (the signal),
// the api keys views by source page. Same values today, so this is the one
// place they meet. AI observability views will come with their own source and
// DataSource cannot tell them apart from traces, so preferences should move to
// source page at that point and this map goes with it.
const SAVED_VIEW_SOURCE: Record<SavedViewSourcePage, SavedviewtypesSourceDTO> =
{
[DataSource.LOGS]: SavedviewtypesSourceDTO.logs,
[DataSource.TRACES]: SavedviewtypesSourceDTO.traces,
[DataSource.METRICS]: SavedviewtypesSourceDTO.metrics,
meter: SavedviewtypesSourceDTO.meter,
};
export function toSavedViewSource(
sourcePage: SavedViewSourcePage,
): SavedviewtypesSourceDTO {
return SAVED_VIEW_SOURCE[sourcePage];
}
// Explorers only save builder queries; v2 carries no queryType, so it is fixed here.
export function getSavedViewQuery(view: SavedviewtypesSavedViewDTO): Query {
const { queries, panelType } = view.spec;
return mapQueryDataFromApi({
queries: queries as QueryEnvelope[],
panelType: panelType as unknown as PANEL_TYPES,
queryType: EQueryType.QUERY_BUILDER,
unit: undefined,
});
}
export function findSavedView(
views: SavedviewtypesSavedViewDTO[] | null | undefined,
id: string,
): SavedviewtypesSavedViewDTO | undefined {
return views?.find((view) => view.id === id);
}

View File

@@ -1,11 +1,18 @@
import { useMutation, UseMutationResult } from 'react-query';
import { useMutation, UseMutationResult, useQueryClient } from 'react-query';
import { invalidateListSavedViews } from 'api/generated/services/saved-view';
import { deleteView } from 'api/saveView/deleteView';
import { DeleteViewPayloadProps } from 'types/api/saveViews/types';
export const useDeleteView = (
uuid: string,
): UseMutationResult<DeleteViewPayloadProps, Error, string> =>
useMutation({
): UseMutationResult<DeleteViewPayloadProps, Error, string> => {
const queryClient = useQueryClient();
return useMutation({
mutationKey: [uuid],
mutationFn: () => deleteView(uuid),
// v1 and v2 share storage; consumers already on v2 must see this write.
// Temporary till the v1 client is deleted with the explorer bar.
onSuccess: () => invalidateListSavedViews(queryClient),
});
};

View File

@@ -1,4 +1,5 @@
import { useMutation, UseMutationResult } from 'react-query';
import { useMutation, UseMutationResult, useQueryClient } from 'react-query';
import { invalidateListSavedViews } from 'api/generated/services/saved-view';
import { saveView } from 'api/saveView/saveView';
import { AxiosResponse } from 'axios';
import { SaveViewPayloadProps, SaveViewProps } from 'types/api/saveViews/types';
@@ -13,8 +14,14 @@ export const useSaveView = ({
Error,
SaveViewProps,
SaveViewPayloadProps
> =>
useMutation({
> => {
const queryClient = useQueryClient();
return useMutation({
mutationKey: [viewName, sourcePage, compositeQuery, extraData],
mutationFn: saveView,
// v1 and v2 share storage; consumers already on v2 must see this write.
// Temporary till the v1 client is deleted with the explorer bar.
onSuccess: () => invalidateListSavedViews(queryClient),
});
};

View File

@@ -1,4 +1,5 @@
import { useMutation, UseMutationResult } from 'react-query';
import { useMutation, UseMutationResult, useQueryClient } from 'react-query';
import { invalidateListSavedViews } from 'api/generated/services/saved-view';
import { updateView } from 'api/saveView/updateView';
import {
UpdateViewPayloadProps,
@@ -16,8 +17,10 @@ export const useUpdateView = ({
Error,
UpdateViewProps,
UpdateViewPayloadProps
> =>
useMutation({
> => {
const queryClient = useQueryClient();
return useMutation({
mutationKey: [viewName, sourcePage, compositeQuery, extraData],
mutationFn: () =>
updateView({
@@ -27,4 +30,8 @@ export const useUpdateView = ({
sourcePage,
viewKey,
}),
// v1 and v2 share storage; consumers already on v2 must see this write.
// Temporary till the v1 client is deleted with the explorer bar.
onSuccess: () => invalidateListSavedViews(queryClient),
});
};

View File

@@ -164,10 +164,10 @@ export const homeMocks = defineStoryMocks({
),
rest.get(
'http://localhost/api/v1/explorer/views',
'http://localhost/api/v2/saved_views',
response.json((req) => {
const sourcePage = req.url.searchParams.get('sourcePage') ?? 'logs';
const signal = isSavedViewSignal(sourcePage) ? sourcePage : 'logs';
const source = req.url.searchParams.get('source') ?? 'logs';
const signal = isSavedViewSignal(source) ? source : 'logs';
return savedViewsResponse(
values.savedViewSignals.includes(signal) ? values.savedViews : 0,

View File

@@ -6,10 +6,21 @@
import { FeatureKeys } from 'constants/features';
import { ORG_PREFERENCES } from 'constants/orgPreferences';
import { checkListStepToPreferenceKeyMap } from 'container/Home/constants';
import type { RuletypesRuleDTO } from 'api/generated/services/sigNoz.schemas';
import {
type ListSavedViews200,
Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5LogAggregationDTOSignal as LogsSignal,
Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregationDTOSignal as MetricsSignal,
Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5TraceAggregationDTOSignal as TracesSignal,
Querybuildertypesv5QueryEnvelopeBuilderDTOType,
type Querybuildertypesv5QueryEnvelopeDTO,
Querybuildertypesv5RequestTypeDTO,
type RuletypesRuleDTO,
SavedviewtypesPanelTypeDTO,
SavedviewtypesSchemaVersionDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import type { ServiceDataProps } from 'api/metrics/getTopLevelOperations';
import { alertRulesFixture } from 'mocks-server/__mockdata__/alert_rules';
import { explorerView } from 'mocks-server/__mockdata__/explorer_views';
import { defaultFeatureFlags } from 'tests/fixtures/appContextMock';
import type { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
import type { MetricRangePayloadV3 } from 'types/api/metrics/getQueryRange';
@@ -165,20 +176,53 @@ const VIEW_NAMES: Record<SavedViewSignal, string[]> = {
export const isSavedViewSignal = (value: string): value is SavedViewSignal =>
SAVED_VIEW_SIGNALS.includes(value as SavedViewSignal);
const SAVED_VIEW_SOURCE: Record<SavedViewSignal, SavedviewtypesSourceDTO> = {
logs: SavedviewtypesSourceDTO.logs,
traces: SavedviewtypesSourceDTO.traces,
metrics: SavedviewtypesSourceDTO.metrics,
};
const SAVED_VIEW_QUERY: Record<
SavedViewSignal,
Querybuildertypesv5QueryEnvelopeDTO
> = {
logs: {
type: Querybuildertypesv5QueryEnvelopeBuilderDTOType.builder_query,
spec: { name: 'A', signal: LogsSignal.logs },
},
traces: {
type: Querybuildertypesv5QueryEnvelopeBuilderDTOType.builder_query,
spec: { name: 'A', signal: TracesSignal.traces },
},
metrics: {
type: Querybuildertypesv5QueryEnvelopeBuilderDTOType.builder_query,
spec: { name: 'A', signal: MetricsSignal.metrics },
},
};
export const savedViewsResponse = (
count: number,
sourcePage: SavedViewSignal,
): Record<string, unknown> => {
const names = VIEW_NAMES[sourcePage];
signal: SavedViewSignal,
): ListSavedViews200 => {
const names = VIEW_NAMES[signal];
return {
status: 'success',
data: Array.from({ length: Math.min(count, names.length) }, (_, index) => ({
...explorerView.data[0],
id: `storybook-${sourcePage}-view-${index + 1}`,
name: names[index],
sourcePage,
tags: [sourcePage],
id: `storybook-${signal}-view-${index + 1}`,
name: `storybook-${signal}-view-${index + 1}`,
source: SAVED_VIEW_SOURCE[signal],
schemaVersion: SavedviewtypesSchemaVersionDTO.v2,
createdAt: '2026-08-20T09:00:00Z',
createdBy: 'storybook@signoz.io',
updatedAt: '2026-08-20T09:00:00Z',
updatedBy: 'storybook@signoz.io',
spec: {
displayName: names[index],
panelType: SavedviewtypesPanelTypeDTO.list,
requestType: Querybuildertypesv5RequestTypeDTO.raw,
queries: [SAVED_VIEW_QUERY[signal]],
},
})),
};
};

View File

@@ -0,0 +1,202 @@
import { renderHook } from '@testing-library/react';
import { useListSavedViews } from 'api/generated/services/saved-view';
import {
SavedviewtypesSavedViewDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import {
defaultLogsSelectedColumns,
defaultTraceSelectedColumns,
ensureLogsRequiredColumns,
} from 'container/OptionsMenu/constants';
import { DataSource } from 'types/common/queryBuilder';
import { usePreferenceSync } from '../sync/usePreferenceSync';
import { PreferenceMode } from '../types';
jest.mock('api/generated/services/saved-view');
const loaderPreferences = { columns: [{ name: 'from-loader' }] };
jest.mock('../loader/usePreferenceLoader', () => ({
usePreferenceLoader: jest.fn(() => ({
preferences: loaderPreferences,
loading: false,
error: null,
})),
}));
jest.mock('../updater/usePreferenceUpdater', () => ({
usePreferenceUpdater: jest.fn(() => ({
updateColumns: jest.fn(),
updateFormatting: jest.fn(),
})),
}));
const mockedUseListSavedViews = useListSavedViews as jest.MockedFunction<
typeof useListSavedViews
>;
function makeView(
id: string,
source: SavedviewtypesSourceDTO,
spec: Partial<SavedviewtypesSavedViewDTO['spec']>,
): SavedviewtypesSavedViewDTO {
return {
id,
source,
schemaVersion: 'v2',
spec: {
displayName: id,
panelType: 'list',
requestType: 'raw',
queries: [],
...spec,
},
} as unknown as SavedviewtypesSavedViewDTO;
}
function mockViews(views: SavedviewtypesSavedViewDTO[]): void {
mockedUseListSavedViews.mockReturnValue({
data: { status: 'success', data: views },
} as unknown as ReturnType<typeof useListSavedViews>);
}
describe('usePreferenceSync in saved view mode', () => {
beforeEach(() => {
mockedUseListSavedViews.mockReset();
});
it('fetches the list for the data source only in saved view mode', () => {
mockViews([]);
renderHook(() =>
usePreferenceSync({
mode: PreferenceMode.DIRECT,
dataSource: DataSource.LOGS,
savedViewId: undefined,
}),
);
expect(mockedUseListSavedViews).toHaveBeenCalledWith(
{ source: 'logs' },
{ query: { enabled: false } },
);
});
it('returns loader preferences outside saved view mode', () => {
mockViews([]);
const { result } = renderHook(() =>
usePreferenceSync({
mode: PreferenceMode.DIRECT,
dataSource: DataSource.LOGS,
savedViewId: undefined,
}),
);
expect(result.current.preferences).toBe(loaderPreferences);
});
it('applies selectedFields and display of the active logs view', () => {
mockViews([
makeView('view-1', SavedviewtypesSourceDTO.logs, {
selectedFields: [{ name: 'service.name' }, { name: 'body' }],
display: { maxLines: 3, format: 'raw', fontSize: 'large', color: 'red' },
}),
]);
const { result } = renderHook(() =>
usePreferenceSync({
mode: PreferenceMode.SAVED_VIEW,
dataSource: DataSource.LOGS,
savedViewId: 'view-1',
}),
);
expect(result.current.preferences?.columns).toStrictEqual(
ensureLogsRequiredColumns([{ name: 'service.name' }, { name: 'body' }]),
);
expect(result.current.preferences?.formatting).toStrictEqual({
maxLines: 3,
format: 'raw',
fontSize: 'large',
version: 1,
});
});
it('falls back to defaults when the view has zero-valued display and no fields', () => {
mockViews([
makeView('view-1', SavedviewtypesSourceDTO.logs, {
selectedFields: undefined,
display: { maxLines: 0, format: '', fontSize: '', color: '' },
}),
]);
const { result } = renderHook(() =>
usePreferenceSync({
mode: PreferenceMode.SAVED_VIEW,
dataSource: DataSource.LOGS,
savedViewId: 'view-1',
}),
);
expect(result.current.preferences?.columns).toStrictEqual(
ensureLogsRequiredColumns(defaultLogsSelectedColumns),
);
expect(result.current.preferences?.formatting).toStrictEqual({
maxLines: 1,
format: 'table',
fontSize: 'small',
version: 1,
});
});
it('passes trace selectedFields through and defaults when absent', () => {
mockViews([
makeView('with-fields', SavedviewtypesSourceDTO.traces, {
selectedFields: [{ name: 'name' }, { name: 'durationNano' }],
}),
makeView('without-fields', SavedviewtypesSourceDTO.traces, {}),
]);
const withFields = renderHook(() =>
usePreferenceSync({
mode: PreferenceMode.SAVED_VIEW,
dataSource: DataSource.TRACES,
savedViewId: 'with-fields',
}),
);
const withoutFields = renderHook(() =>
usePreferenceSync({
mode: PreferenceMode.SAVED_VIEW,
dataSource: DataSource.TRACES,
savedViewId: 'without-fields',
}),
);
expect(withFields.result.current.preferences?.columns).toStrictEqual([
{ name: 'name' },
{ name: 'durationNano' },
]);
expect(withFields.result.current.preferences?.formatting).toBeUndefined();
expect(withoutFields.result.current.preferences?.columns).toBe(
defaultTraceSelectedColumns,
);
});
it('uses defaults when the saved view id is not in the list', () => {
mockViews([makeView('other', SavedviewtypesSourceDTO.logs, {})]);
const { result } = renderHook(() =>
usePreferenceSync({
mode: PreferenceMode.SAVED_VIEW,
dataSource: DataSource.LOGS,
savedViewId: 'missing',
}),
);
expect(result.current.preferences?.columns).toStrictEqual(
ensureLogsRequiredColumns(defaultLogsSelectedColumns),
);
});
});

View File

@@ -1,12 +1,14 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { useEffect, useState } from 'react';
import { useListSavedViews } from 'api/generated/services/saved-view';
import { TelemetryFieldKey } from 'api/v5/v5';
import {
defaultLogsSelectedColumns,
defaultTraceSelectedColumns,
ensureLogsRequiredColumns,
} from 'container/OptionsMenu/constants';
import { defaultSelectedColumns as defaultTracesSelectedColumns } from 'container/TracesExplorer/ListView/configs';
import { useGetAllViews } from 'hooks/saveViews/useGetAllViews';
import { FontSize, LogViewMode } from 'container/OptionsMenu/types';
import { findSavedView, toSavedViewSource } from 'container/SavedViews/utils';
import { DataSource } from 'types/common/queryBuilder';
import { usePreferenceLoader } from '../loader/usePreferenceLoader';
@@ -28,16 +30,16 @@ export function usePreferenceSync({
updateColumns: (newColumns: TelemetryFieldKey[]) => void;
updateFormatting: (newFormatting: FormattingOptions) => void;
} {
const { data: viewsData } = useGetAllViews(
dataSource,
mode === PreferenceMode.SAVED_VIEW,
const { data: viewsData } = useListSavedViews(
{ source: toSavedViewSource(dataSource) },
{ query: { enabled: mode === PreferenceMode.SAVED_VIEW } },
);
const [savedViewPreferences, setSavedViewPreferences] =
useState<Preferences | null>(null);
const updateExtraDataSelectColumns = (
columns: TelemetryFieldKey[],
const withColumnNames = (
columns: TelemetryFieldKey[] | undefined,
): TelemetryFieldKey[] | null => {
if (!columns) {
return null;
@@ -49,27 +51,28 @@ export function usePreferenceSync({
};
useEffect(() => {
const extraData = viewsData?.data?.data?.find(
(view) => view.id === savedViewId,
)?.extraData;
const spec = savedViewId
? findSavedView(viewsData?.data, savedViewId)?.spec
: undefined;
const selectedFields = spec?.selectedFields as
| TelemetryFieldKey[]
| undefined;
const parsedExtraData = JSON.parse(extraData || '{}');
let columns: TelemetryFieldKey[] = [];
let formatting: FormattingOptions | undefined;
if (dataSource === DataSource.LOGS) {
columns = ensureLogsRequiredColumns(
updateExtraDataSelectColumns(parsedExtraData?.selectColumns) ||
defaultLogsSelectedColumns,
withColumnNames(selectedFields) || defaultLogsSelectedColumns,
);
formatting = {
maxLines: parsedExtraData?.maxLines ?? 1,
format: parsedExtraData?.format ?? 'table',
fontSize: parsedExtraData?.fontSize ?? 'small',
version: parsedExtraData?.version ?? 1,
maxLines: spec?.display?.maxLines || 1,
format: (spec?.display?.format as LogViewMode) || 'table',
fontSize: (spec?.display?.fontSize as FontSize) || FontSize.SMALL,
version: 1,
};
}
if (dataSource === DataSource.TRACES) {
columns = parsedExtraData?.selectColumns || defaultTracesSelectedColumns;
columns = selectedFields || defaultTraceSelectedColumns;
}
setSavedViewPreferences({ columns, formatting });
}, [viewsData, dataSource, savedViewId, mode]);

View File

@@ -1,5 +1,5 @@
{
"version": 2,
"version": 3,
"definition": {
"schemaVersion": "v6",
"name": "signoz---ai-o11y-overview",
@@ -437,7 +437,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -489,7 +489,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -722,7 +722,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -782,7 +782,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -850,7 +850,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -868,7 +868,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -886,7 +886,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -904,7 +904,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -961,7 +961,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -979,7 +979,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -997,7 +997,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -1015,7 +1015,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -1076,7 +1076,7 @@
"stepInterval": 0,
"disabled": true,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -1175,7 +1175,7 @@
"stepInterval": 0,
"disabled": true,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -1192,7 +1192,7 @@
"stepInterval": 0,
"disabled": true,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -1248,7 +1248,7 @@
"spec": {
"queries": [
{
"type": "builder_query",
"type": "builder_ai_query",
"spec": {
"name": "A",
"signal": "traces",
@@ -1282,7 +1282,7 @@
}
},
{
"type": "builder_query",
"type": "builder_ai_query",
"spec": {
"name": "B",
"signal": "traces",
@@ -1353,7 +1353,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -2075,7 +2075,7 @@
"stepInterval": 0,
"disabled": false,
"filter": {
"expression": "gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
"expression": "gen_ai.request.model EXISTS AND gen_ai.request.model IN $model AND gen_ai.provider.name IN $provider AND deployment.environment IN $environment AND service.name IN $service_name"
},
"aggregations": [
{
@@ -2724,7 +2724,7 @@
"spec": {
"queries": [
{
"type": "builder_query",
"type": "builder_ai_query",
"spec": {
"name": "A",
"signal": "traces",

View File

@@ -1,5 +1,5 @@
{
"version": 1,
"version": 2,
"definition": {
"name": "gen_ai.agent",
"condition": {
@@ -68,7 +68,7 @@
{
"key": "final_result",
"context": "attribute",
"operation": "copy",
"operation": "move",
"priority": 10
}
]

View File

@@ -1,5 +1,5 @@
{
"version": 2,
"version": 3,
"definition": {
"name": "gen_ai.llm",
"condition": {
@@ -250,19 +250,19 @@
{
"key": "gen_ai.prompt",
"context": "attribute",
"operation": "copy",
"operation": "move",
"priority": 30
},
{
"key": "ai.prompt.messages",
"context": "attribute",
"operation": "copy",
"operation": "move",
"priority": 20
},
{
"key": "input.value",
"context": "attribute",
"operation": "copy",
"operation": "move",
"priority": 10
}
]
@@ -276,25 +276,25 @@
{
"key": "gen_ai.completion",
"context": "attribute",
"operation": "copy",
"operation": "move",
"priority": 30
},
{
"key": "ai.response.toolCalls",
"context": "attribute",
"operation": "copy",
"operation": "move",
"priority": 25
},
{
"key": "ai.response.text",
"context": "attribute",
"operation": "copy",
"operation": "move",
"priority": 20
},
{
"key": "output.value",
"context": "attribute",
"operation": "copy",
"operation": "move",
"priority": 10
}
]