// Libraries import React from 'react'; import Cascader from 'rc-cascader'; import PluginPrism from 'slate-prism'; import Prism from 'prismjs'; // Components import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; // Utils & Services // dom also includes Element polyfills import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom'; import BracesPlugin from 'app/features/explore/slate-plugins/braces'; import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; // Types import { LokiQuery } from '../types'; import { TypeaheadOutput } from 'app/types/explore'; const PRISM_SYNTAX = 'promql'; function getChooserText(hasSytax, hasLogLabels) { if (!hasSytax) { return 'Loading labels...'; } if (!hasLogLabels) { return '(No labels found)'; } return 'Log labels'; } export function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: QueryFieldState): string { // Modify suggestion based on context switch (typeaheadContext) { case 'context-labels': { const nextChar = 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 (getNextCharacter() !== '"') { suggestion = `${suggestion}"`; } break; } default: } return suggestion; } interface CascaderOption { label: string; value: string; children?: CascaderOption[]; disabled?: boolean; } interface LokiQueryFieldProps { datasource: any; error?: string | JSX.Element; hint?: any; history?: any[]; initialQuery?: LokiQuery; onClickHintFix?: (action: any) => void; onPressEnter?: () => void; onQueryChange?: (value: LokiQuery, override?: boolean) => void; } interface LokiQueryFieldState { logLabelOptions: any[]; syntaxLoaded: boolean; } class LokiQueryField extends React.PureComponent { plugins: any[]; pluginsSearch: any[]; languageProvider: any; modifiedSearch: string; modifiedQuery: string; constructor(props: LokiQueryFieldProps, context) { super(props, context); if (props.datasource.languageProvider) { this.languageProvider = props.datasource.languageProvider; } this.plugins = [ BracesPlugin(), RunnerPlugin({ handler: props.onPressEnter }), PluginPrism({ onlyIn: node => node.type === 'code_block', getSyntax: node => 'promql', }), ]; this.pluginsSearch = [RunnerPlugin({ handler: props.onPressEnter })]; this.state = { logLabelOptions: [], syntaxLoaded: false, }; } componentDidMount() { if (this.languageProvider) { this.languageProvider .start() .then(remaining => { remaining.map(task => task.then(this.onUpdateLanguage).catch(() => {})); }) .then(() => this.onUpdateLanguage()); } } loadOptions = (selectedOptions: CascaderOption[]) => { const targetOption = selectedOptions[selectedOptions.length - 1]; this.setState(state => { const nextOptions = state.logLabelOptions.map(option => { if (option.value === targetOption.value) { return { ...option, loading: true, }; } return option; }); return { logLabelOptions: nextOptions }; }); this.languageProvider .fetchLabelValues(targetOption.value) .then(this.onUpdateLanguage) .catch(() => {}); }; onChangeLogLabels = (values: string[], selectedOptions: CascaderOption[]) => { if (selectedOptions.length === 2) { const key = selectedOptions[0].value; const value = selectedOptions[1].value; const query = `{${key}="${value}"}`; this.onChangeQuery(query, true); } }; onChangeQuery = (value: string, override?: boolean) => { // Send text change to parent const { initialQuery, onQueryChange } = this.props; if (onQueryChange) { const query = { ...initialQuery, expr: value, }; onQueryChange(query, override); } }; onClickHintFix = () => { const { hint, onClickHintFix } = this.props; if (onClickHintFix && hint && hint.fix) { onClickHintFix(hint.fix.action); } }; onUpdateLanguage = () => { Prism.languages[PRISM_SYNTAX] = this.languageProvider.getSyntax(); const { logLabelOptions } = this.languageProvider; this.setState({ logLabelOptions, syntaxLoaded: true, }); }; onTypeahead = (typeahead: TypeaheadInput): TypeaheadOutput => { if (!this.languageProvider) { return { suggestions: [] }; } const { history } = this.props; const { prefix, text, value, wrapperNode } = typeahead; // Get DOM-dependent context const wrapperClasses = Array.from(wrapperNode.classList); const labelKeyNode = getPreviousCousin(wrapperNode, '.attr-name'); const labelKey = labelKeyNode && labelKeyNode.textContent; const nextChar = getNextCharacter(); const result = this.languageProvider.provideCompletionItems( { text, value, prefix, wrapperClasses, labelKey }, { history } ); console.log('handleTypeahead', wrapperClasses, text, prefix, nextChar, labelKey, result.context); return result; }; render() { const { error, hint, initialQuery } = this.props; const { logLabelOptions, syntaxLoaded } = this.state; const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined; const hasLogLabels = logLabelOptions && logLabelOptions.length > 0; const chooserText = getChooserText(syntaxLoaded, hasLogLabels); return (
{error ?
{error}
: null} {hint ? (
{hint.label}{' '} {hint.fix ? ( {hint.fix.label} ) : null}
) : null}
); } } export default LokiQueryField;