diff --git a/src/plugins/region_map/public/get_deprecation_message.tsx b/src/plugins/region_map/public/get_deprecation_message.tsx index 5ae39a1291c4c..2606c8ed108e2 100644 --- a/src/plugins/region_map/public/get_deprecation_message.tsx +++ b/src/plugins/region_map/public/get_deprecation_message.tsx @@ -8,8 +8,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { UrlGeneratorContract } from 'src/plugins/share/public'; -import { getCoreService, getQueryService, getShareService } from './kibana_services'; +import { getQueryService, getShareService } from './kibana_services'; import { Vis } from '../../visualizations/public'; import { LegacyMapDeprecationMessage } from '../../maps_legacy/public'; @@ -25,24 +24,16 @@ function getEmsLayerId(id: string | number, layerId: string) { } export function getDeprecationMessage(vis: Vis) { - let mapsRegionMapUrlGenerator: - | UrlGeneratorContract<'MAPS_APP_REGION_MAP_URL_GENERATOR'> - | undefined; - try { - mapsRegionMapUrlGenerator = getShareService().urlGenerators.getUrlGenerator( - 'MAPS_APP_REGION_MAP_URL_GENERATOR' - ); - } catch (error) { - // ignore error thrown when url generator is not available - } - const title = i18n.translate('regionMap.mapVis.regionMapTitle', { defaultMessage: 'Region Map' }); async function onClick(e: React.MouseEvent) { e.preventDefault(); + const locator = getShareService().url.locators.get('MAPS_APP_REGION_MAP_LOCATOR'); + if (!locator) return; + const query = getQueryService(); - const createUrlParams: { [key: string]: any } = { + const params: { [key: string]: any } = { label: vis.title ? vis.title : title, emsLayerId: vis.params.selectedLayer.isEMS ? getEmsLayerId(vis.params.selectedLayer.id, vis.params.selectedLayer.layerId) @@ -59,23 +50,22 @@ export function getDeprecationMessage(vis: Vis) { const bucketAggs = vis.data?.aggs?.byType('buckets'); if (bucketAggs?.length && bucketAggs[0].type.dslName === 'terms') { - createUrlParams.termsFieldName = bucketAggs[0].getField()?.name; - createUrlParams.termsSize = bucketAggs[0].getParam('size'); + params.termsFieldName = bucketAggs[0].getField()?.name; + params.termsSize = bucketAggs[0].getParam('size'); } const metricAggs = vis.data?.aggs?.byType('metrics'); if (metricAggs?.length) { - createUrlParams.metricAgg = metricAggs[0].type.dslName; - createUrlParams.metricFieldName = metricAggs[0].getField()?.name; + params.metricAgg = metricAggs[0].type.dslName; + params.metricFieldName = metricAggs[0].getField()?.name; } - const url = await mapsRegionMapUrlGenerator!.createUrl(createUrlParams); - getCoreService().application.navigateToUrl(url); + locator.navigate(params); } return ( diff --git a/src/plugins/tile_map/public/get_deprecation_message.tsx b/src/plugins/tile_map/public/get_deprecation_message.tsx index 592b2b5f36eb2..6f71aa15b8a6b 100644 --- a/src/plugins/tile_map/public/get_deprecation_message.tsx +++ b/src/plugins/tile_map/public/get_deprecation_message.tsx @@ -8,22 +8,12 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; -import { UrlGeneratorContract } from 'src/plugins/share/public'; -import { getCoreService, getQueryService, getShareService } from './services'; +import { getQueryService, getShareService } from './services'; import { indexPatterns } from '../../data/public'; import { Vis } from '../../visualizations/public'; import { LegacyMapDeprecationMessage } from '../../maps_legacy/public'; export function getDeprecationMessage(vis: Vis) { - let mapsTileMapUrlGenerator: UrlGeneratorContract<'MAPS_APP_TILE_MAP_URL_GENERATOR'> | undefined; - try { - mapsTileMapUrlGenerator = getShareService().urlGenerators.getUrlGenerator( - 'MAPS_APP_TILE_MAP_URL_GENERATOR' - ); - } catch (error) { - // ignore error thrown when url generator is not available - } - const title = i18n.translate('tileMap.vis.mapTitle', { defaultMessage: 'Coordinate Map', }); @@ -31,8 +21,11 @@ export function getDeprecationMessage(vis: Vis) { async function onClick(e: React.MouseEvent) { e.preventDefault(); + const locator = getShareService().url.locators.get('MAPS_APP_TILE_MAP_LOCATOR'); + if (!locator) return; + const query = getQueryService(); - const createUrlParams: { [key: string]: any } = { + const params: { [key: string]: any } = { label: vis.title ? vis.title : title, mapType: vis.params.mapType, colorSchema: vis.params.colorSchema, @@ -45,7 +38,7 @@ export function getDeprecationMessage(vis: Vis) { const bucketAggs = vis.data?.aggs?.byType('buckets'); if (bucketAggs?.length && bucketAggs[0].type.dslName === 'geohash_grid') { - createUrlParams.geoFieldName = bucketAggs[0].getField()?.name; + params.geoFieldName = bucketAggs[0].getField()?.name; } else if (vis.data.indexPattern) { // attempt to default to first geo point field when geohash is not configured yet const geoField = vis.data.indexPattern.fields.find((field) => { @@ -54,23 +47,22 @@ export function getDeprecationMessage(vis: Vis) { ); }); if (geoField) { - createUrlParams.geoFieldName = geoField.name; + params.geoFieldName = geoField.name; } } const metricAggs = vis.data?.aggs?.byType('metrics'); if (metricAggs?.length) { - createUrlParams.metricAgg = metricAggs[0].type.dslName; - createUrlParams.metricFieldName = metricAggs[0].getField()?.name; + params.metricAgg = metricAggs[0].type.dslName; + params.metricFieldName = metricAggs[0].getField()?.name; } - const url = await mapsTileMapUrlGenerator!.createUrl(createUrlParams); - getCoreService().application.navigateToUrl(url); + locator.navigate(params); } return ( diff --git a/x-pack/plugins/maps/public/url_generator.test.ts b/x-pack/plugins/maps/public/locators.test.ts similarity index 52% rename from x-pack/plugins/maps/public/url_generator.test.ts rename to x-pack/plugins/maps/public/locators.test.ts index 827deb4cc9ad4..d6e82d1cdb601 100644 --- a/x-pack/plugins/maps/public/url_generator.test.ts +++ b/x-pack/plugins/maps/public/locators.test.ts @@ -5,49 +5,49 @@ * 2.0. */ -import { createMapsUrlGenerator } from './url_generator'; import { LAYER_TYPE, SOURCE_TYPES, SCALING_TYPES } from '../common/constants'; import { esFilters } from '../../../../src/plugins/data/public'; +import { MapsAppLocatorDefinition } from './locators'; +import { SerializableState } from '../../../../src/plugins/kibana_utils/common'; +import { LayerDescriptor } from '../common/descriptor_types'; -const APP_BASE_PATH: string = 'test/app/maps'; const MAP_ID: string = '2c9c1f60-1909-11e9-919b-ffe5949a18d2'; const LAYER_ID: string = '13823000-99b9-11ea-9eb6-d9e8adceb647'; const INDEX_PATTERN_ID: string = '90943e30-9a47-11e8-b64d-95841ca0b247'; describe('visualize url generator', () => { test('creates a link to a new visualization', async () => { - const generator = createMapsUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - }) - ); - const url = await generator.createUrl!({}); - expect(url).toMatchInlineSnapshot(`"test/app/maps/map#/?_g=()&_a=()"`); + const locator = new MapsAppLocatorDefinition({ + useHash: false, + }); + const location = await locator.getLocation({}); + + expect(location).toMatchObject({ + app: 'maps', + path: '/map#/?_g=()&_a=()', + state: {}, + }); }); test('creates a link with global time range set up', async () => { - const generator = createMapsUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - }) - ); - const url = await generator.createUrl!({ + const locator = new MapsAppLocatorDefinition({ + useHash: false, + }); + const location = await locator.getLocation({ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, }); - expect(url).toMatchInlineSnapshot( - `"test/app/maps/map#/?_g=(time:(from:now-15m,mode:relative,to:now))&_a=()"` - ); + + expect(location).toMatchObject({ + app: 'maps', + path: '/map#/?_g=(time:(from:now-15m,mode:relative,to:now))&_a=()', + state: {}, + }); }); test('creates a link with initialLayers set up', async () => { - const generator = createMapsUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - }) - ); + const locator = new MapsAppLocatorDefinition({ + useHash: false, + }); const initialLayers = [ { id: LAYER_ID, @@ -64,22 +64,22 @@ describe('visualize url generator', () => { }, }, ]; - const url = await generator.createUrl!({ - initialLayers, + const location = await locator.getLocation({ + initialLayers: (initialLayers as unknown) as LayerDescriptor[] & SerializableState, + }); + + expect(location).toMatchObject({ + app: 'maps', + path: `/map#/?_g=()&_a=()&initialLayers=(id%3A'13823000-99b9-11ea-9eb6-d9e8adceb647'%2CsourceDescriptor%3A(geoField%3Atest%2Cid%3A'13823000-99b9-11ea-9eb6-d9e8adceb647'%2CindexPatternId%3A'90943e30-9a47-11e8-b64d-95841ca0b247'%2Clabel%3A'Sample%20Data'%2CscalingType%3ALIMIT%2CtooltipProperties%3A!()%2Ctype%3AES_SEARCH)%2Ctype%3AVECTOR%2Cvisible%3A!t)`, + state: {}, }); - expect(url).toMatchInlineSnapshot( - `"test/app/maps/map#/?_g=()&_a=()&initialLayers=(id%3A'13823000-99b9-11ea-9eb6-d9e8adceb647'%2CsourceDescriptor%3A(geoField%3Atest%2Cid%3A'13823000-99b9-11ea-9eb6-d9e8adceb647'%2CindexPatternId%3A'90943e30-9a47-11e8-b64d-95841ca0b247'%2Clabel%3A'Sample%20Data'%2CscalingType%3ALIMIT%2CtooltipProperties%3A!()%2Ctype%3AES_SEARCH)%2Ctype%3AVECTOR%2Cvisible%3A!t)"` - ); }); test('creates a link with filters, time range, refresh interval and query to a saved visualization', async () => { - const generator = createMapsUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - }) - ); - const url = await generator.createUrl!({ + const locator = new MapsAppLocatorDefinition({ + useHash: false, + }); + const location = await locator.getLocation({ timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, refreshInterval: { pause: false, value: 300 }, mapId: MAP_ID, @@ -106,8 +106,11 @@ describe('visualize url generator', () => { ], query: { query: 'q2', language: 'kuery' }, }); - expect(url).toMatchInlineSnapshot( - `"test/app/maps/map#/${MAP_ID}?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),query:(language:kuery,query:q2))"` - ); + + expect(location).toMatchObject({ + app: 'maps', + path: `/map#/${MAP_ID}?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),query:(language:kuery,query:q2))`, + state: {}, + }); }); }); diff --git a/x-pack/plugins/maps/public/locators.ts b/x-pack/plugins/maps/public/locators.ts new file mode 100644 index 0000000000000..7e2be7c6c7ec9 --- /dev/null +++ b/x-pack/plugins/maps/public/locators.ts @@ -0,0 +1,260 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/* eslint-disable max-classes-per-file */ + +import rison from 'rison-node'; +import type { + TimeRange, + Filter, + Query, + QueryState, + RefreshInterval, +} from '../../../../src/plugins/data/public'; +import { esFilters } from '../../../../src/plugins/data/public'; +import { setStateToKbnUrl } from '../../../../src/plugins/kibana_utils/public'; +import { SerializableState } from '../../../../src/plugins/kibana_utils/common'; +import type { LocatorDefinition, LocatorPublic } from '../../../../src/plugins/share/public'; +import type { LayerDescriptor } from '../common/descriptor_types'; +import { INITIAL_LAYERS_KEY, APP_ID } from '../common/constants'; +import { lazyLoadMapModules } from './lazy_load_bundle'; + +export interface MapsAppLocatorParams extends SerializableState { + /** + * If given, it will load the given map else will load the create a new map page. + */ + mapId?: string; + + /** + * Optionally set the time range in the time picker. + */ + timeRange?: TimeRange; + + /** + * Optionally set the initial Layers. + */ + initialLayers?: LayerDescriptor[] & SerializableState; + + /** + * Optionally set the refresh interval. + */ + refreshInterval?: RefreshInterval & SerializableState; + + /** + * Optionally apply filers. NOTE: if given and used in conjunction with `mapId`, and the + * saved map has filters saved with it, this will _replace_ those filters. + */ + filters?: Filter[]; + + /** + * Optionally set a query. NOTE: if given and used in conjunction with `mapId`, and the + * saved map has a query saved with it, this will _replace_ that query. + */ + query?: Query; + + /** + * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines + * whether to hash the data in the url to avoid url length issues. + */ + hash?: boolean; +} + +export const MAPS_APP_LOCATOR = 'MAPS_APP_LOCATOR' as const; + +export type MapsAppLocator = LocatorPublic; + +export interface MapsAppLocatorDependencies { + useHash: boolean; +} + +export class MapsAppLocatorDefinition implements LocatorDefinition { + public readonly id = MAPS_APP_LOCATOR; + + constructor(protected readonly deps: MapsAppLocatorDependencies) {} + + public readonly getLocation = async (params: MapsAppLocatorParams) => { + const { mapId, filters, query, refreshInterval, timeRange, initialLayers, hash } = params; + const useHash = hash ?? this.deps.useHash; + const appState: { + query?: Query; + filters?: Filter[]; + vis?: unknown; + } = {}; + const queryState: QueryState = {}; + + if (query) appState.query = query; + if (filters && filters.length) + appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); + if (timeRange) queryState.time = timeRange; + if (filters && filters.length) + queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); + if (refreshInterval) queryState.refreshInterval = refreshInterval; + + let path = `/map#/${mapId || ''}`; + path = setStateToKbnUrl('_g', queryState, { useHash }, path); + path = setStateToKbnUrl('_a', appState, { useHash }, path); + + if (initialLayers && initialLayers.length) { + const risonEncodedInitialLayers = ((rison as unknown) as { + encode_array: ( + initialLayers: (LayerDescriptor[] & SerializableState) | undefined + ) => string; + }).encode_array(initialLayers); + path = `${path}&${INITIAL_LAYERS_KEY}=${encodeURIComponent(risonEncodedInitialLayers)}`; + } + + return { + app: APP_ID, + path, + state: {}, + }; + }; +} + +export interface MapsAppTileMapLocatorParams extends SerializableState { + label: string; + mapType: string; + colorSchema: string; + indexPatternId?: string; + geoFieldName?: string; + metricAgg: string; + metricFieldName?: string; + timeRange?: TimeRange; + filters?: Filter[]; + query?: Query; + hash?: boolean; +} + +export type MapsAppTileMapLocator = LocatorPublic; + +export const MAPS_APP_TILE_MAP_LOCATOR = 'MAPS_APP_TILE_MAP_LOCATOR' as const; + +export interface MapsAppTileMapLocatorDependencies { + locator: MapsAppLocator; +} + +export class MapsAppTileMapLocatorDefinition + implements LocatorDefinition { + public readonly id = MAPS_APP_TILE_MAP_LOCATOR; + + constructor(protected readonly deps: MapsAppTileMapLocatorDependencies) {} + + public readonly getLocation = async (params: MapsAppTileMapLocatorParams) => { + const { + label, + mapType, + colorSchema, + indexPatternId, + geoFieldName, + metricAgg, + metricFieldName, + filters, + query, + timeRange, + hash = true, + } = params; + const mapModules = await lazyLoadMapModules(); + const initialLayers = ([] as unknown) as LayerDescriptor[] & SerializableState; + const tileMapLayerDescriptor = mapModules.createTileMapLayerDescriptor({ + label, + mapType, + colorSchema, + indexPatternId, + geoFieldName, + metricAgg, + metricFieldName, + }); + + if (tileMapLayerDescriptor) { + initialLayers.push(tileMapLayerDescriptor); + } + + return await this.deps.locator.getLocation({ + initialLayers, + filters, + query, + timeRange, + hash, + }); + }; +} + +export interface MapsAppRegionMapLocatorParams extends SerializableState { + label: string; + emsLayerId?: string; + leftFieldName?: string; + termsFieldName?: string; + termsSize?: number; + colorSchema: string; + indexPatternId?: string; + indexPatternTitle?: string; + metricAgg: string; + metricFieldName?: string; + timeRange?: TimeRange; + filters?: Filter[]; + query?: Query; + hash?: boolean; +} + +export type MapsAppRegionMapLocator = LocatorPublic; + +export const MAPS_APP_REGION_MAP_LOCATOR = 'MAPS_APP_REGION_MAP_LOCATOR' as const; + +export interface MapsAppRegionMapLocatorDependencies { + locator: MapsAppLocator; +} + +export class MapsAppRegionMapLocatorDefinition + implements LocatorDefinition { + public readonly id = MAPS_APP_REGION_MAP_LOCATOR; + + constructor(protected readonly deps: MapsAppRegionMapLocatorDependencies) {} + + public readonly getLocation = async (params: MapsAppRegionMapLocatorParams) => { + const { + label, + emsLayerId, + leftFieldName, + termsFieldName, + termsSize, + colorSchema, + indexPatternId, + indexPatternTitle, + metricAgg, + metricFieldName, + filters, + query, + timeRange, + hash = true, + } = params; + const mapModules = await lazyLoadMapModules(); + const initialLayers = ([] as unknown) as LayerDescriptor[] & SerializableState; + const regionMapLayerDescriptor = mapModules.createRegionMapLayerDescriptor({ + label, + emsLayerId, + leftFieldName, + termsFieldName, + termsSize, + colorSchema, + indexPatternId, + indexPatternTitle, + metricAgg, + metricFieldName, + }); + if (regionMapLayerDescriptor) { + initialLayers.push(regionMapLayerDescriptor); + } + + return await this.deps.locator.getLocation({ + initialLayers, + filters, + query, + timeRange, + hash, + }); + }; +} diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 740112124a251..b526e7b24d90f 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -36,11 +36,6 @@ import type { } from '../../../../src/plugins/visualizations/public'; import { APP_ICON_SOLUTION, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '../../../../src/plugins/ui_actions/public'; -import { - createMapsUrlGenerator, - createRegionMapUrlGenerator, - createTileMapUrlGenerator, -} from './url_generator'; import { visualizeGeoFieldAction } from './trigger_actions/visualize_geo_field_action'; import { filterByMapExtentAction } from './trigger_actions/filter_by_map_extent_action'; import { MapEmbeddableFactory } from './embeddable/map_embeddable_factory'; @@ -71,6 +66,11 @@ import { import { EMSSettings } from '../common/ems_settings'; import type { SavedObjectTaggingPluginStart } from '../../saved_objects_tagging/public'; import type { ChartsPluginStart } from '../../../../src/plugins/charts/public'; +import { + MapsAppLocatorDefinition, + MapsAppRegionMapLocatorDefinition, + MapsAppTileMapLocatorDefinition, +} from './locators'; export interface MapsPluginSetupDependencies { inspector: InspectorSetupContract; @@ -133,17 +133,21 @@ export class MapsPlugin const emsSettings = new EMSSettings(plugins.mapsEms.config, getIsEnterprisePlus); setEMSSettings(emsSettings); - // register url generators - const getStartServices = async () => { - const [coreStart] = await core.getStartServices(); - return { - appBasePath: coreStart.application.getUrlForApp('maps'), - useHashedUrl: coreStart.uiSettings.get('state:storeInSessionStorage'), - }; - }; - plugins.share.urlGenerators.registerUrlGenerator(createMapsUrlGenerator(getStartServices)); - plugins.share.urlGenerators.registerUrlGenerator(createTileMapUrlGenerator(getStartServices)); - plugins.share.urlGenerators.registerUrlGenerator(createRegionMapUrlGenerator(getStartServices)); + const locator = plugins.share.url.locators.create( + new MapsAppLocatorDefinition({ + useHash: core.uiSettings.get('state:storeInSessionStorage'), + }) + ); + plugins.share.url.locators.create( + new MapsAppTileMapLocatorDefinition({ + locator, + }) + ); + plugins.share.url.locators.create( + new MapsAppRegionMapLocatorDefinition({ + locator, + }) + ); plugins.inspector.registerView(MapView); if (plugins.home) { diff --git a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts index acdc5164cb17d..c6daee554c9fc 100644 --- a/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts +++ b/x-pack/plugins/maps/public/trigger_actions/visualize_geo_field_action.ts @@ -7,6 +7,7 @@ import uuid from 'uuid/v4'; import { i18n } from '@kbn/i18n'; +import { SerializableState } from 'src/plugins/kibana_utils/common'; import { createAction, ACTION_VISUALIZE_GEO_FIELD, @@ -17,10 +18,11 @@ import { getIndexPatternService, getData, getShareService, - getNavigateToApp, + getCore, } from '../kibana_services'; -import { MAPS_APP_URL_GENERATOR, MapsUrlGeneratorState } from '../url_generator'; -import { LAYER_TYPE, SOURCE_TYPES, SCALING_TYPES, APP_ID, MAP_PATH } from '../../common/constants'; +import { MapsAppLocator, MAPS_APP_LOCATOR } from '../locators'; +import { LAYER_TYPE, SOURCE_TYPES, SCALING_TYPES } from '../../common/constants'; +import { LayerDescriptor } from '../../common/descriptor_types'; export const visualizeGeoFieldAction = createAction({ id: ACTION_VISUALIZE_GEO_FIELD, @@ -31,15 +33,19 @@ export const visualizeGeoFieldAction = createAction({ }), isCompatible: async () => !!getVisualizeCapabilities().show, getHref: async (context) => { - const url = await getMapsLink(context); - return url; + const { app, path } = await getMapsLink(context); + + return getCore().application.getUrlForApp(app, { + path, + absolute: false, + }); }, execute: async (context) => { - const url = await getMapsLink(context); - const hash = url.split('#')[1]; + const { app, path, state } = await getMapsLink(context); - getNavigateToApp()(APP_ID, { - path: `${MAP_PATH}/#${hash}`, + getCore().application.navigateToApp(app, { + path, + state, }); }, }); @@ -68,12 +74,13 @@ const getMapsLink = async (context: VisualizeFieldContext) => { }, ]; - const generator = getShareService().urlGenerators.getUrlGenerator(MAPS_APP_URL_GENERATOR); - const urlState: MapsUrlGeneratorState = { + const locator = getShareService().url.locators.get(MAPS_APP_LOCATOR) as MapsAppLocator; + const location = await locator.getLocation({ filters: getData().query.filterManager.getFilters(), query: getData().query.queryString.getQuery(), - initialLayers, + initialLayers: (initialLayers as unknown) as LayerDescriptor[] & SerializableState, timeRange: getData().query.timefilter.timefilter.getTime(), - }; - return generator.createUrl(urlState); + }); + + return location; }; diff --git a/x-pack/plugins/maps/public/url_generator.ts b/x-pack/plugins/maps/public/url_generator.ts deleted file mode 100644 index 9f28b388c4756..0000000000000 --- a/x-pack/plugins/maps/public/url_generator.ts +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import rison from 'rison-node'; -import type { - TimeRange, - Filter, - Query, - QueryState, - RefreshInterval, -} from '../../../../src/plugins/data/public'; -import { esFilters } from '../../../../src/plugins/data/public'; -import { setStateToKbnUrl } from '../../../../src/plugins/kibana_utils/public'; -import type { UrlGeneratorsDefinition } from '../../../../src/plugins/share/public'; -import type { LayerDescriptor } from '../common/descriptor_types'; -import { INITIAL_LAYERS_KEY } from '../common/constants'; -import { lazyLoadMapModules } from './lazy_load_bundle'; - -const STATE_STORAGE_KEY = '_a'; -const GLOBAL_STATE_STORAGE_KEY = '_g'; - -export const MAPS_APP_URL_GENERATOR = 'MAPS_APP_URL_GENERATOR'; -export const MAPS_APP_TILE_MAP_URL_GENERATOR = 'MAPS_APP_TILE_MAP_URL_GENERATOR'; -export const MAPS_APP_REGION_MAP_URL_GENERATOR = 'MAPS_APP_REGION_MAP_URL_GENERATOR'; - -export interface MapsUrlGeneratorState { - /** - * If given, it will load the given map else will load the create a new map page. - */ - mapId?: string; - /** - * Optionally set the time range in the time picker. - */ - timeRange?: TimeRange; - - /** - * Optionally set the initial Layers. - */ - initialLayers?: LayerDescriptor[]; - - /** - * Optionally set the refresh interval. - */ - refreshInterval?: RefreshInterval; - - /** - * Optionally apply filers. NOTE: if given and used in conjunction with `mapId`, and the - * saved map has filters saved with it, this will _replace_ those filters. - */ - filters?: Filter[]; - /** - * Optionally set a query. NOTE: if given and used in conjunction with `mapId`, and the - * saved map has a query saved with it, this will _replace_ that query. - */ - query?: Query; - /** - * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines - * whether to hash the data in the url to avoid url length issues. - */ - hash?: boolean; -} - -type GetStartServices = () => Promise<{ - appBasePath: string; - useHashedUrl: boolean; -}>; - -async function createMapUrl({ - getStartServices, - mapId, - filters, - query, - refreshInterval, - timeRange, - initialLayers, - hash, -}: MapsUrlGeneratorState & { getStartServices: GetStartServices }): Promise { - const startServices = await getStartServices(); - const useHash = hash ?? startServices.useHashedUrl; - const appBasePath = startServices.appBasePath; - - const appState: { - query?: Query; - filters?: Filter[]; - vis?: unknown; - } = {}; - const queryState: QueryState = {}; - - if (query) appState.query = query; - if (filters && filters.length) - appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); - - if (timeRange) queryState.time = timeRange; - if (filters && filters.length) - queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); - if (refreshInterval) queryState.refreshInterval = refreshInterval; - - let url = `${appBasePath}/map#/${mapId || ''}`; - url = setStateToKbnUrl(GLOBAL_STATE_STORAGE_KEY, queryState, { useHash }, url); - url = setStateToKbnUrl(STATE_STORAGE_KEY, appState, { useHash }, url); - - if (initialLayers && initialLayers.length) { - // @ts-ignore - const risonEncodedInitialLayers = rison.encode_array(initialLayers); - url = `${url}&${INITIAL_LAYERS_KEY}=${encodeURIComponent(risonEncodedInitialLayers)}`; - } - - return url; -} - -export const createMapsUrlGenerator = ( - getStartServices: GetStartServices -): UrlGeneratorsDefinition => ({ - id: MAPS_APP_URL_GENERATOR, - createUrl: async (mapsUrlGeneratorState: MapsUrlGeneratorState): Promise => { - return createMapUrl({ ...mapsUrlGeneratorState, getStartServices }); - }, -}); - -export const createTileMapUrlGenerator = ( - getStartServices: GetStartServices -): UrlGeneratorsDefinition => ({ - id: MAPS_APP_TILE_MAP_URL_GENERATOR, - createUrl: async ({ - label, - mapType, - colorSchema, - indexPatternId, - geoFieldName, - metricAgg, - metricFieldName, - filters, - query, - timeRange, - hash, - }: { - label: string; - mapType: string; - colorSchema: string; - indexPatternId?: string; - geoFieldName?: string; - metricAgg: string; - metricFieldName?: string; - timeRange?: TimeRange; - filters?: Filter[]; - query?: Query; - hash?: boolean; - }): Promise => { - const mapModules = await lazyLoadMapModules(); - const initialLayers = []; - const tileMapLayerDescriptor = mapModules.createTileMapLayerDescriptor({ - label, - mapType, - colorSchema, - indexPatternId, - geoFieldName, - metricAgg, - metricFieldName, - }); - if (tileMapLayerDescriptor) { - initialLayers.push(tileMapLayerDescriptor); - } - - return createMapUrl({ - initialLayers, - filters, - query, - timeRange, - hash: true, - getStartServices, - }); - }, -}); - -export const createRegionMapUrlGenerator = ( - getStartServices: GetStartServices -): UrlGeneratorsDefinition => ({ - id: MAPS_APP_REGION_MAP_URL_GENERATOR, - createUrl: async ({ - label, - emsLayerId, - leftFieldName, - termsFieldName, - termsSize, - colorSchema, - indexPatternId, - indexPatternTitle, - metricAgg, - metricFieldName, - filters, - query, - timeRange, - hash, - }: { - label: string; - emsLayerId?: string; - leftFieldName?: string; - termsFieldName?: string; - termsSize?: number; - colorSchema: string; - indexPatternId?: string; - indexPatternTitle?: string; - metricAgg: string; - metricFieldName?: string; - timeRange?: TimeRange; - filters?: Filter[]; - query?: Query; - hash?: boolean; - }): Promise => { - const mapModules = await lazyLoadMapModules(); - const initialLayers = []; - const regionMapLayerDescriptor = mapModules.createRegionMapLayerDescriptor({ - label, - emsLayerId, - leftFieldName, - termsFieldName, - termsSize, - colorSchema, - indexPatternId, - indexPatternTitle, - metricAgg, - metricFieldName, - }); - if (regionMapLayerDescriptor) { - initialLayers.push(regionMapLayerDescriptor); - } - - return createMapUrl({ - initialLayers, - filters, - query, - timeRange, - hash: true, - getStartServices, - }); - }, -});