CloudWatch: Deprecate dynamic labels feature toggle, remove support for Alias in frontend (#67222)

pull/67371/head^2
Shirley 2 years ago committed by GitHub
parent e5aeb7c322
commit 8516e63937
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .betterer.results
  2. 47
      public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor/Alias.tsx
  3. 39
      public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor/MetricsQueryEditor.test.tsx
  4. 37
      public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor/MetricsQueryEditor.tsx
  5. 123
      public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.test.ts
  6. 5
      public/app/plugins/datasource/cloudwatch/migrations/metricQueryMigrations.ts
  7. 11
      public/app/plugins/datasource/cloudwatch/migrations/useMigratedMetricsQuery.test.ts

@ -3881,10 +3881,6 @@ exports[`better eslint`] = {
"public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx:5381": [ "public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/plugins/datasource/cloudwatch/components/MetricsQueryEditor/Alias.tsx:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/plugins/datasource/cloudwatch/datasource.ts:5381": [ "public/app/plugins/datasource/cloudwatch/datasource.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
], ],

@ -1,47 +0,0 @@
import { debounce } from 'lodash';
import React, { useState } from 'react';
import { IconButton, Input, Tooltip } from '@grafana/ui';
export interface Props {
onChange: (alias: any) => void;
value: string;
id?: string;
}
export const Alias = ({ value = '', onChange, id }: Props) => {
const [alias, setAlias] = useState(value);
const propagateOnChange = debounce(onChange, 1500);
onChange = (e: any) => {
setAlias(e.target.value);
propagateOnChange(e.target.value);
};
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<Input id={id} type="text" value={alias} onChange={onChange} aria-label="Optional alias" />
<Tooltip
content={
<span>
Alias pattern will be deprecated in Grafana 10. See{' '}
<a
target="__blank"
rel="noopener noreferrer"
href="https://grafana.com/docs/grafana/latest/datasources/aws-cloudwatch/query-editor/#common-query-editor-fields"
>
documentation
</a>{' '}
for how to use dynamic labels.
</span>
}
interactive
theme="error"
placement="right"
>
<IconButton name="exclamation-triangle" variant="destructive" style={{ marginLeft: 4 }} />
</Tooltip>
</div>
);
};

@ -111,27 +111,22 @@ describe('QueryEditor', () => {
}); });
}); });
describe('when dynamic labels feature toggle is enabled', () => { it('should render label field and not alias field', async () => {
it('should render label field', async () => { const props = setup();
const props = setup();
const originalValue = config.featureToggles.cloudWatchDynamicLabels; render(
config.featureToggles.cloudWatchDynamicLabels = true; <MetricsQueryEditor
{...props}
render( query={{ ...props.query, refId: 'A', alias: 'Period: {{period}} InstanceId: {{InstanceId}}' }}
<MetricsQueryEditor />
{...props} );
query={{ ...props.query, refId: 'A', alias: 'Period: {{period}} InstanceId: {{InstanceId}}' }}
/> expect(await screen.findByText('Label')).toBeInTheDocument();
); expect(screen.queryByText('Alias')).toBeNull();
expect(screen.getByText("Period: ${PROP('Period')} InstanceId: ${PROP('Dim.InstanceId')}"));
expect(await screen.findByText('Label')).toBeInTheDocument();
expect(screen.queryByText('Alias')).toBeNull();
expect(screen.getByText("Period: ${PROP('Period')} InstanceId: ${PROP('Dim.InstanceId')}"));
config.featureToggles.cloudWatchDynamicLabels = originalValue;
});
}); });
// TODO: delete this test when dynamic labels feature flag is removed
describe('when dynamic labels feature toggle is disabled', () => { describe('when dynamic labels feature toggle is disabled', () => {
it('should render alias field', async () => { it('should render alias field', async () => {
const props = setup(); const props = setup();
@ -141,9 +136,9 @@ describe('QueryEditor', () => {
const expected = 'Period: {{period}} InstanceId: {{InstanceId}}'; const expected = 'Period: {{period}} InstanceId: {{InstanceId}}';
render(<MetricsQueryEditor {...props} query={{ ...props.query, refId: 'A', alias: expected }} />); render(<MetricsQueryEditor {...props} query={{ ...props.query, refId: 'A', alias: expected }} />);
expect(await screen.findByText('Alias')).toBeInTheDocument(); expect(await screen.findByText('Label')).toBeInTheDocument();
expect(screen.queryByText('Label')).toBeNull(); expect(screen.queryByText('Alias')).toBeNull();
expect(screen.getByLabelText('Alias - optional')).toHaveValue(expected); expect(screen.getByText("Period: ${PROP('Period')} InstanceId: ${PROP('Dim.InstanceId')}"));
config.featureToggles.cloudWatchDynamicLabels = originalValue; config.featureToggles.cloudWatchDynamicLabels = originalValue;
}); });

@ -2,7 +2,6 @@ import React, { ChangeEvent, useCallback, useEffect, useState } from 'react';
import { QueryEditorProps, SelectableValue } from '@grafana/data'; import { QueryEditorProps, SelectableValue } from '@grafana/data';
import { EditorField, EditorRow, InlineSelect, Space } from '@grafana/experimental'; import { EditorField, EditorRow, InlineSelect, Space } from '@grafana/experimental';
import { config } from '@grafana/runtime';
import { ConfirmModal, Input, RadioButtonGroup } from '@grafana/ui'; import { ConfirmModal, Input, RadioButtonGroup } from '@grafana/ui';
import { MathExpressionQueryField, MetricStatEditor, SQLBuilderEditor } from '../'; import { MathExpressionQueryField, MetricStatEditor, SQLBuilderEditor } from '../';
@ -19,8 +18,6 @@ import {
import { DynamicLabelsField } from '../DynamicLabelsField'; import { DynamicLabelsField } from '../DynamicLabelsField';
import { SQLCodeEditor } from '../SQLCodeEditor'; import { SQLCodeEditor } from '../SQLCodeEditor';
import { Alias } from './Alias';
export interface Props extends QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData> { export interface Props extends QueryEditorProps<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData> {
query: CloudWatchMetricsQuery; query: CloudWatchMetricsQuery;
extraHeaderElementLeft?: React.Dispatch<JSX.Element | undefined>; extraHeaderElementLeft?: React.Dispatch<JSX.Element | undefined>;
@ -182,28 +179,18 @@ export const MetricsQueryEditor = (props: Props) => {
/> />
</EditorField> </EditorField>
{config.featureToggles.cloudWatchDynamicLabels ? ( <EditorField
<EditorField label="Label"
label="Label" width={26}
width={26} optional
optional tooltip="Change time series legend name using Dynamic labels. See documentation for details."
tooltip="Change time series legend name using Dynamic labels. See documentation for details." >
> <DynamicLabelsField
<DynamicLabelsField width={52}
width={52} label={migratedQuery.label ?? ''}
label={migratedQuery.label ?? ''} onChange={(label) => props.onChange({ ...query, label })}
onChange={(label) => props.onChange({ ...query, label })} ></DynamicLabelsField>
></DynamicLabelsField> </EditorField>
</EditorField>
) : (
<EditorField label="Alias" width={26} optional tooltip="Change time series legend name using this field.">
<Alias
id={`${query.refId}-cloudwatch-metric-query-editor-alias`}
value={migratedQuery.alias ?? ''}
onChange={(value: string) => onChange({ ...migratedQuery, alias: value })}
/>
</EditorField>
)}
</EditorRow> </EditorRow>
</> </>
); );

@ -25,68 +25,87 @@ describe('metricQueryMigrations', () => {
matchExact: false, matchExact: false,
expression: '', expression: '',
}; };
describe('when feature is enabled', () => { describe('when label was not previously migrated', () => {
describe('and label was not previously migrated', () => { const cases: TestScenario[] = [
const cases: TestScenario[] = [ { description: 'Metric name', alias: '{{metric}}', label: "${PROP('MetricName')}" },
{ description: 'Metric name', alias: '{{metric}}', label: "${PROP('MetricName')}" }, { description: 'Trim pattern', alias: '{{ metric }}', label: "${PROP('MetricName')}" },
{ description: 'Trim pattern', alias: '{{ metric }}', label: "${PROP('MetricName')}" }, { description: 'Namespace', alias: '{{namespace}}', label: "${PROP('Namespace')}" },
{ description: 'Namespace', alias: '{{namespace}}', label: "${PROP('Namespace')}" }, { description: 'Period', alias: '{{period}}', label: "${PROP('Period')}" },
{ description: 'Period', alias: '{{period}}', label: "${PROP('Period')}" }, { description: 'Region', alias: '{{region}}', label: "${PROP('Region')}" },
{ description: 'Region', alias: '{{region}}', label: "${PROP('Region')}" }, { description: 'Statistic', alias: '{{stat}}', label: "${PROP('Stat')}" },
{ description: 'Statistic', alias: '{{stat}}', label: "${PROP('Stat')}" }, { description: 'Label', alias: '{{label}}', label: '${LABEL}' },
{ description: 'Label', alias: '{{label}}', label: '${LABEL}' }, {
{ description: 'Non-existing alias pattern',
description: 'Non-existing alias pattern', alias: '{{anything_else}}',
alias: '{{anything_else}}', label: "${PROP('Dim.anything_else')}",
label: "${PROP('Dim.anything_else')}", },
}, {
{ description: 'Combined patterns',
description: 'Combined patterns', alias: 'some {{combination}} of {{label}} and {{metric}}',
alias: 'some {{combination}} of {{label}} and {{metric}}', label: "some ${PROP('Dim.combination')} of ${LABEL} and ${PROP('MetricName')}",
label: "some ${PROP('Dim.combination')} of ${LABEL} and ${PROP('MetricName')}", },
}, {
{ description: 'Combined patterns not trimmed',
description: 'Combined patterns not trimmed', alias: 'some {{combination }}{{ label}} and {{metric}}',
alias: 'some {{combination }}{{ label}} and {{metric}}', label: "some ${PROP('Dim.combination')}${LABEL} and ${PROP('MetricName')}",
label: "some ${PROP('Dim.combination')}${LABEL} and ${PROP('MetricName')}", },
}, ];
]; test.each(cases)('given old alias %p, it should be migrated to label: %p', ({ alias, label }) => {
test.each(cases)('given old alias %p, it should be migrated to label: %p', ({ alias, label }) => { const testQuery = { ...baseQuery, alias };
config.featureToggles.cloudWatchDynamicLabels = true; const result = migrateAliasPatterns(testQuery);
const testQuery = { ...baseQuery, alias }; expect(result.label).toBe(label);
const result = migrateAliasPatterns(testQuery); expect(result.alias).toBe(alias);
expect(result.label).toBe(label);
expect(result.alias).toBe(alias);
});
}); });
});
describe('and label was previously migrated', () => { describe('when label was previously migrated', () => {
const cases: TestScenario[] = [ const cases: TestScenario[] = [
{ {
alias: '', alias: '',
label: "${PROP('MetricName')}", label: "${PROP('MetricName')}",
}, },
{ alias: '{{metric}}', label: "${PROP('Period')}" }, { alias: '{{metric}}', label: "${PROP('Period')}" },
{ alias: '{{namespace}}', label: '' }, { alias: '{{namespace}}', label: '' },
]; ];
test.each(cases)('it should not be migrated once again', ({ alias, label }) => { test.each(cases)('it should not be migrated once again', ({ alias, label }) => {
config.featureToggles.cloudWatchDynamicLabels = true; const testQuery = { ...baseQuery, alias, label };
const testQuery = { ...baseQuery, alias, label }; const result = migrateAliasPatterns(testQuery);
const result = migrateAliasPatterns(testQuery); expect(result.label).toBe(label);
expect(result.label).toBe(label); expect(result.alias).toBe(alias);
expect(result.alias).toBe(alias);
});
}); });
}); });
// TODO: delete this test when dynamic labels feature flag is removed
describe('when feature is disabled', () => { describe('when feature is disabled', () => {
const cases: TestScenario[] = [ const cases: TestScenario[] = [
{ alias: '{{period}}', label: "${PROP('MetricName')}" }, { alias: '{{period}}', label: "${PROP('MetricName')}" },
{ alias: '{{metric}}', label: '' }, { alias: '{{metric}}', label: "${PROP('MetricName')}" },
{ alias: '', label: "${PROP('MetricName')}" }, { alias: '', label: "${PROP('MetricName')}" },
{ alias: '', label: undefined }, { alias: '', label: '' },
{ description: 'Metric name', alias: '{{metric}}', label: "${PROP('MetricName')}" },
{ description: 'Trim pattern', alias: '{{ metric }}', label: "${PROP('MetricName')}" },
{ description: 'Namespace', alias: '{{namespace}}', label: "${PROP('Namespace')}" },
{ description: 'Period', alias: '{{period}}', label: "${PROP('Period')}" },
{ description: 'Region', alias: '{{region}}', label: "${PROP('Region')}" },
{ description: 'Statistic', alias: '{{stat}}', label: "${PROP('Stat')}" },
{ description: 'Label', alias: '{{label}}', label: '${LABEL}' },
{
description: 'Non-existing alias pattern',
alias: '{{anything_else}}',
label: "${PROP('Dim.anything_else')}",
},
{
description: 'Combined patterns',
alias: 'some {{combination}} of {{label}} and {{metric}}',
label: "some ${PROP('Dim.combination')} of ${LABEL} and ${PROP('MetricName')}",
},
{
description: 'Combined patterns not trimmed',
alias: 'some {{combination }}{{ label}} and {{metric}}',
label: "some ${PROP('Dim.combination')}${LABEL} and ${PROP('MetricName')}",
},
]; ];
test.each(cases)('should not migrate alias', ({ alias, label }) => { test.each(cases)('should migrate alias anyway', ({ alias, label }) => {
config.featureToggles.cloudWatchDynamicLabels = false; config.featureToggles.cloudWatchDynamicLabels = false;
const testQuery = { ...baseQuery, alias: `${alias}` }; const testQuery = { ...baseQuery, alias: `${alias}` };
if (label !== undefined) { if (label !== undefined) {

@ -1,7 +1,5 @@
import deepEqual from 'fast-deep-equal'; import deepEqual from 'fast-deep-equal';
import { config } from '@grafana/runtime';
import { CloudWatchMetricsQuery } from '../types'; import { CloudWatchMetricsQuery } from '../types';
// Call this function to migrate queries from within the plugin. // Call this function to migrate queries from within the plugin.
@ -20,8 +18,9 @@ const aliasPatterns: Record<string, string> = {
label: `LABEL`, label: `LABEL`,
}; };
// migrateAliasPatterns in the context of https://github.com/grafana/grafana/issues/48434
export function migrateAliasPatterns(query: CloudWatchMetricsQuery): CloudWatchMetricsQuery { export function migrateAliasPatterns(query: CloudWatchMetricsQuery): CloudWatchMetricsQuery {
if (config.featureToggles.cloudWatchDynamicLabels && !query.hasOwnProperty('label')) { if (!query.hasOwnProperty('label')) {
const newQuery = { ...query }; const newQuery = { ...query };
if (!query.hasOwnProperty('label')) { if (!query.hasOwnProperty('label')) {
const regex = /{{\s*(.+?)\s*}}/g; const regex = /{{\s*(.+?)\s*}}/g;

@ -10,10 +10,9 @@ import useMigratedMetricsQuery from './useMigratedMetricsQuery';
describe('usePrepareMetricsQuery', () => { describe('usePrepareMetricsQuery', () => {
const DEFAULT_TEST_QUERY: CloudWatchMetricsQuery = { ...DEFAULT_METRICS_QUERY, refId: 'testId' }; const DEFAULT_TEST_QUERY: CloudWatchMetricsQuery = { ...DEFAULT_METRICS_QUERY, refId: 'testId' };
describe('when dynamic labels are true and there is no label', () => { describe('when there is no label', () => {
const testQuery: CloudWatchMetricsQuery = { ...DEFAULT_TEST_QUERY, alias: 'test' }; const testQuery: CloudWatchMetricsQuery = { ...DEFAULT_TEST_QUERY, alias: 'test' };
it('should replace label with alias and trigger onChangeQuery', async () => { it('should replace label with alias and trigger onChangeQuery', async () => {
config.featureToggles.cloudWatchDynamicLabels = true;
const expectedQuery: CloudWatchMetricsQuery = migrateAliasPatterns(testQuery); const expectedQuery: CloudWatchMetricsQuery = migrateAliasPatterns(testQuery);
const onChangeQuery = jest.fn(); const onChangeQuery = jest.fn();
const { result } = renderHook(() => useMigratedMetricsQuery(testQuery, onChangeQuery)); const { result } = renderHook(() => useMigratedMetricsQuery(testQuery, onChangeQuery));
@ -31,14 +30,16 @@ describe('usePrepareMetricsQuery', () => {
expect(onChangeQuery).toHaveBeenCalledTimes(0); expect(onChangeQuery).toHaveBeenCalledTimes(0);
}); });
}); });
// TODO: delete this test when dynamic labels feature flag is removed
describe('when dynamic labels feature flag is disabled', () => { describe('when dynamic labels feature flag is disabled', () => {
const testQuery: CloudWatchMetricsQuery = { ...DEFAULT_TEST_QUERY }; const testQuery: CloudWatchMetricsQuery = { ...DEFAULT_TEST_QUERY };
it('should not replace label or trigger onChange', async () => { it('should replace label or trigger onChange', async () => {
const expectedQuery: CloudWatchMetricsQuery = migrateAliasPatterns(testQuery);
config.featureToggles.cloudWatchDynamicLabels = false; config.featureToggles.cloudWatchDynamicLabels = false;
const onChangeQuery = jest.fn(); const onChangeQuery = jest.fn();
const { result } = renderHook(() => useMigratedMetricsQuery(testQuery, onChangeQuery)); const { result } = renderHook(() => useMigratedMetricsQuery(testQuery, onChangeQuery));
expect(result.current).toEqual(testQuery); expect(onChangeQuery).toHaveBeenLastCalledWith(result.current);
expect(onChangeQuery).toHaveBeenCalledTimes(0); expect(result.current).toEqual(expectedQuery);
}); });
}); });
}); });

Loading…
Cancel
Save