Skip to content

Commit 6781c2b

Browse files
committed
Use CM-powered QueryEditor in main components
1 parent e07fae9 commit 6781c2b

File tree

9 files changed

+151
-140
lines changed

9 files changed

+151
-140
lines changed

src/LogContext/components/LogContextQueryBuilderSidebar.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import React, { useEffect, useMemo, useState } from "react";
22
// import { Field } from '@grafana/data';
33
import { useTheme2, CollapsableSection, Icon } from '@grafana/ui';
4-
import { LogContextProps, useSearchableFields } from "./LogContextUI";
4+
import { LogContextProps } from "./LogContextUI";
55
import { css, cx } from "@emotion/css";
66
import { LuceneQuery } from "utils/lucene";
7-
import { useQueryBuilderContext } from 'LogContext/QueryBuilder';
7+
import { useQueryBuilderContext } from '@/QueryBuilder/lucene';
88

99

1010
// TODO : define sensible defaults here
@@ -98,23 +98,25 @@ const lcSidebarStyle = css`
9898
`
9999

100100
type QueryBuilderProps = {
101+
searchableFields: any[],
101102
updateQuery: (query: LuceneQuery) => void
102103
}
103104

104105
export function LogContextQueryBuilderSidebar(props: LogContextProps & QueryBuilderProps) {
105106
const builder = useQueryBuilderContext();
106-
const searchableFields = useSearchableFields();
107107

108-
const {row, updateQuery} = props;
108+
const {row, updateQuery, searchableFields} = props;
109109
const [fields, setFields] = useState<Field[]>([]);
110110

111111
const filteredFields = useMemo(() => {
112-
return searchableFields
112+
const searchableFieldsNames = searchableFields.map(f=>f.text);
113+
return row.dataFrame.fields
114+
.filter(f=>searchableFieldsNames.includes(f.name))
113115
// exclude some low-filterability fields
114116
.filter((f)=> !excludedFields.includes(f.name) && isPrimitive(f.type))
115117
// sort fields by name
116118
.sort((f1, f2)=> (f1.name>f2.name ? 1 : -1))
117-
}, [searchableFields]);
119+
}, [row, searchableFields]);
118120

119121
useEffect(() => {
120122
const fields = filteredFields
@@ -157,6 +159,7 @@ export function LogContextQueryBuilderSidebar(props: LogContextProps & QueryBuil
157159
}
158160

159161
const renderFieldSection = (field: Field)=>{
162+
console.log('FIELD', field)
160163
return (
161164
<CollapsableSection
162165
label={LogContextFieldSection(field)}

src/LogContext/components/LogContextQueryEditor.tsx

Lines changed: 0 additions & 89 deletions
This file was deleted.

src/LogContext/components/LogContextUI.tsx

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import React, { useEffect, useState, useCallback, useMemo, createContext } from "react";
2-
import { Field, LogRowModel, } from '@grafana/data';
1+
import React, { useEffect, useState, useCallback, useMemo } from "react";
2+
import { LogRowModel } from '@grafana/data';
33
import { ElasticsearchQuery as DataQuery } from '../../types';
4-
import { LogContextQueryEditor } from "./LogContextQueryEditor";
4+
import { LuceneQueryEditor } from "../../components/LuceneQueryEditor";
55

66
import { css } from "@emotion/css";
77
import { Button } from "@grafana/ui";
8-
import { useQueryBuilder, QueryBuilderContext } from 'LogContext/QueryBuilder';
8+
import { useQueryBuilder, QueryBuilderContext } from '@/QueryBuilder/lucene';
99
import { LogContextQueryBuilderSidebar } from "./LogContextQueryBuilderSidebar";
1010
import { DatasourceContext } from "components/QueryEditor/ElasticsearchQueryContext";
1111
import { QuickwitDataSource } from "datasource";
12-
import { getHook } from "utils/context";
12+
import { useDatasourceFields } from "datasource.utils";
1313

1414
const logContextUiStyle = css`
1515
display: flex;
@@ -28,29 +28,13 @@ export interface LogContextUIProps extends LogContextProps {
2828
datasource: QuickwitDataSource,
2929
updateQuery: (query: string) => void
3030
}
31-
const SearchableFieldsContext = createContext<Field[]|undefined>(undefined)
32-
export const useSearchableFields = getHook(SearchableFieldsContext)
3331

3432
export function LogContextUI(props: LogContextUIProps ){
3533
const builder = useQueryBuilder();
3634
const {query, parsedQuery, setQuery, setParsedQuery} = builder;
3735
const [canRunQuery, setCanRunQuery] = useState<boolean>(false);
3836
const { origQuery, updateQuery, runContextQuery } = props;
39-
const datasource = props.datasource;
40-
const [fields, setFields] = useState<typeof props.row.dataFrame.fields>([]);
41-
42-
useEffect(()=>{
43-
const dfFields = props.row.dataFrame.fields
44-
// Get datasource fields definitions
45-
datasource.getTagKeys({searchable:true}).then((dsFields)=>{
46-
const dsFieldsNames = dsFields.map(f=>f.text)
47-
setFields( dfFields.filter(f=> dsFieldsNames.includes(f.name)))
48-
49-
});
50-
51-
52-
53-
}, [props.row, datasource, setFields])
37+
const [fields, autocompleter] = useDatasourceFields(props.datasource);
5438

5539
useEffect(()=>{
5640
setQuery(origQuery?.query || '')
@@ -79,13 +63,11 @@ export function LogContextUI(props: LogContextUIProps ){
7963
<div className={logContextUiStyle}>
8064
<DatasourceContext.Provider value={props.datasource}>
8165
<QueryBuilderContext.Provider value={builder}>
82-
<SearchableFieldsContext.Provider value={fields}>
83-
<LogContextQueryBuilderSidebar {...props} updateQuery={setParsedQuery}/>
66+
<LogContextQueryBuilderSidebar {...props} updateQuery={setParsedQuery} searchableFields={fields}/>
8467
<div className={css`width:100%; display:flex; flex-direction:column; gap:0.5rem; min-width:0;`}>
8568
{ActionBar}
86-
<LogContextQueryEditor />
69+
<LuceneQueryEditor builder={builder} autocompleter={autocompleter} onChange={builder.setQuery}/>
8770
</div>
88-
</SearchableFieldsContext.Provider>
8971
</QueryBuilderContext.Provider>
9072
</DatasourceContext.Provider>
9173
</div>

src/QueryBuilder.ts renamed to src/QueryBuilder/elastic.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {
22
TermsQuery,
3-
} from './types';
3+
} from '../types';
44

55
export class ElasticQueryBuilder {
66
timeField: string;

src/LogContext/QueryBuilder.ts renamed to src/QueryBuilder/lucene.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import * as lucene from "utils/lucene";
21
import { useState, useCallback, createContext } from 'react';
3-
import { LuceneQuery as QueryBuilder } from 'utils/lucene';
4-
import { getHook } from "utils/context";
2+
import { getHook } from "@/utils/context";
3+
import * as lucene from "@/utils/lucene";
4+
import { LuceneQuery as QueryBuilder } from '@/utils/lucene';
55

6-
type LuceneQueryBuilder = {
6+
export type LuceneQueryBuilder = {
77
query: string,
88
parsedQuery: lucene.LuceneQuery,
99
setQuery: (query: string) => void

src/components/LuceneQueryEditor.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React, { useMemo, useRef} from "react";
2+
import { css } from "@emotion/css";
3+
4+
import { LuceneQueryBuilder } from '@/QueryBuilder/lucene';
5+
6+
import CodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror';
7+
import {linter, Diagnostic, lintGutter} from "@codemirror/lint"
8+
import {autocompletion, CompletionContext} from "@codemirror/autocomplete"
9+
10+
11+
export type LuceneQueryEditorProps = {
12+
placeholder?: string,
13+
builder: LuceneQueryBuilder,
14+
autocompleter: (word: string)=>any,
15+
onChange: (query: string)=>void
16+
}
17+
18+
export function LuceneQueryEditor(props: LuceneQueryEditorProps){
19+
const editorRef = useRef<ReactCodeMirrorRef|null>(null)
20+
21+
const queryLinter = linter( view => {
22+
let diagnostics: Diagnostic[] = [];
23+
24+
const error = props.builder.parsedQuery?.parseError
25+
if (error) {
26+
diagnostics.push({
27+
severity: "error",
28+
message: error.message,
29+
from: view.state.doc.line(error.location.start.line).from + error.location.start.column -1,
30+
to: view.state.doc.line(error.location.end.line).from + error.location.end.column -1,
31+
}) ;
32+
}
33+
return diagnostics
34+
})
35+
36+
const autocomplete = useMemo(()=>autocompletion({
37+
override: [async (context: CompletionContext)=>{
38+
let word = context.matchBefore(/\S*/);
39+
if (!word){ return null }
40+
const suggestions = await props.autocompleter(word?.text);
41+
return {
42+
from: word.from + suggestions.from,
43+
options: suggestions.options
44+
}
45+
}]
46+
}),[props.autocompleter])
47+
48+
return (<CodeMirror
49+
ref={editorRef}
50+
className={css`height:100%`} // XXX : need to set height for both wrapper elements
51+
height="100%"
52+
theme={'dark'}
53+
placeholder={props.placeholder}
54+
value={props.builder.query}
55+
onChange={props.onChange}
56+
extensions={[queryLinter, lintGutter(), autocomplete]}
57+
/>);
58+
}

src/components/QueryEditor/index.tsx

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
import { css } from '@emotion/css';
22

3-
import React from 'react';
3+
import React, { createContext, useCallback, useEffect } from 'react';
44

5-
import { CoreApp, getDefaultTimeRange, GrafanaTheme2, QueryEditorProps } from '@grafana/data';
6-
import { InlineLabel, QueryField, useStyles2 } from '@grafana/ui';
5+
import { CoreApp, Field, getDefaultTimeRange, GrafanaTheme2, QueryEditorProps } from '@grafana/data';
6+
import { InlineLabel, useStyles2 } from '@grafana/ui';
77

88
import { ElasticDatasource } from '@/datasource';
99
import { useNextId } from '@/hooks/useNextId';
1010
import { useDispatch } from '@/hooks/useStatelessReducer';
1111
import { ElasticsearchQuery } from '@/types';
1212

1313
import { BucketAggregationsEditor } from './BucketAggregationsEditor';
14-
import { ElasticsearchProvider } from './ElasticsearchQueryContext';
14+
import { ElasticsearchProvider, useDatasource } from './ElasticsearchQueryContext';
1515
import { MetricAggregationsEditor } from './MetricAggregationsEditor';
1616
import { metricAggregationConfig } from './MetricAggregationsEditor/utils';
1717
import { changeQuery } from './state';
1818
import { QuickwitOptions } from '../../quickwit';
1919
import { QueryTypeSelector } from './QueryTypeSelector';
2020

21+
import { useQueryBuilder } from '@/QueryBuilder/lucene';
22+
import { getHook } from 'utils/context';
23+
import { LuceneQueryEditor } from '@/components/LuceneQueryEditor';
24+
import { useDatasourceFields } from 'datasource.utils';
25+
2126
export type ElasticQueryEditorProps = QueryEditorProps<ElasticDatasource, ElasticsearchQuery, QuickwitOptions>;
2227

2328
export const QueryEditor = ({ query, onChange, onRunQuery, datasource, range, app }: ElasticQueryEditorProps) => {
@@ -45,24 +50,34 @@ const getStyles = (theme: GrafanaTheme2) => ({
4550
`,
4651
});
4752

53+
const SearchableFieldsContext = createContext<Field[]|undefined>(undefined)
54+
export const useSearchableFields = getHook(SearchableFieldsContext)
55+
4856
interface Props {
4957
value: ElasticsearchQuery;
5058
}
5159

5260
export const ElasticSearchQueryField = ({ value, onChange }: { value?: string; onChange: (v: string) => void }) => {
5361
const styles = useStyles2(getStyles);
62+
const builder = useQueryBuilder();
63+
const {query, setQuery} = builder;
64+
const datasource = useDatasource()
65+
const [fields, autocompleter] = useDatasourceFields(datasource);
66+
67+
useEffect(()=>{
68+
setQuery(value || '')
69+
}, [setQuery, value])
70+
71+
const onEditorChange = useCallback((query:string)=>{
72+
setQuery(query);
73+
onChange(query)
74+
},[query, setQuery])
5475

5576
return (
5677
<div className={styles.queryItem}>
57-
<QueryField
58-
query={value}
59-
// By default QueryField calls onChange if onBlur is not defined, this will trigger a rerender
60-
// And slate will claim the focus, making it impossible to leave the field.
61-
onBlur={() => {}}
62-
onChange={onChange}
63-
placeholder="Enter a lucene query"
64-
portalOrigin="elasticsearch"
65-
/>
78+
<SearchableFieldsContext.Provider value={fields}>
79+
<LuceneQueryEditor placeholder="Enter a lucene query" builder={builder} autocompleter={autocompleter} onChange={onEditorChange}/>
80+
</SearchableFieldsContext.Provider>
6681
</div>
6782
);
6883
};

src/datasource.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {
3636
TemplateSrv,
3737
getDataSourceSrv } from '@grafana/runtime';
3838
import { QuickwitOptions } from 'quickwit';
39-
import { ElasticQueryBuilder } from 'QueryBuilder';
39+
import { ElasticQueryBuilder } from '@/QueryBuilder/elastic';
4040
import { colors } from '@grafana/ui';
4141

4242
import { BarAlignment, DataQuery, GraphDrawStyle, StackingMode } from '@grafana/schema';

0 commit comments

Comments
 (0)