diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx index 02355d2566d..2d1e6895d34 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx @@ -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) => { 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 && ( + onQueryChange({ ...parsedQuery, accountId })} + options={[ALL_ACCOUNTS_OPTION, ...accountState?.value]} + allowCustomValue={false} + /> + )} {hasNamespaceField && ( { return this.memoizedGetRequest>>('accounts', { - region: this.templateSrv.replace(region), + region: this.templateSrv.replace(this.getActualRegion(region)), }).then((accounts) => accounts.map((a) => a.value)); } diff --git a/public/app/plugins/datasource/cloudwatch/resources/types.ts b/public/app/plugins/datasource/cloudwatch/resources/types.ts index 72dd51844d4..ef0e071a9e7 100644 --- a/public/app/plugins/datasource/cloudwatch/resources/types.ts +++ b/public/app/plugins/datasource/cloudwatch/resources/types.ts @@ -39,6 +39,7 @@ export interface DescribeLogGroupsRequest extends ResourceRequest { logGroupPattern?: string; limit?: number; listAllLogGroups?: boolean; + accountId?: string; } export interface Account { diff --git a/public/app/plugins/datasource/cloudwatch/types.ts b/public/app/plugins/datasource/cloudwatch/types.ts index ed445ecd9e5..a9113aa445c 100644 --- a/public/app/plugins/datasource/cloudwatch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/types.ts @@ -304,6 +304,7 @@ export interface VariableQuery extends DataQuery { resourceType: string; tags?: MultiFilters; logGroupPrefix?: string; + accountId?: string; } export interface LegacyAnnotationQuery extends MetricStat, DataQuery { diff --git a/public/app/plugins/datasource/cloudwatch/variables.test.ts b/public/app/plugins/datasource/cloudwatch/variables.test.ts index 40ec6950430..88350e3e331 100644 --- a/public/app/plugins/datasource/cloudwatch/variables.test.ts +++ b/public/app/plugins/datasource/cloudwatch/variables.test.ts @@ -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, }); }); }); diff --git a/public/app/plugins/datasource/cloudwatch/variables.ts b/public/app/plugins/datasource/cloudwatch/variables.ts index 76bfcc92366..43209b47efd 100644 --- a/public/app/plugins/datasource/cloudwatch/variables.ts +++ b/public/app/plugins/datasource/cloudwatch/variables.ts @@ -62,10 +62,12 @@ export class CloudWatchVariableSupport extends CustomVariableSupport 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,