<commit_before>import _ from 'lodash';
import React, { Context } from 'react';
import ReactDOM from 'react-dom';
// @ts-ignore
import { Change, Value } from 'slate';
// @ts-ignore
import { Editor } from 'slate-react';
// @ts-ignore
import Plain from 'slate-plain-serializer';
import classnames from 'classnames';

import { CompletionItem, CompletionItemGroup, TypeaheadOutput } from 'app/types/explore';

import ClearPlugin from './slate-plugins/clear';
import NewlinePlugin from './slate-plugins/newline';

import { TypeaheadWithTheme } from './Typeahead';
import { makeFragment, makeValue } from '@grafana/ui';

export const TYPEAHEAD_DEBOUNCE = 100;
export const HIGHLIGHT_WAIT = 500;

function getSuggestionByIndex(suggestions: CompletionItemGroup[], index: number): CompletionItem {
  // Flatten suggestion groups
  const flattenedSuggestions = suggestions.reduce((acc, g) => acc.concat(g.items), []);
  const correctedIndex = Math.max(index, 0) % flattenedSuggestions.length;
  return flattenedSuggestions[correctedIndex];
}

function hasSuggestions(suggestions: CompletionItemGroup[]): boolean {
  return suggestions && suggestions.length > 0;
}

export interface QueryFieldProps {
  additionalPlugins?: any[];
  cleanText?: (text: string) => string;
  disabled?: boolean;
  initialQuery: string | null;
  onRunQuery?: () => void;
  onChange?: (value: string) => void;
  onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput;
  onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string;
  placeholder?: string;
  portalOrigin?: string;
  syntax?: string;
  syntaxLoaded?: boolean;
}

export interface QueryFieldState {
  suggestions: CompletionItemGroup[];
  typeaheadContext: string | null;
  typeaheadIndex: number;
  typeaheadPrefix: string;
  typeaheadText: string;
  value: any;
  lastExecutedValue: Value;
}

export interface TypeaheadInput {
  editorNode: Element;
  prefix: string;
  selection?: Selection;
  text: string;
  value: Value;
  wrapperNode: Element;
}

/**
 * Renders an editor field.
 * Pass initial value as initialQuery and listen to changes in props.onValueChanged.
 * This component can only process strings. Internally it uses Slate Value.
 * Implement props.onTypeahead to use suggestions, see PromQueryField.tsx as an example.
 */
export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldState> {
  menuEl: HTMLElement | null;
  plugins: any[];
  resetTimer: any;
  mounted: boolean;
  updateHighlightsTimer: any;

  constructor(props: QueryFieldProps, context: Context<any>) {
    super(props, context);

    this.updateHighlightsTimer = _.debounce(this.updateLogsHighlights, HIGHLIGHT_WAIT);

    // Base plugins
    this.plugins = [ClearPlugin(), NewlinePlugin(), ...(props.additionalPlugins || [])].filter(p => p);

    this.state = {
      suggestions: [],
      typeaheadContext: null,
      typeaheadIndex: 0,
      typeaheadPrefix: '',
      typeaheadText: '',
      value: makeValue(props.initialQuery || '', props.syntax),
      lastExecutedValue: null,
    };
  }

  componentDidMount() {
    this.mounted = true;
    this.updateMenu();
  }

  componentWillUnmount() {
    this.mounted = false;
    clearTimeout(this.resetTimer);
  }

  componentDidUpdate(prevProps: QueryFieldProps, prevState: QueryFieldState) {
    const { initialQuery, syntax } = this.props;
    const { value, suggestions } = this.state;

    // if query changed from the outside
    if (initialQuery !== prevProps.initialQuery) {
      // and we have a version that differs
      if (initialQuery !== Plain.serialize(value)) {
        this.setState({ value: makeValue(initialQuery, syntax) });
      }
    }

    // Only update menu location when suggestion existence or text/selection changed
    if (value !== prevState.value || hasSuggestions(suggestions) !== hasSuggestions(prevState.suggestions)) {
      this.updateMenu();
    }
  }<commit_msg>Explore: Fixes error when switching from prometheus to loki data sources (#18599)<commit_after>import _ from 'lodash';
import React, { Context } from 'react';
import ReactDOM from 'react-dom';
// @ts-ignore
import { Change, Value } from 'slate';
// @ts-ignore
import { Editor } from 'slate-react';
// @ts-ignore
import Plain from 'slate-plain-serializer';
import classnames from 'classnames';

import { CompletionItem, CompletionItemGroup, TypeaheadOutput } from 'app/types/explore';

import ClearPlugin from './slate-plugins/clear';
import NewlinePlugin from './slate-plugins/newline';

import { TypeaheadWithTheme } from './Typeahead';
import { makeFragment, makeValue } from '@grafana/ui';

export const TYPEAHEAD_DEBOUNCE = 100;
export const HIGHLIGHT_WAIT = 500;

function getSuggestionByIndex(suggestions: CompletionItemGroup[], index: number): CompletionItem {
  // Flatten suggestion groups
  const flattenedSuggestions = suggestions.reduce((acc, g) => acc.concat(g.items), []);
  const correctedIndex = Math.max(index, 0) % flattenedSuggestions.length;
  return flattenedSuggestions[correctedIndex];
}

function hasSuggestions(suggestions: CompletionItemGroup[]): boolean {
  return suggestions && suggestions.length > 0;
}

export interface QueryFieldProps {
  additionalPlugins?: any[];
  cleanText?: (text: string) => string;
  disabled?: boolean;
  initialQuery: string | null;
  onRunQuery?: () => void;
  onChange?: (value: string) => void;
  onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput;
  onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string;
  placeholder?: string;
  portalOrigin?: string;
  syntax?: string;
  syntaxLoaded?: boolean;
}

export interface QueryFieldState {
  suggestions: CompletionItemGroup[];
  typeaheadContext: string | null;
  typeaheadIndex: number;
  typeaheadPrefix: string;
  typeaheadText: string;
  value: any;
  lastExecutedValue: Value;
}

export interface TypeaheadInput {
  editorNode: Element;
  prefix: string;
  selection?: Selection;
  text: string;
  value: Value;
  wrapperNode: Element;
}

/**
 * Renders an editor field.
 * Pass initial value as initialQuery and listen to changes in props.onValueChanged.
 * This component can only process strings. Internally it uses Slate Value.
 * Implement props.onTypeahead to use suggestions, see PromQueryField.tsx as an example.
 */
export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldState> {
  menuEl: HTMLElement | null;
  plugins: any[];
  resetTimer: any;
  mounted: boolean;
  updateHighlightsTimer: any;

  constructor(props: QueryFieldProps, context: Context<any>) {
    super(props, context);

    this.updateHighlightsTimer = _.debounce(this.updateLogsHighlights, HIGHLIGHT_WAIT);

    // Base plugins
    this.plugins = [ClearPlugin(), NewlinePlugin(), ...(props.additionalPlugins || [])].filter(p => p);

    this.state = {
      suggestions: [],
      typeaheadContext: null,
      typeaheadIndex: 0,
      typeaheadPrefix: '',
      typeaheadText: '',
      value: makeValue(props.initialQuery || '', props.syntax),
      lastExecutedValue: null,
    };
  }

  componentDidMount() {
    this.mounted = true;
    this.updateMenu();
  }

  componentWillUnmount() {
    this.mounted = false;
    clearTimeout(this.resetTimer);
  }

  componentDidUpdate(prevProps: QueryFieldProps, prevState: QueryFieldState) {
    const { initialQuery, syntax } = this.props;
    const { value, suggestions } = this.state;

    // if query changed from the outside
    if (initialQuery !== prevProps.initialQuery) {
      // and we have a version that differs
      if (initialQuery !== Plain.serialize(value)) {
        this.setState({ value: makeValue(initialQuery || '', syntax) });
      }
    }

    // Only update menu location when suggestion existence or text/selection changed
    if (value !== prevState.value || hasSuggestions(suggestions) !== hasSuggestions(prevState.suggestions)) {
      this.updateMenu();
    }
  }
{'fraction_non_alphanumeric': 0.11726055092241597, 'fraction_numerical': 0.002906242102602982, 'mean_word_length': 4.340755735492578, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5925401', 'n_tokens_mistral': 2381, 'n_tokens_neox': 2256, 'n_words': 621}