migrations.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import { omitBy, isNil, isNumber, defaultTo } from 'lodash';
  2. import {
  3. PanelModel,
  4. FieldMatcherID,
  5. ConfigOverrideRule,
  6. ThresholdsMode,
  7. ThresholdsConfig,
  8. FieldConfig,
  9. } from '@grafana/data';
  10. import { ReduceTransformerOptions } from '@grafana/data/src/transformations/transformers/reduce';
  11. import { PanelOptions } from './models.gen';
  12. /**
  13. * At 7.0, the `table` panel was swapped from an angular implementation to a react one.
  14. * The models do not match, so this process will delegate to the old implementation when
  15. * a saved table configuration exists.
  16. */
  17. export const tableMigrationHandler = (panel: PanelModel<PanelOptions>): Partial<PanelOptions> => {
  18. // Table was saved as an angular table, lets just swap to the 'table-old' panel
  19. if (!panel.pluginVersion && (panel as any).columns) {
  20. console.log('Was angular table', panel);
  21. }
  22. // Nothing changed
  23. return panel.options;
  24. };
  25. const transformsMap = {
  26. timeseries_to_rows: 'seriesToRows',
  27. timeseries_to_columns: 'seriesToColumns',
  28. timeseries_aggregations: 'reduce',
  29. table: 'merge',
  30. };
  31. const columnsMap = {
  32. avg: 'mean',
  33. min: 'min',
  34. max: 'max',
  35. total: 'sum',
  36. current: 'lastNotNull',
  37. count: 'count',
  38. };
  39. const colorModeMap = {
  40. cell: 'color-background',
  41. row: 'color-background',
  42. value: 'color-text',
  43. };
  44. type Transformations = keyof typeof transformsMap;
  45. type Transformation = {
  46. id: string;
  47. options: ReduceTransformerOptions;
  48. };
  49. type Columns = keyof typeof columnsMap;
  50. type Column = {
  51. value: Columns;
  52. text: string;
  53. };
  54. type ColorModes = keyof typeof colorModeMap;
  55. const generateThresholds = (thresholds: string[], colors: string[]) => {
  56. return [-Infinity, ...thresholds].map((threshold, idx) => ({
  57. color: colors[idx],
  58. value: isNumber(threshold) ? threshold : parseInt(threshold, 10),
  59. }));
  60. };
  61. const migrateTransformations = (
  62. panel: PanelModel<Partial<PanelOptions>> | any,
  63. oldOpts: { columns: any; transform: Transformations }
  64. ) => {
  65. const transformations: Transformation[] = panel.transformations ?? [];
  66. if (Object.keys(transformsMap).includes(oldOpts.transform)) {
  67. const opts: ReduceTransformerOptions = {
  68. reducers: [],
  69. };
  70. if (oldOpts.transform === 'timeseries_aggregations') {
  71. opts.includeTimeField = false;
  72. opts.reducers = oldOpts.columns.map((column: Column) => columnsMap[column.value]);
  73. }
  74. transformations.push({
  75. id: transformsMap[oldOpts.transform],
  76. options: opts,
  77. });
  78. }
  79. return transformations;
  80. };
  81. type Style = {
  82. unit: string;
  83. type: string;
  84. alias: string;
  85. decimals: number;
  86. colors: string[];
  87. colorMode: ColorModes;
  88. pattern: string;
  89. thresholds: string[];
  90. align?: string;
  91. dateFormat: string;
  92. link: boolean;
  93. linkTargetBlank?: boolean;
  94. linkTooltip?: string;
  95. linkUrl?: string;
  96. };
  97. const migrateTableStyleToOverride = (style: Style) => {
  98. const fieldMatcherId = /^\/.*\/$/.test(style.pattern) ? FieldMatcherID.byRegexp : FieldMatcherID.byName;
  99. const override: ConfigOverrideRule = {
  100. matcher: {
  101. id: fieldMatcherId,
  102. options: style.pattern,
  103. },
  104. properties: [],
  105. };
  106. if (style.alias) {
  107. override.properties.push({
  108. id: 'displayName',
  109. value: style.alias,
  110. });
  111. }
  112. if (style.unit) {
  113. override.properties.push({
  114. id: 'unit',
  115. value: style.unit,
  116. });
  117. }
  118. if (style.decimals) {
  119. override.properties.push({
  120. id: 'decimals',
  121. value: style.decimals,
  122. });
  123. }
  124. if (style.type === 'date') {
  125. override.properties.push({
  126. id: 'unit',
  127. value: `time: ${style.dateFormat}`,
  128. });
  129. }
  130. if (style.link) {
  131. override.properties.push({
  132. id: 'links',
  133. value: [
  134. {
  135. title: defaultTo(style.linkTooltip, ''),
  136. url: defaultTo(style.linkUrl, ''),
  137. targetBlank: defaultTo(style.linkTargetBlank, false),
  138. },
  139. ],
  140. });
  141. }
  142. if (style.colorMode) {
  143. override.properties.push({
  144. id: 'custom.displayMode',
  145. value: colorModeMap[style.colorMode],
  146. });
  147. }
  148. if (style.align) {
  149. override.properties.push({
  150. id: 'custom.align',
  151. value: style.align === 'auto' ? null : style.align,
  152. });
  153. }
  154. if (style.thresholds?.length) {
  155. override.properties.push({
  156. id: 'thresholds',
  157. value: {
  158. mode: ThresholdsMode.Absolute,
  159. steps: generateThresholds(style.thresholds, style.colors),
  160. },
  161. });
  162. }
  163. return override;
  164. };
  165. const migrateDefaults = (prevDefaults: Style) => {
  166. let defaults: FieldConfig = {
  167. custom: {},
  168. };
  169. if (prevDefaults) {
  170. defaults = omitBy(
  171. {
  172. unit: prevDefaults.unit,
  173. decimals: prevDefaults.decimals,
  174. displayName: prevDefaults.alias,
  175. custom: {
  176. align: prevDefaults.align === 'auto' ? null : prevDefaults.align,
  177. displayMode: colorModeMap[prevDefaults.colorMode],
  178. },
  179. },
  180. isNil
  181. );
  182. if (prevDefaults.thresholds.length) {
  183. const thresholds: ThresholdsConfig = {
  184. mode: ThresholdsMode.Absolute,
  185. steps: generateThresholds(prevDefaults.thresholds, prevDefaults.colors),
  186. };
  187. defaults.thresholds = thresholds;
  188. }
  189. }
  190. return defaults;
  191. };
  192. /**
  193. * This is called when the panel changes from another panel
  194. */
  195. export const tablePanelChangedHandler = (
  196. panel: PanelModel<Partial<PanelOptions>> | any,
  197. prevPluginId: string,
  198. prevOptions: any
  199. ) => {
  200. // Changing from angular table panel
  201. if (prevPluginId === 'table-old' && prevOptions.angular) {
  202. const oldOpts = prevOptions.angular;
  203. const transformations = migrateTransformations(panel, oldOpts);
  204. const prevDefaults = oldOpts.styles.find((style: any) => style.pattern === '/.*/');
  205. const defaults = migrateDefaults(prevDefaults);
  206. const overrides = oldOpts.styles.filter((style: any) => style.pattern !== '/.*/').map(migrateTableStyleToOverride);
  207. panel.transformations = transformations;
  208. panel.fieldConfig = {
  209. defaults,
  210. overrides,
  211. };
  212. }
  213. return {};
  214. };