mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
CloudWatch: Deprecate dynamic labels feature toggle, remove support for Alias in frontend (#67222)
This commit is contained in:
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
+15
-20
@@ -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;
|
|
||||||
config.featureToggles.cloudWatchDynamicLabels = true;
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<MetricsQueryEditor
|
<MetricsQueryEditor
|
||||||
{...props}
|
{...props}
|
||||||
query={{ ...props.query, refId: 'A', alias: 'Period: {{period}} InstanceId: {{InstanceId}}' }}
|
query={{ ...props.query, refId: 'A', alias: 'Period: {{period}} InstanceId: {{InstanceId}}' }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(await screen.findByText('Label')).toBeInTheDocument();
|
expect(await screen.findByText('Label')).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Alias')).toBeNull();
|
expect(screen.queryByText('Alias')).toBeNull();
|
||||||
expect(screen.getByText("Period: ${PROP('Period')} InstanceId: ${PROP('Dim.InstanceId')}"));
|
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;
|
||||||
});
|
});
|
||||||
|
|||||||
+12
-25
@@ -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', () => {
|
|
||||||
const cases: TestScenario[] = [
|
|
||||||
{
|
|
||||||
alias: '',
|
|
||||||
label: "${PROP('MetricName')}",
|
|
||||||
},
|
|
||||||
{ alias: '{{metric}}', label: "${PROP('Period')}" },
|
|
||||||
{ alias: '{{namespace}}', label: '' },
|
|
||||||
];
|
|
||||||
test.each(cases)('it should not be migrated once again', ({ alias, label }) => {
|
|
||||||
config.featureToggles.cloudWatchDynamicLabels = true;
|
|
||||||
const testQuery = { ...baseQuery, alias, label };
|
|
||||||
const result = migrateAliasPatterns(testQuery);
|
|
||||||
expect(result.label).toBe(label);
|
|
||||||
expect(result.alias).toBe(alias);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when label was previously migrated', () => {
|
||||||
|
const cases: TestScenario[] = [
|
||||||
|
{
|
||||||
|
alias: '',
|
||||||
|
label: "${PROP('MetricName')}",
|
||||||
|
},
|
||||||
|
{ alias: '{{metric}}', label: "${PROP('Period')}" },
|
||||||
|
{ alias: '{{namespace}}', label: '' },
|
||||||
|
];
|
||||||
|
test.each(cases)('it should not be migrated once again', ({ alias, label }) => {
|
||||||
|
const testQuery = { ...baseQuery, alias, label };
|
||||||
|
const result = migrateAliasPatterns(testQuery);
|
||||||
|
expect(result.label).toBe(label);
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user