1import React, { useCallback, useMemo } from 'react';
2import { SegmentAsync } from '@grafana/ui';
3import { actions } from '../state/actions';
4import { GraphiteSegment } from '../types';
5import { SelectableValue } from '@grafana/data';
6import { getAltSegmentsSelectables } from '../state/providers';
7import { debounce } from 'lodash';
8import { GraphiteQueryEditorState } from '../state/store';
9import { useDispatch } from '../state/context';
10
11type Props = {
12  segment: GraphiteSegment;
13  metricIndex: number;
14  state: GraphiteQueryEditorState;
15};
16
17/**
18 * Represents a single metric node in the metric path at the given index. Allows to change the metric name to one of the
19 * provided options or a custom value.
20 *
21 * Options for tag names and metric names are reloaded while user is typing with backend taking care of auto-complete
22 * (auto-complete cannot be implemented in front-end because backend returns only limited number of entries)
23 *
24 * getAltSegmentsSelectables() also returns list of tags for segment with index=0. Once a tag is selected the editor
25 * enters tag-adding mode (see SeriesSection and GraphiteQueryModel.seriesByTagUsed).
26 */
27export function MetricSegment({ metricIndex, segment, state }: Props) {
28  const dispatch = useDispatch();
29  const loadOptions = useCallback(
30    (value: string | undefined) => {
31      return getAltSegmentsSelectables(state, metricIndex, value || '');
32    },
33    [state, metricIndex]
34  );
35  const debouncedLoadOptions = useMemo(() => debounce(loadOptions, 200, { leading: true }), [loadOptions]);
36
37  const onSegmentChanged = useCallback(
38    (selectableValue: SelectableValue<GraphiteSegment | string>) => {
39      // selectableValue.value is always defined because emptyValues are not allowed in SegmentAsync by default
40      dispatch(actions.segmentValueChanged({ segment: selectableValue.value!, index: metricIndex }));
41    },
42    [dispatch, metricIndex]
43  );
44
45  // segmentValueChanged action will destroy SegmentAsync immediately if a tag is selected. To give time
46  // for the clean up the action is debounced.
47  const onSegmentChangedDebounced = useMemo(() => debounce(onSegmentChanged, 100), [onSegmentChanged]);
48
49  return (
50    <SegmentAsync<GraphiteSegment | string>
51      value={segment.value}
52      inputMinWidth={150}
53      allowCustomValue={true}
54      loadOptions={debouncedLoadOptions}
55      reloadOptionsOnChange={true}
56      onChange={onSegmentChangedDebounced}
57    />
58  );
59}
60