mirror of https://github.com/grafana/grafana
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 masterpull/21521/head^2
parent
29687903f8
commit
a35b2ac463
@ -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> |
||||
</> |
||||
); |
||||
} |
@ -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> |
||||
</> |
||||
)} |
||||
</> |
||||
); |
||||
} |
@ -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> |
||||
|
Loading…
Reference in new issue