diff --git a/connectors/grafana-plugin/src/datasource.test.ts b/connectors/grafana-plugin/src/datasource.test.ts index 61ee77d..6d32cfa 100644 --- a/connectors/grafana-plugin/src/datasource.test.ts +++ b/connectors/grafana-plugin/src/datasource.test.ts @@ -237,4 +237,43 @@ describe('DataSource', () => { expect(result.groupBy?.step).toBe('1h'); }); }); + + describe('metricFindQuery', () => { + it('extracts and expands a standard variable query object', async () => { + const scopedVars: ScopedVars = { cluster: { text: 'cluster-a', value: 'cluster-a' } }; + const sql = + "table:metrics_validation:SELECT DISTINCT instance FROM sys_cpu_cores WHERE cluster IN (${cluster:sqlstring})"; + const expandedSql = + "table:metrics_validation:SELECT DISTINCT instance FROM sys_cpu_cores WHERE cluster IN ('cluster-a')"; + const getVariablesResult = jest.spyOn(ds, 'getVariablesResult').mockResolvedValue([]); + mockReplace.mockReturnValue(expandedSql); + + await ds.metricFindQuery({ query: sql, refId: 'StandardVariableQuery' }, { scopedVars }); + + expect(mockReplace).toHaveBeenCalledWith(sql, scopedVars); + expect(getVariablesResult).toHaveBeenCalledWith(expandedSql); + }); + + it('keeps legacy string variable queries compatible', async () => { + const sql = 'show child paths root.sg'; + const getVariablesResult = jest.spyOn(ds, 'getVariablesResult').mockResolvedValue([]); + mockReplace.mockReturnValue(sql); + + await ds.metricFindQuery(sql); + + expect(mockReplace).toHaveBeenCalledWith(sql, undefined); + expect(getVariablesResult).toHaveBeenCalledWith(sql); + }); + + it('rejects variable query objects without a string query field', async () => { + const getVariablesResult = jest.spyOn(ds, 'getVariablesResult').mockResolvedValue([]); + + await expect(ds.metricFindQuery({ refId: 'StandardVariableQuery' })).rejects.toThrow( + 'Variable query must be a string or an object with a string query field' + ); + + expect(mockReplace).not.toHaveBeenCalled(); + expect(getVariablesResult).not.toHaveBeenCalled(); + }); + }); }); diff --git a/connectors/grafana-plugin/src/datasource.ts b/connectors/grafana-plugin/src/datasource.ts index 319f804..0e27c05 100644 --- a/connectors/grafana-plugin/src/datasource.ts +++ b/connectors/grafana-plugin/src/datasource.ts @@ -20,6 +20,21 @@ import { IoTDBOptions, IoTDBQuery } from './types'; import { toMetricFindValue } from './functions'; import { DataSourceWithBackend, getTemplateSrv } from '@grafana/runtime'; +function variableQueryText(query: unknown): string | undefined { + if (typeof query === 'string') { + return query; + } + + if (query && typeof query === 'object' && 'query' in query) { + const queryText = (query as { query?: unknown }).query; + if (typeof queryText === 'string') { + return queryText; + } + } + + return undefined; +} + export class DataSource extends DataSourceWithBackend { username: string; url: string; @@ -138,8 +153,13 @@ export class DataSource extends DataSourceWithBackend } metricFindQuery(query: any, options?: any): Promise { - query = getTemplateSrv().replace(query, options.scopedVars); - return this.getVariablesResult(query); + const queryText = variableQueryText(query); + if (queryText === undefined) { + return Promise.reject(new Error('Variable query must be a string or an object with a string query field')); + } + + const expandedQuery = getTemplateSrv().replace(queryText, options?.scopedVars); + return this.getVariablesResult(expandedQuery); } nodeQuery(query: any, options?: any): Promise {