mirror of
https://github.com/grafana/grafana.git
synced 2025-09-21 18:02:59 +08:00
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:
@ -235,12 +235,14 @@ export function getAnnotationsFromData(
|
|||||||
* Opt out of using the default mapping functionality on frontend.
|
* Opt out of using the default mapping functionality on frontend.
|
||||||
*/
|
*/
|
||||||
export function shouldUseMappingUI(datasource: DataSourceApi): boolean {
|
export function shouldUseMappingUI(datasource: DataSourceApi): boolean {
|
||||||
return datasource.type !== 'prometheus';
|
const { type } = datasource;
|
||||||
|
return type !== 'prometheus' && type !== 'elasticsearch';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use legacy runner. Used only as an escape hatch for easier transition to React based annotation editor.
|
* Use legacy runner. Used only as an escape hatch for easier transition to React based annotation editor.
|
||||||
*/
|
*/
|
||||||
export function shouldUseLegacyRunner(datasource: DataSourceApi): boolean {
|
export function shouldUseLegacyRunner(datasource: DataSourceApi): boolean {
|
||||||
return datasource.type === 'prometheus';
|
const { type } = datasource;
|
||||||
|
return type === 'prometheus' || type === 'elasticsearch';
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -53,13 +53,31 @@ interface Props {
|
|||||||
value: ElasticsearchQuery;
|
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 QueryEditorForm = ({ value }: Props) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const nextId = useNextId();
|
const nextId = useNextId();
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
// To be considered a time series query, the last bucked aggregation must be a Date Histogram
|
// 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(
|
const showBucketAggregationsEditor = value.metrics?.every(
|
||||||
(metric) => !metricAggregationConfig[metric.type].isSingleMetric
|
(metric) => !metricAggregationConfig[metric.type].isSingleMetric
|
||||||
@ -69,17 +87,8 @@ const QueryEditorForm = ({ value }: Props) => {
|
|||||||
<>
|
<>
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<InlineLabel width={17}>Query</InlineLabel>
|
<InlineLabel width={17}>Query</InlineLabel>
|
||||||
<div className={styles.queryFieldWrapper}>
|
<ElasticSearchQueryField onChange={(query) => dispatch(changeQuery(query))} value={value?.query} />
|
||||||
<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>
|
|
||||||
<InlineField
|
<InlineField
|
||||||
label="Alias"
|
label="Alias"
|
||||||
labelWidth={15}
|
labelWidth={15}
|
||||||
|
@ -31,6 +31,7 @@ import { RowContextOptions } from '@grafana/ui/src/components/Logs/LogRowContext
|
|||||||
import { queryLogsVolume } from 'app/core/logs_model';
|
import { queryLogsVolume } from 'app/core/logs_model';
|
||||||
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
|
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
|
||||||
|
import { ElasticsearchAnnotationsQueryEditor } from './components/QueryEditor/AnnotationQueryEditor';
|
||||||
import {
|
import {
|
||||||
BucketAggregation,
|
BucketAggregation,
|
||||||
isBucketAggregationWithField,
|
isBucketAggregationWithField,
|
||||||
@ -117,6 +118,9 @@ export class ElasticDatasource
|
|||||||
this.logLevelField = settingsData.logLevelField || '';
|
this.logLevelField = settingsData.logLevelField || '';
|
||||||
this.dataLinks = settingsData.dataLinks || [];
|
this.dataLinks = settingsData.dataLinks || [];
|
||||||
this.includeFrozen = settingsData.includeFrozen ?? false;
|
this.includeFrozen = settingsData.includeFrozen ?? false;
|
||||||
|
this.annotations = {
|
||||||
|
QueryEditor: ElasticsearchAnnotationsQueryEditor,
|
||||||
|
};
|
||||||
|
|
||||||
if (this.logMessageField === '') {
|
if (this.logMessageField === '') {
|
||||||
this.logMessageField = undefined;
|
this.logMessageField = undefined;
|
||||||
|
@ -4,11 +4,4 @@ import { QueryEditor } from './components/QueryEditor';
|
|||||||
import { ConfigEditor } from './configuration/ConfigEditor';
|
import { ConfigEditor } from './configuration/ConfigEditor';
|
||||||
import { ElasticDatasource } from './datasource';
|
import { ElasticDatasource } from './datasource';
|
||||||
|
|
||||||
class ElasticAnnotationsQueryCtrl {
|
export const plugin = new DataSourcePlugin(ElasticDatasource).setQueryEditor(QueryEditor).setConfigEditor(ConfigEditor);
|
||||||
static templateUrl = 'partials/annotations.editor.html';
|
|
||||||
}
|
|
||||||
|
|
||||||
export const plugin = new DataSourcePlugin(ElasticDatasource)
|
|
||||||
.setQueryEditor(QueryEditor)
|
|
||||||
.setConfigEditor(ConfigEditor)
|
|
||||||
.setAnnotationQueryCtrl(ElasticAnnotationsQueryCtrl);
|
|
||||||
|
@ -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>
|
|
Reference in New Issue
Block a user