1import { 2 ByNamesMatcherMode, 3 DataFrame, 4 DynamicConfigValue, 5 FieldConfigSource, 6 FieldMatcherID, 7 FieldType, 8 getFieldDisplayName, 9 isSystemOverrideWithRef, 10 SystemConfigOverrideRule, 11} from '@grafana/data'; 12import { GraphNGLegendEvent, SeriesVisibilityChangeMode } from '@grafana/ui'; 13 14const displayOverrideRef = 'hideSeriesFrom'; 15const isHideSeriesOverride = isSystemOverrideWithRef(displayOverrideRef); 16 17export const hideSeriesConfigFactory = ( 18 event: GraphNGLegendEvent, 19 fieldConfig: FieldConfigSource<any>, 20 data: DataFrame[] 21): FieldConfigSource<any> => { 22 const { fieldIndex, mode } = event; 23 const { overrides } = fieldConfig; 24 25 const frame = data[fieldIndex.frameIndex]; 26 27 if (!frame) { 28 return fieldConfig; 29 } 30 31 const field = frame.fields[fieldIndex.fieldIndex]; 32 33 if (!field) { 34 return fieldConfig; 35 } 36 37 const displayName = getFieldDisplayName(field, frame, data); 38 const currentIndex = overrides.findIndex(isHideSeriesOverride); 39 40 if (currentIndex < 0) { 41 if (mode === SeriesVisibilityChangeMode.ToggleSelection) { 42 const override = createOverride([displayName]); 43 44 return { 45 ...fieldConfig, 46 overrides: [override, ...fieldConfig.overrides], 47 }; 48 } 49 50 const displayNames = getDisplayNames(data, displayName); 51 const override = createOverride(displayNames); 52 53 return { 54 ...fieldConfig, 55 overrides: [override, ...fieldConfig.overrides], 56 }; 57 } 58 59 const overridesCopy = Array.from(overrides); 60 const [current] = overridesCopy.splice(currentIndex, 1) as SystemConfigOverrideRule[]; 61 62 if (mode === SeriesVisibilityChangeMode.ToggleSelection) { 63 const existing = getExistingDisplayNames(current); 64 65 if (existing[0] === displayName && existing.length === 1) { 66 return { 67 ...fieldConfig, 68 overrides: overridesCopy, 69 }; 70 } 71 72 const override = createOverride([displayName]); 73 74 return { 75 ...fieldConfig, 76 overrides: [override, ...overridesCopy], 77 }; 78 } 79 80 const override = createExtendedOverride(current, displayName); 81 82 if (allFieldsAreExcluded(override, data)) { 83 return { 84 ...fieldConfig, 85 overrides: overridesCopy, 86 }; 87 } 88 89 return { 90 ...fieldConfig, 91 overrides: [override, ...overridesCopy], 92 }; 93}; 94 95const createExtendedOverride = (current: SystemConfigOverrideRule, displayName: string): SystemConfigOverrideRule => { 96 const property = current.properties.find((p) => p.id === 'custom.hideFrom'); 97 const existing = getExistingDisplayNames(current); 98 const index = existing.findIndex((name) => name === displayName); 99 100 if (index < 0) { 101 existing.push(displayName); 102 } else { 103 existing.splice(index, 1); 104 } 105 106 return createOverride(existing, property); 107}; 108 109const getExistingDisplayNames = (rule: SystemConfigOverrideRule): string[] => { 110 const names = rule.matcher.options?.names; 111 if (!Array.isArray(names)) { 112 return []; 113 } 114 return names; 115}; 116 117const createOverride = (names: string[], property?: DynamicConfigValue): SystemConfigOverrideRule => { 118 property = property ?? { 119 id: 'custom.hideFrom', 120 value: { 121 graph: true, 122 legend: false, 123 tooltip: false, 124 }, 125 }; 126 127 return { 128 __systemRef: displayOverrideRef, 129 matcher: { 130 id: FieldMatcherID.byNames, 131 options: { 132 mode: ByNamesMatcherMode.exclude, 133 names: names, 134 prefix: 'All except:', 135 readOnly: true, 136 }, 137 }, 138 properties: [ 139 { 140 ...property, 141 value: { 142 graph: true, 143 legend: false, 144 tooltip: false, 145 }, 146 }, 147 ], 148 }; 149}; 150 151const allFieldsAreExcluded = (override: SystemConfigOverrideRule, data: DataFrame[]): boolean => { 152 return getExistingDisplayNames(override).length === getDisplayNames(data).length; 153}; 154 155const getDisplayNames = (data: DataFrame[], excludeName?: string): string[] => { 156 const unique = new Set<string>(); 157 158 for (const frame of data) { 159 for (const field of frame.fields) { 160 if (field.type !== FieldType.number) { 161 continue; 162 } 163 164 const name = getFieldDisplayName(field, frame, data); 165 166 if (name === excludeName) { 167 continue; 168 } 169 170 unique.add(name); 171 } 172 } 173 174 return Array.from(unique); 175}; 176