mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Loki: Refactor query editor so query field can be used independently (#33276)
* Refactor Loki so LokiQueryField can be used independently * Refactor PromQueryEditor
This commit is contained in:
@@ -1,16 +1,185 @@
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { LokiQueryFieldForm, LokiQueryFieldFormProps } from './LokiQueryFieldForm';
|
||||
// Libraries
|
||||
import React, { ReactNode } from 'react';
|
||||
|
||||
type LokiQueryFieldProps = Omit<
|
||||
LokiQueryFieldFormProps,
|
||||
'labelsLoaded' | 'onLoadOptions' | 'onLabelsRefresh' | 'absoluteRange'
|
||||
>;
|
||||
import {
|
||||
SlatePrism,
|
||||
TypeaheadOutput,
|
||||
SuggestionsState,
|
||||
QueryField,
|
||||
TypeaheadInput,
|
||||
BracesPlugin,
|
||||
DOMUtil,
|
||||
Icon,
|
||||
} from '@grafana/ui';
|
||||
|
||||
export const LokiQueryField: FunctionComponent<LokiQueryFieldProps> = (props) => {
|
||||
const { datasource, range, ...otherProps } = props;
|
||||
const absoluteTimeRange = { from: range!.from!.valueOf(), to: range!.to!.valueOf() }; // Range here is never optional
|
||||
// Utils & Services
|
||||
// dom also includes Element polyfills
|
||||
import { Plugin, Node } from 'slate';
|
||||
import { LokiLabelBrowser } from './LokiLabelBrowser';
|
||||
|
||||
return <LokiQueryFieldForm datasource={datasource} absoluteRange={absoluteTimeRange} {...otherProps} />;
|
||||
};
|
||||
// Types
|
||||
import { ExploreQueryFieldProps, AbsoluteTimeRange } from '@grafana/data';
|
||||
import { LokiQuery, LokiOptions } from '../types';
|
||||
import { LanguageMap, languages as prismLanguages } from 'prismjs';
|
||||
import LokiLanguageProvider, { LokiHistoryItem } from '../language_provider';
|
||||
import LokiDatasource from '../datasource';
|
||||
|
||||
export default LokiQueryField;
|
||||
function getChooserText(hasSyntax: boolean, hasLogLabels: boolean) {
|
||||
if (!hasSyntax) {
|
||||
return 'Loading labels...';
|
||||
}
|
||||
if (!hasLogLabels) {
|
||||
return '(No logs found)';
|
||||
}
|
||||
return 'Log browser';
|
||||
}
|
||||
|
||||
function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: SuggestionsState): string {
|
||||
// Modify suggestion based on context
|
||||
switch (typeaheadContext) {
|
||||
case 'context-labels': {
|
||||
const nextChar = DOMUtil.getNextCharacter();
|
||||
if (!nextChar || nextChar === '}' || nextChar === ',') {
|
||||
suggestion += '=';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'context-label-values': {
|
||||
// Always add quotes and remove existing ones instead
|
||||
if (!typeaheadText.match(/^(!?=~?"|")/)) {
|
||||
suggestion = `"${suggestion}`;
|
||||
}
|
||||
if (DOMUtil.getNextCharacter() !== '"') {
|
||||
suggestion = `${suggestion}"`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
}
|
||||
return suggestion;
|
||||
}
|
||||
|
||||
export interface LokiQueryFieldProps extends ExploreQueryFieldProps<LokiDatasource, LokiQuery, LokiOptions> {
|
||||
history: LokiHistoryItem[];
|
||||
absoluteRange: AbsoluteTimeRange;
|
||||
ExtraFieldElement?: ReactNode;
|
||||
}
|
||||
|
||||
interface LokiQueryFieldState {
|
||||
labelsLoaded: boolean;
|
||||
labelBrowserVisible: boolean;
|
||||
}
|
||||
|
||||
export class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, LokiQueryFieldState> {
|
||||
plugins: Plugin[];
|
||||
|
||||
constructor(props: LokiQueryFieldProps) {
|
||||
super(props);
|
||||
|
||||
this.state = { labelsLoaded: false, labelBrowserVisible: false };
|
||||
|
||||
this.plugins = [
|
||||
BracesPlugin(),
|
||||
SlatePrism(
|
||||
{
|
||||
onlyIn: (node: Node) => node.object === 'block' && node.type === 'code_block',
|
||||
getSyntax: (node: Node) => 'logql',
|
||||
},
|
||||
{ ...(prismLanguages as LanguageMap), logql: this.props.datasource.languageProvider.getSyntax() }
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
async componentDidUpdate() {
|
||||
await this.props.datasource.languageProvider.start();
|
||||
this.setState({ labelsLoaded: true });
|
||||
}
|
||||
|
||||
onChangeLogLabels = (selector: string) => {
|
||||
this.onChangeQuery(selector, true);
|
||||
this.setState({ labelBrowserVisible: false });
|
||||
};
|
||||
|
||||
onChangeQuery = (value: string, override?: boolean) => {
|
||||
// Send text change to parent
|
||||
const { query, onChange, onRunQuery } = this.props;
|
||||
if (onChange) {
|
||||
const nextQuery = { ...query, expr: value };
|
||||
onChange(nextQuery);
|
||||
|
||||
if (override && onRunQuery) {
|
||||
onRunQuery();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onClickChooserButton = () => {
|
||||
this.setState((state) => ({ labelBrowserVisible: !state.labelBrowserVisible }));
|
||||
};
|
||||
|
||||
onTypeahead = async (typeahead: TypeaheadInput): Promise<TypeaheadOutput> => {
|
||||
const { datasource } = this.props;
|
||||
|
||||
if (!datasource.languageProvider) {
|
||||
return { suggestions: [] };
|
||||
}
|
||||
|
||||
const lokiLanguageProvider = datasource.languageProvider as LokiLanguageProvider;
|
||||
const { history } = this.props;
|
||||
const { prefix, text, value, wrapperClasses, labelKey } = typeahead;
|
||||
|
||||
const result = await lokiLanguageProvider.provideCompletionItems(
|
||||
{ text, value, prefix, wrapperClasses, labelKey },
|
||||
{ history }
|
||||
);
|
||||
return result;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { ExtraFieldElement, query, datasource } = this.props;
|
||||
const { labelsLoaded, labelBrowserVisible } = this.state;
|
||||
const lokiLanguageProvider = datasource.languageProvider as LokiLanguageProvider;
|
||||
const cleanText = datasource.languageProvider ? lokiLanguageProvider.cleanText : undefined;
|
||||
const hasLogLabels = lokiLanguageProvider.getLabelKeys().length > 0;
|
||||
const chooserText = getChooserText(labelsLoaded, hasLogLabels);
|
||||
const buttonDisabled = !(labelsLoaded && hasLogLabels);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-inline gf-form-inline--xs-view-flex-column flex-grow-1">
|
||||
<button
|
||||
className="gf-form-label query-keyword pointer"
|
||||
onClick={this.onClickChooserButton}
|
||||
disabled={buttonDisabled}
|
||||
>
|
||||
{chooserText}
|
||||
<Icon name={labelBrowserVisible ? 'angle-down' : 'angle-right'} />
|
||||
</button>
|
||||
<div className="gf-form gf-form--grow flex-shrink-1 min-width-15">
|
||||
<QueryField
|
||||
additionalPlugins={this.plugins}
|
||||
cleanText={cleanText}
|
||||
query={query.expr}
|
||||
onTypeahead={this.onTypeahead}
|
||||
onWillApplySuggestion={willApplySuggestion}
|
||||
onChange={this.onChangeQuery}
|
||||
onBlur={this.props.onBlur}
|
||||
onRunQuery={this.props.onRunQuery}
|
||||
placeholder="Enter a Loki query (run with Shift+Enter)"
|
||||
portalOrigin="loki"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{labelBrowserVisible && (
|
||||
<div className="gf-form">
|
||||
<LokiLabelBrowser languageProvider={lokiLanguageProvider} onChange={this.onChangeLogLabels} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{ExtraFieldElement}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user