<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import { withRouter } from 'react-router';
import moment from 'moment';
import queryString from 'query-string';

import SearchCondition from './SearchCondition';
import ConditionDuration from './ConditionDuration';
import ConditionLimit from './ConditionLimit';
import ConditionName from './ConditionName';
import ConditionTags from './ConditionTags';
import ConditionLookback from './ConditionLookback';
import GlobalDropdownMenuContainer from '../../containers/GlobalSearch/GlobalDropdownMenuContainer';
import {
  isAutocompleteKey,
  defaultConditionValues,
  getConditionKeyListWithAvailability,
  buildQueryParametersWithConditions,
  buildApiQueryParameters,
  extractConditionsFromQueryParameters,
  nextInitialConditionKey,
} from '../../util/global-search';

const propTypes = {
  services: PropTypes.arrayOf(PropTypes.string).isRequired,
  spans: PropTypes.arrayOf(PropTypes.string).isRequired,
  conditions: PropTypes.arrayOf(PropTypes.shape({
    key: PropTypes.string,
    value: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number,
      PropTypes.shape({}),
    ]),
  })).isRequired,
  lookbackCondition: PropTypes.shape({
    value: PropTypes.string,
    endTs: PropTypes.number,
    startTs: PropTypes.number,
  }).isRequired,
  limitCondition: PropTypes.number.isRequired,
  fetchServices: PropTypes.func.isRequired,
  isLoadingServices: PropTypes.bool.isRequired,
  fetchSpans: PropTypes.func.isRequired,
  isLoadingSpans: PropTypes.bool.isRequired,
  fetchTraces: PropTypes.func.isRequired,
  fetchAutocompleteKeys: PropTypes.func.isRequired,
  fetchAutocompleteValues: PropTypes.func.isRequired,
  autocompleteKeys: PropTypes.arrayOf(PropTypes.string).isRequired,
  isLoadingAutocompleteKeys: PropTypes.bool.isRequired,
  autocompleteValues: PropTypes.arrayOf(PropTypes.string).isRequired,
  isLoadingAutocompleteValues: PropTypes.bool.isRequired,
  setLookbackCondition: PropTypes.func.isRequired,
  setLimitCondition: PropTypes.func.isRequired,
  addCondition: PropTypes.func.isRequired,
  deleteCondition: PropTypes.func.isRequired,
  changeConditionKey: PropTypes.func.isRequired,
  changeConditionValue: PropTypes.func.isRequired,
  location: PropTypes.shape({
    search: PropTypes.string.isRequired,
  }).isRequired,
  history: PropTypes.shape({
    push: PropTypes.func.isRequired,
  }).isRequired,
};

class GlobalSearch extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isConditionFocused: false,
    };
    this.handleAddButtonClick = this.handleAddButtonClick.bind(this);
    this.handleSearchButtonClick = this.handleSearchButtonClick.bind(this);
    this.handleDeleteConditionButtonClick = this.handleDeleteConditionButtonClick.bind(this);
    this.handleConditionKeyChange = this.handleConditionKeyChange.bind(this);
    this.handleConditionValueChange = this.handleConditionValueChange.bind(this);
    this.handleLookbackChange = this.handleLookbackChange.bind(this);
    this.handleLimitChange = this.handleLimitChange.bind(this);
    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.handleConditionFocus = this.handleConditionFocus.bind(this);
    this.handleConditionKeyBlur = this.handleConditionKeyBlur.bind(this);
    this.handleConditionValueBlur = this.handleConditionValueBlur.bind(this);
  }

  componentDidMount() {
    document.addEventListener('keydown', this.handleKeyDown);
    const {
      fetchServices,
      fetchSpans,
      fetchAutocompleteKeys,
      location,
      addCondition,
      setLookbackCondition,
      setLimitCondition,
    } = this.props;

    const initialConditions = this.getConditionsFromQueryParameters();
    const { conditions, lookbackCondition, limitCondition } = initialConditions;
    conditions.forEach((condition) => {
      addCondition(condition);
    });
    setLookbackCondition({
      value: lookbackCondition.value || '1h',
      endTs: lookbackCondition.endTs || moment().valueOf(),
      startTs: lookbackCondition.startTs || moment().valueOf(),
    });
    setLimitCondition(limitCondition || 10);

    fetchServices();
    const serviceNameCondition = initialConditions.conditions.find(condition => condition.key === 'serviceName');
    if (serviceNameCondition) {
      fetchSpans(serviceNameCondition.value);
    }
    fetchAutocompleteKeys();
    this.fetchTraces(location);
  }

  componentWillUnmount() {
    document.removeEventListener('keydown', this.handleKeyDown);
  }

  getConditionsFromQueryParameters() {
    const { location } = this.props;
    if (location.search !== '' && location.search !== '?') {
      const queryParameters = queryString.parse(location.search);
      return extractConditionsFromQueryParameters(queryParameters);
    }
    return {
      conditions: [],
      lookbackCondition: {},
      limitCondition: null,
    };
  }

  fetchTraces(location) {
    const { fetchTraces } = this.props;
    if (location.search !== '' && location.search !== '?') {
      const queryParameters = queryString.parse(location.search);
      const apiQueryParameters = buildApiQueryParameters(queryParameters);
      fetchTraces(apiQueryParameters);
    }
  }

  handleAddButtonClick() {
    const { addCondition, conditions, autocompleteKeys } = this.props;
    const nextConditionKey = nextInitialConditionKey(conditions, autocompleteKeys);
    addCondition({
      key: nextConditionKey,
      value: defaultConditionValues(nextConditionKey),
    });
  }

  handleKeyDown(event) {
    const { isConditionFocused } = this.state;
    if (event.key === 'Enter' && !isConditionFocused) {
      this.handleSearchButtonClick();
    }
  }

  handleSearchButtonClick() {
    const {
      history, conditions, lookbackCondition, limitCondition,
    } = this.props;

    const queryParams = buildQueryParametersWithConditions(
      conditions,
      lookbackCondition,
      limitCondition,
    );
    const location = {
      pathname: '/zipkin',
      search: queryParams,
    };
    history.push(location);
    this.fetchTraces(location);
  }

  // Replaces the key of the "index"-th condition with "keyName" and clear value.
  handleConditionKeyChange(index, conditionKey) {
    const {
      changeConditionKey,
      fetchAutocompleteValues,
    } = this.props;
    changeConditionKey(index, conditionKey);

    if (isAutocompleteKey(conditionKey)) {
      fetchAutocompleteValues(conditionKey);
    }
  }

  // Replaces the value of the "index"-th condition with "value".
  handleConditionValueChange(index, conditionValue) {
    const {
      fetchSpans,
      conditions,
      changeConditionValue,
    } = this.props;

    changeConditionValue(index, conditionValue);
    if (conditions[index].key === 'serviceName') {
      fetchSpans(conditionValue);
    }<commit_msg>Matches path pattern of classic UI (#2505)<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { withRouter } from 'react-router';
import moment from 'moment';
import queryString from 'query-string';

import SearchCondition from './SearchCondition';
import ConditionDuration from './ConditionDuration';
import ConditionLimit from './ConditionLimit';
import ConditionName from './ConditionName';
import ConditionTags from './ConditionTags';
import ConditionLookback from './ConditionLookback';
import GlobalDropdownMenuContainer from '../../containers/GlobalSearch/GlobalDropdownMenuContainer';
import {
  isAutocompleteKey,
  defaultConditionValues,
  getConditionKeyListWithAvailability,
  buildQueryParametersWithConditions,
  buildApiQueryParameters,
  extractConditionsFromQueryParameters,
  nextInitialConditionKey,
} from '../../util/global-search';

const propTypes = {
  services: PropTypes.arrayOf(PropTypes.string).isRequired,
  spans: PropTypes.arrayOf(PropTypes.string).isRequired,
  conditions: PropTypes.arrayOf(PropTypes.shape({
    key: PropTypes.string,
    value: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number,
      PropTypes.shape({}),
    ]),
  })).isRequired,
  lookbackCondition: PropTypes.shape({
    value: PropTypes.string,
    endTs: PropTypes.number,
    startTs: PropTypes.number,
  }).isRequired,
  limitCondition: PropTypes.number.isRequired,
  fetchServices: PropTypes.func.isRequired,
  isLoadingServices: PropTypes.bool.isRequired,
  fetchSpans: PropTypes.func.isRequired,
  isLoadingSpans: PropTypes.bool.isRequired,
  fetchTraces: PropTypes.func.isRequired,
  fetchAutocompleteKeys: PropTypes.func.isRequired,
  fetchAutocompleteValues: PropTypes.func.isRequired,
  autocompleteKeys: PropTypes.arrayOf(PropTypes.string).isRequired,
  isLoadingAutocompleteKeys: PropTypes.bool.isRequired,
  autocompleteValues: PropTypes.arrayOf(PropTypes.string).isRequired,
  isLoadingAutocompleteValues: PropTypes.bool.isRequired,
  setLookbackCondition: PropTypes.func.isRequired,
  setLimitCondition: PropTypes.func.isRequired,
  addCondition: PropTypes.func.isRequired,
  deleteCondition: PropTypes.func.isRequired,
  changeConditionKey: PropTypes.func.isRequired,
  changeConditionValue: PropTypes.func.isRequired,
  location: PropTypes.shape({
    search: PropTypes.string.isRequired,
  }).isRequired,
  history: PropTypes.shape({
    push: PropTypes.func.isRequired,
  }).isRequired,
};

class GlobalSearch extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isConditionFocused: false,
    };
    this.handleAddButtonClick = this.handleAddButtonClick.bind(this);
    this.handleSearchButtonClick = this.handleSearchButtonClick.bind(this);
    this.handleDeleteConditionButtonClick = this.handleDeleteConditionButtonClick.bind(this);
    this.handleConditionKeyChange = this.handleConditionKeyChange.bind(this);
    this.handleConditionValueChange = this.handleConditionValueChange.bind(this);
    this.handleLookbackChange = this.handleLookbackChange.bind(this);
    this.handleLimitChange = this.handleLimitChange.bind(this);
    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.handleConditionFocus = this.handleConditionFocus.bind(this);
    this.handleConditionKeyBlur = this.handleConditionKeyBlur.bind(this);
    this.handleConditionValueBlur = this.handleConditionValueBlur.bind(this);
  }

  componentDidMount() {
    document.addEventListener('keydown', this.handleKeyDown);
    const {
      fetchServices,
      fetchSpans,
      fetchAutocompleteKeys,
      location,
      addCondition,
      setLookbackCondition,
      setLimitCondition,
    } = this.props;

    const initialConditions = this.getConditionsFromQueryParameters();
    const { conditions, lookbackCondition, limitCondition } = initialConditions;
    conditions.forEach((condition) => {
      addCondition(condition);
    });
    setLookbackCondition({
      value: lookbackCondition.value || '1h',
      endTs: lookbackCondition.endTs || moment().valueOf(),
      startTs: lookbackCondition.startTs || moment().valueOf(),
    });
    setLimitCondition(limitCondition || 10);

    fetchServices();
    const serviceNameCondition = initialConditions.conditions.find(condition => condition.key === 'serviceName');
    if (serviceNameCondition) {
      fetchSpans(serviceNameCondition.value);
    }
    fetchAutocompleteKeys();
    this.fetchTraces(location);
  }

  componentWillUnmount() {
    document.removeEventListener('keydown', this.handleKeyDown);
  }

  getConditionsFromQueryParameters() {
    const { location } = this.props;
    if (location.search !== '' && location.search !== '?') {
      const queryParameters = queryString.parse(location.search);
      return extractConditionsFromQueryParameters(queryParameters);
    }
    return {
      conditions: [],
      lookbackCondition: {},
      limitCondition: null,
    };
  }

  fetchTraces(location) {
    const { fetchTraces } = this.props;
    if (location.search !== '' && location.search !== '?') {
      const queryParameters = queryString.parse(location.search);
      const apiQueryParameters = buildApiQueryParameters(queryParameters);
      fetchTraces(apiQueryParameters);
    }
  }

  handleAddButtonClick() {
    const { addCondition, conditions, autocompleteKeys } = this.props;
    const nextConditionKey = nextInitialConditionKey(conditions, autocompleteKeys);
    addCondition({
      key: nextConditionKey,
      value: defaultConditionValues(nextConditionKey),
    });
  }

  handleKeyDown(event) {
    const { isConditionFocused } = this.state;
    if (event.key === 'Enter' && !isConditionFocused) {
      this.handleSearchButtonClick();
    }
  }

  handleSearchButtonClick() {
    const {
      history, conditions, lookbackCondition, limitCondition,
    } = this.props;

    const queryParams = buildQueryParametersWithConditions(
      conditions,
      lookbackCondition,
      limitCondition,
    );
    const location = {
      pathname: '/zipkin/',
      search: queryParams,
    };
    history.push(location);
    this.fetchTraces(location);
  }

  // Replaces the key of the "index"-th condition with "keyName" and clear value.
  handleConditionKeyChange(index, conditionKey) {
    const {
      changeConditionKey,
      fetchAutocompleteValues,
    } = this.props;
    changeConditionKey(index, conditionKey);

    if (isAutocompleteKey(conditionKey)) {
      fetchAutocompleteValues(conditionKey);
    }
  }

  // Replaces the value of the "index"-th condition with "value".
  handleConditionValueChange(index, conditionValue) {
    const {
      fetchSpans,
      conditions,
      changeConditionValue,
    } = this.props;

    changeConditionValue(index, conditionValue);
    if (conditions[index].key === 'serviceName') {
      fetchSpans(conditionValue);
    }
{'fraction_non_alphanumeric': 0.10657530252223356, 'fraction_numerical': 0.0007289692374981776, 'mean_word_length': 4.325698757763975, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, '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': '5706640', 'n_tokens_mistral': 3759, 'n_tokens_neox': 3743, 'n_words': 664}