3
0
mirror of https://github.com/grafana/grafana.git synced 2025-02-25 18:55:37 -06:00

CloudWatch: Remove dependency on local TemplateSrv ()

This commit is contained in:
Isabella Siu 2024-01-09 16:52:37 -05:00 committed by GitHub
parent f3bb16c598
commit 4fa6bad7c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 154 additions and 101 deletions

View File

@ -1,7 +1,6 @@
import { of } from 'rxjs';
import { CustomVariableModel, DataQueryRequest } from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { CloudWatchAnnotationQueryRunner } from '../query-runner/CloudWatchAnnotationQueryRunner';
import { CloudWatchQuery } from '../types';
@ -10,10 +9,7 @@ import { CloudWatchSettings, setupMockedTemplateService } from './CloudWatchData
import { TimeRangeMock } from './timeRange';
export function setupMockedAnnotationQueryRunner({ variables }: { variables?: CustomVariableModel[] }) {
let templateService = new TemplateSrv();
if (variables) {
templateService = setupMockedTemplateService(variables);
}
const templateService = setupMockedTemplateService(variables);
const queryMock = jest.fn().mockReturnValue(of({}));
const runner = new CloudWatchAnnotationQueryRunner(CloudWatchSettings, templateService);

View File

@ -6,22 +6,70 @@ import {
DataSourcePluginMeta,
PluginMetaInfo,
PluginType,
ScopedVars,
VariableHide,
} from '@grafana/data';
import { getBackendSrv, setBackendSrv, DataSourceWithBackend } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { getBackendSrv, setBackendSrv, DataSourceWithBackend, TemplateSrv } from '@grafana/runtime';
import { initialCustomVariableModelState } from '../__mocks__/variables';
import { CloudWatchDatasource } from '../datasource';
import { CloudWatchJsonData } from '../types';
import { getVariableName } from '../utils/templateVariableUtils';
const queryMock = jest.fn().mockReturnValue(of({ data: [] }));
jest.spyOn(DataSourceWithBackend.prototype, 'query').mockImplementation((args) => queryMock(args));
const separatorMap = new Map<string, string>([
['pipe', '|'],
['raw', ','],
['text', ' + '],
]);
export function setupMockedTemplateService(variables: CustomVariableModel[]) {
const templateService = new TemplateSrv();
templateService.init(variables);
templateService.getVariables = jest.fn().mockReturnValue(variables);
export function setupMockedTemplateService(variables?: CustomVariableModel[]): TemplateSrv {
const templateService = {
replace: jest.fn().mockImplementation((input: string, scopedVars?: ScopedVars, format?: string) => {
if (!input) {
return '';
}
let output = input;
['datasource', 'dimension'].forEach((name) => {
const variable = scopedVars ? scopedVars[name] : undefined;
if (variable) {
output = output.replace('$' + name, variable.value);
}
});
if (variables) {
variables.forEach((variable) => {
let repVal = '';
let value = format === 'text' ? variable.current.text : variable.current.value;
let separator = separatorMap.get(format ?? 'raw');
if (Array.isArray(value)) {
repVal = value.join(separator);
} else {
repVal = value;
}
output = output.replace('$' + variable.name, repVal);
output = output.replace('[[' + variable.name + ']]', repVal);
});
}
return output;
}),
getVariables: jest.fn().mockReturnValue(variables ?? []),
containsTemplate: jest.fn().mockImplementation((name) => {
const varName = getVariableName(name);
if (!varName || !variables) {
return false;
}
let found = false;
variables.forEach((variable) => {
if (varName === variable.name) {
found = true;
}
});
return found;
}),
updateTimeRange: jest.fn(),
};
return templateService;
}
@ -62,22 +110,14 @@ export const CloudWatchSettings: DataSourceInstanceSettings<CloudWatchJsonData>
export function setupMockedDataSource({
variables,
mockGetVariableName = true,
getMock = jest.fn(),
customInstanceSettings = CloudWatchSettings,
}: {
getMock?: jest.Func;
variables?: CustomVariableModel[];
mockGetVariableName?: boolean;
customInstanceSettings?: DataSourceInstanceSettings<CloudWatchJsonData>;
} = {}) {
let templateService = new TemplateSrv();
if (variables) {
templateService = setupMockedTemplateService(variables);
if (mockGetVariableName) {
templateService.getVariableName = (name: string) => name.replace('$', '');
}
}
const templateService = setupMockedTemplateService(variables);
const datasource = new CloudWatchDatasource(customInstanceSettings, templateService);
datasource.getVariables = () => ['test'];

View File

@ -2,7 +2,6 @@ import { of } from 'rxjs';
import { CustomVariableModel, DataFrame, DataSourceInstanceSettings } from '@grafana/data';
import { BackendDataSourceResponse, toDataQueryResponse } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { CloudWatchLogsQueryRunner } from '../query-runner/CloudWatchLogsQueryRunner';
import { CloudWatchJsonData, CloudWatchLogsQueryStatus, CloudWatchLogsRequest } from '../types';
@ -22,13 +21,7 @@ export function setupMockedLogsQueryRunner({
mockGetVariableName?: boolean;
settings?: DataSourceInstanceSettings<CloudWatchJsonData>;
} = {}) {
let templateService = new TemplateSrv();
if (variables) {
templateService = setupMockedTemplateService(variables);
if (mockGetVariableName) {
templateService.getVariableName = (name: string) => name;
}
}
let templateService = setupMockedTemplateService(variables);
const queryMock = jest.fn().mockReturnValue(of(toDataQueryResponse({ data })));
const runner = new CloudWatchLogsQueryRunner(settings, templateService);

View File

@ -2,7 +2,6 @@ import { of, throwError } from 'rxjs';
import { CustomVariableModel, DataQueryError, DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
import { BackendDataSourceResponse, toDataQueryResponse } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { CloudWatchMetricsQueryRunner } from '../query-runner/CloudWatchMetricsQueryRunner';
import { CloudWatchJsonData, CloudWatchQuery } from '../types';
@ -15,23 +14,15 @@ export function setupMockedMetricsQueryRunner({
results: {},
},
variables,
mockGetVariableName = true,
errorResponse,
instanceSettings = CloudWatchSettings,
}: {
data?: BackendDataSourceResponse;
variables?: CustomVariableModel[];
mockGetVariableName?: boolean;
errorResponse?: DataQueryError;
instanceSettings?: DataSourceInstanceSettings<CloudWatchJsonData>;
} = {}) {
let templateService = new TemplateSrv();
if (variables) {
templateService = setupMockedTemplateService(variables);
if (mockGetVariableName) {
templateService.getVariableName = (name: string) => name.replace('$', '');
}
}
const templateService = setupMockedTemplateService(variables);
const queryMock = errorResponse
? jest.fn().mockImplementation(() => throwError(errorResponse))

View File

@ -1,6 +1,5 @@
import { CustomVariableModel } from '@grafana/data';
import { getBackendSrv, setBackendSrv } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { ResourcesAPI } from '../resources/ResourcesAPI';
@ -16,7 +15,7 @@ export function setupMockedResourcesAPI({
variables?: CustomVariableModel[];
mockGetVariableName?: boolean;
} = {}) {
let templateService = variables ? setupMockedTemplateService(variables) : new TemplateSrv();
let templateService = setupMockedTemplateService(variables);
const api = new ResourcesAPI(CloudWatchSettings, templateService);
let resourceRequestMock = getMock ? getMock : jest.fn().mockReturnValue(response);

View File

@ -4,8 +4,8 @@ import selectEvent from 'react-select-event';
import { CustomVariableModel, DataSourceInstanceSettings } from '@grafana/data';
import * as ui from '@grafana/ui';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { setupMockedTemplateService } from '../../../__mocks__/CloudWatchDataSource';
import { initialVariableModelState } from '../../../__mocks__/variables';
import { CloudWatchDatasource } from '../../../datasource';
import { CloudWatchJsonData, MetricEditorMode, MetricQueryType } from '../../../types';
@ -24,7 +24,6 @@ const setup = () => {
jsonData: { defaultRegion: 'us-east-1' },
} as DataSourceInstanceSettings<CloudWatchJsonData>;
const templateSrv = new TemplateSrv();
const variable: CustomVariableModel = {
...initialVariableModelState,
id: 'var3',
@ -41,7 +40,7 @@ const setup = () => {
query: '',
type: 'custom',
};
templateSrv.init([variable]);
const templateSrv = setupMockedTemplateService([variable]);
const datasource = new CloudWatchDatasource(instanceSettings, templateSrv);
datasource.metricFindQuery = async () => [{ value: 'test', label: 'test', text: 'test' }];

View File

@ -5,7 +5,11 @@ import React from 'react';
import { config } from '@grafana/runtime';
import { logGroupNamesVariable, setupMockedDataSource } from '../../../__mocks__/CloudWatchDataSource';
import {
logGroupNamesVariable,
setupMockedDataSource,
setupMockedTemplateService,
} from '../../../__mocks__/CloudWatchDataSource';
import { LogGroupsField } from './LogGroupsField';
@ -17,6 +21,7 @@ const defaultProps = {
region: '',
onChange: jest.fn(),
};
describe('LogGroupSelection', () => {
beforeEach(() => {
jest.resetAllMocks();
@ -35,6 +40,7 @@ describe('LogGroupSelection', () => {
defaultProps.datasource.resources.getLogGroups = jest
.fn()
.mockResolvedValue([{ value: { arn: 'arn', name: 'loggroupname' } }]);
defaultProps.datasource.resources.templateSrv = setupMockedTemplateService();
render(<LogGroupsField {...defaultProps} legacyLogGroupNames={['loggroupname']} />);
await waitFor(async () => expect(screen.getByText('Select log groups')).toBeInTheDocument());
@ -51,7 +57,8 @@ describe('LogGroupSelection', () => {
defaultProps.datasource.resources.getLogGroups = jest
.fn()
.mockResolvedValue([{ value: { arn: 'arn', name: 'loggroupname' } }]);
render(<LogGroupsField {...defaultProps} legacyLogGroupNames={['loggroupname', logGroupNamesVariable.name]} />);
const varName = '$' + logGroupNamesVariable.name;
render(<LogGroupsField {...defaultProps} legacyLogGroupNames={['loggroupname', varName]} />);
await waitFor(async () => expect(screen.getByText('Select log groups')).toBeInTheDocument());
expect(defaultProps.datasource.resources.getLogGroups).toHaveBeenCalledTimes(1);
@ -61,7 +68,7 @@ describe('LogGroupSelection', () => {
});
expect(defaultProps.onChange).toHaveBeenCalledWith([
{ arn: 'arn', name: 'loggroupname' },
{ arn: logGroupNamesVariable.name, name: logGroupNamesVariable.name },
{ arn: varName, name: varName },
]);
});
@ -70,6 +77,7 @@ describe('LogGroupSelection', () => {
defaultProps.datasource.resources.getLogGroups = jest
.fn()
.mockResolvedValue([{ value: { arn: 'arn', name: 'loggroupname' } }]);
defaultProps.datasource.resources.templateSrv = setupMockedTemplateService();
render(<LogGroupsField {...defaultProps} logGroups={[{ arn: 'arn', name: 'loggroupname' }]} />);
await waitFor(() => expect(screen.getByText('Select log groups')).toBeInTheDocument());
expect(defaultProps.datasource.resources.getLogGroups).not.toHaveBeenCalled();

View File

@ -21,6 +21,7 @@ import {
MetricEditorMode,
MetricQueryType,
} from './types';
import * as templateUtils from './utils/templateVariableUtils';
describe('datasource', () => {
beforeEach(() => {
@ -178,7 +179,6 @@ describe('datasource', () => {
it('should interpolate multi-value template variable for log group names in the query', async () => {
const { datasource, queryMock } = setupMockedDataSource({
variables: [fieldsVariable, logGroupNamesVariable, regionVariable],
mockGetVariableName: false,
});
await lastValueFrom(
datasource
@ -304,7 +304,9 @@ describe('datasource', () => {
it('should replace correct variables in CloudWatchMetricsQuery', () => {
const { datasource, templateService } = setupMockedDataSource();
templateService.replace = jest.fn();
templateService.getVariableName = jest.fn();
const mockGetVariableName = jest
.spyOn(templateUtils, 'getVariableName')
.mockImplementation((name: string) => name.replace('$', ''));
const variableName = 'someVar';
const metricsQuery: CloudWatchMetricsQuery = {
queryMode: 'Metrics',
@ -330,8 +332,8 @@ describe('datasource', () => {
expect(templateService.replace).toHaveBeenCalledWith(`$${variableName}`, {});
expect(templateService.replace).toHaveBeenCalledTimes(8);
expect(templateService.getVariableName).toHaveBeenCalledWith(`$${variableName}`);
expect(templateService.getVariableName).toHaveBeenCalledTimes(1);
expect(mockGetVariableName).toHaveBeenCalledWith(`$${variableName}`);
expect(mockGetVariableName).toHaveBeenCalledTimes(1);
});
});

View File

@ -13,8 +13,7 @@ import {
LogRowModel,
ScopedVars,
} from '@grafana/data';
import { DataSourceWithBackend } from '@grafana/runtime';
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { DataSourceWithBackend, TemplateSrv, getTemplateSrv } from '@grafana/runtime';
import { CloudWatchAnnotationSupport } from './annotationSupport';
import { DEFAULT_METRICS_QUERY, getDefaultLogsQuery } from './defaultQueries';

View File

@ -64,7 +64,6 @@ describe('hooks', () => {
describe('useDimensionKeys', () => {
it('should interpolate variables before calling api', async () => {
const { datasource } = setupMockedDataSource({
mockGetVariableName: true,
variables: [regionVariable, namespaceVariable, accountIdVariable, metricVariable, dimensionVariable],
});
const getDimensionKeysMock = jest.fn().mockResolvedValue([]);

View File

@ -3,6 +3,7 @@ import { Value } from 'slate';
import { TypeaheadOutput } from '@grafana/ui';
import { setupMockedTemplateService } from '../../__mocks__/CloudWatchDataSource';
import { CloudWatchDatasource } from '../../datasource';
import { ResourceResponse } from '../../resources/types';
import { LogGroupField } from '../../types';
@ -124,7 +125,7 @@ function makeDatasource(): CloudWatchDatasource {
* Get suggestion items based on query. Use `^` to mark position of the cursor.
*/
function getProvideCompletionItems(query: string): Promise<TypeaheadOutput> {
const provider = new CloudWatchLogsLanguageProvider(makeDatasource());
const provider = new CloudWatchLogsLanguageProvider(makeDatasource(), setupMockedTemplateService());
const cursorOffset = query.indexOf('^');
const queryWithoutCursor = query.replace('^', '');
let tokens: Token[] = Prism.tokenize(queryWithoutCursor, provider.getSyntax()) as any;

View File

@ -2,9 +2,8 @@ import Prism, { Grammar } from 'prismjs';
import { lastValueFrom } from 'rxjs';
import { AbsoluteTimeRange, HistoryItem, LanguageProvider } from '@grafana/data';
import { BackendDataSourceResponse, FetchResponse } from '@grafana/runtime';
import { BackendDataSourceResponse, FetchResponse, TemplateSrv, getTemplateSrv } from '@grafana/runtime';
import { CompletionItemGroup, SearchFunctionType, Token, TypeaheadInput, TypeaheadOutput } from '@grafana/ui';
import { getTemplateSrv } from 'app/features/templating/template_srv';
import { CloudWatchDatasource } from '../../datasource';
import { CloudWatchQuery, LogGroup } from '../../types';
@ -34,11 +33,13 @@ export class CloudWatchLogsLanguageProvider extends LanguageProvider {
started = false;
declare initialRange: AbsoluteTimeRange;
datasource: CloudWatchDatasource;
templateSrv: TemplateSrv;
constructor(datasource: CloudWatchDatasource, initialValues?: any) {
constructor(datasource: CloudWatchDatasource, templateSrv?: TemplateSrv, initialValues?: any) {
super();
this.datasource = datasource;
this.templateSrv = templateSrv ?? getTemplateSrv();
Object.assign(this, initialValues);
}
@ -132,7 +133,7 @@ export class CloudWatchLogsLanguageProvider extends LanguageProvider {
private fetchFields = async (logGroups: LogGroup[], region: string): Promise<string[]> => {
const interpolatedLogGroups = interpolateStringArrayUsingSingleOrMultiValuedVariable(
getTemplateSrv(),
this.templateSrv,
logGroups.map((lg) => lg.name),
{},
'text'

View File

@ -1,10 +1,9 @@
import { TemplateSrv } from 'app/features/templating/template_srv';
import {
aggregationvariable,
labelsVariable,
metricVariable,
namespaceVariable,
setupMockedTemplateService,
} from '../../__mocks__/CloudWatchDataSource';
import {
createFunctionWithParameter,
@ -25,11 +24,12 @@ describe('SQLGenerator', () => {
from: createFunctionWithParameter('SCHEMA', ['AWS/EC2']),
orderByDirection: 'DESC',
};
let mockTemplateSrv = setupMockedTemplateService();
describe('mandatory fields check', () => {
it('should return undefined if metric and aggregation is missing', () => {
expect(
new SQLGenerator().expressionToSqlQuery({
new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({
from: createFunctionWithParameter('SCHEMA', ['AWS/EC2']),
})
).toBeUndefined();
@ -37,7 +37,7 @@ describe('SQLGenerator', () => {
it('should return undefined if aggregation is missing', () => {
expect(
new SQLGenerator().expressionToSqlQuery({
new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({
from: createFunctionWithParameter('SCHEMA', []),
})
).toBeUndefined();
@ -45,27 +45,27 @@ describe('SQLGenerator', () => {
});
it('should return query if mandatory fields are provided', () => {
expect(new SQLGenerator().expressionToSqlQuery(baseQuery)).not.toBeUndefined();
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery(baseQuery)).not.toBeUndefined();
});
describe('select', () => {
it('should use statistic and metric name', () => {
const select = createFunctionWithParameter('COUNT', ['BytesPerSecond']);
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery, select })).toEqual(
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery, select })).toEqual(
`SELECT COUNT(BytesPerSecond) FROM SCHEMA("AWS/EC2")`
);
});
it('should wrap in double quotes if metric name contains illegal characters ', () => {
const select = createFunctionWithParameter('COUNT', ['Bytes-Per-Second']);
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery, select })).toEqual(
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery, select })).toEqual(
`SELECT COUNT("Bytes-Per-Second") FROM SCHEMA("AWS/EC2")`
);
});
it('should wrap in double quotes if metric name starts with a number ', () => {
const select = createFunctionWithParameter('COUNT', ['4xxErrorRate']);
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery, select })).toEqual(
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery, select })).toEqual(
`SELECT COUNT("4xxErrorRate") FROM SCHEMA("AWS/EC2")`
);
});
@ -75,14 +75,14 @@ describe('SQLGenerator', () => {
describe('with schema contraint', () => {
it('should handle schema without dimensions', () => {
const from = createFunctionWithParameter('SCHEMA', ['AWS/MQ']);
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery, from })).toEqual(
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery, from })).toEqual(
`SELECT SUM(CPUUtilization) FROM SCHEMA("AWS/MQ")`
);
});
it('should handle schema with dimensions', () => {
const from = createFunctionWithParameter('SCHEMA', ['AWS/MQ', 'InstanceId', 'InstanceType']);
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery, from })).toEqual(
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery, from })).toEqual(
`SELECT SUM(CPUUtilization) FROM SCHEMA("AWS/MQ", InstanceId, InstanceType)`
);
});
@ -94,7 +94,7 @@ describe('SQLGenerator', () => {
'Instance.Type',
'Instance-Group',
]);
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery, from })).toEqual(
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery, from })).toEqual(
`SELECT SUM(CPUUtilization) FROM SCHEMA("AWS/MQ", "Instance Id", "Instance.Type", "Instance-Group")`
);
});
@ -103,7 +103,7 @@ describe('SQLGenerator', () => {
describe('without schema', () => {
it('should use the specified namespace', () => {
const from = createProperty('AWS/MQ');
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery, from })).toEqual(
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery, from })).toEqual(
`SELECT SUM(CPUUtilization) FROM "AWS/MQ"`
);
});
@ -111,25 +111,25 @@ describe('SQLGenerator', () => {
});
function assertQueryEndsWith(rest: Partial<SQLExpression>, expectedFilter: string) {
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery, ...rest })).toEqual(
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery, ...rest })).toEqual(
`SELECT SUM(CPUUtilization) FROM SCHEMA("AWS/EC2") ${expectedFilter}`
);
}
describe('filter', () => {
it('should not add WHERE clause in case its empty', () => {
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery })).not.toContain('WHERE');
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery })).not.toContain('WHERE');
});
it('should not add WHERE clause when there is no filter conditions', () => {
const where = createArray([]);
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery, where })).not.toContain('WHERE');
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery, where })).not.toContain('WHERE');
});
// TODO: We should handle this scenario
it.skip('should not add WHERE clause when the operator is incomplete', () => {
const where = createArray([createOperator('Instance-Id', '=')]);
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery, where })).not.toContain('WHERE');
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery, where })).not.toContain('WHERE');
});
it('should handle one top level filter with AND', () => {
@ -280,7 +280,7 @@ describe('SQLGenerator', () => {
describe('group by', () => {
it('should not add GROUP BY clause in case its empty', () => {
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery })).not.toContain('GROUP BY');
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery })).not.toContain('GROUP BY');
});
it('should handle single label', () => {
const groupBy = createArray([createGroupBy('InstanceId')], QueryEditorExpressionType.And);
@ -297,7 +297,7 @@ describe('SQLGenerator', () => {
describe('order by', () => {
it('should not add ORDER BY clause in case its empty', () => {
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery })).not.toContain('ORDER BY');
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery })).not.toContain('ORDER BY');
});
it('should handle SUM ASC', () => {
const orderBy = createFunction('SUM');
@ -315,7 +315,7 @@ describe('SQLGenerator', () => {
});
describe('limit', () => {
it('should not add LIMIT clause in case its empty', () => {
expect(new SQLGenerator().expressionToSqlQuery({ ...baseQuery })).not.toContain('LIMIT');
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery({ ...baseQuery })).not.toContain('LIMIT');
});
it('should be added in case its specified', () => {
@ -346,15 +346,19 @@ describe('SQLGenerator', () => {
orderByDirection: 'DESC',
limit: 100,
};
expect(new SQLGenerator().expressionToSqlQuery(query)).toEqual(
expect(new SQLGenerator(mockTemplateSrv).expressionToSqlQuery(query)).toEqual(
`SELECT COUNT(DroppedBytes) FROM SCHEMA("AWS/MQ", InstanceId, "Instance-Group") WHERE (InstanceId = 'I-123' OR Type != 'some-type') AND (InstanceId != 'I-456' OR Type != 'some-type') GROUP BY InstanceId, InstanceType ORDER BY COUNT() DESC LIMIT 100`
);
});
});
describe('using variables', () => {
const templateService = new TemplateSrv();
templateService.init([metricVariable, namespaceVariable, labelsVariable, aggregationvariable]);
const templateService = setupMockedTemplateService([
metricVariable,
namespaceVariable,
labelsVariable,
aggregationvariable,
]);
it('should interpolate variables correctly', () => {
let query: SQLExpression = {

View File

@ -1,4 +1,4 @@
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import {
QueryEditorArrayExpression,

View File

@ -1,7 +1,7 @@
import { Observable } from 'rxjs';
import { DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { TemplateSrv } from '@grafana/runtime';
import { CloudWatchAnnotationQuery, CloudWatchJsonData, CloudWatchQuery } from '../types';

View File

@ -244,7 +244,7 @@ describe('CloudWatchLogsQueryRunner', () => {
id: '',
region: '$' + regionVariable.name,
refId: 'A',
expression: `stats count(*) by queryType, bin($__interval)`,
expression: `stats count(*) by queryType, bin(20s)`,
};
describe('handleLogQueries', () => {

View File

@ -31,8 +31,7 @@ import {
getDefaultTimeRange,
rangeUtil,
} from '@grafana/data';
import { config, FetchError } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { config, FetchError, TemplateSrv } from '@grafana/runtime';
import {
CloudWatchJsonData,

View File

@ -358,7 +358,7 @@ describe('CloudWatchMetricsQueryRunner', () => {
it('interpolates variables correctly', async () => {
const { runner, queryMock, request } = setupMockedMetricsQueryRunner({
variables: [namespaceVariable, metricVariable, labelsVariable, limitVariable],
variables: [namespaceVariable, metricVariable, limitVariable],
});
runner.handleMetricQueries(
[
@ -376,7 +376,7 @@ describe('CloudWatchMetricsQueryRunner', () => {
expression: '',
metricQueryType: MetricQueryType.Query,
metricEditorMode: MetricEditorMode.Code,
sqlExpression: 'SELECT SUM($metric) FROM "$namespace" GROUP BY ${labels:raw} LIMIT $limit',
sqlExpression: 'SELECT SUM($metric) FROM "$namespace" GROUP BY InstanceId,InstanceType LIMIT $limit',
},
],
request,
@ -666,8 +666,8 @@ describe('CloudWatchMetricsQueryRunner', () => {
queryMode: 'Metrics',
id: '',
region: 'us-east-2',
namespace: namespaceVariable.id,
metricName: metricVariable.id,
namespace: '$' + namespaceVariable.name,
metricName: '$' + metricVariable.name,
period: '',
alias: '',
dimensions: {},
@ -712,7 +712,7 @@ describe('CloudWatchMetricsQueryRunner', () => {
sqlExpression: 'select SUM(CPUUtilization) from $datasource',
dimensions: { InstanceId: '$dimension' },
};
const { runner } = setupMockedMetricsQueryRunner({ variables: [dimensionVariable], mockGetVariableName: false });
const { runner } = setupMockedMetricsQueryRunner({ variables: [dimensionVariable] });
const result = runner.interpolateMetricsQueryVariables(testQuery, {
datasource: { text: 'foo', value: 'foo' },
dimension: { text: 'foo', value: 'foo' },
@ -732,7 +732,6 @@ describe('CloudWatchMetricsQueryRunner', () => {
describe('convertMultiFiltersFormat', () => {
const { runner } = setupMockedMetricsQueryRunner({
variables: [labelsVariable, dimensionVariable],
mockGetVariableName: false,
});
it('converts keys and values correctly', () => {
const filters = { $dimension: ['b'], a: ['$labels', 'bar'] };

View File

@ -12,9 +12,9 @@ import {
rangeUtil,
ScopedVars,
} from '@grafana/data';
import { TemplateSrv } from '@grafana/runtime';
import { notifyApp } from 'app/core/actions';
import { createErrorNotification } from 'app/core/copy/appNotification';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { store } from 'app/store/store';
import { AppNotificationTimeout } from 'app/types';

View File

@ -1,15 +1,15 @@
import { Observable } from 'rxjs';
import { DataSourceInstanceSettings, DataSourceRef, getDataSourceRef, ScopedVars } from '@grafana/data';
import { BackendDataSourceResponse, FetchResponse, getBackendSrv } from '@grafana/runtime';
import { BackendDataSourceResponse, FetchResponse, getBackendSrv, TemplateSrv } from '@grafana/runtime';
import { notifyApp } from 'app/core/actions';
import { createErrorNotification } from 'app/core/copy/appNotification';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { store } from 'app/store/store';
import { AppNotificationTimeout } from 'app/types';
import memoizedDebounce from '../memoizedDebounce';
import { CloudWatchJsonData, Dimensions, MetricRequest, MultiFilters } from '../types';
import { getVariableName } from '../utils/templateVariableUtils';
export abstract class CloudWatchRequest {
templateSrv: TemplateSrv;
@ -62,7 +62,7 @@ export abstract class CloudWatchRequest {
// get the value for a given template variable
expandVariableToArray(value: string, scopedVars: ScopedVars): string[] {
const variableName = this.templateSrv.getVariableName(value);
const variableName = getVariableName(value);
const valueVar = this.templateSrv.getVariables().find(({ name }) => {
return name === variableName;
});
@ -101,7 +101,7 @@ export abstract class CloudWatchRequest {
) {
if (displayErrorIfIsMultiTemplateVariable && !!target) {
const variables = this.templateSrv.getVariables();
const variable = variables.find(({ name }) => name === this.templateSrv.getVariableName(target));
const variable = variables.find(({ name }) => name === getVariableName(target));
const isMultiVariable =
variable?.type === 'custom' || variable?.type === 'query' || variable?.type === 'datasource';
if (isMultiVariable && variable.multi) {

View File

@ -1,8 +1,7 @@
import { memoize } from 'lodash';
import { DataSourceInstanceSettings, SelectableValue } from '@grafana/data';
import { getBackendSrv, config } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { getBackendSrv, config, TemplateSrv } from '@grafana/runtime';
import { CloudWatchRequest } from '../query-runner/CloudWatchRequest';
import { CloudWatchJsonData, LogGroupField, MultiFilters } from '../types';

View File

@ -1,5 +1,29 @@
import { VariableOption, UserProps, OrgProps, DashboardProps, ScopedVars } from '@grafana/data';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { TemplateSrv } from '@grafana/runtime';
/*
* This regex matches 3 types of variable reference with an optional format specifier
* There are 6 capture groups that replace will return
* \$(\w+) $var1
* \[\[(\w+?)(?::(\w+))?\]\] [[var2]] or [[var2:fmt2]]
* \${(\w+)(?:\.([^:^\}]+))?(?::([^\}]+))?} ${var3} or ${var3.fieldPath} or ${var3:fmt3} (or ${var3.fieldPath:fmt3} but that is not a separate capture group)
*/
const variableRegex = /\$(\w+)|\[\[(\w+?)(?::(\w+))?\]\]|\${(\w+)(?:\.([^:^\}]+))?(?::([^\}]+))?}/g;
// Helper function since lastIndex is not reset
const variableRegexExec = (variableString: string) => {
variableRegex.lastIndex = 0;
return variableRegex.exec(variableString);
};
export const getVariableName = (expression: string) => {
const match = variableRegexExec(expression);
if (!match) {
return null;
}
const variableName = match.slice(1).find((match) => match !== undefined);
return variableName;
};
/**
* @remarks
@ -22,7 +46,7 @@ export const interpolateStringArrayUsingSingleOrMultiValuedVariable = (
const format = key === 'value' ? 'pipe' : 'text';
let result: string[] = [];
for (const string of strings) {
const variableName = templateSrv.getVariableName(string);
const variableName = getVariableName(string);
const valueVar = templateSrv.getVariables().find(({ name }) => name === variableName);
if (valueVar && 'current' in valueVar && isVariableOption(valueVar.current)) {
@ -43,7 +67,7 @@ export const interpolateStringArrayUsingSingleOrMultiValuedVariable = (
};
export const isTemplateVariable = (templateSrv: TemplateSrv, string: string) => {
const variableName = templateSrv.getVariableName(string);
const variableName = getVariableName(string);
return templateSrv.getVariables().some(({ name }) => name === variableName);
};