CloudWatch: Add account support to variable queries (#63822)

Co-authored-by: Erik Sundell <erik.sundell87@gmail.com>
pull/63808/head^2
Isabella Siu 2 years ago committed by GitHub
parent 33f66e6350
commit 59ac0a03fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx
  2. 2
      public/app/plugins/datasource/cloudwatch/resources/ResourcesAPI.ts
  3. 1
      public/app/plugins/datasource/cloudwatch/resources/types.ts
  4. 1
      public/app/plugins/datasource/cloudwatch/types.ts
  5. 26
      public/app/plugins/datasource/cloudwatch/variables.test.ts
  6. 22
      public/app/plugins/datasource/cloudwatch/variables.ts

@ -6,9 +6,10 @@ import { InlineField } from '@grafana/ui';
import { Dimensions } from '..';
import { CloudWatchDatasource } from '../../datasource';
import { useDimensionKeys, useMetrics, useNamespaces, useRegions } from '../../hooks';
import { useAccountOptions, useDimensionKeys, useMetrics, useNamespaces, useRegions } from '../../hooks';
import { migrateVariableQuery } from '../../migrations/variableQueryMigrations';
import { CloudWatchJsonData, CloudWatchQuery, VariableQuery, VariableQueryType } from '../../types';
import { ALL_ACCOUNTS_OPTION } from '../Account';
import { MultiFilter } from './MultiFilter';
import { VariableQueryField } from './VariableQueryField';
@ -41,11 +42,13 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
const metrics = useMetrics(datasource, { region, namespace });
const dimensionKeys = useDimensionKeys(datasource, { region, namespace, metricName });
const keysForDimensionFilter = useDimensionKeys(datasource, { region, namespace, metricName, dimensionFilters });
const accountState = useAccountOptions(datasource.resources, query.region);
const onRegionChange = async (region: string) => {
const validatedQuery = await sanitizeQuery({
...parsedQuery,
region,
accountId: undefined,
});
onQueryChange(validatedQuery);
};
@ -98,6 +101,12 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
VariableQueryType.LogGroups,
VariableQueryType.Accounts,
].includes(parsedQuery.queryType);
const hasAccountIDField = [
VariableQueryType.Metrics,
VariableQueryType.DimensionKeys,
VariableQueryType.DimensionValues,
VariableQueryType.LogGroups,
].includes(parsedQuery.queryType);
const hasNamespaceField = [
VariableQueryType.Metrics,
VariableQueryType.DimensionKeys,
@ -108,7 +117,9 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
<VariableQueryField
value={parsedQuery.queryType}
options={queryTypes}
onChange={(value: VariableQueryType) => onQueryChange({ ...parsedQuery, queryType: value })}
onChange={(value: VariableQueryType) =>
onQueryChange({ ...parsedQuery, queryType: value, accountId: undefined })
}
label="Query type"
inputId={`variable-query-type-${query.refId}`}
/>
@ -122,6 +133,18 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => {
inputId={`variable-query-region-${query.refId}`}
/>
)}
{hasAccountIDField &&
accountState.value &&
accountState.value?.length > 0 &&
config.featureToggles.cloudWatchCrossAccountQuerying && (
<VariableQueryField
label="Account"
value={query.accountId ?? null}
onChange={(accountId?: string) => onQueryChange({ ...parsedQuery, accountId })}
options={[ALL_ACCOUNTS_OPTION, ...accountState?.value]}
allowCustomValue={false}
/>
)}
{hasNamespaceField && (
<VariableQueryField
value={namespace}

@ -37,7 +37,7 @@ export class ResourcesAPI extends CloudWatchRequest {
getAccounts({ region }: ResourceRequest): Promise<Account[]> {
return this.memoizedGetRequest<Array<ResourceResponse<Account>>>('accounts', {
region: this.templateSrv.replace(region),
region: this.templateSrv.replace(this.getActualRegion(region)),
}).then((accounts) => accounts.map((a) => a.value));
}

@ -39,6 +39,7 @@ export interface DescribeLogGroupsRequest extends ResourceRequest {
logGroupPattern?: string;
limit?: number;
listAllLogGroups?: boolean;
accountId?: string;
}
export interface Account {

@ -304,6 +304,7 @@ export interface VariableQuery extends DataQuery {
resourceType: string;
tags?: MultiFilters;
logGroupPrefix?: string;
accountId?: string;
}
export interface LegacyAnnotationQuery extends MetricStat, DataQuery {

@ -23,9 +23,9 @@ const defaultQuery: VariableQuery = {
const mock = setupMockedDataSource({ variables: [labelsVariable, dimensionVariable, fieldsVariable] });
mock.datasource.resources.getRegions = jest.fn().mockResolvedValue([{ label: 'a', value: 'a' }]);
mock.datasource.resources.getNamespaces = jest.fn().mockResolvedValue([{ label: 'b', value: 'b' }]);
mock.datasource.resources.getMetrics = jest.fn().mockResolvedValue([{ label: 'c', value: 'c' }]);
mock.datasource.resources.getDimensionKeys = jest.fn().mockResolvedValue([{ label: 'd', value: 'd' }]);
mock.datasource.resources.getAccounts = jest.fn().mockResolvedValue([]);
const getMetrics = jest.fn().mockResolvedValue([{ label: 'c', value: 'c' }]);
const getDimensionKeys = jest.fn().mockResolvedValue([{ label: 'd', value: 'd' }]);
const getDimensionValues = jest.fn().mockResolvedValue([{ label: 'e', value: 'e' }]);
const getEbsVolumeIds = jest.fn().mockResolvedValue([{ label: 'f', value: 'f' }]);
const getEc2InstanceAttribute = jest.fn().mockResolvedValue([{ label: 'g', value: 'g' }]);
@ -48,12 +48,26 @@ describe('variables', () => {
});
it('should run metrics', async () => {
const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.Metrics });
mock.datasource.resources.getMetrics = getMetrics;
const query = { ...defaultQuery, queryType: VariableQueryType.Metrics, accountId: '123' };
const result = await variables.execute(query);
expect(getMetrics).toBeCalledWith({
region: query.region,
namespace: 'foo',
accountId: query.accountId,
});
expect(result).toEqual([{ text: 'c', value: 'c', expandable: true }]);
});
it('should run dimension keys', async () => {
const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.DimensionKeys });
mock.datasource.resources.getDimensionKeys = getDimensionKeys;
const query = { ...defaultQuery, queryType: VariableQueryType.DimensionKeys, accountId: '123' };
const result = await variables.execute(query);
expect(getDimensionKeys).toBeCalledWith({
region: query.region,
namespace: query.namespace,
accountId: query.accountId,
});
expect(result).toEqual([{ text: 'd', value: 'd', expandable: true }]);
});
@ -86,6 +100,7 @@ describe('variables', () => {
metricName: 'abc',
dimensionKey: 'efg',
dimensionFilters: { a: 'b' },
accountId: '123',
};
beforeEach(() => {
mock.datasource.resources.getDimensionValues = getDimensionValues;
@ -111,6 +126,7 @@ describe('variables', () => {
metricName: query.metricName,
dimensionKey: query.dimensionKey,
dimensionFilters: query.dimensionFilters,
accountId: query.accountId,
});
expect(result).toEqual([{ text: 'e', value: 'e', expandable: true }]);
});
@ -218,12 +234,14 @@ describe('variables', () => {
...defaultQuery,
queryType: VariableQueryType.LogGroups,
logGroupPrefix: '$fields',
accountId: '123',
};
await variables.execute(query);
expect(getLogGroups).toBeCalledWith({
region: query.region,
logGroupNamePrefix: 'templatedField',
listAllLogGroups: true,
accountId: query.accountId,
});
});
});

@ -62,10 +62,12 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
return [];
}
}
async handleLogGroupsQuery({ region, logGroupPrefix }: VariableQuery) {
async handleLogGroupsQuery({ region, logGroupPrefix, accountId }: VariableQuery) {
const interpolatedPrefix = this.resources.templateSrv.replace(logGroupPrefix);
return this.resources
.getLogGroups({
accountId,
region,
logGroupNamePrefix: interpolatedPrefix,
listAllLogGroups: true,
@ -89,25 +91,33 @@ export class CloudWatchVariableSupport extends CustomVariableSupport<CloudWatchD
return this.resources.getNamespaces().then((namespaces) => namespaces.map(selectableValueToMetricFindOption));
}
async handleMetricsQuery({ namespace, region }: VariableQuery) {
async handleMetricsQuery({ namespace, region, accountId }: VariableQuery) {
return this.resources
.getMetrics({ namespace, region })
.getMetrics({ namespace, region, accountId })
.then((metrics) => metrics.map(selectableValueToMetricFindOption));
}
async handleDimensionKeysQuery({ namespace, region }: VariableQuery) {
async handleDimensionKeysQuery({ namespace, region, accountId }: VariableQuery) {
return this.resources
.getDimensionKeys({ namespace, region })
.getDimensionKeys({ namespace, region, accountId })
.then((keys) => keys.map(selectableValueToMetricFindOption));
}
async handleDimensionValuesQuery({ namespace, region, dimensionKey, metricName, dimensionFilters }: VariableQuery) {
async handleDimensionValuesQuery({
namespace,
accountId,
region,
dimensionKey,
metricName,
dimensionFilters,
}: VariableQuery) {
if (!dimensionKey || !metricName) {
return [];
}
return this.resources
.getDimensionValues({
region,
accountId,
namespace,
metricName,
dimensionKey,

Loading…
Cancel
Save