diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx new file mode 100644 index 00000000000..55dac72aa8d --- /dev/null +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx @@ -0,0 +1,137 @@ +import React from 'react'; +import { selectOptionInTest } from '@grafana/ui'; +import { act, render, screen } from '@testing-library/react'; +import { CloudWatchMetricsQuery, MetricEditorMode, MetricQueryType, SQLExpression } from '../../types'; +import { setupMockedDataSource } from '../../__mocks__/CloudWatchDataSource'; +import { QueryEditorExpressionType, QueryEditorPropertyType } from '../../expressions'; +import SQLBuilderSelectRow from './SQLBuilderSelectRow'; + +const { datasource } = setupMockedDataSource(); + +const makeSQLQuery = (sql?: SQLExpression): CloudWatchMetricsQuery => ({ + queryMode: 'Metrics', + refId: '', + id: '', + region: 'us-east-1', + namespace: 'ec2', + dimensions: { somekey: 'somevalue' }, + metricQueryType: MetricQueryType.Query, + metricEditorMode: MetricEditorMode.Builder, + sql: sql, +}); + +const query = makeSQLQuery({ + select: { + type: QueryEditorExpressionType.Function, + name: 'AVERAGE', + parameters: [ + { + type: QueryEditorExpressionType.FunctionParameter, + name: 'm1', + }, + ], + }, + from: { + type: QueryEditorExpressionType.Property, + property: { + type: QueryEditorPropertyType.String, + name: 'n1', + }, + }, +}); + +const onQueryChange = jest.fn(); +const baseProps = { + query, + datasource, + onQueryChange, +}; + +const namespaces = [ + { value: 'n1', label: 'n1', text: 'n1' }, + { value: 'n2', label: 'n2', text: 'n2' }, +]; +const metrics = [ + { value: 'm1', label: 'm1', text: 'm1' }, + { value: 'm2', label: 'm2', text: 'm2' }, +]; + +describe('Cloudwatch SQLBuilderSelectRow', () => { + beforeEach(() => { + datasource.getNamespaces = jest.fn().mockResolvedValue(namespaces); + datasource.getMetrics = jest.fn().mockResolvedValue([]); + datasource.getDimensionKeys = jest.fn().mockResolvedValue([]); + datasource.getDimensionValues = jest.fn().mockResolvedValue([]); + onQueryChange.mockReset(); + }); + + it('Should not reset metricName when selecting a namespace if metric exist in new namespace', async () => { + datasource.getMetrics = jest.fn().mockResolvedValue(metrics); + + await act(async () => { + render(); + }); + + expect(screen.getByText('n1')).toBeInTheDocument(); + expect(screen.getByText('m1')).toBeInTheDocument(); + const namespaceSelect = screen.getByLabelText('Namespace'); + await selectOptionInTest(namespaceSelect, 'n2'); + + const expectedQuery = makeSQLQuery({ + select: { + type: QueryEditorExpressionType.Function, + name: 'AVERAGE', + parameters: [ + { + type: QueryEditorExpressionType.FunctionParameter, + name: 'm1', + }, + ], + }, + from: { + type: QueryEditorExpressionType.Property, + property: { + type: QueryEditorPropertyType.String, + name: 'n2', + }, + }, + }); + expect(onQueryChange).toHaveBeenCalledTimes(1); + expect(onQueryChange.mock.calls).toEqual([[{ ...expectedQuery, namespace: 'n2' }]]); + }); + + 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) => { + let mockMetrics = + namespace === 'n1' && region === baseProps.query.region + ? metrics + : [{ value: 'newNamespaceMetric', label: 'newNamespaceMetric', text: 'newNamespaceMetric' }]; + return Promise.resolve(mockMetrics); + }); + + await act(async () => { + render(); + }); + + expect(screen.getByText('n1')).toBeInTheDocument(); + expect(screen.getByText('m1')).toBeInTheDocument(); + const namespaceSelect = screen.getByLabelText('Namespace'); + await selectOptionInTest(namespaceSelect, 'n2'); + + const expectedQuery = makeSQLQuery({ + select: { + type: QueryEditorExpressionType.Function, + name: 'AVERAGE', + }, + from: { + type: QueryEditorExpressionType.Property, + property: { + type: QueryEditorPropertyType.String, + name: 'n2', + }, + }, + }); + expect(onQueryChange).toHaveBeenCalledTimes(1); + expect(onQueryChange.mock.calls).toEqual([[{ ...expectedQuery, namespace: 'n2' }]]); + }); +}); diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.tsx b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.tsx index 4603329c145..1c49d73aba0 100644 --- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo } from 'react'; -import { toOption } from '@grafana/data'; +import { SelectableValue, toOption } from '@grafana/data'; import { EditorField, EditorFieldGroup } from '@grafana/experimental'; import { Select, Switch } from '@grafana/ui'; import { STATISTICS } from '../../cloudwatch-sql/language'; @@ -12,6 +12,7 @@ import { getNamespaceFromExpression, getSchemaLabelKeys as getSchemaLabels, isUsingWithSchema, + removeMetricName, setAggregation, setMetricName, setNamespace, @@ -52,16 +53,32 @@ const SQLBuilderSelectRow: React.FC = ({ datasource, q [unusedDimensionKeys, schemaLabels] ); + const onNamespaceChange = async (query: CloudWatchMetricsQuery) => { + const validatedQuery = await validateMetricName(query); + onQueryChange(validatedQuery); + }; + + const validateMetricName = async (query: CloudWatchMetricsQuery) => { + let { region, sql } = query; + await datasource.getMetrics(query.namespace, region).then((result: Array>) => { + if (!result.some((metric) => metric.value === metricName)) { + sql = removeMetricName(query).sql; + } + }); + return { ...query, sql }; + }; + return ( <>