Compare commits

...

8 Commits

Author SHA1 Message Date
Gaurav Tewari
4692e7aa90 Merge remote-tracking branch 'origin/main' into refactor/saved-views-drop-source-map
# Conflicts:
#	frontend/src/container/SavedViews/utils/hasUnsavedViewChanges.ts
#	frontend/src/container/SavedViews/utils/toSavedViewSource.ts
2026-11-02 14:24:12 +05:30
Gaurav Tewari
eddac1f169 refactor(saved-views): use the source page instead of mapping from DataSource
Callers already know the source page; going through DataSource could not
tell meter or AI observability apart.
2026-11-02 10:48:14 +05:30
Gaurav Tewari
00feed18d3 chore(saved-views): mark the source map for removal 2026-11-02 10:47:18 +05:30
Gaurav Tewari
9b9f07c71a Merge remote-tracking branch 'origin/main' into refactor/saved-view-source-page
# Conflicts:
#	frontend/src/container/SavedViews/hooks/useSavedViewActions.ts
2026-11-02 09:59:05 +05:30
Gaurav Tewari
3fff4d14f2 test: type the mock and update tests 2026-10-30 03:04:30 +05:30
Gaurav Tewari
0d95d6ec83 refactor(saved-views): key saved view options by source page 2026-10-29 18:04:32 +05:30
Gaurav Tewari
44f11a1d84 refactor(options-menu): take source page instead of data source 2026-10-29 17:08:08 +05:30
Gaurav Tewari
7f3fbb3562 refactor(preferences): key preference slices by source page
Preferences belong to an explorer page, not a signal.
2026-10-29 17:08:02 +05:30
9 changed files with 69 additions and 96 deletions

View File

@@ -18,7 +18,6 @@ import {
import ROUTES from 'constants/routes';
import { navigate } from 'lib/router/navigation';
import { QueryParams } from 'constants/query';
import { DataSource } from 'types/common/queryBuilder';
import {
findSavedViewInLists,
@@ -26,7 +25,7 @@ import {
openSavedViewByKey,
} from '../openSavedView';
import {
entityToDataSource,
entityToSavedViewSource,
isSavedViewOpenAction,
resolveActionEntity,
resolveOpenResourceType,
@@ -130,10 +129,14 @@ describe('resolveOpenResource', () => {
).toBe('abc-123');
});
it('maps entity values to explorer data sources', () => {
expect(entityToDataSource('logs')).toBe(DataSource.LOGS);
expect(entityToDataSource('logs_explorer')).toBe(DataSource.LOGS);
expect(entityToDataSource('traces')).toBe(DataSource.TRACES);
it('maps entity values to saved view sources', () => {
expect(entityToSavedViewSource('logs')).toBe(SavedviewtypesSourceDTO.logs);
expect(entityToSavedViewSource('logs_explorer')).toBe(
SavedviewtypesSourceDTO.logs,
);
expect(entityToSavedViewSource('traces')).toBe(
SavedviewtypesSourceDTO.traces,
);
});
it('prefers entity over signal for saved-view source hints', () => {
@@ -144,7 +147,7 @@ describe('resolveOpenResource', () => {
entity: SavedViewEntityDTO.traces,
signal: ApplyFilterSignalDTO.logs,
}),
).toBe(DataSource.TRACES);
).toBe(SavedviewtypesSourceDTO.traces);
});
it('falls back to signal when entity is absent', () => {
@@ -154,7 +157,7 @@ describe('resolveOpenResource', () => {
label: 'Open view',
signal: ApplyFilterSignalDTO.metrics,
}),
).toBe(DataSource.METRICS);
).toBe(SavedviewtypesSourceDTO.metrics);
});
it('normalises saved-view resource types', () => {
@@ -207,7 +210,10 @@ describe('findSavedViewInLists', () => {
const tracesView = makeView('view-traces', SavedviewtypesSourceDTO.traces);
mockedListSavedViews.mockResolvedValueOnce(mockViewsResponse([tracesView]));
const result = await findSavedViewInLists('view-traces', DataSource.TRACES);
const result = await findSavedViewInLists(
'view-traces',
SavedviewtypesSourceDTO.traces,
);
expect(result).toStrictEqual(tracesView);
expect(mockedListSavedViews).toHaveBeenCalledTimes(1);
@@ -275,7 +281,7 @@ describe('openSavedViewByKey', () => {
const view = makeView('view-logs', SavedviewtypesSourceDTO.logs);
mockedGetSavedView.mockResolvedValueOnce(mockViewByIdResponse(view));
await openSavedViewByKey('view-logs', DataSource.LOGS);
await openSavedViewByKey('view-logs', SavedviewtypesSourceDTO.logs);
expect(mockedGetSavedView).toHaveBeenCalledWith({ id: 'view-logs' });
expect(mockedListSavedViews).not.toHaveBeenCalled();
@@ -287,7 +293,7 @@ describe('openSavedViewByKey', () => {
mockedGetSavedView.mockRejectedValueOnce(new Error('not found'));
mockedListSavedViews.mockResolvedValueOnce(mockViewsResponse([view]));
await openSavedViewByKey('view-traces', DataSource.TRACES);
await openSavedViewByKey('view-traces', SavedviewtypesSourceDTO.traces);
expect(mockedListSavedViews).toHaveBeenCalledWith({
source: SavedviewtypesSourceDTO.traces,
@@ -299,8 +305,8 @@ describe('openSavedViewByKey', () => {
mockedGetSavedView.mockRejectedValueOnce(new Error('not found'));
mockedListSavedViews.mockResolvedValue(mockViewsResponse([]));
await expect(openSavedViewByKey('missing', DataSource.LOGS)).rejects.toThrow(
'Saved view not found',
);
await expect(
openSavedViewByKey('missing', SavedviewtypesSourceDTO.logs),
).rejects.toThrow('Saved view not found');
});
});

View File

@@ -2,37 +2,34 @@ import {
getSavedView,
listSavedViews,
} from 'api/generated/services/saved-view';
import { SavedviewtypesSavedViewDTO } from 'api/generated/services/sigNoz.schemas';
import {
SavedviewtypesSavedViewDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { SavedViewSourcePage } from 'container/SavedViews/types';
import { findSavedView } from 'container/SavedViews/utils/findSavedView';
import { getSavedViewQuery } from 'container/SavedViews/utils/getSavedViewQuery';
import { toSavedViewSource } from 'container/SavedViews/utils/toSavedViewSource';
import { SOURCEPAGE_VS_ROUTES } from 'container/SavedViews/constants';
import { navigate } from 'lib/router/navigation';
import { DataSource } from 'types/common/queryBuilder';
import { buildExplorerNavigationUrl } from 'utils/buildExplorerNavigationUrl';
type SavedViewSourceHint = SavedViewSourcePage;
const DEFAULT_PROBE_SOURCES: SavedViewSourceHint[] = [
DataSource.LOGS,
DataSource.TRACES,
DataSource.METRICS,
const DEFAULT_PROBE_SOURCES: SavedviewtypesSourceDTO[] = [
SavedviewtypesSourceDTO.logs,
SavedviewtypesSourceDTO.traces,
SavedviewtypesSourceDTO.metrics,
];
export async function findSavedViewInLists(
viewKey: string,
sourceHint?: SavedViewSourceHint | null,
sourceHint?: SavedviewtypesSourceDTO | null,
): Promise<SavedviewtypesSavedViewDTO | null> {
const sources = sourceHint ? [sourceHint] : DEFAULT_PROBE_SOURCES;
for (const source of sources) {
try {
const response = await listSavedViews({
source: toSavedViewSource(source),
});
const response = await listSavedViews({ source });
const match = findSavedView(response.data, viewKey);
if (match) {
return match;
@@ -47,7 +44,7 @@ export async function findSavedViewInLists(
async function loadSavedView(
viewKey: string,
sourceHint?: SavedViewSourceHint | null,
sourceHint?: SavedviewtypesSourceDTO | null,
): Promise<SavedviewtypesSavedViewDTO> {
try {
const response = await getSavedView({ id: viewKey });
@@ -92,7 +89,7 @@ export function openSavedView(view: SavedviewtypesSavedViewDTO): void {
export async function openSavedViewByKey(
viewKey: string,
sourceHint: SavedViewSourceHint | null | undefined,
sourceHint: SavedviewtypesSourceDTO | null | undefined,
): Promise<void> {
const view = await loadSavedView(viewKey, sourceHint);
openSavedView(view);

View File

@@ -3,7 +3,7 @@ import {
ApplyFilterSignalDTO,
SavedViewEntityDTO,
} from 'api/ai-assistant/sigNozAIAssistantAPI.schemas';
import { DataSource } from 'types/common/queryBuilder';
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
import { ResourceType } from './resourceRoute';
@@ -119,15 +119,15 @@ export function resolveActionEntity(
}
function normalizeToSavedViewEntity(value: string): SavedViewEntityDTO | null {
const source = entityToDataSource(value);
const source = entityToSavedViewSource(value);
switch (source) {
case DataSource.LOGS:
case SavedviewtypesSourceDTO.logs:
return SavedViewEntityDTO.logs;
case DataSource.TRACES:
case SavedviewtypesSourceDTO.traces:
return SavedViewEntityDTO.traces;
case DataSource.METRICS:
case SavedviewtypesSourceDTO.metrics:
return SavedViewEntityDTO.metrics;
case 'meter':
case SavedviewtypesSourceDTO.meter:
return SavedViewEntityDTO.meter;
default:
return null;
@@ -135,26 +135,26 @@ function normalizeToSavedViewEntity(value: string): SavedViewEntityDTO | null {
}
/**
* Maps an action `entity` to an explorer `DataSource` for saved-view lookups.
* Maps an action `entity` to the saved views api source for saved-view lookups.
* Accepts both short (`logs`) and taxonomy (`logs_explorer`) values.
*/
export function entityToDataSource(
export function entityToSavedViewSource(
entity: SavedViewEntityDTO | string,
): DataSource | 'meter' | null {
): SavedviewtypesSourceDTO | null {
const normalized = entity.trim().toLowerCase().replace(/-/g, '_');
switch (normalized) {
case SavedViewEntityDTO.logs:
case ResourceType.logs_explorer:
return DataSource.LOGS;
return SavedviewtypesSourceDTO.logs;
case SavedViewEntityDTO.traces:
case ResourceType.traces_explorer:
return DataSource.TRACES;
return SavedviewtypesSourceDTO.traces;
case SavedViewEntityDTO.metrics:
case ResourceType.metrics_explorer:
return DataSource.METRICS;
return SavedviewtypesSourceDTO.metrics;
case SavedViewEntityDTO.meter:
return 'meter';
return SavedviewtypesSourceDTO.meter;
default:
return null;
}
@@ -166,10 +166,10 @@ export function entityToDataSource(
*/
export function resolveSavedViewSourceHint(
action: MessageActionDTO,
): DataSource | 'meter' | null {
): SavedviewtypesSourceDTO | null {
const entity = resolveActionEntity(action);
if (entity) {
const fromEntity = entityToDataSource(entity);
const fromEntity = entityToSavedViewSource(entity);
if (fromEntity) {
return fromEntity;
}
@@ -178,11 +178,11 @@ export function resolveSavedViewSourceHint(
if (action.signal) {
switch (action.signal) {
case ApplyFilterSignalDTO.logs:
return DataSource.LOGS;
return SavedviewtypesSourceDTO.logs;
case ApplyFilterSignalDTO.traces:
return DataSource.TRACES;
return SavedviewtypesSourceDTO.traces;
case ApplyFilterSignalDTO.metrics:
return DataSource.METRICS;
return SavedviewtypesSourceDTO.metrics;
default: {
const _exhaustive: never = action.signal;
return _exhaustive;

View File

@@ -60,12 +60,14 @@ export function useActiveSavedView(
}
return hasUnsavedViewChanges({
view,
source,
stagedQuery,
panelType: panelType ?? PANEL_TYPES.LIST,
options: hasOptions ? options : undefined,
});
}, [
view,
source,
isError,
isStagedQueryCurrent,
stagedQuery,

View File

@@ -8,12 +8,9 @@ import {
import { PANEL_TYPES } from 'constants/queryBuilder';
import { OptionsQuery } from 'container/OptionsMenu/types';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { SavedViewCreatedFrom } from './events';
export type SavedViewSourcePage = DataSource | 'meter';
export type SavedViewOptions = Pick<
OptionsQuery,
'selectColumns' | 'format' | 'maxLines' | 'fontSize'
@@ -29,6 +26,7 @@ export interface ToSavedViewSpecArgs {
export interface HasUnsavedViewChangesArgs {
view: SavedviewtypesSavedViewDTO;
source: SavedviewtypesSourceDTO;
stagedQuery: Query | null;
panelType: PANEL_TYPES;
options?: SavedViewOptions;

View File

@@ -1,6 +1,7 @@
import {
SavedviewtypesSavedViewDTO,
SavedviewtypesSavedViewSpecDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import {
initialQueriesMap,
@@ -118,6 +119,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view,
source: SavedviewtypesSourceDTO.traces,
stagedQuery: staged,
panelType: PANEL_TYPES.LIST,
options: { ...options, selectColumns: [columns[0]] },
@@ -137,6 +139,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view: bare,
source: SavedviewtypesSourceDTO.traces,
stagedQuery: stagedFrom(bare, DataSource.TRACES),
panelType: PANEL_TYPES.LIST,
options: { ...options, selectColumns: defaultTraceSelectedColumns },
@@ -161,6 +164,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view: logsView,
source: SavedviewtypesSourceDTO.logs,
stagedQuery: logsStaged,
panelType: PANEL_TYPES.LIST,
options: { selectColumns: shownColumns, ...formatting },
@@ -172,6 +176,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view: logsView,
source: SavedviewtypesSourceDTO.logs,
stagedQuery: logsStaged,
panelType: PANEL_TYPES.LIST,
options: { selectColumns: shownColumns, ...formatting, maxLines: 5 },
@@ -191,6 +196,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view: bare,
source: SavedviewtypesSourceDTO.logs,
stagedQuery: stagedFrom(bare, DataSource.LOGS),
panelType: PANEL_TYPES.LIST,
options: {
@@ -234,6 +240,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view: olderView,
source: SavedviewtypesSourceDTO.metrics,
stagedQuery: stagedFrom(olderView, DataSource.METRICS),
panelType: PANEL_TYPES.TIME_SERIES,
}),
@@ -281,6 +288,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view: storedView,
source: SavedviewtypesSourceDTO.metrics,
stagedQuery: stagedWithTag,
panelType: PANEL_TYPES.TIME_SERIES,
}),
@@ -320,6 +328,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view: noOrder,
source: SavedviewtypesSourceDTO.logs,
stagedQuery: stagedFrom(noOrder, DataSource.LOGS),
panelType: PANEL_TYPES.LIST,
}),
@@ -335,6 +344,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view: noOrder,
source: SavedviewtypesSourceDTO.logs,
stagedQuery: sorted,
panelType: PANEL_TYPES.LIST,
}),
@@ -344,6 +354,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view: saved,
source: SavedviewtypesSourceDTO.logs,
stagedQuery: stagedFrom(saved, DataSource.LOGS),
panelType: PANEL_TYPES.LIST,
}),
@@ -360,6 +371,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view: legacy,
source: SavedviewtypesSourceDTO.logs,
stagedQuery: stagedFrom(legacy, DataSource.LOGS),
panelType: PANEL_TYPES.LIST,
}),
@@ -371,6 +383,7 @@ describe('hasUnsavedViewChanges', () => {
expect(
hasUnsavedViewChanges({
view,
source: SavedviewtypesSourceDTO.traces,
stagedQuery: staged,
panelType: PANEL_TYPES.LIST,
options: { ...options, ...formatting, maxLines: 5 },

View File

@@ -1,17 +0,0 @@
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
import { DataSource } from 'types/common/queryBuilder';
import { toSavedViewSource } from '../toSavedViewSource';
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);
});
});

View File

@@ -1,13 +1,11 @@
import { TelemetrytypesFieldContextDTO } from 'api/generated/services/sigNoz.schemas';
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
import isEqual from 'lodash-es/isEqual';
import { DataSource } from 'types/common/queryBuilder';
import { HasUnsavedViewChangesArgs } from '../types';
import { getSavedViewQuery } from './getSavedViewQuery';
import { toSavedViewPanelType } from './toSavedViewSpec';
import { getViewColumnsAndFormatting } from './getViewColumnsAndFormatting';
import { toSavedViewSource } from './toSavedViewSource';
// Legacy context names the server stores under their v5 name.
const FIELD_CONTEXT_ALIASES: Record<string, TelemetrytypesFieldContextDTO> = {
@@ -48,6 +46,7 @@ function withoutEmptyValues(value: unknown): unknown {
// a graph in older views) drop out; the builder's shape gets different defaults.
export function hasUnsavedViewChanges({
view,
source,
stagedQuery,
panelType,
options,
@@ -77,18 +76,14 @@ export function hasUnsavedViewChanges({
return false;
}
const dataSource = stagedQuery.builder.queryData[0]?.dataSource;
const { columns, formatting } = getViewColumnsAndFormatting(
view.spec,
toSavedViewSource(dataSource),
);
const { columns, formatting } = getViewColumnsAndFormatting(view.spec, source);
const toNames = (fields: { name?: string; key?: string }[]): unknown[] =>
fields.map((field) => field.name ?? field.key);
if (!isEqual(toNames(columns), toNames(options.selectColumns ?? []))) {
return true;
}
if (dataSource !== DataSource.LOGS || !formatting) {
if (!formatting) {
return false;
}
return (

View File

@@ -1,21 +0,0 @@
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
import { DataSource } from 'types/common/queryBuilder';
import { SavedViewSourcePage } from '../types';
// The views API files each view under a source page, but callers only know the
// signal they query: a builder query's dataSource, or the AI assistant's hint.
// TODO: remove in a follow-up once callers pass the source page directly.
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];
}