1import React, { useMemo } from 'react';
2import {
3  DataTransformerID,
4  SelectableValue,
5  standardTransformers,
6  TransformerRegistryItem,
7  TransformerUIProps,
8} from '@grafana/data';
9import { InlineField, InlineFieldRow, RadioButtonGroup, Select, FilterPill } from '@grafana/ui';
10
11import {
12  LabelsToFieldsMode,
13  LabelsToFieldsOptions,
14} from '@grafana/data/src/transformations/transformers/labelsToFields';
15
16const modes: Array<SelectableValue<LabelsToFieldsMode>> = [
17  { value: LabelsToFieldsMode.Columns, label: 'Columns' },
18  { value: LabelsToFieldsMode.Rows, label: 'Rows' },
19];
20
21export const LabelsAsFieldsTransformerEditor: React.FC<TransformerUIProps<LabelsToFieldsOptions>> = ({
22  input,
23  options,
24  onChange,
25}) => {
26  const labelWidth = 20;
27
28  const { labelNames, selected } = useMemo(() => {
29    let labelNames: Array<SelectableValue<string>> = [];
30    let uniqueLabels: Record<string, boolean> = {};
31
32    for (const frame of input) {
33      for (const field of frame.fields) {
34        if (!field.labels) {
35          continue;
36        }
37
38        for (const labelName of Object.keys(field.labels)) {
39          if (!uniqueLabels[labelName]) {
40            labelNames.push({ value: labelName, label: labelName });
41            uniqueLabels[labelName] = true;
42          }
43        }
44      }
45    }
46
47    const selected = new Set(options.keepLabels?.length ? options.keepLabels : Object.keys(uniqueLabels));
48    return { labelNames, selected };
49  }, [options.keepLabels, input]);
50
51  const onValueLabelChange = (value: SelectableValue<string> | null) => {
52    onChange({ ...options, valueLabel: value?.value });
53  };
54
55  const onToggleSelection = (v: string) => {
56    if (selected.has(v)) {
57      selected.delete(v);
58    } else {
59      selected.add(v);
60    }
61    if (selected.size === labelNames.length || !selected.size) {
62      const { keepLabels, ...rest } = options;
63      onChange(rest);
64    } else {
65      onChange({ ...options, keepLabels: [...selected] });
66    }
67  };
68
69  return (
70    <div>
71      <InlineFieldRow>
72        <InlineField label={'Mode'} labelWidth={labelWidth}>
73          <RadioButtonGroup
74            options={modes}
75            value={options.mode ?? LabelsToFieldsMode.Columns}
76            onChange={(v) => onChange({ ...options, mode: v })}
77          />
78        </InlineField>
79      </InlineFieldRow>
80      <InlineFieldRow>
81        <InlineField label={'Labels'} labelWidth={labelWidth}>
82          <>
83            {labelNames.map((o, i) => {
84              const label = o.label!;
85              return (
86                <FilterPill
87                  key={`${label}/${i}`}
88                  onClick={() => onToggleSelection(label)}
89                  label={label}
90                  selected={selected.has(label)}
91                />
92              );
93            })}
94          </>
95        </InlineField>
96      </InlineFieldRow>
97      {options.mode !== LabelsToFieldsMode.Rows && (
98        <InlineFieldRow>
99          <InlineField
100            label={'Value field name'}
101            labelWidth={labelWidth}
102            tooltip="Replace the value field name with a label"
103            htmlFor="labels-to-fields-as-name"
104          >
105            <Select
106              menuShouldPortal
107              inputId="labels-to-fields-as-name"
108              isClearable={true}
109              allowCustomValue={false}
110              placeholder="(Optional) Select label"
111              options={labelNames}
112              value={options?.valueLabel}
113              onChange={onValueLabelChange}
114              className="min-width-16"
115            />
116          </InlineField>
117        </InlineFieldRow>
118      )}
119    </div>
120  );
121};
122
123export const labelsToFieldsTransformerRegistryItem: TransformerRegistryItem<LabelsToFieldsOptions> = {
124  id: DataTransformerID.labelsToFields,
125  editor: LabelsAsFieldsTransformerEditor,
126  transformation: standardTransformers.labelsToFieldsTransformer,
127  name: 'Labels to fields',
128  description: `Groups series by time and return labels or tags as fields.
129                Useful for showing time series with labels in a table where each label key becomes a separate column`,
130};
131