Skip to content

Commit 9d504cd

Browse files
committed
[Explore] add dataset signal type to avoid prometheus in logs
Signed-off-by: Joshua Li <[email protected]>
1 parent 4fe1241 commit 9d504cd

File tree

3 files changed

+125
-11
lines changed

3 files changed

+125
-11
lines changed

src/plugins/data/common/datasets/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ export interface Dataset extends BaseDataset {
274274
timeFieldName?: string;
275275
/** Optional language to default to from the language selector */
276276
language?: string;
277+
/** Optional signal type for the dataset (e.g., 'logs', 'metrics', 'traces') */
278+
signalType?: string;
277279
/** Optional reference to the source dataset. Example usage is for indexed views to store the
278280
* reference to the table dataset
279281
*/

src/plugins/explore/public/application/utils/state_management/utils/redux_persistence.test.ts

Lines changed: 89 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,8 +669,8 @@ describe('redux_persistence', () => {
669669
timeFieldName: undefined,
670670
dataSource: undefined,
671671
});
672-
// Should preserve other URL query state
673-
expect(result.query.query).toBe(mockQueryState.query); // Preserves original query
672+
// When dataset changes, query is cleared since it may be incompatible
673+
expect(result.query.query).toBe('');
674674
});
675675

676676
it('should reject traces dataset for logs flavor and fetch compatible one', async () => {
@@ -767,4 +767,91 @@ describe('redux_persistence', () => {
767767
});
768768
});
769769
});
770+
771+
describe('Metrics flavor SignalType handling', () => {
772+
it('should accept Metrics datasets for Metrics flavor', async () => {
773+
const metricsServices = {
774+
...mockServices,
775+
core: { application: { currentAppId$: of('explore/metrics') } },
776+
data: {
777+
...mockServices.data,
778+
dataViews: {
779+
get: jest.fn(() => Promise.resolve({ signalType: CORE_SIGNAL_TYPES.METRICS })),
780+
},
781+
},
782+
} as any;
783+
784+
(metricsServices.data.query.queryString.getDatasetService as jest.Mock).mockReturnValue({
785+
getType: jest.fn(() => ({
786+
fetch: jest.fn(() => Promise.resolve({ children: [{ id: 'prometheus-test' }] })),
787+
toDataset: jest.fn(() => ({
788+
id: 'prometheus-test',
789+
title: 'Prometheus',
790+
type: 'PROMETHEUS',
791+
language: 'PROMQL',
792+
signalType: CORE_SIGNAL_TYPES.METRICS,
793+
})),
794+
})),
795+
});
796+
797+
const result = await getPreloadedState(metricsServices);
798+
expect(result.query.dataset).toBeDefined();
799+
expect(result.query.dataset?.id).toBe('prometheus-test');
800+
});
801+
802+
it('should reject non-Metrics datasets for Metrics flavor', async () => {
803+
const metricsServices = {
804+
...mockServices,
805+
core: { application: { currentAppId$: of('explore/metrics') } },
806+
data: {
807+
...mockServices.data,
808+
dataViews: {
809+
get: jest.fn(() => Promise.resolve({ signalType: CORE_SIGNAL_TYPES.LOGS })),
810+
},
811+
},
812+
} as any;
813+
814+
(metricsServices.data.query.queryString.getDatasetService as jest.Mock).mockReturnValue({
815+
getType: jest.fn(() => ({
816+
fetch: jest.fn(() => Promise.resolve({ children: [{ id: 'logs-test' }] })),
817+
toDataset: jest.fn(() => ({
818+
id: 'logs-test',
819+
title: 'Logs',
820+
type: 'INDEX_PATTERN',
821+
})),
822+
})),
823+
});
824+
825+
const result = await getPreloadedState(metricsServices);
826+
expect(result.query.dataset).toBeUndefined();
827+
});
828+
829+
it('should reject Metrics datasets for Logs flavor', async () => {
830+
const logsServices = {
831+
...mockServices,
832+
core: { application: { currentAppId$: of('explore/logs') } },
833+
data: {
834+
...mockServices.data,
835+
dataViews: {
836+
get: jest.fn(() => Promise.resolve({ signalType: CORE_SIGNAL_TYPES.METRICS })),
837+
},
838+
},
839+
} as any;
840+
841+
(logsServices.data.query.queryString.getDatasetService as jest.Mock).mockReturnValue({
842+
getType: jest.fn(() => ({
843+
fetch: jest.fn(() => Promise.resolve({ children: [{ id: 'prometheus-test' }] })),
844+
toDataset: jest.fn(() => ({
845+
id: 'prometheus-test',
846+
title: 'Prometheus',
847+
type: 'PROMETHEUS',
848+
signalType: CORE_SIGNAL_TYPES.METRICS,
849+
})),
850+
})),
851+
});
852+
853+
const result = await getPreloadedState(logsServices);
854+
expect(result.query.dataset).toBeUndefined();
855+
});
856+
});
770857
});

src/plugins/explore/public/application/utils/state_management/utils/redux_persistence.ts

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,25 @@ export const loadReduxState = async (services: ExploreServices): Promise<RootSta
7979
type: queryState.dataset.type,
8080
timeFieldName: queryState.dataset.timeFieldName,
8181
dataSource: queryState.dataset.dataSource,
82+
signalType: queryState.dataset.signalType,
8283
};
8384
}
8485

8586
// Always call getPreloadedQueryState to ensure SignalType validation runs
8687
const resolvedQueryState = await getPreloadedQueryState(services, urlDataset);
8788

8889
// Use the resolved dataset but preserve other query state from URL if available
90+
// When the dataset changes (due to signal type filtering), also update the language
91+
const datasetChanged =
92+
queryState?.dataset?.id !== resolvedQueryState.dataset?.id ||
93+
queryState?.dataset?.type !== resolvedQueryState.dataset?.type;
94+
8995
const finalQueryState: QueryState = queryState
9096
? {
9197
...queryState,
9298
dataset: resolvedQueryState.dataset,
99+
language: datasetChanged ? resolvedQueryState.language : queryState.language,
100+
query: datasetChanged ? '' : queryState.query,
93101
}
94102
: resolvedQueryState;
95103
services.data.query.queryString.setQuery(finalQueryState);
@@ -184,15 +192,21 @@ const fetchFirstAvailableDataset = async (
184192
dataset.type !== DEFAULT_DATA.SET_TYPES.INDEX_PATTERN
185193
);
186194

195+
// Get effective signal type from dataView or dataset (for Prometheus which sets signalType directly)
196+
const effectiveSignalType = dataView?.signalType || dataset.signalType;
197+
187198
// If requiredSignalType is specified, dataset must match it
188199
if (requiredSignalType) {
189-
if (dataView?.signalType === requiredSignalType) {
200+
if (effectiveSignalType === requiredSignalType) {
190201
return dataset;
191202
}
192203
} else {
193-
// If requiredSignalType is not specified (i.e., not Traces),
194-
// dataset should not have signalType equal to Traces
195-
if (dataView?.signalType !== CORE_SIGNAL_TYPES.TRACES) {
204+
// If requiredSignalType is not specified (i.e., Logs flavor),
205+
// dataset should not have signalType equal to Traces or Metrics
206+
if (
207+
effectiveSignalType !== CORE_SIGNAL_TYPES.TRACES &&
208+
effectiveSignalType !== CORE_SIGNAL_TYPES.METRICS
209+
) {
196210
return dataset;
197211
}
198212
}
@@ -220,7 +234,11 @@ const resolveDataset = async (
220234
const currentAppId = await getCurrentAppId(services);
221235
const flavorFromAppId = getFlavorFromAppId(currentAppId);
222236
const requiredSignalType =
223-
flavorFromAppId === ExploreFlavor.Traces ? CORE_SIGNAL_TYPES.TRACES : undefined;
237+
flavorFromAppId === ExploreFlavor.Traces
238+
? CORE_SIGNAL_TYPES.TRACES
239+
: flavorFromAppId === ExploreFlavor.Metrics
240+
? CORE_SIGNAL_TYPES.METRICS
241+
: undefined;
224242

225243
// Get existing dataset from QueryStringManager or use preferred dataset
226244
const queryStringQuery = services.data?.query?.queryString?.getQuery();
@@ -235,15 +253,21 @@ const resolveDataset = async (
235253
existingDataset.type !== DEFAULT_DATA.SET_TYPES.INDEX_PATTERN
236254
);
237255

256+
// Get effective signal type from dataView or preferredDataset (for Prometheus which sets signalType directly)
257+
const effectiveSignalType = dataView?.signalType || preferredDataset?.signalType;
258+
238259
// If requiredSignalType is specified, dataset must match it
239260
if (requiredSignalType) {
240-
if (dataView?.signalType === requiredSignalType) {
261+
if (effectiveSignalType === requiredSignalType) {
241262
return existingDataset;
242263
}
243264
} else {
244-
// If requiredSignalType is not specified (i.e., not Traces),
245-
// dataset should not have signalType equal to Traces
246-
if (dataView?.signalType !== CORE_SIGNAL_TYPES.TRACES) {
265+
// If requiredSignalType is not specified (i.e., Logs flavor),
266+
// dataset should not have signalType equal to Traces or Metrics
267+
if (
268+
effectiveSignalType !== CORE_SIGNAL_TYPES.TRACES &&
269+
effectiveSignalType !== CORE_SIGNAL_TYPES.METRICS
270+
) {
247271
return existingDataset;
248272
}
249273
}
@@ -280,6 +304,7 @@ const getPreloadedQueryState = async (
280304
type: selectedDataset.type,
281305
timeFieldName: selectedDataset.timeFieldName,
282306
dataSource: selectedDataset.dataSource,
307+
signalType: selectedDataset.signalType,
283308
};
284309
}
285310
}

0 commit comments

Comments
 (0)