Cloudwatch: Break out resource requests from datasource file (#55358)

* break out resource requests from datasource file

* fix lint issues
This commit is contained in:
Erik Sundell 2022-09-20 07:50:54 +02:00 committed by GitHub
parent 4df41972f1
commit caba98590d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 298 additions and 303 deletions

View File

@ -56,7 +56,7 @@ exports[`no enzyme tests`] = {
"public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.test.tsx:145048794": [ "public/app/features/dimensions/editors/ThresholdsEditor/ThresholdsEditor.test.tsx:145048794": [
[0, 17, 13, "RegExp match", "2409514259"] [0, 17, 13, "RegExp match", "2409514259"]
], ],
"public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:3543762762": [ "public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:2210656642": [
[1, 19, 13, "RegExp match", "2409514259"] [1, 19, 13, "RegExp match", "2409514259"]
] ]
}` }`
@ -5905,12 +5905,18 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/cloud-monitoring/types.ts:5381": [ "public/app/plugins/datasource/cloud-monitoring/types.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
], ],
"public/app/plugins/datasource/cloudwatch/__mocks__/LogsQueryRunner.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts:5381": [ "public/app/plugins/datasource/cloudwatch/__mocks__/monarch/Monaco.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
], ],
"public/app/plugins/datasource/cloudwatch/api.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
],
"public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:5381": [ "public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
], ],
@ -5953,15 +5959,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "1"] [0, 0, 0, "Do not use any type assertions.", "1"]
], ],
"public/app/plugins/datasource/cloudwatch/datasource.ts:5381": [ "public/app/plugins/datasource/cloudwatch/datasource.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
[0, 0, 0, "Unexpected any. Specify a different type.", "6"],
[0, 0, 0, "Unexpected any. Specify a different type.", "7"],
[0, 0, 0, "Unexpected any. Specify a different type.", "8"]
], ],
"public/app/plugins/datasource/cloudwatch/guards.ts:5381": [ "public/app/plugins/datasource/cloudwatch/guards.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
@ -5985,9 +5983,6 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"] [0, 0, 0, "Unexpected any. Specify a different type.", "1"]
], ],
"public/app/plugins/datasource/cloudwatch/metric-math/completion/CompletionItemProvider.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts:5381": [ "public/app/plugins/datasource/cloudwatch/query-runner/CloudWatchLogsQueryRunner.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"] [0, 0, 0, "Unexpected any. Specify a different type.", "1"]

View File

@ -77,8 +77,8 @@ export function setupMockedDataSource({
const datasource = new CloudWatchDatasource(CloudWatchSettings, templateService, timeSrv); const datasource = new CloudWatchDatasource(CloudWatchSettings, templateService, timeSrv);
datasource.getVariables = () => ['test']; datasource.getVariables = () => ['test'];
datasource.getNamespaces = jest.fn().mockResolvedValue([]); datasource.api.getNamespaces = jest.fn().mockResolvedValue([]);
datasource.getRegions = jest.fn().mockResolvedValue([]); datasource.api.getRegions = jest.fn().mockResolvedValue([]);
datasource.logsQueryRunner.defaultLogGroups = []; datasource.logsQueryRunner.defaultLogGroups = [];
const fetchMock = jest.fn().mockReturnValue(of({})); const fetchMock = jest.fn().mockReturnValue(of({}));
setBackendSrv({ setBackendSrv({

View File

@ -1,6 +1,6 @@
import { of } from 'rxjs'; import { of } from 'rxjs';
import { DataFrame } from '@grafana/data'; import { CustomVariableModel, DataFrame } from '@grafana/data';
import { BackendDataSourceResponse, getBackendSrv, setBackendSrv } from '@grafana/runtime'; import { BackendDataSourceResponse, getBackendSrv, setBackendSrv } from '@grafana/runtime';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { TemplateSrv } from 'app/features/templating/template_srv'; import { TemplateSrv } from 'app/features/templating/template_srv';
@ -16,7 +16,7 @@ export function setupMockedLogsQueryRunner({
}, },
variables, variables,
mockGetVariableName = true, mockGetVariableName = true,
}: { data?: BackendDataSourceResponse; variables?: any; mockGetVariableName?: boolean } = {}) { }: { data?: BackendDataSourceResponse; variables?: CustomVariableModel[]; mockGetVariableName?: boolean } = {}) {
let templateService = new TemplateSrv(); let templateService = new TemplateSrv();
if (variables) { if (variables) {
templateService = setupMockedTemplateService(variables); templateService = setupMockedTemplateService(variables);

View File

@ -0,0 +1,111 @@
import { DataSourceInstanceSettings } from '@grafana/data';
import { getBackendSrv, BackendSrv } from '@grafana/runtime';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { CloudWatchRequest } from './query-runner/CloudWatchRequest';
import { CloudWatchJsonData, Dimensions } from './types';
export class CloudWatchAPI extends CloudWatchRequest {
private backendSrv: BackendSrv;
constructor(instanceSettings: DataSourceInstanceSettings<CloudWatchJsonData>, templateSrv: TemplateSrv) {
super(instanceSettings, templateSrv);
this.backendSrv = getBackendSrv();
}
resourceRequest(subtype: string, parameters?: any): Promise<Array<{ text: any; label: any; value: any }>> {
return this.backendSrv.get(`/api/datasources/${this.instanceSettings.id}/resources/${subtype}`, parameters);
}
getRegions(): Promise<Array<{ label: string; value: string; text: string }>> {
return this.resourceRequest('regions').then((regions: any) => [
{ label: 'default', value: 'default', text: 'default' },
...regions,
]);
}
getNamespaces() {
return this.resourceRequest('namespaces');
}
async getMetrics(namespace: string | undefined, region?: string) {
if (!namespace) {
return [];
}
return this.resourceRequest('metrics', {
region: this.templateSrv.replace(this.getActualRegion(region)),
namespace: this.templateSrv.replace(namespace),
});
}
async getAllMetrics(region: string): Promise<Array<{ metricName: string; namespace: string }>> {
const values = await this.resourceRequest('all-metrics', {
region: this.templateSrv.replace(this.getActualRegion(region)),
});
return values.map((v) => ({ metricName: v.value, namespace: v.text }));
}
async getDimensionKeys(
namespace: string | undefined,
region: string,
dimensionFilters: Dimensions = {},
metricName = ''
) {
if (!namespace) {
return [];
}
return this.resourceRequest('dimension-keys', {
region: this.templateSrv.replace(this.getActualRegion(region)),
namespace: this.templateSrv.replace(namespace),
dimensionFilters: JSON.stringify(this.convertDimensionFormat(dimensionFilters, {})),
metricName,
});
}
async getDimensionValues(
region: string,
namespace: string | undefined,
metricName: string | undefined,
dimensionKey: string,
filterDimensions: {}
) {
if (!namespace || !metricName) {
return [];
}
const values = await this.resourceRequest('dimension-values', {
region: this.templateSrv.replace(this.getActualRegion(region)),
namespace: this.templateSrv.replace(namespace),
metricName: this.templateSrv.replace(metricName.trim()),
dimensionKey: this.templateSrv.replace(dimensionKey),
dimensions: JSON.stringify(this.convertDimensionFormat(filterDimensions, {})),
});
return values;
}
getEbsVolumeIds(region: string, instanceId: string) {
return this.resourceRequest('ebs-volume-ids', {
region: this.templateSrv.replace(this.getActualRegion(region)),
instanceId: this.templateSrv.replace(instanceId),
});
}
getEc2InstanceAttribute(region: string, attributeName: string, filters: any) {
return this.resourceRequest('ec2-instance-attribute', {
region: this.templateSrv.replace(this.getActualRegion(region)),
attributeName: this.templateSrv.replace(attributeName),
filters: JSON.stringify(this.convertMultiFilterFormat(filters, 'filter key')),
});
}
getResourceARNs(region: string, resourceType: string, tags: any) {
return this.resourceRequest('resource-arns', {
region: this.templateSrv.replace(this.getActualRegion(region)),
resourceType: this.templateSrv.replace(resourceType),
tags: JSON.stringify(this.convertMultiFilterFormat(tags, 'tag name')),
});
}
}

View File

@ -3,7 +3,7 @@ import { uniq } from 'lodash';
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import type { Monaco, monacoTypes } from '@grafana/ui'; import type { Monaco, monacoTypes } from '@grafana/ui';
import { CloudWatchDatasource } from '../../datasource'; import { CloudWatchAPI } from '../../api';
import { CompletionItemProvider } from '../../monarch/CompletionItemProvider'; import { CompletionItemProvider } from '../../monarch/CompletionItemProvider';
import { LinkedToken } from '../../monarch/LinkedToken'; import { LinkedToken } from '../../monarch/LinkedToken';
import { TRIGGER_SUGGEST } from '../../monarch/commands'; import { TRIGGER_SUGGEST } from '../../monarch/commands';
@ -34,9 +34,9 @@ type CompletionItem = monacoTypes.languages.CompletionItem;
export class SQLCompletionItemProvider extends CompletionItemProvider { export class SQLCompletionItemProvider extends CompletionItemProvider {
region: string; region: string;
constructor(datasource: CloudWatchDatasource, templateSrv: TemplateSrv = getTemplateSrv()) { constructor(api: CloudWatchAPI, templateSrv: TemplateSrv = getTemplateSrv()) {
super(datasource, templateSrv); super(api, templateSrv);
this.region = datasource.getActualRegion(); this.region = api.getActualRegion() ?? '';
this.getStatementPosition = getStatementPosition; this.getStatementPosition = getStatementPosition;
this.getSuggestionKinds = getSuggestionKinds; this.getSuggestionKinds = getSuggestionKinds;
this.tokenTypes = SQLTokenTypes; this.tokenTypes = SQLTokenTypes;
@ -112,14 +112,14 @@ export class SQLCompletionItemProvider extends CompletionItemProvider {
const namespaceToken = getNamespaceToken(currentToken); const namespaceToken = getNamespaceToken(currentToken);
if (namespaceToken?.value) { if (namespaceToken?.value) {
// if a namespace is specified, only suggest metrics for the namespace // if a namespace is specified, only suggest metrics for the namespace
const metrics = await this.datasource.getMetrics( const metrics = await this.api.getMetrics(
this.templateSrv.replace(namespaceToken?.value.replace(/\"/g, '')), this.templateSrv.replace(namespaceToken?.value.replace(/\"/g, '')),
this.templateSrv.replace(this.region) this.templateSrv.replace(this.region)
); );
metrics.map((m) => addSuggestion(m.value)); metrics.map((m) => addSuggestion(m.value));
} else { } else {
// If no namespace is specified in the query, just list all metrics // If no namespace is specified in the query, just list all metrics
const metrics = await this.datasource.getAllMetrics(this.templateSrv.replace(this.region)); const metrics = await this.api.getAllMetrics(this.templateSrv.replace(this.region));
uniq(metrics.map((m) => m.metricName)).map((m) => addSuggestion(m, { insertText: m })); uniq(metrics.map((m) => m.metricName)).map((m) => addSuggestion(m, { insertText: m }));
} }
} }
@ -147,12 +147,12 @@ export class SQLCompletionItemProvider extends CompletionItemProvider {
let namespaces = []; let namespaces = [];
if (metricNameToken?.value) { if (metricNameToken?.value) {
// if a metric is specified, only suggest namespaces that actually have that metric // if a metric is specified, only suggest namespaces that actually have that metric
const metrics = await this.datasource.getAllMetrics(this.region); const metrics = await this.api.getAllMetrics(this.region);
const metricName = this.templateSrv.replace(metricNameToken.value); const metricName = this.templateSrv.replace(metricNameToken.value);
namespaces = metrics.filter((m) => m.metricName === metricName).map((m) => m.namespace); namespaces = metrics.filter((m) => m.metricName === metricName).map((m) => m.namespace);
} else { } else {
// if no metric is specified, just suggest all namespaces // if no metric is specified, just suggest all namespaces
const ns = await this.datasource.getNamespaces(); const ns = await this.api.getNamespaces();
namespaces = ns.map((n) => n.value); namespaces = ns.map((n) => n.value);
} }
namespaces.map((n) => addSuggestion(`"${n}"`, { insertText: `"${n}"` })); namespaces.map((n) => addSuggestion(`"${n}"`, { insertText: `"${n}"` }));
@ -179,7 +179,7 @@ export class SQLCompletionItemProvider extends CompletionItemProvider {
dimensionFilter = (labelKeyTokens || []).reduce((acc, curr) => { dimensionFilter = (labelKeyTokens || []).reduce((acc, curr) => {
return { ...acc, [curr.value]: null }; return { ...acc, [curr.value]: null };
}, {}); }, {});
const keys = await this.datasource.getDimensionKeys( const keys = await this.api.getDimensionKeys(
this.templateSrv.replace(namespaceToken.value.replace(/\"/g, '')), this.templateSrv.replace(namespaceToken.value.replace(/\"/g, '')),
this.templateSrv.replace(this.region), this.templateSrv.replace(this.region),
dimensionFilter, dimensionFilter,
@ -199,7 +199,7 @@ export class SQLCompletionItemProvider extends CompletionItemProvider {
const metricNameToken = getMetricNameToken(currentToken); const metricNameToken = getMetricNameToken(currentToken);
const labelKey = currentToken?.getPreviousNonWhiteSpaceToken()?.getPreviousNonWhiteSpaceToken(); const labelKey = currentToken?.getPreviousNonWhiteSpaceToken()?.getPreviousNonWhiteSpaceToken();
if (namespaceToken?.value && labelKey?.value && metricNameToken?.value) { if (namespaceToken?.value && labelKey?.value && metricNameToken?.value) {
const values = await this.datasource.getDimensionValues( const values = await this.api.getDimensionValues(
this.templateSrv.replace(this.region), this.templateSrv.replace(this.region),
this.templateSrv.replace(namespaceToken.value.replace(/\"/g, '')), this.templateSrv.replace(namespaceToken.value.replace(/\"/g, '')),
this.templateSrv.replace(metricNameToken.value), this.templateSrv.replace(metricNameToken.value),
@ -266,12 +266,12 @@ export class SQLCompletionItemProvider extends CompletionItemProvider {
} }
} }
// always suggest template variables this.templateSrv.getVariables().map((v) => {
this.templateVariables.map((v) => { const variable = `$${v.name}`;
addSuggestion(v, { addSuggestion(variable, {
range, range,
label: v, label: variable,
insertText: v, insertText: variable,
kind: monaco.languages.CompletionItemKind.Variable, kind: monaco.languages.CompletionItemKind.Variable,
sortText: CompletionItemPriority.Low, sortText: CompletionItemPriority.Low,
}); });

View File

@ -29,10 +29,10 @@ const q: CloudWatchQuery = {
alarmNamePrefix: '', alarmNamePrefix: '',
}; };
ds.datasource.getRegions = jest.fn().mockResolvedValue([]); ds.datasource.api.getRegions = jest.fn().mockResolvedValue([]);
ds.datasource.getNamespaces = jest.fn().mockResolvedValue([]); ds.datasource.api.getNamespaces = jest.fn().mockResolvedValue([]);
ds.datasource.getMetrics = jest.fn().mockResolvedValue([]); ds.datasource.api.getMetrics = jest.fn().mockResolvedValue([]);
ds.datasource.getDimensionKeys = jest.fn().mockResolvedValue([]); ds.datasource.api.getDimensionKeys = jest.fn().mockResolvedValue([]);
ds.datasource.getVariables = jest.fn().mockReturnValue([]); ds.datasource.getVariables = jest.fn().mockReturnValue([]);
const props: QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData> = { const props: QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData> = {
@ -51,7 +51,7 @@ describe('AnnotationQueryEditor', () => {
}); });
it('should return an error component in case CloudWatchQuery is not CloudWatchAnnotationQuery', async () => { it('should return an error component in case CloudWatchQuery is not CloudWatchAnnotationQuery', async () => {
ds.datasource.getDimensionValues = jest.fn().mockResolvedValue([[{ label: 'dimVal1', value: 'dimVal1' }]]); ds.datasource.api.getDimensionValues = jest.fn().mockResolvedValue([[{ label: 'dimVal1', value: 'dimVal1' }]]);
render( render(
<AnnotationQueryEditor {...props} query={{ ...props.query, queryMode: 'Metrics' } as CloudWatchMetricsQuery} /> <AnnotationQueryEditor {...props} query={{ ...props.query, queryMode: 'Metrics' } as CloudWatchMetricsQuery} />
); );
@ -59,7 +59,7 @@ describe('AnnotationQueryEditor', () => {
}); });
it('should not display wildcard option in dimension value dropdown', async () => { it('should not display wildcard option in dimension value dropdown', async () => {
ds.datasource.getDimensionValues = jest.fn().mockResolvedValue([[{ label: 'dimVal1', value: 'dimVal1' }]]); ds.datasource.api.getDimensionValues = jest.fn().mockResolvedValue([[{ label: 'dimVal1', value: 'dimVal1' }]]);
(props.query as CloudWatchAnnotationQuery).dimensions = { instanceId: 'instance-123' }; (props.query as CloudWatchAnnotationQuery).dimensions = { instanceId: 'instance-123' };
render(<AnnotationQueryEditor {...props} />); render(<AnnotationQueryEditor {...props} />);
const valueElement = screen.getByText('instance-123'); const valueElement = screen.getByText('instance-123');

View File

@ -52,7 +52,7 @@ export default class CloudWatchLink extends Component<Props, State> {
source: query.logGroupNames ?? [], source: query.logGroupNames ?? [],
}; };
return encodeUrl(urlProps, datasource.getActualRegion(query.region)); return encodeUrl(urlProps, datasource.api.getActualRegion(query.region));
} }
render() { render() {

View File

@ -12,12 +12,14 @@ import { ConfigEditor, Props } from './ConfigEditor';
jest.mock('app/features/plugins/datasource_srv', () => ({ jest.mock('app/features/plugins/datasource_srv', () => ({
getDatasourceSrv: () => ({ getDatasourceSrv: () => ({
loadDatasource: jest.fn().mockResolvedValue({ loadDatasource: jest.fn().mockResolvedValue({
getRegions: jest.fn().mockResolvedValue([ api: {
{ getRegions: jest.fn().mockResolvedValue([
label: 'ap-east-1', {
value: 'ap-east-1', label: 'ap-east-1',
}, value: 'ap-east-1',
]), },
]),
},
getActualRegion: jest.fn().mockReturnValue('ap-east-1'), getActualRegion: jest.fn().mockReturnValue('ap-east-1'),
getVariables: jest.fn().mockReturnValue([]), getVariables: jest.fn().mockReturnValue([]),
logsQueryRunner: { logsQueryRunner: {

View File

@ -63,7 +63,7 @@ export const ConfigEditor: FC<Props> = (props: Props) => {
{...props} {...props}
loadRegions={ loadRegions={
datasource && datasource &&
(() => datasource!.getRegions().then((r) => r.filter((r) => r.value !== 'default').map((v) => v.value))) (() => datasource.api.getRegions().then((r) => r.filter((r) => r.value !== 'default').map((v) => v.value)))
} }
> >
<InlineField label="Namespaces of Custom Metrics" labelWidth={28} tooltip="Namespaces of Custom Metrics."> <InlineField label="Namespaces of Custom Metrics" labelWidth={28} tooltip="Namespaces of Custom Metrics.">

View File

@ -10,9 +10,9 @@ const ds = setupMockedDataSource({
variables: [], variables: [],
}); });
ds.datasource.getNamespaces = jest.fn().mockResolvedValue([]); ds.datasource.api.getNamespaces = jest.fn().mockResolvedValue([]);
ds.datasource.getMetrics = jest.fn().mockResolvedValue([]); ds.datasource.api.getMetrics = jest.fn().mockResolvedValue([]);
ds.datasource.getDimensionKeys = jest.fn().mockResolvedValue([]); ds.datasource.api.getDimensionKeys = jest.fn().mockResolvedValue([]);
ds.datasource.getVariables = jest.fn().mockReturnValue([]); ds.datasource.getVariables = jest.fn().mockReturnValue([]);
const q: CloudWatchMetricsQuery = { const q: CloudWatchMetricsQuery = {
id: '', id: '',

View File

@ -50,7 +50,7 @@ export const FilterItem: FunctionComponent<Props> = ({
return []; return [];
} }
return datasource return datasource.api
.getDimensionValues(region, namespace, metricName, filter.key, dimensionsExcludingCurrentKey) .getDimensionValues(region, namespace, metricName, filter.key, dimensionsExcludingCurrentKey)
.then((result: Array<SelectableValue<string>>) => { .then((result: Array<SelectableValue<string>>) => {
if (result.length && !disableExpressions) { if (result.length && !disableExpressions) {

View File

@ -98,7 +98,7 @@ export const LogGroupSelector: React.FC<LogGroupSelectorProps> = ({
} }
setLoadingLogGroups(true); setLoadingLogGroups(true);
return fetchLogGroupOptions(datasource.getActualRegion(region)) return fetchLogGroupOptions(datasource.getActualRegion(region) ?? '')
.then((logGroups) => { .then((logGroups) => {
const newSelectedLogGroups = intersection( const newSelectedLogGroups = intersection(
selectedLogGroups, selectedLogGroups,

View File

@ -11,9 +11,9 @@ const ds = setupMockedDataSource({
variables: [], variables: [],
}); });
ds.datasource.getNamespaces = jest.fn().mockResolvedValue([]); ds.datasource.api.getNamespaces = jest.fn().mockResolvedValue([]);
ds.datasource.getMetrics = jest.fn().mockResolvedValue([]); ds.datasource.api.getMetrics = jest.fn().mockResolvedValue([]);
ds.datasource.getDimensionKeys = jest.fn().mockResolvedValue([]); ds.datasource.api.getDimensionKeys = jest.fn().mockResolvedValue([]);
ds.datasource.getVariables = jest.fn().mockReturnValue([]); ds.datasource.getVariables = jest.fn().mockReturnValue([]);
const metricStat: MetricStat = { const metricStat: MetricStat = {
region: 'us-east-2', region: 'us-east-2',
@ -121,8 +121,8 @@ describe('MetricStatEditor', () => {
}; };
beforeEach(() => { beforeEach(() => {
propsNamespaceMetrics.datasource.getNamespaces = jest.fn().mockResolvedValue(namespaces); propsNamespaceMetrics.datasource.api.getNamespaces = jest.fn().mockResolvedValue(namespaces);
propsNamespaceMetrics.datasource.getMetrics = jest.fn().mockResolvedValue(metrics); propsNamespaceMetrics.datasource.api.getMetrics = jest.fn().mockResolvedValue(metrics);
onChange.mockClear(); onChange.mockClear();
onRunQuery.mockClear(); onRunQuery.mockClear();
}); });
@ -148,7 +148,7 @@ describe('MetricStatEditor', () => {
}); });
it('should remove metricName from metricStat if it does not exist in new namespace', async () => { it('should remove metricName from metricStat if it does not exist in new namespace', async () => {
propsNamespaceMetrics.datasource.getMetrics = jest propsNamespaceMetrics.datasource.api.getMetrics = jest
.fn() .fn()
.mockImplementation((namespace: string, region: string) => { .mockImplementation((namespace: string, region: string) => {
let mockMetrics = let mockMetrics =

View File

@ -6,6 +6,7 @@ import { EditorField, EditorFieldGroup, EditorRow, EditorRows, EditorSwitch, Sel
import { Dimensions } from '..'; import { Dimensions } from '..';
import { CloudWatchDatasource } from '../../datasource'; import { CloudWatchDatasource } from '../../datasource';
import { useDimensionKeys, useMetrics, useNamespaces } from '../../hooks'; import { useDimensionKeys, useMetrics, useNamespaces } from '../../hooks';
import { standardStatistics } from '../../standardStatistics';
import { MetricStat } from '../../types'; import { MetricStat } from '../../types';
import { appendTemplateVariables, toOption } from '../../utils/utils'; import { appendTemplateVariables, toOption } from '../../utils/utils';
@ -46,7 +47,7 @@ export function MetricStatEditor({
if (!metricName) { if (!metricName) {
return metricStat; return metricStat;
} }
await datasource.getMetrics(namespace, region).then((result: Array<SelectableValue<string>>) => { await datasource.api.getMetrics(namespace, region).then((result: Array<SelectableValue<string>>) => {
if (!result.find((metric) => metric.value === metricName)) { if (!result.find((metric) => metric.value === metricName)) {
metricName = ''; metricName = '';
} }
@ -89,15 +90,15 @@ export function MetricStatEditor({
<Select <Select
inputId={`${refId}-metric-stat-editor-select-statistic`} inputId={`${refId}-metric-stat-editor-select-statistic`}
allowCustomValue allowCustomValue
value={toOption(metricStat.statistic ?? datasource.standardStatistics[0])} value={toOption(metricStat.statistic ?? standardStatistics[0])}
options={appendTemplateVariables( options={appendTemplateVariables(
datasource, datasource,
datasource.standardStatistics.filter((s) => s !== metricStat.statistic).map(toOption) standardStatistics.filter((s) => s !== metricStat.statistic).map(toOption)
)} )}
onChange={({ value: statistic }) => { onChange={({ value: statistic }) => {
if ( if (
!statistic || !statistic ||
(!datasource.standardStatistics.includes(statistic) && (!standardStatistics.includes(statistic) &&
!/^p\d{2}(?:\.\d{1,2})?$/.test(statistic) && !/^p\d{2}(?:\.\d{1,2})?$/.test(statistic) &&
!statistic.startsWith('$')) !statistic.startsWith('$'))
) { ) {

View File

@ -46,10 +46,10 @@ const setup = () => {
const datasource = new CloudWatchDatasource(instanceSettings, templateSrv as any, {} as any); const datasource = new CloudWatchDatasource(instanceSettings, templateSrv as any, {} as any);
datasource.metricFindQuery = async () => [{ value: 'test', label: 'test', text: 'test' }]; datasource.metricFindQuery = async () => [{ value: 'test', label: 'test', text: 'test' }];
datasource.getNamespaces = jest.fn().mockResolvedValue([]); datasource.api.getNamespaces = jest.fn().mockResolvedValue([]);
datasource.getMetrics = jest.fn().mockResolvedValue([]); datasource.api.getMetrics = jest.fn().mockResolvedValue([]);
datasource.getRegions = jest.fn().mockResolvedValue([]); datasource.api.getRegions = jest.fn().mockResolvedValue([]);
datasource.getDimensionKeys = jest.fn().mockResolvedValue([]); datasource.api.getDimensionKeys = jest.fn().mockResolvedValue([]);
const props: Props = { const props: Props = {
query: { query: {
@ -150,7 +150,7 @@ describe('QueryEditor', () => {
if (props.query.queryMode !== 'Metrics') { if (props.query.queryMode !== 'Metrics') {
fail(`expected props.query.queryMode to be 'Metrics', got '${props.query.queryMode}' instead`); fail(`expected props.query.queryMode to be 'Metrics', got '${props.query.queryMode}' instead`);
} }
props.datasource.getDimensionValues = jest.fn().mockResolvedValue([[{ label: 'dimVal1', value: 'dimVal1' }]]); props.datasource.api.getDimensionValues = jest.fn().mockResolvedValue([[{ label: 'dimVal1', value: 'dimVal1' }]]);
props.query.metricQueryType = MetricQueryType.Search; props.query.metricQueryType = MetricQueryType.Search;
props.query.metricEditorMode = MetricEditorMode.Builder; props.query.metricEditorMode = MetricEditorMode.Builder;
props.query.dimensions = { instanceId: 'instance-123' }; props.query.dimensions = { instanceId: 'instance-123' };

View File

@ -10,7 +10,7 @@ import MetricsQueryHeader from './MetricsQueryHeader';
const ds = setupMockedDataSource({ const ds = setupMockedDataSource({
variables: [], variables: [],
}); });
ds.datasource.getRegions = jest.fn().mockResolvedValue([]); ds.datasource.api.getRegions = jest.fn().mockResolvedValue([]);
const query: CloudWatchMetricsQuery = { const query: CloudWatchMetricsQuery = {
id: '', id: '',
region: 'us-east-2', region: 'us-east-2',

View File

@ -9,7 +9,7 @@ import QueryHeader from './QueryHeader';
const ds = setupMockedDataSource({ const ds = setupMockedDataSource({
variables: [], variables: [],
}); });
ds.datasource.getRegions = jest.fn().mockResolvedValue([]); ds.datasource.api.getRegions = jest.fn().mockResolvedValue([]);
describe('QueryHeader', () => { describe('QueryHeader', () => {
it('should display metric options for metrics', async () => { it('should display metric options for metrics', async () => {

View File

@ -22,10 +22,10 @@ const makeSQLQuery = (sql?: SQLExpression): CloudWatchMetricsQuery => ({
describe('Cloudwatch SQLBuilderEditor', () => { describe('Cloudwatch SQLBuilderEditor', () => {
beforeEach(() => { beforeEach(() => {
datasource.getNamespaces = jest.fn().mockResolvedValue([]); datasource.api.getNamespaces = jest.fn().mockResolvedValue([]);
datasource.getMetrics = jest.fn().mockResolvedValue([]); datasource.api.getMetrics = jest.fn().mockResolvedValue([]);
datasource.getDimensionKeys = jest.fn().mockResolvedValue([]); datasource.api.getDimensionKeys = jest.fn().mockResolvedValue([]);
datasource.getDimensionValues = jest.fn().mockResolvedValue([]); datasource.api.getDimensionValues = jest.fn().mockResolvedValue([]);
}); });
const baseProps = { const baseProps = {
@ -47,7 +47,7 @@ describe('Cloudwatch SQLBuilderEditor', () => {
}); });
render(<SQLBuilderEditor {...baseProps} query={query} />); render(<SQLBuilderEditor {...baseProps} query={query} />);
await waitFor(() => expect(datasource.getNamespaces).toHaveBeenCalled()); await waitFor(() => expect(datasource.api.getNamespaces).toHaveBeenCalled());
expect(screen.getByText('AWS/EC2')).toBeInTheDocument(); expect(screen.getByText('AWS/EC2')).toBeInTheDocument();
expect(screen.getByLabelText('With schema')).not.toBeChecked(); expect(screen.getByLabelText('With schema')).not.toBeChecked();
@ -68,7 +68,7 @@ describe('Cloudwatch SQLBuilderEditor', () => {
}); });
render(<SQLBuilderEditor {...baseProps} query={query} />); render(<SQLBuilderEditor {...baseProps} query={query} />);
await waitFor(() => expect(datasource.getNamespaces).toHaveBeenCalled()); await waitFor(() => expect(datasource.api.getNamespaces).toHaveBeenCalled());
expect(screen.getByText('AWS/EC2')).toBeInTheDocument(); expect(screen.getByText('AWS/EC2')).toBeInTheDocument();
expect(screen.getByLabelText('With schema')).toBeChecked(); expect(screen.getByLabelText('With schema')).toBeChecked();
@ -95,7 +95,12 @@ describe('Cloudwatch SQLBuilderEditor', () => {
render(<SQLBuilderEditor {...baseProps} query={query} />); render(<SQLBuilderEditor {...baseProps} query={query} />);
await waitFor(() => await waitFor(() =>
expect(datasource.getDimensionKeys).toHaveBeenCalledWith('AWS/EC2', query.region, { InstanceId: null }, undefined) expect(datasource.api.getDimensionKeys).toHaveBeenCalledWith(
'AWS/EC2',
query.region,
{ InstanceId: null },
undefined
)
); );
expect(screen.getByText('AWS/EC2')).toBeInTheDocument(); expect(screen.getByText('AWS/EC2')).toBeInTheDocument();
expect(screen.getByLabelText('With schema')).toBeChecked(); expect(screen.getByLabelText('With schema')).toBeChecked();
@ -117,7 +122,7 @@ describe('Cloudwatch SQLBuilderEditor', () => {
}); });
render(<SQLBuilderEditor {...baseProps} query={query} />); render(<SQLBuilderEditor {...baseProps} query={query} />);
await waitFor(() => expect(datasource.getNamespaces).toHaveBeenCalled()); await waitFor(() => expect(datasource.api.getNamespaces).toHaveBeenCalled());
expect(screen.getByText('AVERAGE')).toBeInTheDocument(); expect(screen.getByText('AVERAGE')).toBeInTheDocument();
expect(screen.getByText('CPUUtilization')).toBeInTheDocument(); expect(screen.getByText('CPUUtilization')).toBeInTheDocument();
@ -133,7 +138,7 @@ describe('Cloudwatch SQLBuilderEditor', () => {
}); });
render(<SQLBuilderEditor {...baseProps} query={query} />); render(<SQLBuilderEditor {...baseProps} query={query} />);
await waitFor(() => expect(datasource.getNamespaces).toHaveBeenCalled()); await waitFor(() => expect(datasource.api.getNamespaces).toHaveBeenCalled());
expect(screen.getByText('AVG')).toBeInTheDocument(); expect(screen.getByText('AVG')).toBeInTheDocument();
const directionElement = screen.getByLabelText('Direction'); const directionElement = screen.getByLabelText('Direction');
@ -145,7 +150,7 @@ describe('Cloudwatch SQLBuilderEditor', () => {
const query = makeSQLQuery({}); const query = makeSQLQuery({});
render(<SQLBuilderEditor {...baseProps} query={query} />); render(<SQLBuilderEditor {...baseProps} query={query} />);
await waitFor(() => expect(datasource.getNamespaces).toHaveBeenCalled()); await waitFor(() => expect(datasource.api.getNamespaces).toHaveBeenCalled());
expect(screen.queryByText('AVG')).toBeNull(); expect(screen.queryByText('AVG')).toBeNull();
const directionElement = screen.getByLabelText('Direction'); const directionElement = screen.getByLabelText('Direction');

View File

@ -60,15 +60,15 @@ const metrics = [
describe('Cloudwatch SQLBuilderSelectRow', () => { describe('Cloudwatch SQLBuilderSelectRow', () => {
beforeEach(() => { beforeEach(() => {
datasource.getNamespaces = jest.fn().mockResolvedValue(namespaces); datasource.api.getNamespaces = jest.fn().mockResolvedValue(namespaces);
datasource.getMetrics = jest.fn().mockResolvedValue([]); datasource.api.getMetrics = jest.fn().mockResolvedValue([]);
datasource.getDimensionKeys = jest.fn().mockResolvedValue([]); datasource.api.getDimensionKeys = jest.fn().mockResolvedValue([]);
datasource.getDimensionValues = jest.fn().mockResolvedValue([]); datasource.api.getDimensionValues = jest.fn().mockResolvedValue([]);
onQueryChange.mockReset(); onQueryChange.mockReset();
}); });
it('Should not reset metricName when selecting a namespace if metric exist in new namespace', async () => { it('Should not reset metricName when selecting a namespace if metric exist in new namespace', async () => {
datasource.getMetrics = jest.fn().mockResolvedValue(metrics); datasource.api.getMetrics = jest.fn().mockResolvedValue(metrics);
await act(async () => { await act(async () => {
render(<SQLBuilderSelectRow {...baseProps} />); render(<SQLBuilderSelectRow {...baseProps} />);
@ -103,7 +103,7 @@ describe('Cloudwatch SQLBuilderSelectRow', () => {
}); });
it('Should reset metricName when selecting a namespace if metric does not exist in new namespace', async () => { it('Should reset metricName when selecting a namespace if metric does not exist in new namespace', async () => {
datasource.getMetrics = jest.fn().mockImplementation((namespace: string, region: string) => { datasource.api.getMetrics = jest.fn().mockImplementation((namespace: string, region: string) => {
let mockMetrics = let mockMetrics =
namespace === 'n1' && region === baseProps.query.region namespace === 'n1' && region === baseProps.query.region
? metrics ? metrics

View File

@ -62,7 +62,7 @@ const SQLBuilderSelectRow: React.FC<SQLBuilderSelectRowProps> = ({ datasource, q
const validateMetricName = async (query: CloudWatchMetricsQuery) => { const validateMetricName = async (query: CloudWatchMetricsQuery) => {
let { region, sql } = query; let { region, sql } = query;
await datasource.getMetrics(query.namespace, region).then((result: Array<SelectableValue<string>>) => { await datasource.api.getMetrics(query.namespace, region).then((result: Array<SelectableValue<string>>) => {
if (!result.some((metric) => metric.value === metricName)) { if (!result.some((metric) => metric.value === metricName)) {
sql = removeMetricName(query).sql; sql = removeMetricName(query).sql;
} }

View File

@ -108,7 +108,7 @@ const FilterItem: React.FC<FilterItemProps> = (props) => {
return []; return [];
} }
return datasource return datasource.api
.getDimensionValues(query.region, namespace, metricName, filter.property.name, {}) .getDimensionValues(query.region, namespace, metricName, filter.property.name, {})
.then((result: Array<SelectableValue<string>>) => { .then((result: Array<SelectableValue<string>>) => {
return appendTemplateVariables(datasource, result); return appendTemplateVariables(datasource, result);

View File

@ -22,22 +22,22 @@ const defaultQuery = {
const ds = setupMockedDataSource(); const ds = setupMockedDataSource();
ds.datasource.getRegions = jest.fn().mockResolvedValue([ ds.datasource.api.getRegions = jest.fn().mockResolvedValue([
{ label: 'a1', value: 'a1' }, { label: 'a1', value: 'a1' },
{ label: 'b1', value: 'b1' }, { label: 'b1', value: 'b1' },
{ label: 'c1', value: 'c1' }, { label: 'c1', value: 'c1' },
]); ]);
ds.datasource.getNamespaces = jest.fn().mockResolvedValue([ ds.datasource.api.getNamespaces = jest.fn().mockResolvedValue([
{ label: 'x2', value: 'x2' }, { label: 'x2', value: 'x2' },
{ label: 'y2', value: 'y2' }, { label: 'y2', value: 'y2' },
{ label: 'z2', value: 'z2' }, { label: 'z2', value: 'z2' },
]); ]);
ds.datasource.getMetrics = jest.fn().mockResolvedValue([ ds.datasource.api.getMetrics = jest.fn().mockResolvedValue([
{ label: 'h3', value: 'h3' }, { label: 'h3', value: 'h3' },
{ label: 'i3', value: 'i3' }, { label: 'i3', value: 'i3' },
{ label: 'j3', value: 'j3' }, { label: 'j3', value: 'j3' },
]); ]);
ds.datasource.getDimensionKeys = jest ds.datasource.api.getDimensionKeys = jest
.fn() .fn()
.mockImplementation((_namespace: string, region: string, dimensionFilters?: Dimensions) => { .mockImplementation((_namespace: string, region: string, dimensionFilters?: Dimensions) => {
if (!!dimensionFilters) { if (!!dimensionFilters) {
@ -55,12 +55,12 @@ ds.datasource.getDimensionKeys = jest
} }
return Promise.resolve([{ label: 't4', value: 't4' }]); return Promise.resolve([{ label: 't4', value: 't4' }]);
}); });
ds.datasource.getDimensionValues = jest.fn().mockResolvedValue([ ds.datasource.api.getDimensionValues = jest.fn().mockResolvedValue([
{ label: 'foo', value: 'foo' }, { label: 'foo', value: 'foo' },
{ label: 'bar', value: 'bar' }, { label: 'bar', value: 'bar' },
]); ]);
ds.datasource.getVariables = jest.fn().mockReturnValue([]); ds.datasource.getVariables = jest.fn().mockReturnValue([]);
ds.datasource.getEc2InstanceAttribute = jest.fn().mockReturnValue([]); ds.datasource.api.getEc2InstanceAttribute = jest.fn().mockReturnValue([]);
const onChange = jest.fn(); const onChange = jest.fn();
const defaultProps: Props = { const defaultProps: Props = {
@ -141,7 +141,7 @@ describe('VariableEditor', () => {
await select(keySelect, 'v4', { await select(keySelect, 'v4', {
container: document.body, container: document.body,
}); });
expect(ds.datasource.getDimensionKeys).toHaveBeenCalledWith('z2', 'a1', {}, ''); expect(ds.datasource.api.getDimensionKeys).toHaveBeenCalledWith('z2', 'a1', {}, '');
expect(onChange).toHaveBeenCalledWith({ expect(onChange).toHaveBeenCalledWith({
...defaultQuery, ...defaultQuery,
queryType: VariableQueryType.DimensionValues, queryType: VariableQueryType.DimensionValues,
@ -224,8 +224,8 @@ describe('VariableEditor', () => {
container: document.body, container: document.body,
}); });
expect(ds.datasource.getMetrics).toHaveBeenCalledWith('z2', 'b1'); expect(ds.datasource.api.getMetrics).toHaveBeenCalledWith('z2', 'b1');
expect(ds.datasource.getDimensionKeys).toHaveBeenCalledWith('z2', 'b1'); expect(ds.datasource.api.getDimensionKeys).toHaveBeenCalledWith('z2', 'b1');
expect(props.onChange).toHaveBeenCalledWith({ expect(props.onChange).toHaveBeenCalledWith({
...defaultQuery, ...defaultQuery,
refId: 'CloudWatchVariableQueryEditor-VariableQuery', refId: 'CloudWatchVariableQueryEditor-VariableQuery',

View File

@ -65,14 +65,14 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
const sanitizeQuery = async (query: VariableQuery) => { const sanitizeQuery = async (query: VariableQuery) => {
let { metricName, dimensionKey, dimensionFilters, namespace, region } = query; let { metricName, dimensionKey, dimensionFilters, namespace, region } = query;
if (metricName) { if (metricName) {
await datasource.getMetrics(namespace, region).then((result: Array<SelectableValue<string>>) => { await datasource.api.getMetrics(namespace, region).then((result: Array<SelectableValue<string>>) => {
if (!result.find((metric) => metric.value === metricName)) { if (!result.find((metric) => metric.value === metricName)) {
metricName = ''; metricName = '';
} }
}); });
} }
if (dimensionKey) { if (dimensionKey) {
await datasource.getDimensionKeys(namespace, region).then((result: Array<SelectableValue<string>>) => { await datasource.api.getDimensionKeys(namespace, region).then((result: Array<SelectableValue<string>>) => {
if (!result.find((key) => key.value === dimensionKey)) { if (!result.find((key) => key.value === dimensionKey)) {
dimensionKey = ''; dimensionKey = '';
dimensionFilters = {}; dimensionFilters = {};

View File

@ -204,7 +204,7 @@ describe('datasource', () => {
describe('resource requests', () => { describe('resource requests', () => {
it('should map resource response to metric response', async () => { it('should map resource response to metric response', async () => {
const datasource = setupMockedDataSource().datasource; const datasource = setupMockedDataSource().datasource;
datasource.doMetricResourceRequest = jest.fn().mockResolvedValue([ datasource.api.resourceRequest = jest.fn().mockResolvedValue([
{ {
text: 'AWS/EC2', text: 'AWS/EC2',
value: 'CPUUtilization', value: 'CPUUtilization',
@ -214,7 +214,7 @@ describe('datasource', () => {
value: 'CPUPercentage', value: 'CPUPercentage',
}, },
]); ]);
const allMetrics = await datasource.getAllMetrics('us-east-2'); const allMetrics = await datasource.api.getAllMetrics('us-east-2');
expect(allMetrics[0].metricName).toEqual('CPUUtilization'); expect(allMetrics[0].metricName).toEqual('CPUUtilization');
expect(allMetrics[0].namespace).toEqual('AWS/EC2'); expect(allMetrics[0].namespace).toEqual('AWS/EC2');
expect(allMetrics[1].metricName).toEqual('CPUPercentage'); expect(allMetrics[1].metricName).toEqual('CPUPercentage');

View File

@ -18,6 +18,7 @@ import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_sr
import { RowContextOptions } from '../../../features/logs/components/LogRowContextProvider'; import { RowContextOptions } from '../../../features/logs/components/LogRowContextProvider';
import { CloudWatchAnnotationSupport } from './annotationSupport'; import { CloudWatchAnnotationSupport } from './annotationSupport';
import { CloudWatchAPI } from './api';
import { SQLCompletionItemProvider } from './cloudwatch-sql/completion/CompletionItemProvider'; import { SQLCompletionItemProvider } from './cloudwatch-sql/completion/CompletionItemProvider';
import { isCloudWatchAnnotationQuery, isCloudWatchLogsQuery, isCloudWatchMetricsQuery } from './guards'; import { isCloudWatchAnnotationQuery, isCloudWatchLogsQuery, isCloudWatchMetricsQuery } from './guards';
import { CloudWatchLanguageProvider } from './language_provider'; import { CloudWatchLanguageProvider } from './language_provider';
@ -31,7 +32,6 @@ import {
CloudWatchLogsQuery, CloudWatchLogsQuery,
CloudWatchMetricsQuery, CloudWatchMetricsQuery,
CloudWatchQuery, CloudWatchQuery,
Dimensions,
} from './types'; } from './types';
import { CloudWatchVariableSupport } from './variables'; import { CloudWatchVariableSupport } from './variables';
@ -39,18 +39,18 @@ export class CloudWatchDatasource
extends DataSourceWithBackend<CloudWatchQuery, CloudWatchJsonData> extends DataSourceWithBackend<CloudWatchQuery, CloudWatchJsonData>
implements DataSourceWithLogsContextSupport<CloudWatchLogsQuery> implements DataSourceWithLogsContextSupport<CloudWatchLogsQuery>
{ {
defaultRegion: any; defaultRegion?: string;
languageProvider: CloudWatchLanguageProvider; languageProvider: CloudWatchLanguageProvider;
sqlCompletionItemProvider: SQLCompletionItemProvider; sqlCompletionItemProvider: SQLCompletionItemProvider;
metricMathCompletionItemProvider: MetricMathCompletionItemProvider; metricMathCompletionItemProvider: MetricMathCompletionItemProvider;
type = 'cloudwatch'; type = 'cloudwatch';
standardStatistics = ['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount'];
private metricsQueryRunner: CloudWatchMetricsQueryRunner; private metricsQueryRunner: CloudWatchMetricsQueryRunner;
private annotationQueryRunner: CloudWatchAnnotationQueryRunner; private annotationQueryRunner: CloudWatchAnnotationQueryRunner;
// this member should be private too, but we need to fix https://github.com/grafana/grafana/issues/55243 to enable that // this member should be private too, but we need to fix https://github.com/grafana/grafana/issues/55243 to enable that
logsQueryRunner: CloudWatchLogsQueryRunner; logsQueryRunner: CloudWatchLogsQueryRunner;
api: CloudWatchAPI;
constructor( constructor(
instanceSettings: DataSourceInstanceSettings<CloudWatchJsonData>, instanceSettings: DataSourceInstanceSettings<CloudWatchJsonData>,
@ -59,18 +59,17 @@ export class CloudWatchDatasource
) { ) {
super(instanceSettings); super(instanceSettings);
this.defaultRegion = instanceSettings.jsonData.defaultRegion; this.defaultRegion = instanceSettings.jsonData.defaultRegion;
this.api = new CloudWatchAPI(instanceSettings, templateSrv);
this.languageProvider = new CloudWatchLanguageProvider(this); this.languageProvider = new CloudWatchLanguageProvider(this);
this.sqlCompletionItemProvider = new SQLCompletionItemProvider(this, this.templateSrv); this.sqlCompletionItemProvider = new SQLCompletionItemProvider(this.api, this.templateSrv);
this.metricMathCompletionItemProvider = new MetricMathCompletionItemProvider(this, this.templateSrv); this.metricMathCompletionItemProvider = new MetricMathCompletionItemProvider(this.api, this.templateSrv);
this.variables = new CloudWatchVariableSupport(this);
this.annotations = CloudWatchAnnotationSupport;
this.metricsQueryRunner = new CloudWatchMetricsQueryRunner(instanceSettings, templateSrv); this.metricsQueryRunner = new CloudWatchMetricsQueryRunner(instanceSettings, templateSrv);
this.logsQueryRunner = new CloudWatchLogsQueryRunner(instanceSettings, templateSrv, timeSrv); this.logsQueryRunner = new CloudWatchLogsQueryRunner(instanceSettings, templateSrv, timeSrv);
this.annotationQueryRunner = new CloudWatchAnnotationQueryRunner(instanceSettings, templateSrv); this.annotationQueryRunner = new CloudWatchAnnotationQueryRunner(instanceSettings, templateSrv);
this.variables = new CloudWatchVariableSupport(this.api, this.logsQueryRunner);
this.annotations = CloudWatchAnnotationSupport;
} }
// datasource api
filterQuery(query: CloudWatchQuery) { filterQuery(query: CloudWatchQuery) {
return query.hide !== true || (isCloudWatchMetricsQuery(query) && query.id !== ''); return query.hide !== true || (isCloudWatchMetricsQuery(query) && query.id !== '');
} }
@ -79,18 +78,31 @@ export class CloudWatchDatasource
options = cloneDeep(options); options = cloneDeep(options);
let queries = options.targets.filter(this.filterQuery); let queries = options.targets.filter(this.filterQuery);
const { logQueries, metricsQueries, annotationQueries } = getTargetsByQueryMode(queries);
const logQueries: CloudWatchLogsQuery[] = [];
const metricsQueries: CloudWatchMetricsQuery[] = [];
const annotationQueries: CloudWatchAnnotationQuery[] = [];
queries.forEach((query) => {
if (isCloudWatchAnnotationQuery(query)) {
annotationQueries.push(query);
} else if (isCloudWatchLogsQuery(query)) {
logQueries.push(query);
} else {
metricsQueries.push(query);
}
});
const dataQueryResponses: Array<Observable<DataQueryResponse>> = []; const dataQueryResponses: Array<Observable<DataQueryResponse>> = [];
if (logQueries.length > 0) { if (logQueries.length) {
dataQueryResponses.push(this.logsQueryRunner.handleLogQueries(logQueries, options)); dataQueryResponses.push(this.logsQueryRunner.handleLogQueries(logQueries, options));
} }
if (metricsQueries.length > 0) { if (metricsQueries.length) {
dataQueryResponses.push(this.metricsQueryRunner.handleMetricQueries(metricsQueries, options)); dataQueryResponses.push(this.metricsQueryRunner.handleMetricQueries(metricsQueries, options));
} }
if (annotationQueries.length > 0) { if (annotationQueries.length) {
dataQueryResponses.push(this.annotationQueryRunner.handleAnnotationQuery(annotationQueries, options)); dataQueryResponses.push(this.annotationQueryRunner.handleAnnotationQuery(annotationQueries, options));
} }
// No valid targets, return the empty result to save a round trip. // No valid targets, return the empty result to save a round trip.
@ -111,8 +123,9 @@ export class CloudWatchDatasource
return queries.map((query) => ({ return queries.map((query) => ({
...query, ...query,
region: this.getActualRegion( region: this.metricsQueryRunner.replaceVariableAndDisplayWarningIfMulti(
this.metricsQueryRunner.replaceVariableAndDisplayWarningIfMulti(query.region, scopedVars) this.getActualRegion(query.region),
scopedVars
), ),
...(isCloudWatchMetricsQuery(query) && ...(isCloudWatchMetricsQuery(query) &&
this.metricsQueryRunner.interpolateMetricsQueryVariables(query, scopedVars)), this.metricsQueryRunner.interpolateMetricsQueryVariables(query, scopedVars)),
@ -155,134 +168,10 @@ export class CloudWatchDatasource
return this.templateSrv.getVariables().map((v) => `$${v.name}`); return this.templateSrv.getVariables().map((v) => `$${v.name}`);
} }
getDefaultRegion() {
return this.defaultRegion;
}
getActualRegion(region?: string) { getActualRegion(region?: string) {
if (region === 'default' || region === undefined || region === '') { if (region === 'default' || region === undefined || region === '') {
return this.getDefaultRegion(); return this.defaultRegion;
} }
return region; return region;
} }
doMetricResourceRequest(subtype: string, parameters?: any): Promise<Array<{ text: any; label: any; value: any }>> {
return this.getResource(subtype, parameters);
}
// resource requests
getRegions(): Promise<Array<{ label: string; value: string; text: string }>> {
return this.doMetricResourceRequest('regions').then((regions: any) => [
{ label: 'default', value: 'default', text: 'default' },
...regions,
]);
}
getNamespaces() {
return this.doMetricResourceRequest('namespaces');
}
async getMetrics(namespace: string | undefined, region?: string) {
if (!namespace) {
return [];
}
return this.doMetricResourceRequest('metrics', {
region: this.templateSrv.replace(this.getActualRegion(region)),
namespace: this.templateSrv.replace(namespace),
});
}
async getAllMetrics(region: string): Promise<Array<{ metricName: string; namespace: string }>> {
const values = await this.doMetricResourceRequest('all-metrics', {
region: this.templateSrv.replace(this.getActualRegion(region)),
});
return values.map((v) => ({ metricName: v.value, namespace: v.text }));
}
async getDimensionKeys(
namespace: string | undefined,
region: string,
dimensionFilters: Dimensions = {},
metricName = ''
) {
if (!namespace) {
return [];
}
return this.doMetricResourceRequest('dimension-keys', {
region: this.templateSrv.replace(this.getActualRegion(region)),
namespace: this.templateSrv.replace(namespace),
dimensionFilters: JSON.stringify(this.metricsQueryRunner.convertDimensionFormat(dimensionFilters, {})),
metricName,
});
}
async getDimensionValues(
region: string,
namespace: string | undefined,
metricName: string | undefined,
dimensionKey: string,
filterDimensions: {}
) {
if (!namespace || !metricName) {
return [];
}
const values = await this.doMetricResourceRequest('dimension-values', {
region: this.templateSrv.replace(this.getActualRegion(region)),
namespace: this.templateSrv.replace(namespace),
metricName: this.templateSrv.replace(metricName.trim()),
dimensionKey: this.templateSrv.replace(dimensionKey),
dimensions: JSON.stringify(this.metricsQueryRunner.convertDimensionFormat(filterDimensions, {})),
});
return values;
}
getEbsVolumeIds(region: string, instanceId: string) {
return this.doMetricResourceRequest('ebs-volume-ids', {
region: this.templateSrv.replace(this.getActualRegion(region)),
instanceId: this.templateSrv.replace(instanceId),
});
}
getEc2InstanceAttribute(region: string, attributeName: string, filters: any) {
return this.doMetricResourceRequest('ec2-instance-attribute', {
region: this.templateSrv.replace(this.getActualRegion(region)),
attributeName: this.templateSrv.replace(attributeName),
filters: JSON.stringify(this.metricsQueryRunner.convertMultiFilterFormat(filters, 'filter key')),
});
}
getResourceARNs(region: string, resourceType: string, tags: any) {
return this.doMetricResourceRequest('resource-arns', {
region: this.templateSrv.replace(this.getActualRegion(region)),
resourceType: this.templateSrv.replace(resourceType),
tags: JSON.stringify(this.metricsQueryRunner.convertMultiFilterFormat(tags, 'tag name')),
});
}
}
function getTargetsByQueryMode(targets: CloudWatchQuery[]) {
const logQueries: CloudWatchLogsQuery[] = [];
const metricsQueries: CloudWatchMetricsQuery[] = [];
const annotationQueries: CloudWatchAnnotationQuery[] = [];
targets.forEach((query) => {
if (isCloudWatchAnnotationQuery(query)) {
annotationQueries.push(query);
} else if (isCloudWatchLogsQuery(query)) {
logQueries.push(query);
} else {
metricsQueries.push(query);
}
});
return {
logQueries,
metricsQueries,
annotationQueries,
};
} }

View File

@ -19,7 +19,7 @@ export const useRegions = (datasource: CloudWatchDatasource): [Array<SelectableV
options: datasource.getVariables().map(toOption), options: datasource.getVariables().map(toOption),
}; };
datasource datasource.api
.getRegions() .getRegions()
.then((regions: Array<SelectableValue<string>>) => setRegions([...regions, variableOptionGroup])) .then((regions: Array<SelectableValue<string>>) => setRegions([...regions, variableOptionGroup]))
.finally(() => setRegionsIsLoading(false)); .finally(() => setRegionsIsLoading(false));
@ -31,7 +31,7 @@ export const useRegions = (datasource: CloudWatchDatasource): [Array<SelectableV
export const useNamespaces = (datasource: CloudWatchDatasource) => { export const useNamespaces = (datasource: CloudWatchDatasource) => {
const [namespaces, setNamespaces] = useState<Array<SelectableValue<string>>>([]); const [namespaces, setNamespaces] = useState<Array<SelectableValue<string>>>([]);
useEffect(() => { useEffect(() => {
datasource.getNamespaces().then((namespaces) => { datasource.api.getNamespaces().then((namespaces) => {
setNamespaces(appendTemplateVariables(datasource, namespaces)); setNamespaces(appendTemplateVariables(datasource, namespaces));
}); });
}, [datasource]); }, [datasource]);
@ -42,7 +42,7 @@ export const useNamespaces = (datasource: CloudWatchDatasource) => {
export const useMetrics = (datasource: CloudWatchDatasource, region: string, namespace: string | undefined) => { export const useMetrics = (datasource: CloudWatchDatasource, region: string, namespace: string | undefined) => {
const [metrics, setMetrics] = useState<Array<SelectableValue<string>>>([]); const [metrics, setMetrics] = useState<Array<SelectableValue<string>>>([]);
useEffect(() => { useEffect(() => {
datasource.getMetrics(namespace, region).then((result: Array<SelectableValue<string>>) => { datasource.api.getMetrics(namespace, region).then((result: Array<SelectableValue<string>>) => {
setMetrics(appendTemplateVariables(datasource, result)); setMetrics(appendTemplateVariables(datasource, result));
}); });
}, [datasource, region, namespace]); }, [datasource, region, namespace]);
@ -61,7 +61,7 @@ export const useDimensionKeys = (
// doing deep comparison to avoid making new api calls to list metrics unless dimension filter object props changes // doing deep comparison to avoid making new api calls to list metrics unless dimension filter object props changes
useDeepCompareEffect(() => { useDeepCompareEffect(() => {
datasource datasource.api
.getDimensionKeys(namespace, region, dimensionFilter, metricName) .getDimensionKeys(namespace, region, dimensionFilter, metricName)
.then((result: Array<SelectableValue<string>>) => { .then((result: Array<SelectableValue<string>>) => {
setDimensionKeys(appendTemplateVariables(datasource, result)); setDimensionKeys(appendTemplateVariables(datasource, result));

View File

@ -1,10 +1,10 @@
import { getTemplateSrv } from '@grafana/runtime';
import { Monaco, monacoTypes } from '@grafana/ui'; import { Monaco, monacoTypes } from '@grafana/ui';
import { setupMockedTemplateService } from '../../__mocks__/CloudWatchDataSource';
import * as MetricMathTestData from '../../__mocks__/metric-math-test-data'; import * as MetricMathTestData from '../../__mocks__/metric-math-test-data';
import MonacoMock from '../../__mocks__/monarch/Monaco'; import MonacoMock from '../../__mocks__/monarch/Monaco';
import TextModel from '../../__mocks__/monarch/TextModel'; import TextModel from '../../__mocks__/monarch/TextModel';
import { CloudWatchDatasource } from '../../datasource'; import { CloudWatchAPI } from '../../api';
import cloudWatchMetricMathLanguageDefinition from '../definition'; import cloudWatchMetricMathLanguageDefinition from '../definition';
import { import {
METRIC_MATH_FNS, METRIC_MATH_FNS,
@ -19,10 +19,9 @@ import { MetricMathCompletionItemProvider } from './CompletionItemProvider';
const getSuggestions = async (value: string, position: monacoTypes.IPosition) => { const getSuggestions = async (value: string, position: monacoTypes.IPosition) => {
const setup = new MetricMathCompletionItemProvider( const setup = new MetricMathCompletionItemProvider(
{ {
getVariables: () => [],
getActualRegion: () => 'us-east-2', getActualRegion: () => 'us-east-2',
} as any as CloudWatchDatasource, } as CloudWatchAPI,
getTemplateSrv() setupMockedTemplateService([])
); );
const monaco = MonacoMock as Monaco; const monaco = MonacoMock as Monaco;
const provider = setup.getCompletionProvider(monaco, cloudWatchMetricMathLanguageDefinition); const provider = setup.getCompletionProvider(monaco, cloudWatchMetricMathLanguageDefinition);

View File

@ -1,7 +1,7 @@
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import type { Monaco, monacoTypes } from '@grafana/ui'; import type { Monaco, monacoTypes } from '@grafana/ui';
import { CloudWatchDatasource } from '../../datasource'; import { CloudWatchAPI } from '../../api';
import { CompletionItemProvider } from '../../monarch/CompletionItemProvider'; import { CompletionItemProvider } from '../../monarch/CompletionItemProvider';
import { LinkedToken } from '../../monarch/LinkedToken'; import { LinkedToken } from '../../monarch/LinkedToken';
import { TRIGGER_SUGGEST } from '../../monarch/commands'; import { TRIGGER_SUGGEST } from '../../monarch/commands';
@ -21,8 +21,8 @@ import { MetricMathTokenTypes } from './types';
type CompletionItem = monacoTypes.languages.CompletionItem; type CompletionItem = monacoTypes.languages.CompletionItem;
export class MetricMathCompletionItemProvider extends CompletionItemProvider { export class MetricMathCompletionItemProvider extends CompletionItemProvider {
constructor(datasource: CloudWatchDatasource, templateSrv: TemplateSrv = getTemplateSrv()) { constructor(api: CloudWatchAPI, templateSrv: TemplateSrv = getTemplateSrv()) {
super(datasource, templateSrv); super(api, templateSrv);
this.getStatementPosition = getStatementPosition; this.getStatementPosition = getStatementPosition;
this.getSuggestionKinds = getSuggestionKinds; this.getSuggestionKinds = getSuggestionKinds;
this.tokenTypes = MetricMathTokenTypes; this.tokenTypes = MetricMathTokenTypes;
@ -110,11 +110,12 @@ export class MetricMathCompletionItemProvider extends CompletionItemProvider {
} }
// always suggest template variables // always suggest template variables
this.templateVariables.map((v) => { this.templateSrv.getVariables().map((v) => {
addSuggestion(v, { const variable = `$${v.name}`;
addSuggestion(variable, {
range, range,
label: v, label: variable,
insertText: v, insertText: variable,
kind: monaco.languages.CompletionItemKind.Variable, kind: monaco.languages.CompletionItemKind.Variable,
sortText: CompletionItemPriority.Low, sortText: CompletionItemPriority.Low,
}); });

View File

@ -1,7 +1,7 @@
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime'; import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import type { Monaco, monacoTypes } from '@grafana/ui'; import type { Monaco, monacoTypes } from '@grafana/ui';
import { CloudWatchDatasource } from '../datasource'; import { CloudWatchAPI } from '../api';
import { LinkedToken } from './LinkedToken'; import { LinkedToken } from './LinkedToken';
import { linkedTokenBuilder } from './linkedTokenBuilder'; import { linkedTokenBuilder } from './linkedTokenBuilder';
@ -18,15 +18,13 @@ CompletionItemProvider is an extendable class which needs to implement :
- getSuggestions - getSuggestions
*/ */
export class CompletionItemProvider implements Completeable { export class CompletionItemProvider implements Completeable {
templateVariables: string[]; api: CloudWatchAPI;
datasource: CloudWatchDatasource;
templateSrv: TemplateSrv; templateSrv: TemplateSrv;
tokenTypes: TokenTypes; tokenTypes: TokenTypes;
constructor(datasource: CloudWatchDatasource, templateSrv: TemplateSrv = getTemplateSrv()) { constructor(api: CloudWatchAPI, templateSrv: TemplateSrv = getTemplateSrv()) {
this.datasource = datasource; this.api = api;
this.templateSrv = templateSrv; this.templateSrv = templateSrv;
this.templateVariables = this.datasource.getVariables();
this.templateSrv = templateSrv; this.templateSrv = templateSrv;
// implement with more specific tokens when extending this class // implement with more specific tokens when extending this class

View File

@ -6,10 +6,10 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
import { CloudWatchAnnotationQuery, CloudWatchJsonData, CloudWatchQuery } from '../types'; import { CloudWatchAnnotationQuery, CloudWatchJsonData, CloudWatchQuery } from '../types';
import { CloudWatchQueryRunner } from './CloudWatchQueryRunner'; import { CloudWatchRequest } from './CloudWatchRequest';
// This class handles execution of CloudWatch annotation queries // This class handles execution of CloudWatch annotation queries
export class CloudWatchAnnotationQueryRunner extends CloudWatchQueryRunner { export class CloudWatchAnnotationQueryRunner extends CloudWatchRequest {
constructor(instanceSettings: DataSourceInstanceSettings<CloudWatchJsonData>, templateSrv: TemplateSrv) { constructor(instanceSettings: DataSourceInstanceSettings<CloudWatchJsonData>, templateSrv: TemplateSrv) {
super(instanceSettings, templateSrv); super(instanceSettings, templateSrv);
} }

View File

@ -51,13 +51,13 @@ import { addDataLinksToLogsResponse } from '../utils/datalinks';
import { runWithRetry } from '../utils/logsRetry'; import { runWithRetry } from '../utils/logsRetry';
import { increasingInterval } from '../utils/rxjs/increasingInterval'; import { increasingInterval } from '../utils/rxjs/increasingInterval';
import { CloudWatchQueryRunner } from './CloudWatchQueryRunner'; import { CloudWatchRequest } from './CloudWatchRequest';
export const LOG_IDENTIFIER_INTERNAL = '__log__grafana_internal__'; export const LOG_IDENTIFIER_INTERNAL = '__log__grafana_internal__';
export const LOGSTREAM_IDENTIFIER_INTERNAL = '__logstream__grafana_internal__'; export const LOGSTREAM_IDENTIFIER_INTERNAL = '__logstream__grafana_internal__';
// This class handles execution of CloudWatch logs query data queries // This class handles execution of CloudWatch logs query data queries
export class CloudWatchLogsQueryRunner extends CloudWatchQueryRunner { export class CloudWatchLogsQueryRunner extends CloudWatchRequest {
logsTimeout: string; logsTimeout: string;
defaultLogGroups: string[]; defaultLogGroups: string[];
logQueries: Record<string, { id: string; region: string; statsQuery: boolean }> = {}; logQueries: Record<string, { id: string; region: string; statsQuery: boolean }> = {};

View File

@ -34,7 +34,7 @@ import {
MetricRequest, MetricRequest,
} from '../types'; } from '../types';
import { CloudWatchQueryRunner } from './CloudWatchQueryRunner'; import { CloudWatchRequest } from './CloudWatchRequest';
const displayAlert = (datasourceName: string, region: string) => const displayAlert = (datasourceName: string, region: string) =>
store.dispatch( store.dispatch(
@ -47,9 +47,8 @@ const displayAlert = (datasourceName: string, region: string) =>
) )
) )
); );
// This class handles execution of CloudWatch metrics query data queries // This class handles execution of CloudWatch metrics query data queries
export class CloudWatchMetricsQueryRunner extends CloudWatchQueryRunner { export class CloudWatchMetricsQueryRunner extends CloudWatchRequest {
debouncedAlert: (datasourceName: string, region: string) => void = memoizedDebounce( debouncedAlert: (datasourceName: string, region: string) => void = memoizedDebounce(
displayAlert, displayAlert,
AppNotificationTimeout.Error AppNotificationTimeout.Error

View File

@ -11,7 +11,7 @@ import { AppNotificationTimeout } from 'app/types';
import memoizedDebounce from '../memoizedDebounce'; import memoizedDebounce from '../memoizedDebounce';
import { CloudWatchJsonData, Dimensions, MetricRequest, MultiFilters, TSDBResponse } from '../types'; import { CloudWatchJsonData, Dimensions, MetricRequest, MultiFilters, TSDBResponse } from '../types';
export abstract class CloudWatchQueryRunner { export abstract class CloudWatchRequest {
templateSrv: TemplateSrv; templateSrv: TemplateSrv;
ref: DataSourceRef; ref: DataSourceRef;
dsQueryEndpoint = '/api/ds/query'; dsQueryEndpoint = '/api/ds/query';

View File

@ -0,0 +1 @@
export const standardStatistics = ['Average', 'Maximum', 'Minimum', 'Sum', 'SampleCount'];

View File

@ -14,18 +14,18 @@ const defaultQuery: VariableQuery = {
refId: '', refId: '',
}; };
const ds = setupMockedDataSource({ variables: [labelsVariable, dimensionVariable] }); const mock = setupMockedDataSource({ variables: [labelsVariable, dimensionVariable] });
ds.datasource.getRegions = jest.fn().mockResolvedValue([{ label: 'a', value: 'a' }]); mock.datasource.api.getRegions = jest.fn().mockResolvedValue([{ label: 'a', value: 'a' }]);
ds.datasource.getNamespaces = jest.fn().mockResolvedValue([{ label: 'b', value: 'b' }]); mock.datasource.api.getNamespaces = jest.fn().mockResolvedValue([{ label: 'b', value: 'b' }]);
ds.datasource.getMetrics = jest.fn().mockResolvedValue([{ label: 'c', value: 'c' }]); mock.datasource.api.getMetrics = jest.fn().mockResolvedValue([{ label: 'c', value: 'c' }]);
ds.datasource.getDimensionKeys = jest.fn().mockResolvedValue([{ label: 'd', value: 'd' }]); mock.datasource.api.getDimensionKeys = jest.fn().mockResolvedValue([{ label: 'd', value: 'd' }]);
ds.datasource.logsQueryRunner.describeAllLogGroups = jest.fn().mockResolvedValue(['a', 'b']); mock.datasource.logsQueryRunner.describeAllLogGroups = jest.fn().mockResolvedValue(['a', 'b']);
const getDimensionValues = jest.fn().mockResolvedValue([{ label: 'e', value: 'e' }]); const getDimensionValues = jest.fn().mockResolvedValue([{ label: 'e', value: 'e' }]);
const getEbsVolumeIds = jest.fn().mockResolvedValue([{ label: 'f', value: 'f' }]); const getEbsVolumeIds = jest.fn().mockResolvedValue([{ label: 'f', value: 'f' }]);
const getEc2InstanceAttribute = jest.fn().mockResolvedValue([{ label: 'g', value: 'g' }]); const getEc2InstanceAttribute = jest.fn().mockResolvedValue([{ label: 'g', value: 'g' }]);
const getResourceARNs = jest.fn().mockResolvedValue([{ label: 'h', value: 'h' }]); const getResourceARNs = jest.fn().mockResolvedValue([{ label: 'h', value: 'h' }]);
const variables = new CloudWatchVariableSupport(ds.datasource); const variables = new CloudWatchVariableSupport(mock.datasource.api, mock.datasource.logsQueryRunner);
describe('variables', () => { describe('variables', () => {
it('should run regions', async () => { it('should run regions', async () => {
@ -57,7 +57,7 @@ describe('variables', () => {
dimensionFilters: { a: 'b' }, dimensionFilters: { a: 'b' },
}; };
beforeEach(() => { beforeEach(() => {
ds.datasource.getDimensionValues = getDimensionValues; mock.datasource.api.getDimensionValues = getDimensionValues;
getDimensionValues.mockClear(); getDimensionValues.mockClear();
}); });
@ -87,7 +87,7 @@ describe('variables', () => {
describe('EBS volume ids', () => { describe('EBS volume ids', () => {
beforeEach(() => { beforeEach(() => {
ds.datasource.getEbsVolumeIds = getEbsVolumeIds; mock.datasource.api.getEbsVolumeIds = getEbsVolumeIds;
getEbsVolumeIds.mockClear(); getEbsVolumeIds.mockClear();
}); });
@ -116,7 +116,7 @@ describe('variables', () => {
ec2Filters: { a: ['b'] }, ec2Filters: { a: ['b'] },
}; };
beforeEach(() => { beforeEach(() => {
ds.datasource.getEc2InstanceAttribute = getEc2InstanceAttribute; mock.datasource.api.getEc2InstanceAttribute = getEc2InstanceAttribute;
getEc2InstanceAttribute.mockClear(); getEc2InstanceAttribute.mockClear();
}); });
@ -141,7 +141,7 @@ describe('variables', () => {
tags: { a: ['b'] }, tags: { a: ['b'] },
}; };
beforeEach(() => { beforeEach(() => {
ds.datasource.getResourceARNs = getResourceARNs; mock.datasource.api.getResourceARNs = getResourceARNs;
getResourceARNs.mockClear(); getResourceARNs.mockClear();
}); });

View File

@ -3,17 +3,17 @@ import { map } from 'rxjs/operators';
import { CustomVariableSupport, DataQueryRequest, DataQueryResponse } from '@grafana/data'; import { CustomVariableSupport, DataQueryRequest, DataQueryResponse } from '@grafana/data';
import { CloudWatchAPI } from './api';
import { VariableQueryEditor } from './components/VariableQueryEditor/VariableQueryEditor'; import { VariableQueryEditor } from './components/VariableQueryEditor/VariableQueryEditor';
import { CloudWatchDatasource } from './datasource'; import { CloudWatchDatasource } from './datasource';
import { migrateVariableQuery } from './migrations/variableQueryMigrations'; import { migrateVariableQuery } from './migrations/variableQueryMigrations';
import { CloudWatchLogsQueryRunner } from './query-runner/CloudWatchLogsQueryRunner';
import { standardStatistics } from './standardStatistics';
import { VariableQuery, VariableQueryType } from './types'; import { VariableQuery, VariableQueryType } from './types';
export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchDatasource, VariableQuery> { export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchDatasource, VariableQuery> {
private readonly datasource: CloudWatchDatasource; constructor(private readonly api: CloudWatchAPI, private readonly logsQueryRunner: CloudWatchLogsQueryRunner) {
constructor(datasource: CloudWatchDatasource) {
super(); super();
this.datasource = datasource;
this.query = this.query.bind(this); this.query = this.query.bind(this);
} }
@ -55,7 +55,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
} }
async handleLogGroupsQuery({ region, logGroupPrefix }: VariableQuery) { async handleLogGroupsQuery({ region, logGroupPrefix }: VariableQuery) {
const logGroups = await this.datasource.logsQueryRunner.describeAllLogGroups({ const logGroups: string[] = await this.logsQueryRunner.describeAllLogGroups({
region, region,
logGroupNamePrefix: logGroupPrefix, logGroupNamePrefix: logGroupPrefix,
}); });
@ -67,7 +67,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
} }
async handleRegionsQuery() { async handleRegionsQuery() {
const regions = await this.datasource.getRegions(); const regions = await this.api.getRegions();
return regions.map((s: { label: string; value: string }) => ({ return regions.map((s: { label: string; value: string }) => ({
text: s.label, text: s.label,
value: s.value, value: s.value,
@ -76,7 +76,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
} }
async handleNamespacesQuery() { async handleNamespacesQuery() {
const namespaces = await this.datasource.getNamespaces(); const namespaces = await this.api.getNamespaces();
return namespaces.map((s: { label: string; value: string }) => ({ return namespaces.map((s: { label: string; value: string }) => ({
text: s.label, text: s.label,
value: s.value, value: s.value,
@ -85,7 +85,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
} }
async handleMetricsQuery({ namespace, region }: VariableQuery) { async handleMetricsQuery({ namespace, region }: VariableQuery) {
const metrics = await this.datasource.getMetrics(namespace, region); const metrics = await this.api.getMetrics(namespace, region);
return metrics.map((s: { label: string; value: string }) => ({ return metrics.map((s: { label: string; value: string }) => ({
text: s.label, text: s.label,
value: s.value, value: s.value,
@ -94,7 +94,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
} }
async handleDimensionKeysQuery({ namespace, region }: VariableQuery) { async handleDimensionKeysQuery({ namespace, region }: VariableQuery) {
const keys = await this.datasource.getDimensionKeys(namespace, region); const keys = await this.api.getDimensionKeys(namespace, region);
return keys.map((s: { label: string; value: string }) => ({ return keys.map((s: { label: string; value: string }) => ({
text: s.label, text: s.label,
value: s.value, value: s.value,
@ -106,13 +106,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
if (!dimensionKey || !metricName) { if (!dimensionKey || !metricName) {
return []; return [];
} }
const keys = await this.datasource.getDimensionValues( const keys = await this.api.getDimensionValues(region, namespace, metricName, dimensionKey, dimensionFilters ?? {});
region,
namespace,
metricName,
dimensionKey,
dimensionFilters ?? {}
);
return keys.map((s: { label: string; value: string }) => ({ return keys.map((s: { label: string; value: string }) => ({
text: s.label, text: s.label,
value: s.value, value: s.value,
@ -124,7 +118,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
if (!instanceID) { if (!instanceID) {
return []; return [];
} }
const ids = await this.datasource.getEbsVolumeIds(region, instanceID); const ids = await this.api.getEbsVolumeIds(region, instanceID);
return ids.map((s: { label: string; value: string }) => ({ return ids.map((s: { label: string; value: string }) => ({
text: s.label, text: s.label,
value: s.value, value: s.value,
@ -136,7 +130,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
if (!attributeName) { if (!attributeName) {
return []; return [];
} }
const values = await this.datasource.getEc2InstanceAttribute(region, attributeName, ec2Filters ?? {}); const values = await this.api.getEc2InstanceAttribute(region, attributeName, ec2Filters ?? {});
return values.map((s: { label: string; value: string }) => ({ return values.map((s: { label: string; value: string }) => ({
text: s.label, text: s.label,
value: s.value, value: s.value,
@ -148,7 +142,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
if (!resourceType) { if (!resourceType) {
return []; return [];
} }
const keys = await this.datasource.getResourceARNs(region, resourceType, tags ?? {}); const keys = await this.api.getResourceARNs(region, resourceType, tags ?? {});
return keys.map((s: { label: string; value: string }) => ({ return keys.map((s: { label: string; value: string }) => ({
text: s.label, text: s.label,
value: s.value, value: s.value,
@ -157,7 +151,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
} }
async handleStatisticsQuery() { async handleStatisticsQuery() {
return this.datasource.standardStatistics.map((s: string) => ({ return standardStatistics.map((s: string) => ({
text: s, text: s,
value: s, value: s,
expandable: true, expandable: true,