getVisualizationOptions.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import { get as lodashGet } from 'lodash';
  2. import React from 'react';
  3. import {
  4. EventBus,
  5. InterpolateFunction,
  6. PanelData,
  7. StandardEditorContext,
  8. VariableSuggestionsScope,
  9. } from '@grafana/data';
  10. import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin';
  11. import {
  12. isNestedPanelOptions,
  13. NestedValueAccess,
  14. PanelOptionsEditorBuilder,
  15. } from '@grafana/data/src/utils/OptionsUIBuilders';
  16. import { getDataLinksVariableSuggestions } from 'app/features/panel/panellinks/link_srv';
  17. import { OptionsPaneCategoryDescriptor } from './OptionsPaneCategoryDescriptor';
  18. import { OptionsPaneItemDescriptor } from './OptionsPaneItemDescriptor';
  19. import { getOptionOverrides } from './state/getOptionOverrides';
  20. import { OptionPaneRenderProps } from './types';
  21. import { setOptionImmutably, updateDefaultFieldConfigValue } from './utils';
  22. type categoryGetter = (categoryNames?: string[]) => OptionsPaneCategoryDescriptor;
  23. interface GetStandardEditorContextProps {
  24. data: PanelData | undefined;
  25. replaceVariables: InterpolateFunction;
  26. options: Record<string, unknown>;
  27. eventBus: EventBus;
  28. instanceState: OptionPaneRenderProps['instanceState'];
  29. }
  30. export function getStandardEditorContext({
  31. data,
  32. replaceVariables,
  33. options,
  34. eventBus,
  35. instanceState,
  36. }: GetStandardEditorContextProps): StandardEditorContext<unknown, unknown> {
  37. const dataSeries = data?.series ?? [];
  38. const context: StandardEditorContext<unknown, unknown> = {
  39. data: dataSeries,
  40. replaceVariables,
  41. options,
  42. eventBus,
  43. getSuggestions: (scope?: VariableSuggestionsScope) => getDataLinksVariableSuggestions(dataSeries, scope),
  44. instanceState,
  45. };
  46. return context;
  47. }
  48. export function getVisualizationOptions(props: OptionPaneRenderProps): OptionsPaneCategoryDescriptor[] {
  49. const { plugin, panel, onPanelOptionsChanged, onFieldConfigsChange, data, dashboard, instanceState } = props;
  50. const currentOptions = panel.getOptions();
  51. const currentFieldConfig = panel.fieldConfig;
  52. const categoryIndex: Record<string, OptionsPaneCategoryDescriptor> = {};
  53. const context = getStandardEditorContext({
  54. data,
  55. replaceVariables: panel.replaceVariables,
  56. options: currentOptions,
  57. eventBus: dashboard.events,
  58. instanceState,
  59. });
  60. const getOptionsPaneCategory = (categoryNames?: string[]): OptionsPaneCategoryDescriptor => {
  61. const categoryName = (categoryNames && categoryNames[0]) ?? `${plugin.meta.name}`;
  62. const category = categoryIndex[categoryName];
  63. if (category) {
  64. return category;
  65. }
  66. return (categoryIndex[categoryName] = new OptionsPaneCategoryDescriptor({
  67. title: categoryName,
  68. id: categoryName,
  69. }));
  70. };
  71. const access: NestedValueAccess = {
  72. getValue: (path: string) => lodashGet(currentOptions, path),
  73. onChange: (path: string, value: any) => {
  74. const newOptions = setOptionImmutably(currentOptions, path, value);
  75. onPanelOptionsChanged(newOptions);
  76. },
  77. };
  78. // Load the options into categories
  79. fillOptionsPaneItems(plugin.getPanelOptionsSupplier(), access, getOptionsPaneCategory, context);
  80. /**
  81. * Field options
  82. */
  83. for (const fieldOption of plugin.fieldConfigRegistry.list()) {
  84. if (
  85. fieldOption.isCustom &&
  86. fieldOption.showIf &&
  87. !fieldOption.showIf(currentFieldConfig.defaults.custom, data?.series)
  88. ) {
  89. continue;
  90. }
  91. if (fieldOption.hideFromDefaults) {
  92. continue;
  93. }
  94. const category = getOptionsPaneCategory(fieldOption.category);
  95. const Editor = fieldOption.editor;
  96. const defaults = currentFieldConfig.defaults;
  97. const value = fieldOption.isCustom
  98. ? defaults.custom
  99. ? lodashGet(defaults.custom, fieldOption.path)
  100. : undefined
  101. : lodashGet(defaults, fieldOption.path);
  102. if (fieldOption.getItemsCount) {
  103. category.props.itemsCount = fieldOption.getItemsCount(value);
  104. }
  105. category.addItem(
  106. new OptionsPaneItemDescriptor({
  107. title: fieldOption.name,
  108. description: fieldOption.description,
  109. overrides: getOptionOverrides(fieldOption, currentFieldConfig, data?.series),
  110. render: function renderEditor() {
  111. const onChange = (v: any) => {
  112. onFieldConfigsChange(
  113. updateDefaultFieldConfigValue(currentFieldConfig, fieldOption.path, v, fieldOption.isCustom)
  114. );
  115. };
  116. return <Editor value={value} onChange={onChange} item={fieldOption} context={context} id={fieldOption.id} />;
  117. },
  118. })
  119. );
  120. }
  121. return Object.values(categoryIndex);
  122. }
  123. /**
  124. * This will iterate all options panes and add register them with the configured categories
  125. *
  126. * @internal
  127. */
  128. export function fillOptionsPaneItems(
  129. supplier: PanelOptionsSupplier<any>,
  130. access: NestedValueAccess,
  131. getOptionsPaneCategory: categoryGetter,
  132. context: StandardEditorContext<any, any>,
  133. parentCategory?: OptionsPaneCategoryDescriptor
  134. ) {
  135. const builder = new PanelOptionsEditorBuilder<any>();
  136. supplier(builder, context);
  137. for (const pluginOption of builder.getItems()) {
  138. if (pluginOption.showIf && !pluginOption.showIf(context.options, context.data)) {
  139. continue;
  140. }
  141. let category = parentCategory;
  142. if (!category) {
  143. category = getOptionsPaneCategory(pluginOption.category);
  144. } else if (pluginOption.category?.[0]?.length) {
  145. category = category.getCategory(pluginOption.category[0]);
  146. }
  147. // Nested options get passed up one level
  148. if (isNestedPanelOptions(pluginOption)) {
  149. const subAccess = pluginOption.getNestedValueAccess(access);
  150. const subContext = subAccess.getContext
  151. ? subAccess.getContext(context)
  152. : { ...context, options: access.getValue(pluginOption.path) };
  153. fillOptionsPaneItems(
  154. pluginOption.getBuilder(),
  155. subAccess,
  156. getOptionsPaneCategory,
  157. subContext,
  158. category // parent category
  159. );
  160. continue;
  161. }
  162. const Editor = pluginOption.editor;
  163. category.addItem(
  164. new OptionsPaneItemDescriptor({
  165. title: pluginOption.name,
  166. description: pluginOption.description,
  167. render: function renderEditor() {
  168. return (
  169. <Editor
  170. value={access.getValue(pluginOption.path)}
  171. onChange={(value: any) => {
  172. access.onChange(pluginOption.path, value);
  173. }}
  174. item={pluginOption}
  175. context={context}
  176. id={pluginOption.id}
  177. />
  178. );
  179. },
  180. })
  181. );
  182. }
  183. }