Skip to content

Commit

Permalink
Merge pull request #80 from quickwit-oss/ddelemeny/improve-editor-a11y
Browse files Browse the repository at this point in the history
Improve LuceneQueryEditor keyboard a11y, fixes #76
  • Loading branch information
fmassot authored Feb 20, 2024
2 parents 0afa5cb + d63aae3 commit 0c234f5
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 15 deletions.
8 changes: 7 additions & 1 deletion src/LogContext/components/LogContextUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ export function LogContextUI(props: LogContextUIProps ){
<LogContextQueryBuilderSidebar {...props} builder={builder} updateQuery={setParsedQuery} searchableFields={fields}/>
<div className={css`width:100%; display:flex; flex-direction:column; gap:0.5rem; min-width:0;`}>
{ActionBar}
<LuceneQueryEditor value={builder.query} autocompleter={getSuggestions} onChange={builder.setQuery}/>
<LuceneQueryEditor
placeholder="Shift-Enter to run the query, Ctrl-Space to autocomplete"
value={builder.query}
autocompleter={getSuggestions}
onChange={builder.setQuery}
onSubmit={runQuery}
/>
</div>
</DatasourceContext.Provider>
</div>
Expand Down
21 changes: 12 additions & 9 deletions src/components/LuceneQueryEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import React, { useRef, useCallback } from "react";
import { debounceTime } from 'rxjs';
import { useObservableCallback, useSubscription } from 'observable-hooks'
import { css } from "@emotion/css";


import CodeMirror, { ReactCodeMirrorRef } from '@uiw/react-codemirror';
import CodeMirror, { ReactCodeMirrorRef, keymap } from '@uiw/react-codemirror';
import {linter, Diagnostic, lintGutter} from "@codemirror/lint"
import {autocompletion, CompletionContext} from "@codemirror/autocomplete"
import { LuceneQuery } from "utils/lucene";
Expand All @@ -15,6 +13,7 @@ export type LuceneQueryEditorProps = {
value: string,
autocompleter: (word: string) => any,
onChange: (query: string) => void
onSubmit: (query: string) => void
}

export function LuceneQueryEditor(props: LuceneQueryEditorProps){
Expand Down Expand Up @@ -49,18 +48,22 @@ export function LuceneQueryEditor(props: LuceneQueryEditorProps){

const autocomplete = autocompletion({ override: [datasourceCompletions] })

const [onChange, textChanged$] = useObservableCallback<string>(event$ => event$.pipe(debounceTime(1000)))

useSubscription(textChanged$, props.onChange)

return (<CodeMirror
ref={editorRef}
className={css`height:100%`} // XXX : need to set height for both wrapper elements
height="100%"
theme={'dark'}
placeholder={props.placeholder}
value={props.value}
onChange={onChange}
extensions={[queryLinter, lintGutter(), autocomplete]}
onChange={props.onChange}
indentWithTab={false}
extensions={[
queryLinter, lintGutter(),
autocomplete,
keymap.of([{key:'Shift-Enter', run:(target)=>{
props.onSubmit(target.state.doc.toString())
return true;
}}])
]}
/>);
}
2 changes: 2 additions & 0 deletions src/components/QueryEditor/AnnotationQueryEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ export function ElasticsearchAnnotationsQueryEditor(props: Props) {
target: newTarget,
});
}}
// XXX : ain't used at the moment, fix the build
onSubmit={()=>null}
/>
</div>

Expand Down
41 changes: 36 additions & 5 deletions src/components/QueryEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { css } from '@emotion/css';

import React, { createContext } from 'react';
import { debounceTime, throttleTime } from 'rxjs';
import { useObservableCallback, useSubscription } from 'observable-hooks'

import { CoreApp, Field, getDefaultTimeRange, GrafanaTheme2, QueryEditorProps } from '@grafana/data';
import { InlineLabel, useStyles2 } from '@grafana/ui';
Expand Down Expand Up @@ -34,7 +36,7 @@ export const QueryEditor = ({ query, onChange, onRunQuery, datasource, range, ap
query={query}
range={range || getDefaultTimeRange()}
>
<QueryEditorForm value={query} />
<QueryEditorForm value={query} onRunQuery={onRunQuery} />
</ElasticsearchProvider>
);
};
Expand All @@ -54,21 +56,33 @@ export const useSearchableFields = getHook(SearchableFieldsContext)

interface Props {
value: ElasticsearchQuery;
onRunQuery: () => void
}

export const ElasticSearchQueryField = ({ value, onChange }: { value?: string; onChange: (v: string) => void }) => {
type ElasticSearchQueryFieldProps = {
value?: string;
onChange: (v: string) => void
onSubmit: (v: string) => void
}
export const ElasticSearchQueryField = ({ value, onChange, onSubmit }: ElasticSearchQueryFieldProps) => {
const styles = useStyles2(getStyles);
const datasource = useDatasource()
const { getSuggestions } = useDatasourceFields(datasource);

return (
<div className={styles.queryItem}>
<LuceneQueryEditor placeholder="Enter a lucene query" value={value || ''} autocompleter={getSuggestions} onChange={onChange}/>
<LuceneQueryEditor
placeholder="Enter a lucene query - Type Shift-Enter to run query, Ctrl-Space to autocomplete"
value={value || ''}
autocompleter={getSuggestions}
onChange={onChange}
onSubmit={onSubmit}
/>
</div>
);
};

const QueryEditorForm = ({ value }: Props) => {
const QueryEditorForm = ({ value, onRunQuery }: Props) => {
const dispatch = useDispatch();
const nextId = useNextId();
const styles = useStyles2(getStyles);
Expand All @@ -77,6 +91,20 @@ const QueryEditorForm = ({ value }: Props) => {
(metric) => metricAggregationConfig[metric.type].impliedQueryType === 'metrics'
);

const onChange = (query: string) => {
dispatch(changeQuery(query))
}
const onSubmit = (query: string) => {
onChange(query)
onRunQuery()
}

const [onChangeCB, textChanged$] = useObservableCallback<string>(event$ => event$.pipe(debounceTime(1000)))
const [onSubmitCB, submitted$] = useObservableCallback<string>(event$=>event$.pipe(throttleTime(500)))

useSubscription(textChanged$, onChange)
useSubscription(submitted$, onSubmit)

return (
<>
<div className={styles.root}>
Expand All @@ -87,7 +115,10 @@ const QueryEditorForm = ({ value }: Props) => {
</div>
<div className={styles.root}>
<InlineLabel width={17}>Lucene Query</InlineLabel>
<ElasticSearchQueryField onChange={(query) => dispatch(changeQuery(query))} value={value?.query} />
<ElasticSearchQueryField
onChange={onChangeCB}
value={value?.query}
onSubmit={onSubmitCB}/>
</div>

<MetricAggregationsEditor nextId={nextId} />
Expand Down

0 comments on commit 0c234f5

Please sign in to comment.