From 7756181ff97c76be15aeef7c068c9aaa1a938bcc Mon Sep 17 00:00:00 2001 From: nikki-kiga <42276368+nikki-kiga@users.noreply.github.com> Date: Fri, 8 Oct 2021 15:49:04 -0700 Subject: [PATCH] Geomap: Custom markers using ResourcePicker with standard marker fallbacks (#39919) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add custom icons * use resourcePicker for marker icon * use regular shapes for specific icons * update type * update svgs and remove marker shape selection * update types and resourcePicker * add migration and update markers Co-authored-by: Ryan McKinley * quick cleanup * update marker path and remove any * update migration test * use inline snapshot * remove unused * Docs: Add documentation for library elements API (#39829) * LibraryElements: Adds api documentation * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/http_api/library_element.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Refactor: changes after PR comments * Apply suggestions from code review Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Chore: updates after review Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * CodeEditor: making sure we trigger the latest onSave callback provided to the component (#39835) * Fix: prevent queryDisplyText in QueryRowHeader from overflowing (#40094) * Revert "Fix Query Editor Row horizontal overflow (#39419)" This reverts commit 42b1fa0f62d592e0749e90d4a63c4a56066a1915. * fix: prevent queryDisplyText in QueryRowHeader from overflowing * Search: Fix local storage key (#40127) * Default to 'General' if no folder title is present * Add bottom padding * Live: remote write sampling (#40079) * Stat: recompute shared y range during streaming updates (#39485) * NavBar: Order App plugins alphabetically (#40078) * NavBar: Order App plugins alphabetically * Update pkg/api/index.go Co-authored-by: Will Browne Co-authored-by: Will Browne * Schema: use the generated graph.gen.ts (#40090) * actually generate graph.gen.ts * getting closer * keep file where it is * manual fixes * Update packages/grafana-schema/src/schema/graph.gen.ts Co-authored-by: sam boyer * Docs: Whats new in 8.2 (#39945) * Added time range controls updates * Added plugins catalog update * Added enterprise images * Added community contributions highlights for 8.2 * accessibility statement * Update docs/sources/whatsnew/whats-new-in-v8-2.md Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> * Update docs/sources/whatsnew/whats-new-in-v8-2.md Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> * Update docs/sources/whatsnew/whats-new-in-v8-2.md Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> * Update docs/sources/whatsnew/whats-new-in-v8-2.md Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> * Update docs/sources/whatsnew/whats-new-in-v8-2.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/whatsnew/whats-new-in-v8-2.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/whatsnew/whats-new-in-v8-2.md Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> * Update docs/sources/whatsnew/whats-new-in-v8-2.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/whatsnew/whats-new-in-v8-2.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Live: array for Processor, Outputter and Subscriber in channel rule top level (#39677) * ReleaseNotes: Updated changelog and release notes for 8.1.7 (#40081) * more * more Co-authored-by: sam boyer Co-authored-by: Petros Kolyvas Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Alexander Emelin Co-authored-by: Grot (@grafanabot) <43478413+grafanabot@users.noreply.github.com> * Access Control: Add scope type prefix (#40076) * prefix runtime scopes with key type * Bump mocha from 7.0.1 to 9.1.2 (#39979) * Bump mocha from 7.0.1 to 9.1.2 Bumps [mocha](https://github.com/mochajs/mocha) from 7.0.1 to 9.1.2. - [Release notes](https://github.com/mochajs/mocha/releases) - [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md) - [Commits](https://github.com/mochajs/mocha/compare/v7.0.1...v9.1.2) --- updated-dependencies: - dependency-name: mocha dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] * kick drone * Remove mocha as it's not used by anything * kick drone Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ashley Harrison * ReleaseNotes: Updated changelog and release notes for 8.2.0 (#40141) * ReleaseNotes: Updated changelog and release notes for 8.2.0 * Add link & remove empty line in CHANGELOG * remove empty line Co-authored-by: Elfo404 * Create search filters by interface (#39843) * Extract search users to a new service * Fix wire provider * Fix common_test and remove RouteRegister * Remove old endpoints * Fix test * Create search filters using interfaces * Move Enterprise filter, rename filter for filters and allow use filters with params * Each filter has unique key * Back activeLast30Days filter to OSS * Fix tests * Delete unusued param * Move filters to searchusers service and small refactor * Fix tests * Encryption: Refactor securejsondata.SecureJsonData to stop relying on global functions (#38865) * Encryption: Add support to encrypt/decrypt sjd * Add datasources.Service as a proxy to datasources db operations * Encrypt ds.SecureJsonData before calling SQLStore * Move ds cache code into ds service * Fix tlsmanager tests * Fix pluginproxy tests * Remove some securejsondata.GetEncryptedJsonData usages * Add pluginsettings.Service as a proxy for plugin settings db operations * Add AlertNotificationService as a proxy for alert notification db operations * Remove some securejsondata.GetEncryptedJsonData usages * Remove more securejsondata.GetEncryptedJsonData usages * Fix lint errors * Minor fixes * Remove encryption global functions usages from ngalert * Fix lint errors * Minor fixes * Minor fixes * Remove securejsondata.DecryptedValue usage * Refactor the refactor * Remove securejsondata.DecryptedValue usage * Move securejsondata to migrations package * Move securejsondata to migrations package * Minor fix * Fix integration test * Fix integration tests * Undo undesired changes * Fix tests * Add context.Context into encryption methods * Fix tests * Fix tests * Fix tests * Trigger CI * Fix test * Add names to params of encryption service interface * Remove bus from CacheServiceImpl * Add logging * Add keys to logger Co-authored-by: Emil Tullstedt * Add missing key to logger Co-authored-by: Emil Tullstedt * Undo changes in markdown files * Fix formatting * Add context to secrets service * Rename decryptSecureJsonData to decryptSecureJsonDataFn * Name args in GetDecryptedValueFn * Add template back to NewAlertmanagerNotifier * Copy GetDecryptedValueFn to ngalert * Add logging to pluginsettings * Fix pluginsettings test Co-authored-by: Tania B Co-authored-by: Emil Tullstedt * Admin: Enable extending filters (#39825) * Setup extensible filters * Fix test * Handle filter as array * Add className * Abstract getFilters * Make docs link external * Use underline for links in tooltips instead of link color Co-authored-by: Selene * Chore: update latest.json to 8.2 (#40153) * Doc: Fixed issue 40017 (#40152) * Added content as suggested by Will * removed a few extra words. * Update docs/sources/administration/configuration.md Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> * docs: Add keepCokkies cofiguration option in datasources (#39890) Signed-off-by: Vinayak Kadam * Grammar issues (#40168) * Packaging: document systemd net bind capability rpm and deb installations (#40165) * add systemd net bind capability docs for rpm and deb Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Alerting: declare constants for __dashboardUid__ and __panelId__ literals (#39976) * Babel: Remove unused plugin (#40172) * removed unused babel plugin * Update lock file * Chore(dependencies): Remove puppeteer since we don't use it anywhere (#40137) * Folders: Prevents deletion of General folder (#40192) * Datasources: Fix deletion of datasource if plugin cannot be found (#40095) * fix(pluginsettings): reject with error so datasource plugin loading failures still render ui * feat(pluginpage): handle plugin loading error * refactor(datasources): separate out datasource and meta loading so store has info for deletion * fix(datasourcesettings): introduce loading flag to wait for datasource and meta loading * test(datasourcesettings): fix failing test * test(datasources): assert loading status of datasource settings * test(datasources): update action tests for latest changes * Replace SAML library with fork (#40149) * Update saml library to latest * Use fork of crewjam/saml with fix for certificate chain bug * CloudMonitoring: Migrate to use backend plugin SDK contracts (#38650) * Use SDK contracts for cloudmonitoring * Get build running, tests passing and do some refactoring (#38754) * fix build+tests and refactor * remove alerting stuff * remove unused field * fix plugin fetch * end to end * resp rename * tidy annotations * reformatting * update refID * reformat imports * fix styling * clean up unmarshalling * uncomment + fix tests * appease linter * remove spaces * remove old cruft * add check for empty queries * update tests * remove pm as dep * adjust proxy route contract * fix service loading * use UNIX val * fix endpoint + resp * h@ckz for frontend * fix resp * fix interval * always set custom meta * remove unused param * fix labels fetch * fix linter * fix test + remove unused field * apply pr feedback * fix grafana-auto intervals * fix tests * resolve conflicts * fix bad merge * fix conflicts * remove bad logger import Co-authored-by: Will Browne Co-authored-by: Will Browne * Do codegen and check no-diff of all (non-blacklisted) CUE->TS codegen during CI (#39922) * Add file blacklist to `grafana-cli cue gen-ts` cmd * Add CI step checking all cuetsification is done * Add dummy command to make the next one fail * Generate drone bits * Check diff output failure * Echo list of untracked files, for failure locality * Move git cleanness checking into script * Blacklist of cue files is complete and correct * Remove news panel plugin from cuetsify blacklist * Dummy commit, check that untracked gen still fail * Tie off remaining errors * Re-add barchart to blacklist * Remove file left around by earlier pipeline * Commit generated news models.gen.ts * Include eslint as part of cuetsified output gen * Update pkg/cmd/grafana-cli/commands/cuetsify_command.go Co-authored-by: Ashley Harrison * Update scripts/drone/steps/lib.star Co-authored-by: Maria Alexandra <239999+axelavargas@users.noreply.github.com> * Update drone.yml * Last fix on .drone.yml Co-authored-by: Ashley Harrison Co-authored-by: Maria Alexandra <239999+axelavargas@users.noreply.github.com> * Alerting: add organziation ID to the ngAlert webhook payload (#40189) * Alerting: add organziation ID to the ngAlert webhook payload * remove systemcallfilters sections from systemd unit files (#40176) * Add Headers to http client Options (#40214) * Docs: Add required library for the image renderer (#40201) * update permissions scopes and description for role scopes (#40206) * Chore: Migrate yarn from v1 to v2 (#39082) * Chore: Migrate yarn from v1 to v2 Co-authored-by: Hugo Häggmark * ReleaseNotes: Updated changelog and release notes for 8.2.0 (#40233) Co-authored-by: Ryan McKinley Co-authored-by: Hugo Häggmark Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Marcus Andersson Co-authored-by: Giordano Ricci Co-authored-by: Alex Khomenko Co-authored-by: Alexander Emelin Co-authored-by: Leon Sorokin Co-authored-by: Ashley Harrison Co-authored-by: Will Browne Co-authored-by: Ryan McKinley Co-authored-by: sam boyer Co-authored-by: Petros Kolyvas Co-authored-by: Fiona Artiaga <89225282+GrafanaWriter@users.noreply.github.com> Co-authored-by: Grot (@grafanabot) <43478413+grafanabot@users.noreply.github.com> Co-authored-by: Karl Persson Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Selene Co-authored-by: Joan López de la Franca Beltran <5459617+joanlopez@users.noreply.github.com> Co-authored-by: Tania B Co-authored-by: Emil Tullstedt Co-authored-by: Vinayak Co-authored-by: Anne E. Ulrich Co-authored-by: Kevin Minehart Co-authored-by: Yuriy Tseretyan Co-authored-by: Torkel Ödegaard Co-authored-by: Jack Westbrook Co-authored-by: Alexander Zobnin Co-authored-by: idafurjes <36131195+idafurjes@users.noreply.github.com> Co-authored-by: Will Browne Co-authored-by: Maria Alexandra <239999+axelavargas@users.noreply.github.com> Co-authored-by: Jean-Philippe Quéméner Co-authored-by: Dimitris Sotirakis Co-authored-by: Agnès Toulet <35176601+AgnesToulet@users.noreply.github.com> Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> Co-authored-by: Hugo Häggmark --- .../editors/ResourceDimensionEditor.tsx | 18 +- .../dimensions/editors/ResourcePicker.tsx | 21 ++- public/app/features/dimensions/types.ts | 9 + .../panel/geomap/layers/data/markersLayer.tsx | 81 ++++++--- .../plugins/panel/geomap/migrations.test.ts | 169 +++++++++++++++++- public/app/plugins/panel/geomap/migrations.ts | 27 ++- public/app/plugins/panel/geomap/module.tsx | 3 +- .../panel/geomap/utils/regularShapes.ts | 59 ++++-- public/img/icons/marker/circle.svg | 1 + public/img/icons/marker/cross.svg | 1 + public/img/icons/marker/plane.svg | 1 + public/img/icons/marker/square.svg | 1 + public/img/icons/marker/star.svg | 1 + public/img/icons/marker/triangle.svg | 1 + public/img/icons/marker/x-mark.svg | 1 + 15 files changed, 344 insertions(+), 50 deletions(-) create mode 100644 public/img/icons/marker/circle.svg create mode 100644 public/img/icons/marker/cross.svg create mode 100644 public/img/icons/marker/plane.svg create mode 100644 public/img/icons/marker/square.svg create mode 100644 public/img/icons/marker/star.svg create mode 100644 public/img/icons/marker/triangle.svg create mode 100644 public/img/icons/marker/x-mark.svg diff --git a/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx b/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx index 801c15ca24f..74871153863 100644 --- a/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx +++ b/public/app/features/dimensions/editors/ResourceDimensionEditor.tsx @@ -4,6 +4,7 @@ import { ResourceDimensionConfig, ResourceDimensionMode, ResourceDimensionOption import { InlineField, InlineFieldRow, RadioButtonGroup, Button, Modal, Input } from '@grafana/ui'; import { FieldNamePicker } from '../../../../../packages/grafana-ui/src/components/MatchersUI/FieldNamePicker'; import { ResourcePicker } from './ResourcePicker'; +import { ResourceFolderName } from '..'; const resourceOptions = [ { label: 'Fixed', value: ResourceDimensionMode.Fixed, description: 'Fixed value' }, @@ -58,21 +59,24 @@ export const ResourceDimensionEditor: FC< }, []); const mode = value?.mode ?? ResourceDimensionMode.Fixed; + const showSourceRadio = item.settings?.showSourceRadio ?? true; const mediaType = item.settings?.resourceType ?? 'icon'; + const folderName = item.settings?.folderName ?? ResourceFolderName.Icon; return ( <> {isOpen && ( setOpen(false)} closeOnEscape> - + )} - - - - - - + {showSourceRadio && ( + + + + + + )} {mode !== ResourceDimensionMode.Fixed && ( diff --git a/public/app/features/dimensions/editors/ResourcePicker.tsx b/public/app/features/dimensions/editors/ResourcePicker.tsx index 0f7e27363f1..4672070bb1d 100644 --- a/public/app/features/dimensions/editors/ResourcePicker.tsx +++ b/public/app/features/dimensions/editors/ResourcePicker.tsx @@ -18,11 +18,13 @@ import { css } from '@emotion/css'; import { getPublicOrAbsoluteUrl } from '../resource'; import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; import { FileElement, GrafanaDatasource } from 'app/plugins/datasource/grafana/datasource'; +import { ResourceFolderName } from '..'; interface Props { value?: string; //img/icons/unicons/0-plus.svg onChange: (value?: string) => void; mediaType: 'icon' | 'image'; + folderName: ResourceFolderName; } interface ResourceItem { @@ -33,12 +35,13 @@ interface ResourceItem { } export function ResourcePicker(props: Props) { - const { value, onChange, mediaType } = props; - const folders = (mediaType === 'icon' ? ['img/icons/unicons', 'img/icons/iot'] : ['img/bg']).map((v) => ({ + const { value, onChange, mediaType, folderName } = props; + const folders = getFolders(mediaType).map((v) => ({ label: v, value: v, })); - const folderOfCurrentValue = value ? folders.filter((folder) => value.indexOf(folder.value) > -1)[0] : folders[0]; + + const folderOfCurrentValue = value || folderName ? folderIfExists(folders, value ?? folderName) : folders[0]; const [currentFolder, setCurrentFolder] = useState>(folderOfCurrentValue); const [tabs, setTabs] = useState([ { label: 'Select', active: true }, @@ -169,3 +172,15 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => { `, }; }); + +const getFolders = (mediaType: 'icon' | 'image') => { + if (mediaType === 'icon') { + return [ResourceFolderName.Icon, ResourceFolderName.IOT, ResourceFolderName.Marker]; + } else { + return [ResourceFolderName.BG]; + } +}; + +const folderIfExists = (folders: Array<{ label: string; value: string }>, path: string) => { + return folders.filter((folder) => path.indexOf(folder.value) > -1)[0] ?? folders[0]; +}; diff --git a/public/app/features/dimensions/types.ts b/public/app/features/dimensions/types.ts index bb028304dcb..097e244cb46 100644 --- a/public/app/features/dimensions/types.ts +++ b/public/app/features/dimensions/types.ts @@ -71,6 +71,8 @@ export interface ColorDimensionConfig extends BaseDimensionConfig {} /** Places that use the value */ export interface ResourceDimensionOptions { resourceType: 'icon' | 'image'; + folderName?: ResourceFolderName; + showSourceRadio?: boolean; } export enum ResourceDimensionMode { @@ -84,3 +86,10 @@ export enum ResourceDimensionMode { export interface ResourceDimensionConfig extends BaseDimensionConfig { mode: ResourceDimensionMode; } + +export enum ResourceFolderName { + Icon = 'img/icons/unicons', + IOT = 'img/icons/iot', + Marker = 'img/icons/marker', + BG = 'img/bg', +} diff --git a/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx b/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx index 6e1b6534773..666bb678bcb 100644 --- a/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx @@ -11,6 +11,7 @@ import Feature from 'ol/Feature'; import { Point } from 'ol/geom'; import * as layer from 'ol/layer'; import * as source from 'ol/source'; +import * as style from 'ol/style'; import tinycolor from 'tinycolor2'; import { dataFrameToPoints, getLocationMatchers } from '../../utils/location'; @@ -19,11 +20,15 @@ import { ScaleDimensionConfig, getScaledDimension, getColorDimension, + ResourceDimensionConfig, + ResourceDimensionMode, + ResourceFolderName, + getPublicOrAbsoluteUrl, } from 'app/features/dimensions'; -import { ScaleDimensionEditor, ColorDimensionEditor } from 'app/features/dimensions/editors'; +import { ScaleDimensionEditor, ColorDimensionEditor, ResourceDimensionEditor } from 'app/features/dimensions/editors'; import { ObservablePropsWrapper } from '../../components/ObservablePropsWrapper'; import { MarkersLegend, MarkersLegendProps } from './MarkersLegend'; -import { circleMarker, markerMakers } from '../../utils/regularShapes'; +import { StyleMaker, getMarkerFromPath, MarkerShapePath } from '../../utils/regularShapes'; import { ReplaySubject } from 'rxjs'; // Configuration options for Circle overlays @@ -31,13 +36,15 @@ export interface MarkersConfig { size: ScaleDimensionConfig; color: ColorDimensionConfig; fillOpacity: number; - shape?: string; showLegend?: boolean; + markerSymbol: ResourceDimensionConfig; } +const DEFAULT_SIZE = 5; + const defaultOptions: MarkersConfig = { size: { - fixed: 5, + fixed: DEFAULT_SIZE, min: 2, max: 15, }, @@ -45,8 +52,11 @@ const defaultOptions: MarkersConfig = { fixed: 'dark-green', // picked from theme }, fillOpacity: 0.4, - shape: 'circle', showLegend: true, + markerSymbol: { + mode: ResourceDimensionMode.Fixed, + fixed: MarkerShapePath.Circle, + }, }; export const MARKERS_LAYER_ID = 'markers'; @@ -88,7 +98,6 @@ export const markersLayer: MapLayerRegistryItem = { if (config.showLegend) { legend = ; } - const shape = markerMakers.getIfExists(config.shape) ?? circleMarker; return { init: () => vectorLayer, @@ -98,6 +107,24 @@ export const markersLayer: MapLayerRegistryItem = { return; // ignore empty } + const markerPath = + getPublicOrAbsoluteUrl(config.markerSymbol?.fixed) ?? getPublicOrAbsoluteUrl(MarkerShapePath.Circle); + + const marker = getMarkerFromPath(config.markerSymbol?.fixed); + + const makeIconStyle = (color: string, fillColor: string, radius: number) => { + return new style.Style({ + image: new style.Icon({ + src: markerPath, + color, + // opacity, + scale: (DEFAULT_SIZE + radius) / 100, + }), + }); + }; + + const shape: StyleMaker = marker?.make ?? makeIconStyle; + const features: Feature[] = []; for (const frame of data.series) { @@ -126,8 +153,7 @@ export const markersLayer: MapLayerRegistryItem = { frame, rowIndex: i, }); - - dot.setStyle(shape!.make(color, fillColor, radius)); + dot.setStyle(shape(color, fillColor, radius)); features.push(dot); } @@ -150,17 +176,6 @@ export const markersLayer: MapLayerRegistryItem = { // Marker overlay options registerOptionsUI: (builder) => { builder - .addCustomEditor({ - id: 'config.color', - path: 'config.color', - name: 'Marker Color', - editor: ColorDimensionEditor, - settings: {}, - defaultValue: { - // Configured values - fixed: 'grey', - }, - }) .addCustomEditor({ id: 'config.size', path: 'config.size', @@ -172,18 +187,33 @@ export const markersLayer: MapLayerRegistryItem = { }, defaultValue: { // Configured values - fixed: 5, + fixed: DEFAULT_SIZE, min: 1, max: 20, }, }) - .addSelect({ - path: 'config.shape', - name: 'Marker Shape', + .addCustomEditor({ + id: 'config.markerSymbol', + path: 'config.markerSymbol', + name: 'Marker Symbol', + editor: ResourceDimensionEditor, + defaultValue: defaultOptions.markerSymbol, settings: { - options: markerMakers.selectOptions().options, + resourceType: 'icon', + showSourceRadio: false, + folderName: ResourceFolderName.Marker, + }, + }) + .addCustomEditor({ + id: 'config.color', + path: 'config.color', + name: 'Marker Color', + editor: ColorDimensionEditor, + settings: {}, + defaultValue: { + // Configured values + fixed: 'grey', }, - defaultValue: 'circle', }) .addSliderInput({ path: 'config.fillOpacity', @@ -194,7 +224,6 @@ export const markersLayer: MapLayerRegistryItem = { max: 1, step: 0.1, }, - showIf: (cfg) => markerMakers.getIfExists((cfg as any).config?.shape)?.hasFill, }) .addBooleanSwitch({ path: 'config.showLegend', diff --git a/public/app/plugins/panel/geomap/migrations.test.ts b/public/app/plugins/panel/geomap/migrations.test.ts index fbe64d78869..c4024e5acdc 100644 --- a/public/app/plugins/panel/geomap/migrations.test.ts +++ b/public/app/plugins/panel/geomap/migrations.test.ts @@ -1,6 +1,5 @@ import { PanelModel, FieldConfigSource } from '@grafana/data'; -import { mapPanelChangedHandler } from './migrations'; - +import { mapMigrationHandler, mapPanelChangedHandler } from './migrations'; describe('Worldmap Migrations', () => { let prevFieldConfig: FieldConfigSource; @@ -106,3 +105,169 @@ const simpleWorldmapConfig = { valueName: 'total', datasource: null, }; + +describe('geomap migrations', () => { + it('updates marker', () => { + const panel = { + id: 2, + gridPos: { + h: 9, + w: 12, + x: 0, + y: 0, + }, + type: 'geomap', + title: 'Panel Title', + fieldConfig: { + defaults: { + thresholds: { + mode: 'absolute', + steps: [ + { + color: 'green', + value: null, + }, + { + color: 'red', + value: 80, + }, + ], + }, + mappings: [], + color: { + mode: 'thresholds', + }, + }, + overrides: [], + }, + options: { + view: { + id: 'zero', + lat: 0, + lon: 0, + zoom: 1, + }, + basemap: { + type: 'default', + config: {}, + }, + layers: [ + { + config: { + color: { + fixed: 'dark-green', + }, + fillOpacity: 0.4, + markerSymbol: { + fixed: '', + mode: 'fixed', + }, + shape: 'circle', + showLegend: true, + size: { + fixed: 5, + max: 15, + min: 2, + }, + }, + location: { + mode: 'auto', + }, + type: 'markers', + }, + ], + controls: { + showZoom: true, + mouseWheelZoom: true, + showAttribution: true, + showScale: false, + showDebug: false, + }, + }, + pluginVersion: '8.3.0-pre', + datasource: null, + } as PanelModel; + panel.options = mapMigrationHandler(panel); + + expect(panel).toMatchInlineSnapshot(` + Object { + "datasource": null, + "fieldConfig": Object { + "defaults": Object { + "color": Object { + "mode": "thresholds", + }, + "mappings": Array [], + "thresholds": Object { + "mode": "absolute", + "steps": Array [ + Object { + "color": "green", + "value": null, + }, + Object { + "color": "red", + "value": 80, + }, + ], + }, + }, + "overrides": Array [], + }, + "gridPos": Object { + "h": 9, + "w": 12, + "x": 0, + "y": 0, + }, + "id": 2, + "options": Object { + "basemap": Object { + "config": Object {}, + "type": "default", + }, + "controls": Object { + "mouseWheelZoom": true, + "showAttribution": true, + "showDebug": false, + "showScale": false, + "showZoom": true, + }, + "layers": Array [ + Object { + "config": Object { + "color": Object { + "fixed": "dark-green", + }, + "fillOpacity": 0.4, + "markerSymbol": Object { + "fixed": "img/icons/marker/circle.svg", + "mode": "fixed", + }, + "showLegend": true, + "size": Object { + "fixed": 5, + "max": 15, + "min": 2, + }, + }, + "location": Object { + "mode": "auto", + }, + "type": "markers", + }, + ], + "view": Object { + "id": "zero", + "lat": 0, + "lon": 0, + "zoom": 1, + }, + }, + "pluginVersion": "8.3.0-pre", + "title": "Panel Title", + "type": "geomap", + } + `); + }); +}); diff --git a/public/app/plugins/panel/geomap/migrations.ts b/public/app/plugins/panel/geomap/migrations.ts index 26a2fbdd880..f832668ceff 100644 --- a/public/app/plugins/panel/geomap/migrations.ts +++ b/public/app/plugins/panel/geomap/migrations.ts @@ -1,5 +1,6 @@ -import { FieldConfigSource, PanelTypeChangedHandler, Threshold, ThresholdsMode } from '@grafana/data'; +import { FieldConfigSource, PanelModel, PanelTypeChangedHandler, Threshold, ThresholdsMode } from '@grafana/data'; import { GeomapPanelOptions } from './types'; +import { markerMakers } from './utils/regularShapes'; import { MapCenterID } from './view'; /** @@ -97,3 +98,27 @@ function asNumber(v: any): number | undefined { const num = +v; return isNaN(num) ? undefined : num; } + +export const mapMigrationHandler = (panel: PanelModel): Partial => { + const pluginVersion = panel?.pluginVersion; + if (pluginVersion?.startsWith('8.1') || pluginVersion?.startsWith('8.2') || pluginVersion?.startsWith('8.3')) { + if (panel.options?.layers?.length > 0) { + const layer = panel.options.layers[0]; + if (layer?.type === 'markers') { + const shape = layer?.config?.shape; + if (shape) { + const marker = markerMakers.getIfExists(shape); + if (marker?.aliasIds && marker.aliasIds?.length > 0) { + layer.config.markerSymbol = { + fixed: marker.aliasIds[0], + mode: 'fixed', + }; + delete layer.config.shape; + } + return { ...panel.options, layers: Object.assign([], ...panel.options.layers, { 0: layer }) }; + } + } + } + } + return panel.options; +}; diff --git a/public/app/plugins/panel/geomap/module.tsx b/public/app/plugins/panel/geomap/module.tsx index c7edc522803..73d7507086d 100644 --- a/public/app/plugins/panel/geomap/module.tsx +++ b/public/app/plugins/panel/geomap/module.tsx @@ -3,13 +3,14 @@ import { PanelPlugin } from '@grafana/data'; import { GeomapPanel } from './GeomapPanel'; import { MapViewEditor } from './editor/MapViewEditor'; import { defaultView, GeomapPanelOptions } from './types'; -import { mapPanelChangedHandler } from './migrations'; +import { mapPanelChangedHandler, mapMigrationHandler } from './migrations'; import { getLayerEditor } from './editor/layerEditor'; import { config } from '@grafana/runtime'; export const plugin = new PanelPlugin(GeomapPanel) .setNoPadding() .setPanelChangeHandler(mapPanelChangedHandler) + .setMigrationHandler(mapMigrationHandler) .useFieldConfig() .setPanelOptions((builder, context) => { let category = ['Map view']; diff --git a/public/app/plugins/panel/geomap/utils/regularShapes.ts b/public/app/plugins/panel/geomap/utils/regularShapes.ts index 74091f93e49..454405dbbee 100644 --- a/public/app/plugins/panel/geomap/utils/regularShapes.ts +++ b/public/app/plugins/panel/geomap/utils/regularShapes.ts @@ -1,15 +1,38 @@ import { Fill, RegularShape, Stroke, Style, Circle } from 'ol/style'; import { Registry, RegistryItem } from '@grafana/data'; -interface MarkerMaker extends RegistryItem { - make: (color: string, fillColor: string, radius: number) => Style; +export type StyleMaker = (color: string, fillColor: string, radius: number, markerPath?: string) => Style; + +export interface MarkerMaker extends RegistryItem { + // path to icon that will be shown (but then replaced) + aliasIds: string[]; + make: StyleMaker; hasFill: boolean; } +export enum RegularShapeId { + Circle = 'circle', + Square = 'square', + Triangle = 'triangle', + Star = 'star', + Cross = 'cross', + X = 'x', +} + +export enum MarkerShapePath { + Circle = 'img/icons/marker/circle.svg', + Square = 'img/icons/marker/square.svg', + Triangle = 'img/icons/marker/triangle.svg', + Star = 'img/icons/marker/star.svg', + Cross = 'img/icons/marker/cross.svg', + X = 'img/icons/marker/x-mark.svg', +} + export const circleMarker: MarkerMaker = { - id: 'circle', + id: RegularShapeId.Circle, name: 'Circle', hasFill: true, + aliasIds: [MarkerShapePath.Circle], make: (color: string, fillColor: string, radius: number) => { return new Style({ image: new Circle({ @@ -21,12 +44,13 @@ export const circleMarker: MarkerMaker = { }, }; -export const markerMakers = new Registry(() => [ +const makers: MarkerMaker[] = [ circleMarker, { - id: 'square', + id: RegularShapeId.Square, name: 'Square', hasFill: true, + aliasIds: [MarkerShapePath.Square], make: (color: string, fillColor: string, radius: number) => { return new Style({ image: new RegularShape({ @@ -40,9 +64,10 @@ export const markerMakers = new Registry(() => [ }, }, { - id: 'triangle', + id: RegularShapeId.Triangle, name: 'Triangle', hasFill: true, + aliasIds: [MarkerShapePath.Triangle], make: (color: string, fillColor: string, radius: number) => { return new Style({ image: new RegularShape({ @@ -57,9 +82,10 @@ export const markerMakers = new Registry(() => [ }, }, { - id: 'star', + id: RegularShapeId.Star, name: 'Star', hasFill: true, + aliasIds: [MarkerShapePath.Star], make: (color: string, fillColor: string, radius: number) => { return new Style({ image: new RegularShape({ @@ -74,9 +100,10 @@ export const markerMakers = new Registry(() => [ }, }, { - id: 'cross', + id: RegularShapeId.Cross, name: 'Cross', hasFill: false, + aliasIds: [MarkerShapePath.Cross], make: (color: string, fillColor: string, radius: number) => { return new Style({ image: new RegularShape({ @@ -91,9 +118,10 @@ export const markerMakers = new Registry(() => [ }, }, { - id: 'x', + id: RegularShapeId.X, name: 'X', hasFill: false, + aliasIds: [MarkerShapePath.X], make: (color: string, fillColor: string, radius: number) => { return new Style({ image: new RegularShape({ @@ -107,4 +135,15 @@ export const markerMakers = new Registry(() => [ }); }, }, -]); +]; + +export const markerMakers = new Registry(() => makers); + +export const getMarkerFromPath = (svgPath: string): MarkerMaker | undefined => { + for (const [key, val] of Object.entries(MarkerShapePath)) { + if (val === svgPath) { + return markerMakers.getIfExists(key); + } + } + return undefined; +}; diff --git a/public/img/icons/marker/circle.svg b/public/img/icons/marker/circle.svg new file mode 100644 index 00000000000..afb4749ff10 --- /dev/null +++ b/public/img/icons/marker/circle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/icons/marker/cross.svg b/public/img/icons/marker/cross.svg new file mode 100644 index 00000000000..399f74920d5 --- /dev/null +++ b/public/img/icons/marker/cross.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/icons/marker/plane.svg b/public/img/icons/marker/plane.svg new file mode 100644 index 00000000000..a49c2c502fb --- /dev/null +++ b/public/img/icons/marker/plane.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/icons/marker/square.svg b/public/img/icons/marker/square.svg new file mode 100644 index 00000000000..856cd02fca3 --- /dev/null +++ b/public/img/icons/marker/square.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/icons/marker/star.svg b/public/img/icons/marker/star.svg new file mode 100644 index 00000000000..7fc7a836a7e --- /dev/null +++ b/public/img/icons/marker/star.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/icons/marker/triangle.svg b/public/img/icons/marker/triangle.svg new file mode 100644 index 00000000000..9258e6e11a1 --- /dev/null +++ b/public/img/icons/marker/triangle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/icons/marker/x-mark.svg b/public/img/icons/marker/x-mark.svg new file mode 100644 index 00000000000..f1432176da4 --- /dev/null +++ b/public/img/icons/marker/x-mark.svg @@ -0,0 +1 @@ + \ No newline at end of file