From e493bec3a87c30791143fd99897b873b092d6be8 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 28 Apr 2023 12:20:10 -0700 Subject: [PATCH] Geomap: Improve migration logic (#65500) --- packages/grafana-data/src/types/dashboard.ts | 5 ++ .../features/dashboard/state/PanelModel.ts | 2 +- public/app/features/geo/utils/location.ts | 9 +-- .../plugins/panel/geomap/migrations.test.ts | 54 +++++++++++++- public/app/plugins/panel/geomap/migrations.ts | 73 +++++++++++++++++-- 5 files changed, 128 insertions(+), 15 deletions(-) diff --git a/packages/grafana-data/src/types/dashboard.ts b/packages/grafana-data/src/types/dashboard.ts index 1608efd8eb8..36787674903 100644 --- a/packages/grafana-data/src/types/dashboard.ts +++ b/packages/grafana-data/src/types/dashboard.ts @@ -1,3 +1,5 @@ +import { DataTransformerConfig } from '@grafana/schema'; + import { FieldConfigSource } from './fieldOverrides'; import { DataQuery, DataSourceRef } from './query'; @@ -38,6 +40,9 @@ export interface PanelModel { /** The queries in a panel */ targets?: DataQuery[]; + /** Optionally process data after query */ + transformations?: DataTransformerConfig[]; + /** alerting v1 object */ alert?: any; } diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index dff99dbd546..dc582275148 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -487,7 +487,7 @@ export class PanelModel implements DataConfigSource, IPanelModel { const oldOptions: any = this.getOptionsToRemember(); const prevFieldConfig = this.fieldConfig; const oldPluginId = this.type; - const wasAngular = this.isAngularPlugin(); + const wasAngular = this.isAngularPlugin() || Boolean(autoMigrateAngular[oldPluginId]); this.cachedPluginOptions[oldPluginId] = { properties: oldOptions, fieldConfig: prevFieldConfig, diff --git a/public/app/features/geo/utils/location.ts b/public/app/features/geo/utils/location.ts index 02a25c40fa3..91f009d54b6 100644 --- a/public/app/features/geo/utils/location.ts +++ b/public/app/features/geo/utils/location.ts @@ -82,11 +82,10 @@ export async function getLocationMatchers(src?: FrameGeometrySource): Promise undefined; // In manual mode, don't automatically find field - } + const m = src?.lookup?.length + ? getFieldMatcher({ id: FieldMatcherID.byName, options: src.lookup }) + : getFieldMatcher({ id: FieldMatcherID.byType, options: FieldType.string }); + info.lookup = getFieldFinder(m); break; case FrameGeometrySourceMode.Coords: if (src?.latitude) { diff --git a/public/app/plugins/panel/geomap/migrations.test.ts b/public/app/plugins/panel/geomap/migrations.test.ts index 3b6f3d97ca2..e498578e303 100644 --- a/public/app/plugins/panel/geomap/migrations.test.ts +++ b/public/app/plugins/panel/geomap/migrations.test.ts @@ -55,7 +55,49 @@ describe('Worldmap Migrations', () => { "mouseWheelZoom": true, "showZoom": true, }, - "layers": [], + "layers": [ + { + "config": { + "showLegend": true, + "style": { + "color": { + "fixed": "dark-green", + }, + "opacity": 0.4, + "rotation": { + "fixed": 0, + "max": 360, + "min": -360, + "mode": "mod", + }, + "size": { + "fixed": 5, + "max": 30, + "min": 2, + }, + "symbol": { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed", + }, + "textConfig": { + "fontSize": 12, + "offsetX": 0, + "offsetY": 0, + "textAlign": "center", + "textBaseline": "middle", + }, + }, + }, + "location": { + "gazetteer": "public/gazetteer/countries.json", + "lookup": undefined, + "mode": "lookup", + }, + "name": "", + "tooltip": true, + "type": "markers", + }, + ], "tooltip": { "mode": "details", }, @@ -66,6 +108,16 @@ describe('Worldmap Migrations', () => { "zoom": 6, }, }, + "transformations": [ + { + "id": "reduce", + "options": { + "reducers": [ + "sum", + ], + }, + }, + ], } `); }); diff --git a/public/app/plugins/panel/geomap/migrations.ts b/public/app/plugins/panel/geomap/migrations.ts index 7342d0982e8..6e6369be899 100644 --- a/public/app/plugins/panel/geomap/migrations.ts +++ b/public/app/plugins/panel/geomap/migrations.ts @@ -1,9 +1,19 @@ import { cloneDeep } from 'lodash'; -import { FieldConfigSource, PanelModel, PanelTypeChangedHandler, Threshold, ThresholdsMode } from '@grafana/data'; +import { + FieldConfigSource, + PanelModel, + PanelTypeChangedHandler, + Threshold, + ThresholdsMode, + fieldReducers, + FrameGeometrySourceMode, + DataTransformerConfig, + DataTransformerID, +} from '@grafana/data'; import { ResourceDimensionMode } from 'app/features/dimensions'; -import { MarkersConfig } from './layers/data/markersLayer'; +import { defaultMarkersConfig, MarkersConfig } from './layers/data/markersLayer'; import { getMarkerAsPath } from './style/markers'; import { defaultStyleConfig } from './style/types'; import { PanelOptions, TooltipMode } from './types'; @@ -15,10 +25,13 @@ import { MapCenterID } from './view'; export const mapPanelChangedHandler: PanelTypeChangedHandler = (panel, prevPluginId, prevOptions, prevFieldConfig) => { // Changing from angular/worldmap panel to react/openlayers if (prevPluginId === 'grafana-worldmap-panel' && prevOptions.angular) { - const { fieldConfig, options } = worldmapToGeomapOptions({ + const { fieldConfig, options, xform } = worldmapToGeomapOptions({ ...prevOptions.angular, fieldConfig: prevFieldConfig, }); + if (xform?.id?.length) { + panel.transformations = panel.transformations ? [...panel.transformations, xform] : [xform]; + } panel.fieldConfig = fieldConfig; // Mutates the incoming panel return options; } @@ -26,12 +39,17 @@ export const mapPanelChangedHandler: PanelTypeChangedHandler = (panel, prevPlugi return {}; }; -export function worldmapToGeomapOptions(angular: any): { fieldConfig: FieldConfigSource; options: PanelOptions } { +export function worldmapToGeomapOptions(angular: any): { + fieldConfig: FieldConfigSource; + options: PanelOptions; + xform?: DataTransformerConfig; +} { const fieldConfig: FieldConfigSource = { defaults: {}, overrides: [], }; + const markersLayer = cloneDeep(defaultMarkersConfig); const options: PanelOptions = { view: { id: MapCenterID.Zero, @@ -44,9 +62,7 @@ export function worldmapToGeomapOptions(angular: any): { fieldConfig: FieldConfi type: 'default', // was carto name: 'Basemap', }, - layers: [ - // TODO? depends on current configs - ], + layers: [markersLayer], tooltip: { mode: TooltipMode.Details }, }; @@ -55,6 +71,47 @@ export function worldmapToGeomapOptions(angular: any): { fieldConfig: FieldConfi fieldConfig.defaults.decimals = v; } + // Set the markers range + const style = markersLayer.config!.style; + v = asNumber(angular.circleMaxSize); + if (v) { + style.size!.max = v; + } + v = asNumber(angular.circleMinSize); + if (v) { + style.size!.min = v; + } + + let xform: DataTransformerConfig | undefined = undefined; + const reducer = fieldReducers.getIfExists(angular.valueName); + if (reducer && angular.locationData?.length) { + xform = { + id: DataTransformerID.reduce, + options: { + reducers: [reducer.id], + }, + }; + + switch (angular.locationData) { + case 'countries': + case 'countries_3letter': + markersLayer.location = { + mode: FrameGeometrySourceMode.Lookup, + gazetteer: 'public/gazetteer/countries.json', + lookup: undefined, // will default to first string field from reducer + }; + break; + + case 'states': + markersLayer.location = { + mode: FrameGeometrySourceMode.Lookup, + gazetteer: 'public/gazetteer/usa-states.json', + lookup: undefined, // will default to first string field from reducer + }; + break; + } + } + // Convert thresholds and color values if (angular.thresholds && angular.colors) { const levels = angular.thresholds.split(',').map((strVale: string) => { @@ -99,7 +156,7 @@ export function worldmapToGeomapOptions(angular: any): { fieldConfig: FieldConfi options.view.id = mapCenters[angular.mapCenter as any]; options.view.lat = asNumber(angular.mapCenterLatitude); options.view.lon = asNumber(angular.mapCenterLongitude); - return { fieldConfig, options }; + return { fieldConfig, options, xform }; } function asNumber(v: any): number | undefined {