mirror of
https://github.com/grafana/grafana.git
synced 2024-11-27 03:11:01 -06:00
CloudWatch: Annotation Editor rewrite (#20765)
* wip: react rewrite * Cleanup * Break out non annontations specific fields * Cleanup. Make annontations editor a functional component * Remove redundant classnames * Add paneldata to props * Cleanup * Fix rebase merge problem * Updates after pr feedback * Fix conflict with master
This commit is contained in:
parent
29687903f8
commit
a35b2ac463
@ -54,16 +54,20 @@ func (e *CloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryCo
|
||||
alarmNames = filterAlarms(resp, namespace, metricName, dimensions, statistics, period)
|
||||
} else {
|
||||
if region == "" || namespace == "" || metricName == "" || len(statistics) == 0 {
|
||||
return result, nil
|
||||
return result, errors.New("Invalid annotations query")
|
||||
}
|
||||
|
||||
var qd []*cloudwatch.Dimension
|
||||
for k, v := range dimensions {
|
||||
if vv, ok := v.(string); ok {
|
||||
qd = append(qd, &cloudwatch.Dimension{
|
||||
Name: aws.String(k),
|
||||
Value: aws.String(vv),
|
||||
})
|
||||
if vv, ok := v.([]interface{}); ok {
|
||||
for _, vvv := range vv {
|
||||
if vvvv, ok := vvv.(string); ok {
|
||||
qd = append(qd, &cloudwatch.Dimension{
|
||||
Name: aws.String(k),
|
||||
Value: aws.String(vvvv),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, s := range statistics {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { react2AngularDirective } from 'app/core/utils/react2angular';
|
||||
import { QueryEditor as StackdriverQueryEditor } from 'app/plugins/datasource/stackdriver/components/QueryEditor';
|
||||
import { AnnotationQueryEditor as StackdriverAnnotationQueryEditor } from 'app/plugins/datasource/stackdriver/components/AnnotationQueryEditor';
|
||||
import { AnnotationQueryEditor as CloudWatchAnnotationQueryEditor } from 'app/plugins/datasource/cloudwatch/components/AnnotationQueryEditor';
|
||||
import PageHeader from './components/PageHeader/PageHeader';
|
||||
import EmptyListCTA from './components/EmptyListCTA/EmptyListCTA';
|
||||
import { TagFilter } from './components/TagFilter/TagFilter';
|
||||
@ -93,6 +94,11 @@ export function registerAngularDirectives() {
|
||||
['datasource', { watchDepth: 'reference' }],
|
||||
['templateSrv', { watchDepth: 'reference' }],
|
||||
]);
|
||||
react2AngularDirective('cloudwatchAnnotationQueryEditor', CloudWatchAnnotationQueryEditor, [
|
||||
'query',
|
||||
'onChange',
|
||||
['datasource', { watchDepth: 'reference' }],
|
||||
]);
|
||||
react2AngularDirective('secretFormField', SecretFormField, [
|
||||
'value',
|
||||
'isConfigured',
|
||||
|
@ -0,0 +1,31 @@
|
||||
import _ from 'lodash';
|
||||
import { AnnotationQuery } from './types';
|
||||
|
||||
export class CloudWatchAnnotationsQueryCtrl {
|
||||
static templateUrl = 'partials/annotations.editor.html';
|
||||
annotation: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor() {
|
||||
_.defaultsDeep(this.annotation, {
|
||||
namespace: '',
|
||||
metricName: '',
|
||||
expression: '',
|
||||
dimensions: {},
|
||||
region: 'default',
|
||||
id: '',
|
||||
alias: '',
|
||||
statistics: ['Average'],
|
||||
matchExact: true,
|
||||
prefixMatching: false,
|
||||
actionPrefix: '',
|
||||
alarmNamePrefix: '',
|
||||
});
|
||||
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
|
||||
onChange(query: AnnotationQuery) {
|
||||
Object.assign(this.annotation, query);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import { Switch } from '@grafana/ui';
|
||||
import { PanelData } from '@grafana/data';
|
||||
import { CloudWatchQuery, AnnotationQuery } from '../types';
|
||||
import CloudWatchDatasource from '../datasource';
|
||||
import { QueryField, QueryFieldsEditor } from './';
|
||||
|
||||
export type Props = {
|
||||
query: AnnotationQuery;
|
||||
datasource: CloudWatchDatasource;
|
||||
onChange: (value: AnnotationQuery) => void;
|
||||
data?: PanelData;
|
||||
};
|
||||
|
||||
export function AnnotationQueryEditor(props: React.PropsWithChildren<Props>) {
|
||||
const { query, onChange } = props;
|
||||
return (
|
||||
<>
|
||||
<QueryFieldsEditor
|
||||
{...props}
|
||||
onChange={(editorQuery: CloudWatchQuery) => onChange({ ...query, ...editorQuery })}
|
||||
hideWilcard
|
||||
></QueryFieldsEditor>
|
||||
<div className="gf-form-inline">
|
||||
<Switch
|
||||
label="Enable Prefix Matching"
|
||||
labelClass="query-keyword"
|
||||
checked={query.prefixMatching}
|
||||
onChange={() => onChange({ ...query, prefixMatching: !query.prefixMatching })}
|
||||
/>
|
||||
|
||||
<div className="gf-form gf-form--grow">
|
||||
<QueryField label="Action">
|
||||
<input
|
||||
disabled={!query.prefixMatching}
|
||||
className="gf-form-input width-12"
|
||||
value={query.actionPrefix || ''}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
onChange({ ...query, actionPrefix: event.target.value })
|
||||
}
|
||||
/>
|
||||
</QueryField>
|
||||
<QueryField label="Alarm Name">
|
||||
<input
|
||||
disabled={!query.prefixMatching}
|
||||
className="gf-form-input width-12"
|
||||
value={query.alarmNamePrefix || ''}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
onChange({ ...query, alarmNamePrefix: event.target.value })
|
||||
}
|
||||
/>
|
||||
</QueryField>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -1,18 +1,13 @@
|
||||
import React, { PureComponent, ChangeEvent } from 'react';
|
||||
import { SelectableValue, ExploreQueryFieldProps } from '@grafana/data';
|
||||
import { Input, Segment, SegmentAsync, ValidationEvents, EventsWithValidation, Switch } from '@grafana/ui';
|
||||
import { ExploreQueryFieldProps } from '@grafana/data';
|
||||
import { Input, ValidationEvents, EventsWithValidation, Switch } from '@grafana/ui';
|
||||
import { CloudWatchQuery } from '../types';
|
||||
import CloudWatchDatasource from '../datasource';
|
||||
import { SelectableStrings } from '../types';
|
||||
import { Stats, Dimensions, QueryInlineField, QueryField, Alias } from './';
|
||||
import { QueryField, Alias, QueryFieldsEditor } from './';
|
||||
|
||||
export type Props = ExploreQueryFieldProps<CloudWatchDatasource, CloudWatchQuery>;
|
||||
|
||||
interface State {
|
||||
regions: SelectableStrings;
|
||||
namespaces: SelectableStrings;
|
||||
metricNames: SelectableStrings;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
showMeta: boolean;
|
||||
}
|
||||
|
||||
@ -26,7 +21,7 @@ const idValidationEvents: ValidationEvents = {
|
||||
};
|
||||
|
||||
export class QueryEditor extends PureComponent<Props, State> {
|
||||
state: State = { regions: [], namespaces: [], metricNames: [], variableOptionGroup: {}, showMeta: false };
|
||||
state: State = { showMeta: false };
|
||||
|
||||
static getDerivedStateFromProps(props: Props, state: State) {
|
||||
const { query } = props;
|
||||
@ -70,128 +65,32 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
return state;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { datasource } = this.props;
|
||||
const variableOptionGroup = {
|
||||
label: 'Template Variables',
|
||||
options: this.props.datasource.variables.map(this.toOption),
|
||||
};
|
||||
Promise.all([datasource.metricFindQuery('regions()'), datasource.metricFindQuery('namespaces()')]).then(
|
||||
([regions, namespaces]) => {
|
||||
this.setState({
|
||||
...this.state,
|
||||
regions: [...regions, variableOptionGroup],
|
||||
namespaces: [...namespaces, variableOptionGroup],
|
||||
variableOptionGroup,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadMetricNames = async () => {
|
||||
const { namespace, region } = this.props.query;
|
||||
return this.props.datasource.metricFindQuery(`metrics(${namespace},${region})`).then(this.appendTemplateVariables);
|
||||
};
|
||||
|
||||
appendTemplateVariables = (values: SelectableValue[]) => [
|
||||
...values,
|
||||
{ label: 'Template Variables', options: this.props.datasource.variables.map(this.toOption) },
|
||||
];
|
||||
|
||||
toOption = (value: any) => ({ label: value, value });
|
||||
|
||||
onChange(query: CloudWatchQuery) {
|
||||
const { onChange, onRunQuery } = this.props;
|
||||
onChange(query);
|
||||
onRunQuery();
|
||||
}
|
||||
|
||||
// Load dimension values based on current selected dimensions.
|
||||
// Remove the new dimension key and all dimensions that has a wildcard as selected value
|
||||
loadDimensionValues = (newKey: string) => {
|
||||
const { datasource, query } = this.props;
|
||||
const { [newKey]: value, ...dim } = query.dimensions;
|
||||
const newDimensions = Object.entries(dim).reduce(
|
||||
(result, [key, value]) => (value === '*' ? result : { ...result, [key]: value }),
|
||||
{}
|
||||
);
|
||||
return datasource
|
||||
.getDimensionValues(query.region, query.namespace, query.metricName, newKey, newDimensions)
|
||||
.then(values => (values.length ? [{ value: '*', text: '*', label: '*' }, ...values] : values))
|
||||
.then(this.appendTemplateVariables);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { query, datasource, onChange, onRunQuery, data } = this.props;
|
||||
const { regions, namespaces, variableOptionGroup: variableOptionGroup, showMeta } = this.state;
|
||||
const { data, query, onRunQuery } = this.props;
|
||||
const { showMeta } = this.state;
|
||||
const metaDataExist = data && Object.values(data).length && data.state === 'Done';
|
||||
return (
|
||||
<>
|
||||
<QueryInlineField label="Region">
|
||||
<Segment
|
||||
value={query.region}
|
||||
placeholder="Select region"
|
||||
options={regions}
|
||||
allowCustomValue
|
||||
onChange={({ value: region }) => this.onChange({ ...query, region })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
{query.expression.length === 0 && (
|
||||
<>
|
||||
<QueryInlineField label="Namespace">
|
||||
<Segment
|
||||
value={query.namespace}
|
||||
placeholder="Select namespace"
|
||||
allowCustomValue
|
||||
options={namespaces}
|
||||
onChange={({ value: namespace }) => this.onChange({ ...query, namespace })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
<QueryInlineField label="Metric Name">
|
||||
<SegmentAsync
|
||||
value={query.metricName}
|
||||
placeholder="Select metric name"
|
||||
allowCustomValue
|
||||
loadOptions={this.loadMetricNames}
|
||||
onChange={({ value: metricName }) => this.onChange({ ...query, metricName })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
<QueryInlineField label="Stats">
|
||||
<Stats
|
||||
stats={datasource.standardStatistics.map(this.toOption)}
|
||||
values={query.statistics}
|
||||
onChange={statistics => this.onChange({ ...query, statistics })}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
<QueryInlineField label="Dimensions">
|
||||
<Dimensions
|
||||
dimensions={query.dimensions}
|
||||
onChange={dimensions => this.onChange({ ...query, dimensions })}
|
||||
loadKeys={() =>
|
||||
datasource.getDimensionKeys(query.namespace, query.region).then(this.appendTemplateVariables)
|
||||
}
|
||||
loadValues={this.loadDimensionValues}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
</>
|
||||
)}
|
||||
<QueryFieldsEditor {...this.props}></QueryFieldsEditor>
|
||||
{query.statistics.length <= 1 && (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<QueryField
|
||||
className="query-keyword"
|
||||
label="Id"
|
||||
tooltip="Id can include numbers, letters, and underscore, and must start with a lowercase letter."
|
||||
>
|
||||
<Input
|
||||
className="gf-form-input width-8"
|
||||
onBlur={onRunQuery}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => onChange({ ...query, id: event.target.value })}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
this.onChange({ ...query, id: event.target.value })
|
||||
}
|
||||
validationEvents={idValidationEvents}
|
||||
value={query.id || ''}
|
||||
/>
|
||||
@ -208,7 +107,7 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
onBlur={onRunQuery}
|
||||
value={query.expression || ''}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
onChange({ ...query, expression: event.target.value })
|
||||
this.onChange({ ...query, expression: event.target.value })
|
||||
}
|
||||
/>
|
||||
</QueryField>
|
||||
@ -217,19 +116,20 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
)}
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<QueryField className="query-keyword" label="Period" tooltip="Minimum interval between points in seconds">
|
||||
<QueryField label="Period" tooltip="Minimum interval between points in seconds">
|
||||
<Input
|
||||
className="gf-form-input width-8"
|
||||
value={query.period || ''}
|
||||
placeholder="auto"
|
||||
onBlur={onRunQuery}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => onChange({ ...query, period: event.target.value })}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) =>
|
||||
this.onChange({ ...query, period: event.target.value })
|
||||
}
|
||||
/>
|
||||
</QueryField>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<QueryField
|
||||
className="query-keyword"
|
||||
label="Alias"
|
||||
tooltip="Alias replacement variables: {{metric}}, {{stat}}, {{namespace}}, {{region}}, {{period}}, {{label}}, {{YOUR_DIMENSION_NAME}}"
|
||||
>
|
||||
@ -247,7 +147,6 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
onClick={() =>
|
||||
metaDataExist &&
|
||||
this.setState({
|
||||
...this.state,
|
||||
showMeta: !showMeta,
|
||||
})
|
||||
}
|
||||
|
@ -0,0 +1,144 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Segment, SegmentAsync } from '@grafana/ui';
|
||||
import { CloudWatchQuery, SelectableStrings } from '../types';
|
||||
import CloudWatchDatasource from '../datasource';
|
||||
import { Stats, Dimensions, QueryInlineField } from './';
|
||||
|
||||
export type Props = {
|
||||
query: CloudWatchQuery;
|
||||
datasource: CloudWatchDatasource;
|
||||
onRunQuery?: () => void;
|
||||
onChange: (value: CloudWatchQuery) => void;
|
||||
hideWilcard?: boolean;
|
||||
};
|
||||
|
||||
interface State {
|
||||
regions: SelectableStrings;
|
||||
namespaces: SelectableStrings;
|
||||
metricNames: SelectableStrings;
|
||||
variableOptionGroup: SelectableValue<string>;
|
||||
showMeta: boolean;
|
||||
}
|
||||
|
||||
export function QueryFieldsEditor({
|
||||
query,
|
||||
datasource,
|
||||
onChange,
|
||||
onRunQuery = () => {},
|
||||
hideWilcard = false,
|
||||
}: React.PropsWithChildren<Props>) {
|
||||
const [state, setState] = useState<State>({
|
||||
regions: [],
|
||||
namespaces: [],
|
||||
metricNames: [],
|
||||
variableOptionGroup: {},
|
||||
showMeta: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const variableOptionGroup = {
|
||||
label: 'Template Variables',
|
||||
options: datasource.variables.map(toOption),
|
||||
};
|
||||
|
||||
Promise.all([datasource.metricFindQuery('regions()'), datasource.metricFindQuery('namespaces()')]).then(
|
||||
([regions, namespaces]) => {
|
||||
setState({
|
||||
...state,
|
||||
regions: [...regions, variableOptionGroup],
|
||||
namespaces: [...namespaces, variableOptionGroup],
|
||||
variableOptionGroup,
|
||||
});
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
const loadMetricNames = async () => {
|
||||
const { namespace, region } = query;
|
||||
return datasource.metricFindQuery(`metrics(${namespace},${region})`).then(appendTemplateVariables);
|
||||
};
|
||||
|
||||
const appendTemplateVariables = (values: SelectableValue[]) => [
|
||||
...values,
|
||||
{ label: 'Template Variables', options: datasource.variables.map(toOption) },
|
||||
];
|
||||
|
||||
const toOption = (value: any) => ({ label: value, value });
|
||||
|
||||
const onQueryChange = (query: CloudWatchQuery) => {
|
||||
onChange(query);
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
// Load dimension values based on current selected dimensions.
|
||||
// Remove the new dimension key and all dimensions that has a wildcard as selected value
|
||||
const loadDimensionValues = (newKey: string) => {
|
||||
const { [newKey]: value, ...dim } = query.dimensions;
|
||||
const newDimensions = Object.entries(dim).reduce(
|
||||
(result, [key, value]) => (value === '*' ? result : { ...result, [key]: value }),
|
||||
{}
|
||||
);
|
||||
return datasource
|
||||
.getDimensionValues(query.region, query.namespace, query.metricName, newKey, newDimensions)
|
||||
.then(values => (values.length ? [{ value: '*', text: '*', label: '*' }, ...values] : values))
|
||||
.then(appendTemplateVariables);
|
||||
};
|
||||
|
||||
const { regions, namespaces, variableOptionGroup } = state;
|
||||
return (
|
||||
<>
|
||||
<QueryInlineField label="Region">
|
||||
<Segment
|
||||
value={query.region}
|
||||
placeholder="Select region"
|
||||
options={regions}
|
||||
allowCustomValue
|
||||
onChange={({ value: region }) => onChange({ ...query, region })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
{query.expression.length === 0 && (
|
||||
<>
|
||||
<QueryInlineField label="Namespace">
|
||||
<Segment
|
||||
value={query.namespace}
|
||||
placeholder="Select namespace"
|
||||
allowCustomValue
|
||||
options={namespaces}
|
||||
onChange={({ value: namespace }) => onChange({ ...query, namespace })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
<QueryInlineField label="Metric Name">
|
||||
<SegmentAsync
|
||||
value={query.metricName}
|
||||
placeholder="Select metric name"
|
||||
allowCustomValue
|
||||
loadOptions={loadMetricNames}
|
||||
onChange={({ value: metricName }) => onChange({ ...query, metricName })}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
<QueryInlineField label="Stats">
|
||||
<Stats
|
||||
stats={datasource.standardStatistics.map(toOption)}
|
||||
values={query.statistics}
|
||||
onChange={statistics => onQueryChange({ ...query, statistics })}
|
||||
variableOptionGroup={variableOptionGroup}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
|
||||
<QueryInlineField label="Dimensions">
|
||||
<Dimensions
|
||||
dimensions={query.dimensions}
|
||||
onChange={dimensions => onQueryChange({ ...query, dimensions })}
|
||||
loadKeys={() => datasource.getDimensionKeys(query.namespace, query.region).then(appendTemplateVariables)}
|
||||
loadValues={loadDimensionValues}
|
||||
/>
|
||||
</QueryInlineField>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -2,3 +2,4 @@ export { Stats } from './Stats';
|
||||
export { Dimensions } from './Dimensions';
|
||||
export { QueryInlineField, QueryField } from './Forms';
|
||||
export { Alias } from './Alias';
|
||||
export { QueryFieldsEditor } from './QueryFieldsEditor';
|
||||
|
@ -3,12 +3,9 @@ import { DataSourcePlugin } from '@grafana/data';
|
||||
import { ConfigEditor } from './components/ConfigEditor';
|
||||
import { QueryEditor } from './components/QueryEditor';
|
||||
import CloudWatchDatasource from './datasource';
|
||||
import { CloudWatchAnnotationsQueryCtrl } from './annotations_query_ctrl';
|
||||
import { CloudWatchJsonData, CloudWatchQuery } from './types';
|
||||
|
||||
class CloudWatchAnnotationsQueryCtrl {
|
||||
static templateUrl = 'partials/annotations.editor.html';
|
||||
}
|
||||
|
||||
export const plugin = new DataSourcePlugin<CloudWatchDatasource, CloudWatchQuery, CloudWatchJsonData>(
|
||||
CloudWatchDatasource
|
||||
)
|
||||
|
@ -1,18 +1,5 @@
|
||||
<cloudwatch-query-parameter target="ctrl.annotation" datasource="ctrl.datasource"></cloudwatch-query-parameter>
|
||||
|
||||
<div class="editor-row" style="padding: 2rem 0">
|
||||
<div class="section">
|
||||
<h5>Prefix matching</h5>
|
||||
<div class="gf-form-inline">
|
||||
<gf-form-switch class="gf-form" label="Enable" checked="ctrl.annotation.prefixMatching" switch-class="max-width-6"></gf-form-switch>
|
||||
<div class="gf-form" ng-if="ctrl.annotation.prefixMatching">
|
||||
<span class="gf-form-label">Action</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.actionPrefix'></input>
|
||||
</div>
|
||||
<div class="gf-form" ng-if="ctrl.annotation.prefixMatching">
|
||||
<span class="gf-form-label">Alarm Name</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.alarmNamePrefix'></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<cloudwatch-annotation-query-editor
|
||||
datasource="ctrl.datasource"
|
||||
on-change="ctrl.onChange"
|
||||
query="ctrl.annotation"
|
||||
></cloudwatch-annotation-query-editor>
|
||||
|
@ -13,6 +13,12 @@ export interface CloudWatchQuery extends DataQuery {
|
||||
matchExact: boolean;
|
||||
}
|
||||
|
||||
export interface AnnotationQuery extends CloudWatchQuery {
|
||||
prefixMatching: boolean;
|
||||
actionPrefix: string;
|
||||
alarmNamePrefix: string;
|
||||
}
|
||||
|
||||
export type SelectableStrings = Array<SelectableValue<string>>;
|
||||
|
||||
export interface CloudWatchJsonData extends DataSourceJsonData {
|
||||
|
Loading…
Reference in New Issue
Block a user