getPanelOptionsWithDefaults.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import { mergeWith, isArray, isObject, unset, isEqual } from 'lodash';
  2. import {
  3. ConfigOverrideRule,
  4. DynamicConfigValue,
  5. FieldColorConfigSettings,
  6. FieldColorModeId,
  7. fieldColorModeRegistry,
  8. FieldConfigOptionsRegistry,
  9. FieldConfigProperty,
  10. FieldConfigSource,
  11. PanelPlugin,
  12. ThresholdsConfig,
  13. ThresholdsMode,
  14. } from '@grafana/data';
  15. export interface Props {
  16. plugin: PanelPlugin;
  17. currentFieldConfig: FieldConfigSource;
  18. currentOptions: Record<string, any>;
  19. isAfterPluginChange: boolean;
  20. }
  21. export interface OptionDefaults {
  22. options: any;
  23. fieldConfig: FieldConfigSource;
  24. }
  25. export function getPanelOptionsWithDefaults({
  26. plugin,
  27. currentOptions,
  28. currentFieldConfig,
  29. isAfterPluginChange,
  30. }: Props): OptionDefaults {
  31. const optionsWithDefaults = mergeWith(
  32. {},
  33. plugin.defaults,
  34. currentOptions || {},
  35. (objValue: any, srcValue: any): any => {
  36. if (isArray(srcValue)) {
  37. return srcValue;
  38. }
  39. }
  40. );
  41. const fieldConfigWithDefaults = applyFieldConfigDefaults(currentFieldConfig, plugin);
  42. const fieldConfigWithOptimalColorMode = adaptFieldColorMode(plugin, fieldConfigWithDefaults, isAfterPluginChange);
  43. return { options: optionsWithDefaults, fieldConfig: fieldConfigWithOptimalColorMode };
  44. }
  45. function applyFieldConfigDefaults(existingFieldConfig: FieldConfigSource, plugin: PanelPlugin): FieldConfigSource {
  46. const pluginDefaults = plugin.fieldConfigDefaults;
  47. const result: FieldConfigSource = {
  48. defaults: mergeWith(
  49. {},
  50. pluginDefaults.defaults,
  51. existingFieldConfig ? existingFieldConfig.defaults : {},
  52. (objValue: any, srcValue: any): any => {
  53. if (isArray(srcValue)) {
  54. return srcValue;
  55. }
  56. }
  57. ),
  58. overrides: existingFieldConfig?.overrides ?? [],
  59. };
  60. cleanProperties(result.defaults, '', plugin.fieldConfigRegistry);
  61. // Thresholds base values are null in JSON but need to be converted to -Infinity
  62. if (result.defaults.thresholds) {
  63. fixThresholds(result.defaults.thresholds);
  64. }
  65. // Filter out overrides for properties that cannot be found in registry
  66. result.overrides = filterFieldConfigOverrides(result.overrides, (prop) => {
  67. return plugin.fieldConfigRegistry.getIfExists(prop.id) !== undefined;
  68. });
  69. for (const override of result.overrides) {
  70. for (const property of override.properties) {
  71. if (property.id === 'thresholds') {
  72. fixThresholds(property.value as ThresholdsConfig);
  73. }
  74. }
  75. }
  76. return result;
  77. }
  78. export function filterFieldConfigOverrides(
  79. overrides: ConfigOverrideRule[],
  80. condition: (value: DynamicConfigValue) => boolean
  81. ): ConfigOverrideRule[] {
  82. return overrides
  83. .map((x) => {
  84. const properties = x.properties.filter(condition);
  85. return {
  86. ...x,
  87. properties,
  88. };
  89. })
  90. .filter((x) => x.properties.length > 0);
  91. }
  92. function cleanProperties(obj: any, parentPath: string, fieldConfigRegistry: FieldConfigOptionsRegistry) {
  93. let found = false;
  94. for (const propName of Object.keys(obj)) {
  95. const value = obj[propName];
  96. const fullPath = `${parentPath}${propName}`;
  97. const existsInRegistry = !!fieldConfigRegistry.getIfExists(fullPath);
  98. // need to check early here as some standard properties have nested properies
  99. if (existsInRegistry) {
  100. found = true;
  101. continue;
  102. }
  103. if (isArray(value) || !isObject(value)) {
  104. if (!existsInRegistry) {
  105. unset(obj, propName);
  106. }
  107. } else {
  108. const childPropFound = cleanProperties(value, `${fullPath}.`, fieldConfigRegistry);
  109. // If no child props found unset the main object
  110. if (!childPropFound) {
  111. unset(obj, propName);
  112. }
  113. }
  114. }
  115. return found;
  116. }
  117. function adaptFieldColorMode(
  118. plugin: PanelPlugin,
  119. fieldConfig: FieldConfigSource,
  120. isAfterPluginChange: boolean
  121. ): FieldConfigSource {
  122. if (!isAfterPluginChange) {
  123. return fieldConfig;
  124. }
  125. // adjust to prefered field color setting if needed
  126. const color = plugin.fieldConfigRegistry.getIfExists(FieldConfigProperty.Color);
  127. if (color && color.settings) {
  128. const colorSettings = color.settings as FieldColorConfigSettings;
  129. const mode = fieldColorModeRegistry.getIfExists(fieldConfig.defaults.color?.mode);
  130. // When no support fo value colors, use classic palette
  131. if (!colorSettings.byValueSupport) {
  132. if (!mode || mode.isByValue) {
  133. fieldConfig.defaults.color = { mode: FieldColorModeId.PaletteClassic };
  134. return fieldConfig;
  135. }
  136. }
  137. // When supporting value colors and prefering thresholds, use Thresholds mode.
  138. // Otherwise keep current mode
  139. if (colorSettings.byValueSupport && colorSettings.preferThresholdsMode && mode?.id !== FieldColorModeId.Fixed) {
  140. if (!mode || !mode.isByValue) {
  141. fieldConfig.defaults.color = { mode: FieldColorModeId.Thresholds };
  142. return fieldConfig;
  143. }
  144. }
  145. // If panel support bySeries then we should default to that when switching to this panel as that is most likely
  146. // what users will expect. Example scenario a user who has a graph panel (time series) and switches to Gauge and
  147. // then back to time series we want the graph panel color mode to reset to classic palette and not preserve the
  148. // Gauge prefered thresholds mode.
  149. if (colorSettings.bySeriesSupport && mode?.isByValue) {
  150. fieldConfig.defaults.color = { mode: FieldColorModeId.PaletteClassic };
  151. return fieldConfig;
  152. }
  153. }
  154. return fieldConfig;
  155. }
  156. function fixThresholds(thresholds: ThresholdsConfig) {
  157. if (!thresholds.mode) {
  158. thresholds.mode = ThresholdsMode.Absolute;
  159. }
  160. if (!thresholds.steps) {
  161. thresholds.steps = [];
  162. } else if (thresholds.steps.length) {
  163. // First value is always -Infinity
  164. // JSON saves it as null
  165. thresholds.steps[0].value = -Infinity;
  166. }
  167. }
  168. export function restoreCustomOverrideRules(current: FieldConfigSource, old: FieldConfigSource): FieldConfigSource {
  169. const result = {
  170. defaults: {
  171. ...current.defaults,
  172. custom: old.defaults.custom,
  173. },
  174. overrides: [...current.overrides],
  175. };
  176. for (const override of old.overrides) {
  177. for (const prop of override.properties) {
  178. if (isCustomFieldProp(prop)) {
  179. const currentOverride = result.overrides.find((o) => isEqual(o.matcher, override.matcher));
  180. if (currentOverride) {
  181. if (currentOverride !== override) {
  182. currentOverride.properties.push(prop);
  183. }
  184. } else {
  185. result.overrides.push(override);
  186. }
  187. }
  188. }
  189. }
  190. return result;
  191. }
  192. export function isCustomFieldProp(prop: DynamicConfigValue): boolean {
  193. return prop.id.startsWith('custom.');
  194. }
  195. export function isStandardFieldProp(prop: DynamicConfigValue): boolean {
  196. return !isCustomFieldProp(prop);
  197. }