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