1import { useMemo } from 'react';
2import { DataFrame, Field, getFieldDisplayName, SelectableValue } from '@grafana/data';
3import { getFieldTypeIcon } from '../../types';
4
5/**
6 * @internal
7 */
8export interface FrameFieldsDisplayNames {
9  // The display names
10  display: Set<string>;
11
12  // raw field names (that are explicitly not visible)
13  raw: Set<string>;
14
15  // Field mappings (duplicates are not supported)
16  fields: Map<string, Field>;
17}
18
19/**
20 * @internal
21 */
22export function frameHasName(name: string | undefined, names: FrameFieldsDisplayNames) {
23  if (!name) {
24    return false;
25  }
26  return names.display.has(name) || names.raw.has(name);
27}
28
29/**
30 * Retuns the distinct names in a set of frames
31 */
32function getFrameFieldsDisplayNames(data: DataFrame[], filter?: (field: Field) => boolean): FrameFieldsDisplayNames {
33  const names: FrameFieldsDisplayNames = {
34    display: new Set<string>(),
35    raw: new Set<string>(),
36    fields: new Map<string, Field>(),
37  };
38
39  for (const frame of data) {
40    for (const field of frame.fields) {
41      if (filter && !filter(field)) {
42        continue;
43      }
44      const disp = getFieldDisplayName(field, frame, data);
45      names.display.add(disp);
46      names.fields.set(disp, field);
47      if (field.name && disp !== field.name) {
48        names.raw.add(field.name);
49        names.fields.set(field.name, field);
50      }
51    }
52  }
53  return names;
54}
55
56/**
57 * @internal
58 */
59export function useFieldDisplayNames(data: DataFrame[], filter?: (field: Field) => boolean): FrameFieldsDisplayNames {
60  return useMemo(() => {
61    return getFrameFieldsDisplayNames(data, filter);
62  }, [data, filter]);
63}
64
65/**
66 * @internal
67 */
68export function useSelectOptions(
69  displayNames: FrameFieldsDisplayNames,
70  currentName?: string,
71  firstItem?: SelectableValue<string>,
72  fieldType?: string
73): Array<SelectableValue<string>> {
74  return useMemo(() => {
75    let found = false;
76    const options: Array<SelectableValue<string>> = [];
77    if (firstItem) {
78      options.push(firstItem);
79    }
80    for (const name of displayNames.display) {
81      if (!found && name === currentName) {
82        found = true;
83      }
84      const field = displayNames.fields.get(name);
85      if (!fieldType || fieldType === field?.type) {
86        options.push({
87          value: name,
88          label: name,
89          icon: field ? getFieldTypeIcon(field) : undefined,
90        });
91      }
92    }
93    for (const name of displayNames.raw) {
94      if (!displayNames.display.has(name)) {
95        if (!found && name === currentName) {
96          found = true;
97        }
98        options.push({
99          value: name,
100          label: `${name} (base field name)`,
101        });
102      }
103    }
104
105    if (currentName && !found) {
106      options.push({
107        value: currentName,
108        label: `${currentName} (not found)`,
109      });
110    }
111    return options;
112  }, [displayNames, currentName, firstItem, fieldType]);
113}
114