From 992c0604f9badc2de1356ce721cd0a5667712bb4 Mon Sep 17 00:00:00 2001 From: Isabella Siu Date: Tue, 19 Apr 2022 10:50:18 -0400 Subject: [PATCH] CloudWatch: add dimensions component to variable editor (#47596) --- package.json | 2 +- .../Dimensions.test.tsx | 0 .../Dimensions.tsx | 12 +- .../FilterItem.tsx | 6 +- .../VariableQueryEditor.test.tsx | 98 ++++++++-- .../VariableQueryEditor.tsx | 37 ++-- .../VariableQueryField.tsx | 7 +- .../VariableQueryEditor/VariableTextField.tsx | 4 +- .../datasource/cloudwatch/components/index.ts | 2 +- .../datasource/cloudwatch/migration.test.ts | 13 +- .../datasource/cloudwatch/migrations.ts | 13 +- .../plugins/datasource/cloudwatch/types.ts | 9 +- .../datasource/cloudwatch/variables.test.ts | 172 ++++++++++++++++++ .../datasource/cloudwatch/variables.ts | 16 +- yarn.lock | 10 +- 15 files changed, 338 insertions(+), 63 deletions(-) rename public/app/plugins/datasource/cloudwatch/components/{MetricStatEditor => Dimensions}/Dimensions.test.tsx (100%) rename public/app/plugins/datasource/cloudwatch/components/{MetricStatEditor => Dimensions}/Dimensions.tsx (88%) rename public/app/plugins/datasource/cloudwatch/components/{MetricStatEditor => Dimensions}/FilterItem.tsx (96%) create mode 100644 public/app/plugins/datasource/cloudwatch/variables.test.ts diff --git a/package.json b/package.json index 2706e103b50..8c2633c5788 100644 --- a/package.json +++ b/package.json @@ -249,7 +249,7 @@ "@grafana/aws-sdk": "0.0.35", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "0.0.2-canary.22", + "@grafana/experimental": "^0.0.2-canary.25", "@grafana/google-sdk": "0.0.3", "@grafana/lezer-logql": "^0.0.11", "@grafana/runtime": "workspace:*", diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.test.tsx b/public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.test.tsx similarity index 100% rename from public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.test.tsx rename to public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.test.tsx diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.tsx b/public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.tsx similarity index 88% rename from public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.tsx rename to public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.tsx index ae645e73d6e..baccc38d70e 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/Dimensions.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/Dimensions/Dimensions.tsx @@ -1,13 +1,13 @@ import { isEqual } from 'lodash'; -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { SelectableValue } from '@grafana/data'; import { EditorList } from '@grafana/experimental'; import { CloudWatchDatasource } from '../../datasource'; -import { CloudWatchMetricsQuery, Dimensions as DimensionsType } from '../../types'; +import { Dimensions as DimensionsType, DimensionsQuery } from '../../types'; import { FilterItem } from './FilterItem'; export interface Props { - query: CloudWatchMetricsQuery; + query: DimensionsQuery; onChange: (dimensions: DimensionsType) => void; datasource: CloudWatchDatasource; dimensionKeys: Array>; @@ -43,8 +43,8 @@ const filterConditionsToDimensions = (filters: DimensionFilterCondition[]) => { }; export const Dimensions: React.FC = ({ query, datasource, dimensionKeys, disableExpressions, onChange }) => { - const dimensionFilters = useMemo(() => dimensionsToFilterConditions(query.dimensions), [query.dimensions]); - const [items, setItems] = useState(dimensionFilters); + const [items, setItems] = useState([]); + useEffect(() => setItems(dimensionsToFilterConditions(query.dimensions)), [query.dimensions]); const onDimensionsChange = (newItems: Array>) => { setItems(newItems); @@ -67,7 +67,7 @@ export const Dimensions: React.FC = ({ query, datasource, dimensionKeys, function makeRenderFilter( datasource: CloudWatchDatasource, - query: CloudWatchMetricsQuery, + query: DimensionsQuery, dimensionKeys: Array>, disableExpressions: boolean ) { diff --git a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/FilterItem.tsx b/public/app/plugins/datasource/cloudwatch/components/Dimensions/FilterItem.tsx similarity index 96% rename from public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/FilterItem.tsx rename to public/app/plugins/datasource/cloudwatch/components/Dimensions/FilterItem.tsx index b9c95a4a6c2..649d706f390 100644 --- a/public/app/plugins/datasource/cloudwatch/components/MetricStatEditor/FilterItem.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/Dimensions/FilterItem.tsx @@ -5,12 +5,12 @@ import { GrafanaTheme2, SelectableValue, toOption } from '@grafana/data'; import { InputGroup, AccessoryButton } from '@grafana/experimental'; import { Select, stylesFactory, useTheme2 } from '@grafana/ui'; import { CloudWatchDatasource } from '../../datasource'; -import { CloudWatchMetricsQuery, Dimensions } from '../../types'; +import { Dimensions, DimensionsQuery } from '../../types'; import { appendTemplateVariables } from '../../utils/utils'; import { DimensionFilterCondition } from './Dimensions'; export interface Props { - query: CloudWatchMetricsQuery; + query: DimensionsQuery; datasource: CloudWatchDatasource; filter: DimensionFilterCondition; dimensionKeys: Array>; @@ -96,7 +96,7 @@ export const FilterItem: FunctionComponent = ({ } }} /> - + ); diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx index 62d5c7cc65c..942bf5e7a83 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.test.tsx @@ -1,7 +1,7 @@ -import { render, screen, waitFor } from '@testing-library/react'; +import { render, screen, waitFor, within } from '@testing-library/react'; import React from 'react'; import { select } from 'react-select-event'; -import { VariableQueryType } from '../../types'; +import { Dimensions, VariableQueryType } from '../../types'; import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; import { VariableQueryEditor, Props } from './VariableQueryEditor'; @@ -11,7 +11,6 @@ const defaultQuery = { region: '', metricName: '', dimensionKey: '', - dimensionFilters: '', ec2Filters: '', instanceID: '', attributeName: '', @@ -37,26 +36,42 @@ ds.datasource.getMetrics = jest.fn().mockResolvedValue([ { label: 'i3', value: 'i3' }, { label: 'j3', value: 'j3' }, ]); -ds.datasource.getDimensionKeys = jest.fn().mockImplementation((namespace: string, region: string) => { - if (region === 'a1') { - return Promise.resolve([ - { label: 'q4', value: 'q4' }, - { label: 'r4', value: 'r4' }, - { label: 's4', value: 's4' }, - ]); - } - return Promise.resolve([{ label: 't4', value: 't4' }]); -}); +ds.datasource.getDimensionKeys = jest + .fn() + .mockImplementation((namespace: string, region: string, dimensionFilters?: Dimensions) => { + if (!!dimensionFilters) { + return Promise.resolve([ + { label: 's4', value: 's4' }, + { label: 'v4', value: 'v4' }, + ]); + } + if (region === 'a1') { + return Promise.resolve([ + { label: 'q4', value: 'q4' }, + { label: 'r4', value: 'r4' }, + { label: 's4', value: 's4' }, + ]); + } + return Promise.resolve([{ label: 't4', value: 't4' }]); + }); +ds.datasource.getDimensionValues = jest.fn().mockResolvedValue([ + { label: 'foo', value: 'foo' }, + { label: 'bar', value: 'bar' }, +]); ds.datasource.getVariables = jest.fn().mockReturnValue([]); +const onChange = jest.fn(); const defaultProps: Props = { - onChange: jest.fn(), + onChange: onChange, query: defaultQuery, datasource: ds.datasource, onRunQuery: () => {}, }; describe('VariableEditor', () => { + beforeEach(() => { + onChange.mockClear(); + }); describe('and a new variable is created', () => { it('should trigger a query using the first query type in the array', async () => { const props = defaultProps; @@ -100,6 +115,56 @@ describe('VariableEditor', () => { expect(metricSelect).not.toBeInTheDocument(); }); }); + it('should parse dimensionFilters correctly', async () => { + const props = defaultProps; + props.query = { + ...defaultQuery, + queryType: VariableQueryType.DimensionValues, + namespace: 'z2', + region: 'a1', + metricName: 'i3', + dimensionKey: 's4', + dimensionFilters: { s4: 'foo' }, + }; + render(); + + const filterItem = screen.getByTestId('cloudwatch-dimensions-filter-item'); + expect(filterItem).toBeInTheDocument(); + expect(within(filterItem).getByText('s4')).toBeInTheDocument(); + expect(within(filterItem).getByText('foo')).toBeInTheDocument(); + + // change filter key + const keySelect = screen.getByRole('combobox', { name: 'Dimensions filter key' }); + // confirms getDimensionKeys was called with filter and that the element uses keysForDimensionFilter + await select(keySelect, 'v4', { + container: document.body, + }); + expect(ds.datasource.getDimensionKeys).toHaveBeenCalledWith('z2', 'a1', {}, ''); + expect(onChange).toHaveBeenCalledWith({ + ...defaultQuery, + queryType: VariableQueryType.DimensionValues, + namespace: 'z2', + region: 'a1', + metricName: 'i3', + dimensionKey: 's4', + dimensionFilters: { v4: undefined }, + }); + + // set filter value + const valueSelect = screen.getByRole('combobox', { name: 'Dimensions filter value' }); + await select(valueSelect, 'bar', { + container: document.body, + }); + expect(onChange).toHaveBeenCalledWith({ + ...defaultQuery, + queryType: VariableQueryType.DimensionValues, + namespace: 'z2', + region: 'a1', + metricName: 'i3', + dimensionKey: 's4', + dimensionFilters: { v4: 'bar' }, + }); + }); }); describe('and a different region is selected', () => { it('should clear invalid fields', async () => { @@ -111,6 +176,7 @@ describe('VariableEditor', () => { region: 'a1', metricName: 'i3', dimensionKey: 's4', + dimensionFilters: { s4: 'foo' }, }; render(); @@ -118,7 +184,6 @@ describe('VariableEditor', () => { expect(querySelect).toBeInTheDocument(); expect(screen.queryByText('Dimension Values')).toBeInTheDocument(); const regionSelect = screen.getByRole('combobox', { name: 'Region' }); - regionSelect.click(); await select(regionSelect, 'b1', { container: document.body, }); @@ -133,8 +198,9 @@ describe('VariableEditor', () => { region: 'b1', // metricName i3 exists in the new region and should not be removed metricName: 'i3', - // dimensionKey s4 does not exist in the new region and should be removed + // dimensionKey s4 and valueDimension do not exist in the new region and should be removed dimensionKey: '', + dimensionFilters: {}, }); }); }); diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx index 6a02346cfb7..c95173f860a 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryEditor.tsx @@ -7,6 +7,8 @@ import { useDimensionKeys, useMetrics, useNamespaces, useRegions } from '../../h import { CloudWatchJsonData, CloudWatchQuery, VariableQuery, VariableQueryType } from '../../types'; import { migrateVariableQuery } from '../../migrations'; import { VariableQueryField } from './VariableQueryField'; +import { Dimensions } from '..'; +import { InlineField } from '@grafana/ui'; export type Props = QueryEditorProps; @@ -25,11 +27,12 @@ const queryTypes: Array<{ value: string; label: string }> = [ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { const parsedQuery = migrateVariableQuery(query); - const { region, namespace, metricName, dimensionKey } = parsedQuery; + const { region, namespace, metricName, dimensionKey, dimensionFilters } = parsedQuery; const [regions, regionIsLoading] = useRegions(datasource); const namespaces = useNamespaces(datasource); const metrics = useMetrics(datasource, region, namespace); const dimensionKeys = useDimensionKeys(datasource, region, namespace, metricName); + const keysForDimensionFilter = useDimensionKeys(datasource, region, namespace, metricName, dimensionFilters ?? {}); const onRegionChange = async (region: string) => { const validatedQuery = await sanitizeQuery({ @@ -48,7 +51,10 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { }; const onQueryChange = (newQuery: VariableQuery) => { - onChange({ ...newQuery, refId: 'CloudWatchVariableQueryEditor-VariableQuery' }); + onChange({ + ...newQuery, + refId: 'CloudWatchVariableQueryEditor-VariableQuery', + }); }; // Reset dimensionValue parameters if namespace or region change @@ -58,7 +64,6 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { await datasource.getMetrics(namespace, region).then((result: Array>) => { if (!result.find((metric) => metric.value === metricName)) { metricName = ''; - dimensionFilters = ''; } }); } @@ -66,7 +71,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { await datasource.getDimensionKeys(namespace, region).then((result: Array>) => { if (!result.find((key) => key.value === dimensionKey)) { dimensionKey = ''; - dimensionFilters = ''; + dimensionFilters = {}; } }); } @@ -86,7 +91,6 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { VariableQueryType.DimensionKeys, VariableQueryType.DimensionValues, ].includes(parsedQuery.queryType); - return ( <> { options={queryTypes} onChange={(value: VariableQueryType) => onQueryChange({ ...parsedQuery, queryType: value })} label="Query Type" + inputId={`variable-query-type-${query.refId}`} /> {hasRegionField && ( { onChange={(value: string) => onRegionChange(value)} label="Region" isLoading={regionIsLoading} + inputId={`variable-query-region-${query.refId}`} /> )} {hasNamespaceField && ( @@ -110,6 +116,7 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { options={namespaces} onChange={(value: string) => onNamespaceChange(value)} label="Namespace" + inputId={`variable-query-namespace-${query.refId}`} /> )} {parsedQuery.queryType === VariableQueryType.DimensionValues && ( @@ -119,20 +126,26 @@ export const VariableQueryEditor = ({ query, datasource, onChange }: Props) => { options={metrics} onChange={(value: string) => onQueryChange({ ...parsedQuery, metricName: value })} label="Metric" + inputId={`variable-query-metric-${query.refId}`} /> onQueryChange({ ...parsedQuery, dimensionKey: value })} label="Dimension Key" + inputId={`variable-query-dimension-key-${query.refId}`} /> - onQueryChange({ ...parsedQuery, dimensionFilters: value })} - label="Filters" - /> + + { + onChange({ ...parsedQuery, dimensionFilters: dimensions }); + }} + dimensionKeys={keysForDimensionFilter} + disableExpressions={true} + datasource={datasource} + /> + )} {parsedQuery.queryType === VariableQueryType.EBSVolumeIDs && ( diff --git a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryField.tsx b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryField.tsx index cb1fa8e82dc..890f00ba2c0 100644 --- a/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/VariableQueryEditor/VariableQueryField.tsx @@ -10,9 +10,9 @@ interface VariableQueryFieldProps { options: SelectableValue[]; value: T | null; label: string; + inputId?: string; allowCustomValue?: boolean; isLoading?: boolean; - inputId?: string; } export const VariableQueryField = ({ @@ -22,9 +22,10 @@ export const VariableQueryField = ({ options, allowCustomValue = false, isLoading = false, + inputId = label, }: VariableQueryFieldProps) => { return ( - + setLocalValue(e.currentTarget.value)} onBlur={() => onBlur(localValue)} - width={TEXT_WIDTH} /> ); diff --git a/public/app/plugins/datasource/cloudwatch/components/index.ts b/public/app/plugins/datasource/cloudwatch/components/index.ts index 3de2ca07ed2..0362b31b34f 100644 --- a/public/app/plugins/datasource/cloudwatch/components/index.ts +++ b/public/app/plugins/datasource/cloudwatch/components/index.ts @@ -1,4 +1,4 @@ -export { Dimensions } from './MetricStatEditor/Dimensions'; +export { Dimensions } from './Dimensions/Dimensions'; export { QueryInlineField, QueryField } from './Forms'; export { Alias } from './Alias'; export { PanelQueryEditor } from './PanelQueryEditor'; diff --git a/public/app/plugins/datasource/cloudwatch/migration.test.ts b/public/app/plugins/datasource/cloudwatch/migration.test.ts index 4c56fd06946..7f92bc72411 100644 --- a/public/app/plugins/datasource/cloudwatch/migration.test.ts +++ b/public/app/plugins/datasource/cloudwatch/migration.test.ts @@ -206,7 +206,7 @@ describe('migration', () => { expect(query.namespace).toBe('AWS/RDS'); expect(query.metricName).toBe('CPUUtilization'); expect(query.dimensionKey).toBe('DBInstanceIdentifier'); - expect(query.dimensionFilters).toBe(''); + expect(query.dimensionFilters).toStrictEqual({}); }); }); describe('and filter param is defined by user', () => { @@ -219,9 +219,18 @@ describe('migration', () => { expect(query.namespace).toBe('AWS/RDS'); expect(query.metricName).toBe('CPUUtilization'); expect(query.dimensionKey).toBe('DBInstanceIdentifier'); - expect(query.dimensionFilters).toBe('{"InstanceId":"$instance_id"}'); + expect(query.dimensionFilters).toStrictEqual({ InstanceId: '$instance_id' }); }); }); }); }); + describe('when resource_arns query is used', () => { + it('should parse the query', () => { + const query = migrateVariableQuery('resource_arns(us-east-1,rds:db,{"environment":["$environment"]})'); + expect(query.queryType).toBe(VariableQueryType.ResourceArns); + expect(query.region).toBe('us-east-1'); + expect(query.resourceType).toBe('rds:db'); + expect(query.tags).toBe('{"environment":["$environment"]}'); + }); + }); }); diff --git a/public/app/plugins/datasource/cloudwatch/migrations.ts b/public/app/plugins/datasource/cloudwatch/migrations.ts index 8bc3387e28d..5bac7b8f45f 100644 --- a/public/app/plugins/datasource/cloudwatch/migrations.ts +++ b/public/app/plugins/datasource/cloudwatch/migrations.ts @@ -80,7 +80,7 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery): Variable region: '', metricName: '', dimensionKey: '', - dimensionFilters: '', + dimensionFilters: {}, ec2Filters: '', instanceID: '', attributeName: '', @@ -122,7 +122,14 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery): Variable newQuery.namespace = dimensionValuesQuery[2]; newQuery.metricName = dimensionValuesQuery[3]; newQuery.dimensionKey = dimensionValuesQuery[4]; - newQuery.dimensionFilters = dimensionValuesQuery[6] || ''; + newQuery.dimensionFilters = {}; + if (!!dimensionValuesQuery[6]) { + try { + newQuery.dimensionFilters = JSON.parse(dimensionValuesQuery[6]); + } catch { + throw new Error(`unable to migrate poorly formed filters: ${dimensionValuesQuery[6]}`); + } + } return newQuery; } @@ -148,7 +155,7 @@ export function migrateVariableQuery(rawQuery: string | VariableQuery): Variable newQuery.queryType = VariableQueryType.ResourceArns; newQuery.region = resourceARNsQuery[1]; newQuery.resourceType = resourceARNsQuery[2]; - newQuery.tags = JSON.parse(resourceARNsQuery[3]) || ''; + newQuery.tags = resourceARNsQuery[3] || ''; return newQuery; } diff --git a/public/app/plugins/datasource/cloudwatch/types.ts b/public/app/plugins/datasource/cloudwatch/types.ts index 472d400b55b..c3f661b5333 100644 --- a/public/app/plugins/datasource/cloudwatch/types.ts +++ b/public/app/plugins/datasource/cloudwatch/types.ts @@ -35,6 +35,13 @@ export interface SQLExpression { limit?: number; } +export interface DimensionsQuery extends DataQuery { + namespace: string; + region: string; + metricName?: string; + dimensions?: Dimensions; +} + export interface CloudWatchMetricsQuery extends DataQuery { queryMode?: 'Metrics'; metricQueryType?: MetricQueryType; @@ -386,7 +393,7 @@ export interface VariableQuery extends DataQuery { region: string; metricName: string; dimensionKey: string; - dimensionFilters: string; + dimensionFilters?: Dimensions; ec2Filters: string; instanceID: string; attributeName: string; diff --git a/public/app/plugins/datasource/cloudwatch/variables.test.ts b/public/app/plugins/datasource/cloudwatch/variables.test.ts new file mode 100644 index 00000000000..5b2d09e77da --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/variables.test.ts @@ -0,0 +1,172 @@ +import { VariableQuery, VariableQueryType } from './types'; +import { CloudWatchVariableSupport } from './variables'; +import { setupMockedDataSource } from './__mocks__/CloudWatchDataSource'; + +const defaultQuery: VariableQuery = { + queryType: VariableQueryType.Regions, + namespace: 'foo', + region: 'bar', + metricName: '', + dimensionKey: '', + ec2Filters: '', + instanceID: '', + attributeName: '', + resourceType: '', + tags: '', + refId: '', +}; + +const ds = setupMockedDataSource(); +ds.datasource.getRegions = jest.fn().mockResolvedValue([{ label: 'a', value: 'a' }]); +ds.datasource.getNamespaces = jest.fn().mockResolvedValue([{ label: 'b', value: 'b' }]); +ds.datasource.getMetrics = jest.fn().mockResolvedValue([{ label: 'c', value: 'c' }]); +ds.datasource.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' }]); +const getResourceARNs = jest.fn().mockResolvedValue([{ label: 'h', value: 'h' }]); + +const variables = new CloudWatchVariableSupport(ds.datasource); + +describe('variables', () => { + it('should run regions', async () => { + const result = await variables.execute({ ...defaultQuery }); + expect(result).toEqual([{ text: 'a', value: 'a', expandable: true }]); + }); + + it('should run namespaces', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.Namespaces }); + expect(result).toEqual([{ text: 'b', value: 'b', expandable: true }]); + }); + + it('should run metrics', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.Metrics }); + expect(result).toEqual([{ text: 'c', value: 'c', expandable: true }]); + }); + + it('should run dimension keys', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.DimensionKeys }); + expect(result).toEqual([{ text: 'd', value: 'd', expandable: true }]); + }); + + describe('dimension values', () => { + const query = { + ...defaultQuery, + queryType: VariableQueryType.DimensionValues, + metricName: 'abc', + dimensionKey: 'efg', + dimensionFilters: { a: 'b' }, + }; + beforeEach(() => { + ds.datasource.getDimensionValues = getDimensionValues; + getDimensionValues.mockClear(); + }); + + it('should not run if dimension key not set', async () => { + const result = await variables.execute({ ...query, dimensionKey: '' }); + expect(getDimensionValues).not.toBeCalled(); + expect(result).toEqual([]); + }); + + it('should not run if metric name not set', async () => { + const result = await variables.execute({ ...query, metricName: '' }); + expect(getDimensionValues).not.toBeCalled(); + expect(result).toEqual([]); + }); + it('should run if values are set', async () => { + const result = await variables.execute(query); + expect(getDimensionValues).toBeCalledWith( + query.region, + query.namespace, + query.metricName, + query.dimensionKey, + query.dimensionFilters + ); + expect(result).toEqual([{ text: 'e', value: 'e', expandable: true }]); + }); + }); + + describe('EBS volume ids', () => { + beforeEach(() => { + ds.datasource.getEbsVolumeIds = getEbsVolumeIds; + getEbsVolumeIds.mockClear(); + }); + + it('should not run if instance id not set', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.EBSVolumeIDs }); + expect(getEbsVolumeIds).not.toBeCalled(); + expect(result).toEqual([]); + }); + + it('should run if instance id set', async () => { + const result = await variables.execute({ + ...defaultQuery, + queryType: VariableQueryType.EBSVolumeIDs, + instanceID: 'foo', + }); + expect(getEbsVolumeIds).toBeCalledWith(defaultQuery.region, 'foo'); + expect(result).toEqual([{ text: 'f', value: 'f', expandable: true }]); + }); + }); + + describe('EC2 instance attributes', () => { + const query = { + ...defaultQuery, + queryType: VariableQueryType.EC2InstanceAttributes, + attributeName: 'abc', + ec2Filters: '{"a":["b"]}', + }; + beforeEach(() => { + ds.datasource.getEc2InstanceAttribute = getEc2InstanceAttribute; + getEc2InstanceAttribute.mockClear(); + }); + + it('should not run if instance id not set', async () => { + const result = await variables.execute({ ...query, attributeName: '' }); + expect(getEc2InstanceAttribute).not.toBeCalled(); + expect(result).toEqual([]); + }); + + it('should run if instance id set', async () => { + const result = await variables.execute(query); + expect(getEc2InstanceAttribute).toBeCalledWith(query.region, query.attributeName, { a: ['b'] }); + expect(result).toEqual([{ text: 'g', value: 'g', expandable: true }]); + }); + }); + + describe('resource arns', () => { + const query = { + ...defaultQuery, + queryType: VariableQueryType.ResourceArns, + resourceType: 'abc', + tags: '{"a":["b"]}', + }; + beforeEach(() => { + ds.datasource.getResourceARNs = getResourceARNs; + getResourceARNs.mockClear(); + }); + + it('should not run if instance id not set', async () => { + const result = await variables.execute({ ...query, resourceType: '' }); + expect(getResourceARNs).not.toBeCalled(); + expect(result).toEqual([]); + }); + + it('should run if instance id set', async () => { + const result = await variables.execute(query); + expect(getResourceARNs).toBeCalledWith(query.region, query.resourceType, { a: ['b'] }); + expect(result).toEqual([{ text: 'h', value: 'h', expandable: true }]); + }); + }); + + it('should run statistics', async () => { + const result = await variables.execute({ ...defaultQuery, queryType: VariableQueryType.Statistics }); + expect(result).toEqual([ + { text: 'Average', value: 'Average', expandable: true }, + { text: 'Maximum', value: 'Maximum', expandable: true }, + { text: 'Minimum', value: 'Minimum', expandable: true }, + { text: 'Sum', value: 'Sum', expandable: true }, + { text: 'SampleCount', value: 'SampleCount', expandable: true }, + ]); + }); +}); diff --git a/public/app/plugins/datasource/cloudwatch/variables.ts b/public/app/plugins/datasource/cloudwatch/variables.ts index 1443df38ce3..cea8084b049 100644 --- a/public/app/plugins/datasource/cloudwatch/variables.ts +++ b/public/app/plugins/datasource/cloudwatch/variables.ts @@ -91,11 +91,13 @@ export class CloudWatchVariableSupport extends CustomVariableSupport ({ text: s.label, value: s.value, @@ -119,7 +121,7 @@ export class CloudWatchVariableSupport extends CustomVariableSupport