Elasticsearch: Migrate annotation editor to react (#49529)

* Migrate annotation editor to react

* Gio patch

* Update types

* Optional and conform to older design
This commit is contained in:
Joey Tawadrous
2022-05-31 09:26:40 +01:00
committed by GitHub
parent 8fa8c9dc8b
commit 387267b30a
6 changed files with 121 additions and 60 deletions

View File

@ -0,0 +1,91 @@
import React from 'react';
import { AnnotationQuery } from '@grafana/data';
import { EditorRow, EditorField } from '@grafana/experimental';
import { Input } from '@grafana/ui';
import { ElasticsearchQuery } from '../../types';
import { ElasticQueryEditorProps, ElasticSearchQueryField } from './index';
type Props = ElasticQueryEditorProps & {
annotation?: AnnotationQuery<ElasticsearchQuery>;
onAnnotationChange?: (annotation: AnnotationQuery<ElasticsearchQuery>) => void;
};
export function ElasticsearchAnnotationsQueryEditor(props: Props) {
const annotation = props.annotation!;
const onAnnotationChange = props.onAnnotationChange!;
return (
<>
<div className="gf-form-group">
<ElasticSearchQueryField
value={annotation.target?.query}
onChange={(query) => {
onAnnotationChange({
...annotation,
query,
});
}}
/>
</div>
<div className="gf-form-group">
<h6>Field mappings</h6>
<EditorRow>
<EditorField label="Time">
<Input
type="text"
placeholder="@timestamp"
value={annotation.timeField}
onChange={(e) => {
onAnnotationChange({
...annotation,
timeField: e.currentTarget.value,
});
}}
/>
</EditorField>
<EditorField label="Time End">
<Input
type="text"
value={annotation.timeEndField}
onChange={(e) => {
onAnnotationChange({
...annotation,
timeEndField: e.currentTarget.value,
});
}}
/>
</EditorField>
<EditorField label="Text">
<Input
type="text"
value={annotation.textField}
onChange={(e) => {
onAnnotationChange({
...annotation,
textField: e.currentTarget.value,
});
}}
/>
</EditorField>
<EditorField label="Tags">
<Input
type="text"
placeholder="tags"
value={annotation.tagsField}
onChange={(e) => {
onAnnotationChange({
...annotation,
tagsField: e.currentTarget.value,
});
}}
/>
</EditorField>
</EditorRow>
</div>
</>
);
}

View File

@ -53,13 +53,31 @@ interface Props {
value: ElasticsearchQuery;
}
export const ElasticSearchQueryField = ({ value, onChange }: { value?: string; onChange: (v: string) => void }) => {
const styles = useStyles2(getStyles);
return (
<div className={styles.queryFieldWrapper}>
<QueryField
query={value}
// By default QueryField calls onChange if onBlur is not defined, this will trigger a rerender
// And slate will claim the focus, making it impossible to leave the field.
onBlur={() => {}}
onChange={onChange}
placeholder="Lucene Query"
portalOrigin="elasticsearch"
/>
</div>
);
};
const QueryEditorForm = ({ value }: Props) => {
const dispatch = useDispatch();
const nextId = useNextId();
const styles = useStyles2(getStyles);
// To be considered a time series query, the last bucked aggregation must be a Date Histogram
const isTimeSeriesQuery = value.bucketAggs?.slice(-1)[0]?.type === 'date_histogram';
const isTimeSeriesQuery = value?.bucketAggs?.slice(-1)[0]?.type === 'date_histogram';
const showBucketAggregationsEditor = value.metrics?.every(
(metric) => !metricAggregationConfig[metric.type].isSingleMetric
@ -69,17 +87,8 @@ const QueryEditorForm = ({ value }: Props) => {
<>
<div className={styles.root}>
<InlineLabel width={17}>Query</InlineLabel>
<div className={styles.queryFieldWrapper}>
<QueryField
query={value.query}
// By default QueryField calls onChange if onBlur is not defined, this will trigger a rerender
// And slate will claim the focus, making it impossible to leave the field.
onBlur={() => {}}
onChange={(query) => dispatch(changeQuery(query))}
placeholder="Lucene Query"
portalOrigin="elasticsearch"
/>
</div>
<ElasticSearchQueryField onChange={(query) => dispatch(changeQuery(query))} value={value?.query} />
<InlineField
label="Alias"
labelWidth={15}

View File

@ -31,6 +31,7 @@ import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContext
import { queryLogsVolume } from 'app/core/logs_model';
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { ElasticsearchAnnotationsQueryEditor } from './components/QueryEditor/AnnotationQueryEditor';
import {
BucketAggregation,
isBucketAggregationWithField,
@ -117,6 +118,9 @@ export class ElasticDatasource
this.logLevelField = settingsData.logLevelField || '';
this.dataLinks = settingsData.dataLinks || [];
this.includeFrozen = settingsData.includeFrozen ?? false;
this.annotations = {
QueryEditor: ElasticsearchAnnotationsQueryEditor,
};
if (this.logMessageField === '') {
this.logMessageField = undefined;

View File

@ -4,11 +4,4 @@ import { QueryEditor } from './components/QueryEditor';
import { ConfigEditor } from './configuration/ConfigEditor';
import { ElasticDatasource } from './datasource';
class ElasticAnnotationsQueryCtrl {
static templateUrl = 'partials/annotations.editor.html';
}
export const plugin = new DataSourcePlugin(ElasticDatasource)
.setQueryEditor(QueryEditor)
.setConfigEditor(ConfigEditor)
.setAnnotationQueryCtrl(ElasticAnnotationsQueryCtrl);
export const plugin = new DataSourcePlugin(ElasticDatasource).setQueryEditor(QueryEditor).setConfigEditor(ConfigEditor);

View File

@ -1,38 +0,0 @@
<div class="gf-form-group">
<div class="gf-form" ng-if="ctrl.annotation.index">
<span class="gf-form-label width-14">Index name</span>
<input type="text" class="gf-form-input max-width-20" ng-model='ctrl.annotation.index' placeholder="events-*"></input>
</div>
<div class="gf-form-group">
<div class="gf-form">
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.query'
placeholder="Elasticsearch lucene query"></input>
</div>
</div>
</div>
<div class="gf-form-group">
<h6>Field mappings</h6>
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label">Time</span>
<input type="text" class="gf-form-input max-width-14" ng-model='ctrl.annotation.timeField' placeholder="@timestamp"></input>
</div>
<div class="gf-form">
<span class="gf-form-label">Time End</span>
<input type="text" class="gf-form-input max-width-14" ng-model='ctrl.annotation.timeEndField' placeholder=""></input>
</div>
<div class="gf-form">
<span class="gf-form-label">Text</span>
<input type="text" class="gf-form-input max-width-14" ng-model='ctrl.annotation.textField' placeholder=""></input>
</div>
<div class="gf-form">
<span class="gf-form-label">Tags</span>
<input type="text" class="gf-form-input max-width-10" ng-model='ctrl.annotation.tagsField' placeholder="tags"></input>
</div>
<div class="gf-form" ng-show="ctrl.annotation.titleField">
<span class="gf-form-label">Title <em class="muted">(deprecated)</em></span>
<input type="text" class="gf-form-input max-width-16" ng-model='ctrl.annotation.titleField' placeholder="desc"></input>
</div>
</div>
</div>