From eb85cc4ef876d9b9e05e751c939add897d2989b4 Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Sun, 25 May 2025 01:44:41 +0200 Subject: [PATCH] update metadata fetch --- .../src/language_provider.test.ts | 130 ++++++++++++++++++ .../src/language_provider.ts | 40 +++++- 2 files changed, 163 insertions(+), 7 deletions(-) diff --git a/packages/grafana-prometheus/src/language_provider.test.ts b/packages/grafana-prometheus/src/language_provider.test.ts index b2f15e91db9..cdbb774ad63 100644 --- a/packages/grafana-prometheus/src/language_provider.test.ts +++ b/packages/grafana-prometheus/src/language_provider.test.ts @@ -725,6 +725,136 @@ describe('Prometheus Language Provider', () => { ); }); }); + + describe('getMetricsMetadata', () => { + it('should fetch metadata if not already loaded', async () => { + const mockMetadata = { + metric1: [ + { + type: 'counter', + help: 'help text 1', + }, + ], + metric2: [ + { + type: 'gauge', + help: 'help text 2', + }, + ], + }; + + const expectedMetadata = { + metric1: { + type: 'counter', + help: 'help text 1', + }, + metric2: { + type: 'gauge', + help: 'help text 2', + }, + }; + + const languageProvider = new LanguageProvider({ + ...defaultDatasource, + metadataRequest: () => ({ data: { data: mockMetadata } }), + } as unknown as PrometheusDatasource); + + const requestSpy = jest.spyOn(languageProvider, 'request'); + + const result = await languageProvider.getMetricsMetadata(); + + expect(requestSpy).toHaveBeenCalledWith( + '/api/v1/metadata', + {}, + { + showErrorAlert: false, + headers: { 'X-Grafana-Cache': 'private, max-age=86400' }, + } + ); + expect(result).toEqual(expect.objectContaining(expectedMetadata)); + }); + + it('should return cached metadata without fetching if already loaded', async () => { + const mockMetadata = { + metric1: [ + { + type: 'counter', + help: 'help text 1', + }, + ], + }; + + const expectedMetadata = { + metric1: { + type: 'counter', + help: 'help text 1', + }, + }; + + const languageProvider = new LanguageProvider({ + ...defaultDatasource, + metadataRequest: () => ({ data: { data: mockMetadata } }), + } as unknown as PrometheusDatasource); + + // First call to load metadata + const firstResult = await languageProvider.getMetricsMetadata(); + expect(firstResult).toEqual(expect.objectContaining(expectedMetadata)); + + const requestSpy = jest.spyOn(languageProvider, 'request'); + + // Second call should use cached data + const result = await languageProvider.getMetricsMetadata(); + + expect(requestSpy).not.toHaveBeenCalled(); + expect(result).toEqual(expect.objectContaining(expectedMetadata)); + }); + + it('should handle empty metadata response', async () => { + const languageProvider = new LanguageProvider({ + ...defaultDatasource, + metadataRequest: () => ({ data: { data: {} } }), + } as unknown as PrometheusDatasource); + + const result = await languageProvider.getMetricsMetadata(); + + // Even with empty input, fixSummariesMetadata might add some default entries + expect(Object.keys(result).length).toBeGreaterThanOrEqual(0); + }); + + it('should handle error during metadata fetch', async () => { + jest.spyOn(console, 'error').mockImplementation(); + const languageProvider = new LanguageProvider({ + ...defaultDatasource, + metadataRequest: () => Promise.reject(new Error('Metadata fetch failed')), + } as unknown as PrometheusDatasource); + + const result = await languageProvider.getMetricsMetadata(); + + expect(result).toEqual(undefined); + }); + + it('should use configured cache duration from datasource', async () => { + const mockDays = 7; + const languageProvider = new LanguageProvider({ + ...defaultDatasource, + getDaysToCacheMetadata: () => mockDays, + metadataRequest: () => ({ data: { data: {} } }), + } as unknown as PrometheusDatasource); + + const requestSpy = jest.spyOn(languageProvider, 'request'); + + await languageProvider.getMetricsMetadata(); + + expect(requestSpy).toHaveBeenCalledWith( + '/api/v1/metadata', + {}, + { + showErrorAlert: false, + headers: { 'X-Grafana-Cache': `private, max-age=${mockDays * 86400}` }, + } + ); + }); + }); }); describe('removeQuotesIfExist', () => { diff --git a/packages/grafana-prometheus/src/language_provider.ts b/packages/grafana-prometheus/src/language_provider.ts index c6405015232..f32b30534a9 100644 --- a/packages/grafana-prometheus/src/language_provider.ts +++ b/packages/grafana-prometheus/src/language_provider.ts @@ -84,13 +84,19 @@ const getDefaultCacheHeaders = (cacheLevel: PrometheusCacheLevel) => { }; export default class PromQlLanguageProvider extends LanguageProvider { + declare startTask: Promise; + declare labelFetchTs: number; + + private _metricsMetadata?: PromMetricsMetadata; + histogramMetrics: string[]; metrics: string[]; + /** + * @deprecated Use getMetricsMetadata() method instead + */ metricsMetadata?: PromMetricsMetadata; - declare startTask: Promise; datasource: PrometheusDatasource; labelKeys: string[] = []; - declare labelFetchTs: number; constructor(datasource: PrometheusDatasource, initialValues?: Partial) { super(); @@ -142,7 +148,7 @@ export default class PromQlLanguageProvider extends LanguageProvider { * * @returns {Promise} Promise that resolves when metadata has been fetched */ - fetchMetadata = async () => { + private _fetchMetadata = async () => { const secondsInDay = 86400; const headers = buildCacheHeaders(this.datasource.getDaysToCacheMetadata() * secondsInDay); const metadata = await this.request( @@ -153,8 +159,7 @@ export default class PromQlLanguageProvider extends LanguageProvider { ...headers, } ); - this.metricsMetadata = fixSummariesMetadata(metadata); - return this.metricsMetadata; + return fixSummariesMetadata(metadata); }; // =================================== @@ -256,11 +261,32 @@ export default class PromQlLanguageProvider extends LanguageProvider { // ====================================================================== + public getMetricsMetadata = async (): Promise => { + if (!this._metricsMetadata) { + this._metricsMetadata = await this._fetchMetadata(); + } + + return this._metricsMetadata; + }; + + // ====================================================================== + /** - * @deprecated Use fetchMetadata instead + * @deprecated Use _fetchMetadata instead. If you want metadata itself use getMetricsMetadata() method. */ async loadMetricsMetadata() { - this.fetchMetadata(); + const secondsInDay = 86400; + const headers = buildCacheHeaders(this.datasource.getDaysToCacheMetadata() * secondsInDay); + this.metricsMetadata = fixSummariesMetadata( + await this.request( + API_V1.METADATA, + {}, + { + showErrorAlert: false, + ...headers, + } + ) + ); } getLabelKeys(): string[] {