AngularMigrate: Auto migrate graph to multiple panels (#83992)

* AngularMigrate: Auto migrate graph to multiple panels

* add unit test, and histogram migration

* add new cases to existing angular migration gdev dashboard

* fix stat feature toggle handling so all panels dont turn into stat panels 😅; fix betterer

* Use same function when clicking manual migrate button

* Update

---------

Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
pull/84072/head
Torkel Ödegaard 1 year ago committed by GitHub
parent 9c520acf9c
commit edd1864439
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      .betterer.results
  2. 469
      devenv/dev-dashboards/migrations/migrations.json
  3. 4
      public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts
  4. 56
      public/app/features/dashboard/state/DashboardModel.ts
  5. 6
      public/app/features/dashboard/state/PanelModel.ts
  6. 51
      public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts
  7. 32
      public/app/plugins/panel/barchart/migrations.test.ts
  8. 36
      public/app/plugins/panel/barchart/migrations.ts
  9. 2
      public/app/plugins/panel/barchart/module.tsx
  10. 4
      public/app/plugins/panel/graph/module.ts
  11. 28
      public/app/plugins/panel/histogram/migrations.test.ts
  12. 30
      public/app/plugins/panel/histogram/migrations.ts
  13. 2
      public/app/plugins/panel/histogram/module.tsx

@ -3008,6 +3008,9 @@ exports[`better eslint`] = {
"public/app/features/dashboard/state/actions.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/dashboard/state/getPanelPluginToMigrateTo.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
"public/app/features/dashboard/state/initDashboard.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],

@ -241,7 +241,7 @@
}
},
"percentage": false,
"pluginVersion": "10.4.0-pre",
"pluginVersion": "11.0.0-pre",
"pointradius": 2,
"points": false,
"renderer": "flot",
@ -311,7 +311,281 @@
"content": "# Graph panel >> Timeseries panel\n\nKnown issues:\n* hiding null/empty series\n* time regions",
"mode": "markdown"
},
"pluginVersion": "10.4.0-pre",
"pluginVersion": "11.0.0-pre",
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A"
}
],
"title": "Status + Notes",
"type": "text"
},
{
"aliasColors": {},
"bars": true,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 11,
"w": 16,
"x": 0,
"y": 11
},
"hiddenSeries": false,
"id": 28,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": false,
"total": false,
"values": false
},
"lines": false,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"percentage": false,
"pluginVersion": "11.0.0-pre",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "random_walk",
"seriesCount": 3
}
],
"thresholds": [],
"timeRegions": [],
"title": "Flot graph - x axis series mode",
"tooltip": {
"shared": false,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "series",
"show": true,
"values": [
"total"
]
},
"yaxes": [
{
"$$hashKey": "object:88",
"format": "short",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:89",
"format": "short",
"logBase": 1,
"show": true
}
],
"yaxis": {
"align": false
}
},
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"gridPos": {
"h": 11,
"w": 8,
"x": 16,
"y": 11
},
"id": 29,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"content": "# Graph panel >> Bar chart panel\n",
"mode": "markdown"
},
"pluginVersion": "11.0.0-pre",
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A"
}
],
"title": "Status + Notes",
"type": "text"
},
{
"aliasColors": {},
"bars": true,
"dashLength": 10,
"dashes": false,
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 11,
"w": 16,
"x": 0,
"y": 22
},
"hiddenSeries": false,
"id": 30,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": false,
"total": false,
"values": false
},
"lines": false,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true,
"legend": {
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"percentage": false,
"pluginVersion": "11.0.0-pre",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"refId": "A",
"scenarioId": "random_walk",
"seriesCount": 3
}
],
"thresholds": [],
"timeRegions": [],
"title": "Flot graph - x axis histogram mode",
"tooltip": {
"shared": false,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"mode": "histogram",
"show": true,
"values": []
},
"yaxes": [
{
"$$hashKey": "object:193",
"format": "short",
"logBase": 1,
"show": true
},
{
"$$hashKey": "object:194",
"format": "short",
"logBase": 1,
"show": true
}
],
"yaxis": {
"align": false
}
},
{
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"gridPos": {
"h": 11,
"w": 8,
"x": 16,
"y": 22
},
"id": 31,
"options": {
"code": {
"language": "plaintext",
"showLineNumbers": false,
"showMiniMap": false
},
"content": "# Graph panel >> Histogram panel\n",
"mode": "markdown"
},
"pluginVersion": "11.0.0-pre",
"targets": [
{
"datasource": {
@ -335,7 +609,7 @@
"h": 10,
"w": 16,
"x": 0,
"y": 11
"y": 33
},
"id": 2,
"options": {
@ -411,7 +685,7 @@
"h": 10,
"w": 8,
"x": 16,
"y": 11
"y": 33
},
"id": 7,
"options": {
@ -423,7 +697,7 @@
"content": "# Table (old) >> Table\n\nKnown issues:\n* wrapping text\n* style changes",
"mode": "markdown"
},
"pluginVersion": "10.4.0-pre",
"pluginVersion": "11.0.0-pre",
"targets": [
{
"datasource": {
@ -460,7 +734,7 @@
"h": 8,
"w": 8,
"x": 0,
"y": 21
"y": 43
},
"id": 9,
"mappingType": 1,
@ -517,64 +791,65 @@
"valueName": "avg"
},
{
"colorBackground": false,
"colorValue": true,
"colors": [
"#299c46",
"#73BF69",
"#d44a3a"
],
"datasource": {
"type": "testdata",
"uid": "PD8C576611E62080A"
},
"format": "ms",
"gauge": {
"maxValue": 100,
"minValue": 0,
"show": false,
"thresholdLabels": false,
"thresholdMarkers": true
"fieldConfig": {
"defaults": {
"mappings": [
{
"options": {
"match": "null",
"result": {
"text": "N/A"
}
},
"type": "special"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 80
}
]
},
"unit": "ms"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 8,
"x": 8,
"y": 21
"y": 43
},
"id": 23,
"mappingType": 1,
"mappingTypes": [
{
"name": "value to text",
"value": 1
},
{
"name": "range to text",
"value": 2
}
],
"maxDataPoints": 100,
"nullPointMode": "connected",
"pluginVersion": "6.2.0-pre",
"postfix": "",
"postfixFontSize": "50%",
"prefix": "p95",
"prefixFontSize": "80%",
"rangeMaps": [
{
"from": "null",
"text": "N/A",
"to": "null"
}
],
"sparkline": {
"fillColor": "rgba(31, 118, 189, 0.18)",
"full": false,
"lineColor": "rgb(31, 120, 193)",
"show": true
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": [
"mean"
],
"fields": "",
"values": false
},
"showPercentChange": false,
"textMode": "auto",
"wideLayout": true
},
"tableColumn": "",
"pluginVersion": "11.0.0-pre",
"targets": [
{
"datasource": {
@ -584,18 +859,8 @@
"refId": "A"
}
],
"thresholds": "",
"title": "singlestat (old, internal. Migrated if schema < 28)",
"type": "singlestat",
"valueFontSize": "120%",
"valueMaps": [
{
"op": "=",
"text": "N/A",
"value": "null"
}
],
"valueName": "avg"
"type": "stat"
},
{
"datasource": {
@ -606,7 +871,7 @@
"h": 8,
"w": 8,
"x": 16,
"y": 21
"y": 43
},
"id": 10,
"options": {
@ -618,7 +883,7 @@
"content": "# Singlestat >> Stat\n\nKnown issues:\n* limited options",
"mode": "markdown"
},
"pluginVersion": "10.4.0-pre",
"pluginVersion": "11.0.0-pre",
"targets": [
{
"datasource": {
@ -640,7 +905,7 @@
"h": 10,
"w": 16,
"x": 0,
"y": 29
"y": 51
},
"id": 24,
"options": {
@ -693,7 +958,7 @@
"h": 10,
"w": 8,
"x": 16,
"y": 29
"y": 51
},
"id": 25,
"options": {
@ -705,7 +970,7 @@
"content": "# grafana-piechart-panel >> piechart\n\nKnown issues:\n* TBD",
"mode": "markdown"
},
"pluginVersion": "10.4.0-pre",
"pluginVersion": "11.0.0-pre",
"targets": [
{
"datasource": {
@ -719,47 +984,35 @@
"type": "text"
},
{
"circleMaxSize": 30,
"circleMinSize": 2,
"colors": [
"rgba(245, 54, 54, 0.9)",
"rgba(237, 129, 40, 0.89)",
"rgba(50, 172, 45, 0.97)"
],
"datasource": {
"type": "grafana-testdata-datasource",
"uid": "PD8C576611E62080A"
},
"fieldConfig": {
"defaults": {
"custom": {
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
}
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "rgba(245, 54, 54, 0.9)"
},
{
"color": "rgba(237, 129, 40, 0.89)",
"value": 0
},
{
"color": "rgba(50, 172, 45, 0.97)",
"value": 10
}
]
}
},
"overrides": []
},
"decimals": 0,
"esMetric": "Count",
"gridPos": {
"h": 10,
"w": 16,
"x": 0,
"y": 39
"y": 61
},
"hideEmpty": false,
"hideZero": false,
"id": 26,
"initialZoom": 1,
"locationData": "countries",
"mapCenter": "(0°, 0°)",
"mapCenterLatitude": 0,
"mapCenterLongitude": 0,
"maxDataPoints": 1,
"mouseWheelZoom": false,
"options": {
"basemap": {
"name": "Basemap",
@ -831,6 +1084,15 @@
}
},
"pluginVersion": "10.4.0-pre",
"showLegend": true,
"stickyLabels": false,
"tableQueryOptions": {
"geohashField": "geohash",
"latitudeField": "latitude",
"longitudeField": "longitude",
"metricField": "metric",
"queryType": "geohash"
},
"targets": [
{
"csvFileName": "flight_info_by_state.csv",
@ -842,6 +1104,7 @@
"scenarioId": "csv_file"
}
],
"thresholds": "0,10",
"title": "grafana-worldmap-panel",
"transformations": [
{
@ -859,7 +1122,10 @@
}
}
],
"type": "grafana-worldmap-panel"
"type": "grafana-worldmap-panel",
"unitPlural": "",
"unitSingle": "",
"valueName": "total"
},
{
"datasource": {
@ -870,7 +1136,7 @@
"h": 10,
"w": 8,
"x": 16,
"y": 39
"y": 61
},
"id": 27,
"options": {
@ -882,7 +1148,7 @@
"content": "# grafana-worldmap-panel >> geomap\n\nKnown issues:\n* TBD",
"mode": "markdown"
},
"pluginVersion": "10.4.0-pre",
"pluginVersion": "11.0.0-pre",
"targets": [
{
"datasource": {
@ -910,10 +1176,11 @@
"from": "now-6h",
"to": "now"
},
"timeRangeUpdatedDuringEditOrView": false,
"timepicker": {},
"timezone": "",
"title": "Devenv - Panel migrations",
"uid": "cdd412c4",
"version": 67,
"version": 68,
"weekStart": ""
}

@ -71,9 +71,7 @@ export interface SaveModelToSceneOptions {
export function transformSaveModelToScene(rsp: DashboardDTO): DashboardScene {
// Just to have migrations run
const oldModel = new DashboardModel(rsp.dashboard, rsp.meta, {
autoMigrateOldPanels: false,
});
const oldModel = new DashboardModel(rsp.dashboard, rsp.meta);
const scene = createDashboardSceneFromDashboardModel(oldModel);
// TODO: refactor createDashboardSceneFromDashboardModel to work on Dashboard schema model

@ -127,9 +127,6 @@ export class DashboardModel implements TimeModel {
options?: {
// By default this uses variables from redux state
getVariablesFromState?: GetVariables;
// Force the loader to migrate panels
autoMigrateOldPanels?: boolean;
}
) {
this.getVariablesFromState = options?.getVariablesFromState ?? getVariablesByKey;
@ -169,59 +166,6 @@ export class DashboardModel implements TimeModel {
this.initMeta(meta);
this.updateSchema(data);
// Auto-migrate old angular panels
const shouldMigrateAllAngularPanels =
options?.autoMigrateOldPanels || !config.angularSupportEnabled || config.featureToggles.autoMigrateOldPanels;
const shouldMigrateExplicitAngularPanels =
config.featureToggles.autoMigrateGraphPanel ||
config.featureToggles.autoMigrateTablePanel ||
config.featureToggles.autoMigratePiechartPanel ||
config.featureToggles.autoMigrateWorldmapPanel ||
config.featureToggles.autoMigrateStatPanel;
// Handles both granular and all angular panel migration
if (shouldMigrateAllAngularPanels || shouldMigrateExplicitAngularPanels) {
for (const panel of this.panelIterator()) {
if (
!panel.autoMigrateFrom &&
panel.type === 'graph' &&
(config.featureToggles.autoMigrateGraphPanel || shouldMigrateAllAngularPanels)
) {
panel.autoMigrateFrom = panel.type;
panel.type = 'timeseries';
} else if (
!panel.autoMigrateFrom &&
panel.type === 'table-old' &&
(config.featureToggles.autoMigrateTablePanel || shouldMigrateAllAngularPanels)
) {
panel.autoMigrateFrom = panel.type;
panel.type = 'table';
} else if (
!panel.autoMigrateFrom &&
panel.type === 'grafana-piechart-panel' &&
(config.featureToggles.autoMigratePiechartPanel || shouldMigrateAllAngularPanels)
) {
panel.autoMigrateFrom = panel.type;
panel.type = 'piechart';
} else if (
!panel.autoMigrateFrom &&
panel.type === 'grafana-worldmap-panel' &&
(config.featureToggles.autoMigrateWorldmapPanel || shouldMigrateAllAngularPanels)
) {
panel.autoMigrateFrom = panel.type;
panel.type = 'geomap';
} else if (
!panel.autoMigrateFrom &&
(panel.type === 'singlestat' || panel.type === 'grafana-singlestat-panel') &&
(config.featureToggles.autoMigrateStatPanel || shouldMigrateAllAngularPanels)
) {
panel.autoMigrateFrom = panel.type;
panel.type = 'stat';
}
}
}
this.addBuiltInAnnotationQuery();
this.sortPanelsByGridPos();
this.panelsAffectedByVariableChange = null;

@ -36,6 +36,8 @@ import {
import { PanelQueryRunner } from '../../query/state/PanelQueryRunner';
import { TimeOverrideResult } from '../utils/panel';
import { getPanelPluginToMigrateTo } from './getPanelPluginToMigrateTo';
export interface GridPos {
x: number;
y: number;
@ -148,7 +150,7 @@ export const autoMigrateAngular: Record<string, string> = {
'grafana-worldmap-panel': 'geomap',
};
const autoMigratePanelType: Record<string, string> = {
export const autoMigrateRemovedPanelPlugins: Record<string, string> = {
'heatmap-new': 'heatmap', // this was a temporary development panel that is now standard
};
@ -257,7 +259,7 @@ export class PanelModel implements DataConfigSource, IPanelModel {
(this as any)[property] = model[property];
}
const newType = autoMigratePanelType[this.type];
const newType = getPanelPluginToMigrateTo(this);
if (newType) {
this.autoMigrateFrom = this.type;
this.type = newType;

@ -0,0 +1,51 @@
import config from 'app/core/config';
import { autoMigrateRemovedPanelPlugins, autoMigrateAngular } from './PanelModel';
export function getPanelPluginToMigrateTo(panel: any, forceMigration?: boolean): string | undefined {
if (autoMigrateRemovedPanelPlugins[panel.type]) {
return autoMigrateRemovedPanelPlugins[panel.type];
}
// Auto-migrate old angular panels
const shouldMigrateAllAngularPanels =
forceMigration || !config.angularSupportEnabled || config.featureToggles.autoMigrateOldPanels;
// Graph needs special logic as it can be migrated to multiple panels
if (panel.type === 'graph' && (shouldMigrateAllAngularPanels || config.featureToggles.autoMigrateGraphPanel)) {
if (panel.xaxis?.mode === 'series') {
return 'barchart';
}
if (panel.xaxis?.mode === 'histogram') {
return 'histogram';
}
return 'timeseries';
}
if (shouldMigrateAllAngularPanels) {
return autoMigrateAngular[panel.type];
}
if (panel.type === 'table-old' && config.featureToggles.autoMigrateTablePanel) {
return 'table';
}
if (panel.type === 'grafana-piechart-panel' && config.featureToggles.autoMigratePiechartPanel) {
return 'piechart';
}
if (panel.type === 'grafana-worldmap-panel' && config.featureToggles.autoMigrateWorldmapPanel) {
return 'geomap';
}
if (
(panel.type === 'singlestat' || panel.type === 'grafana-singlestat-panel') &&
config.featureToggles.autoMigrateStatPanel
) {
return 'stat';
}
return undefined;
}

@ -0,0 +1,32 @@
import { FieldConfigSource, PanelModel } from '@grafana/data';
import { changeToBarChartPanelMigrationHandler } from './migrations';
describe('Bar chart Migrations', () => {
let prevFieldConfig: FieldConfigSource;
beforeEach(() => {
prevFieldConfig = {
defaults: {},
overrides: [],
};
});
it('From old graph', () => {
const old = {
angular: {
xaxis: {
mode: 'series',
values: 'avg',
},
},
};
const panel = {} as PanelModel;
panel.options = changeToBarChartPanelMigrationHandler(panel, 'graph', old, prevFieldConfig);
const transform = panel.transformations![0];
expect(transform.id).toBe('reduce');
expect(transform.options.reducers).toBe('avg');
});
});

@ -0,0 +1,36 @@
import { PanelTypeChangedHandler } from '@grafana/data';
/*
* This is called when the panel changes from another panel
*/
export const changeToBarChartPanelMigrationHandler: PanelTypeChangedHandler = (
panel,
prevPluginId,
prevOptions,
prevFieldConfig
) => {
if (prevPluginId === 'graph') {
const graphOptions: GraphOptions = prevOptions.angular;
if (graphOptions.xaxis?.mode === 'series') {
const tranformations = panel.transformations || [];
tranformations.push({
id: 'reduce',
options: {
reducers: graphOptions.xaxis?.values ?? ['sum'],
},
});
panel.transformations = tranformations;
}
}
return {};
};
interface GraphOptions {
xaxis: {
mode: 'series' | 'time' | 'histogram';
values?: string[];
};
}

@ -16,11 +16,13 @@ import { ThresholdsStyleEditor } from '../timeseries/ThresholdsStyleEditor';
import { BarChartPanel } from './BarChartPanel';
import { TickSpacingEditor } from './TickSpacingEditor';
import { changeToBarChartPanelMigrationHandler } from './migrations';
import { FieldConfig, Options, defaultFieldConfig, defaultOptions } from './panelcfg.gen';
import { BarChartSuggestionsSupplier } from './suggestions';
import { prepareBarChartDisplayValues } from './utils';
export const plugin = new PanelPlugin<Options, FieldConfig>(BarChartPanel)
.setPanelChangeHandler(changeToBarChartPanelMigrationHandler)
.useFieldConfig({
standardOptions: {
[FieldConfigProperty.Color]: {

@ -14,6 +14,7 @@ import { MetricsPanelCtrl } from 'app/angular/panel/metrics_panel_ctrl';
import config from 'app/core/config';
import TimeSeries from 'app/core/time_series2';
import { ThresholdMapper } from 'app/features/alerting/state/ThresholdMapper';
import { getPanelPluginToMigrateTo } from 'app/features/dashboard/state/getPanelPluginToMigrateTo';
import { changePanelPlugin } from 'app/features/panel/state/actions';
import { dispatch } from 'app/store/store';
@ -356,7 +357,8 @@ export class GraphCtrl extends MetricsPanelCtrl {
};
migrateToReact() {
this.onPluginTypeChange(config.panels['timeseries']);
const panelType = getPanelPluginToMigrateTo(this.panel, true);
this.onPluginTypeChange(config.panels[panelType!]);
}
}

@ -0,0 +1,28 @@
import { FieldConfigSource, PanelModel } from '@grafana/data';
import { changeToHistogramPanelMigrationHandler } from './migrations';
describe('Histogram migrations', () => {
let prevFieldConfig: FieldConfigSource;
beforeEach(() => {
prevFieldConfig = {
defaults: {},
overrides: [],
};
});
it('From old graph', () => {
const old = {
angular: {
xaxis: {
mode: 'histogram',
},
},
};
const panel = {} as PanelModel;
panel.options = changeToHistogramPanelMigrationHandler(panel, 'graph', old, prevFieldConfig);
expect(panel.options.combine).toBe(true);
});
});

@ -0,0 +1,30 @@
import { PanelTypeChangedHandler } from '@grafana/data';
/*
* This is called when the panel changes from another panel
*/
export const changeToHistogramPanelMigrationHandler: PanelTypeChangedHandler = (
panel,
prevPluginId,
prevOptions,
prevFieldConfig
) => {
if (prevPluginId === 'graph') {
const graphOptions: GraphOptions = prevOptions.angular;
if (graphOptions.xaxis?.mode === 'histogram') {
return {
combine: true,
};
}
}
return {};
};
interface GraphOptions {
xaxis: {
mode: 'series' | 'time' | 'histogram';
values?: string[];
};
}

@ -3,10 +3,12 @@ import { histogramFieldInfo } from '@grafana/data/src/transformations/transforme
import { commonOptionsBuilder, graphFieldOptions } from '@grafana/ui';
import { HistogramPanel } from './HistogramPanel';
import { changeToHistogramPanelMigrationHandler } from './migrations';
import { FieldConfig, Options, defaultFieldConfig, defaultOptions } from './panelcfg.gen';
import { originalDataHasHistogram } from './utils';
export const plugin = new PanelPlugin<Options, FieldConfig>(HistogramPanel)
.setPanelChangeHandler(changeToHistogramPanelMigrationHandler)
.setPanelOptions((builder) => {
builder
.addCustomEditor({

Loading…
Cancel
Save