From 33a7269b4f5edbf4cad66724e7c69d3b551a4553 Mon Sep 17 00:00:00 2001
From: Yaelle Chaudy <42030685+yaelleC@users.noreply.github.com>
Date: Tue, 1 Feb 2022 09:13:25 +0100
Subject: [PATCH] Cloudwatch : Fixed reseting metric name when changing
namespace in Metric Query (#44612)
* Added logic to reset metric name when changing namespace in Metric Query
* Added tests for reseting or not metricName onChange namespace
* Added tests for reseting or not metricName onChange namespace
* Removed comment
* Cleaned up tests
* Refactored namespace to be updated in utils
* Removed unecessary overrides for query and onQueryChange
* Renamed tests + used some instead of find
---
.../SQLBuilderSelectRow.test.tsx | 137 ++++++++++++++++++
.../SQLBuilderEditor/SQLBuilderSelectRow.tsx | 21 ++-
.../components/SQLBuilderEditor/utils.ts | 9 ++
3 files changed, 165 insertions(+), 2 deletions(-)
create mode 100644 public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/SQLBuilderSelectRow.test.tsx
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 (
<>
diff --git a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/utils.ts b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/utils.ts
index c2901c3a562..a0b28ca62f4 100644
--- a/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/utils.ts
+++ b/public/app/plugins/datasource/cloudwatch/components/SQLBuilderEditor/utils.ts
@@ -146,6 +146,8 @@ export function setSql(query: CloudWatchMetricsQuery, sql: SQLExpression): Cloud
export function setNamespace(query: CloudWatchMetricsQuery, namespace: string | undefined): CloudWatchMetricsQuery {
const sql = query.sql ?? {};
+ //updating `namespace` props for CloudWatchMetricsQuery
+ query.namespace = namespace ? namespace : '';
if (namespace === undefined) {
return setSql(query, {
@@ -230,6 +232,13 @@ export function setMetricName(query: CloudWatchMetricsQuery, metricName: string)
});
}
+export function removeMetricName(query: CloudWatchMetricsQuery): CloudWatchMetricsQuery {
+ const queryWithNoParams = { ...query };
+ delete queryWithNoParams.sql?.select?.parameters;
+
+ return queryWithNoParams;
+}
+
export function setAggregation(query: CloudWatchMetricsQuery, aggregation: string): CloudWatchMetricsQuery {
return setSql(query, {
select: {