From 19e33401fb40f59183fa4c7c7c34ac06bc353381 Mon Sep 17 00:00:00 2001 From: Kawika Avilla Date: Thu, 29 Apr 2021 01:30:46 +0000 Subject: [PATCH] [Build] Update default maps client Update to not use EMS so it works out of the box. Related to: https://github.com/opensearch-project/OpenSearch-Dashboards/issues/221 Fails linter and should problem write more unit tests but working with smoke tests. Signed-off-by: Kawika Avilla --- .../bin/opensearch-dashboards-docker | 4 +- src/legacy/server/config/schema.js | 70 ++++++++++++++++++- src/plugins/maps_legacy/config.ts | 3 + .../public/common/opensearch_maps_client.js | 35 ++++++++++ .../maps_legacy/public/map/map_messages.js | 59 +++++++++++++++- .../public/map/opensearch_dashboards_map.js | 12 +++- .../public/map/service_settings.js | 40 ++++++++++- src/plugins/maps_legacy/server/index.ts | 1 + .../region_map/public/choropleth_layer.js | 4 ++ .../public/components/region_map_options.tsx | 23 ------ 10 files changed, 220 insertions(+), 31 deletions(-) create mode 100644 src/plugins/maps_legacy/public/common/opensearch_maps_client.js diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/opensearch-dashboards-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/opensearch-dashboards-docker index a9b7c527e40b..91a3de5014af 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/opensearch-dashboards-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/opensearch-dashboards-docker @@ -64,8 +64,8 @@ opensearch_dashboards_vars=( logging.silent logging.useUTC logging.verbose - map.includeElasticMapsService - map.proxyElasticMapsServiceInMaps + map.includeOpenSearchMapsService + map.proxyOpenSearchMapsServiceInMaps map.regionmap map.tilemap.options.attribution map.tilemap.options.maxZoom diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index be1b856ae4da..67776919bdc6 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -152,7 +152,75 @@ export default () => path: HANDLED_IN_NEW_PLATFORM, stats: HANDLED_IN_NEW_PLATFORM, status: HANDLED_IN_NEW_PLATFORM, - map: HANDLED_IN_NEW_PLATFORM, + + map: Joi.object({ + includeOpenSearchMapsService: Joi.boolean().default(true), + proxyOpenSearchMapsServiceInMaps: Joi.boolean().default(false), + tilemap: Joi.object({ + url: Joi.string(), + options: Joi.object({ + attribution: Joi.string(), + minZoom: Joi.number().min(0, 'Must be 0 or higher').default(0), + maxZoom: Joi.number().default(10), + tileSize: Joi.number(), + subdomains: Joi.array().items(Joi.string()).single(), + errorTileUrl: Joi.string().uri(), + tms: Joi.boolean(), + reuseTiles: Joi.boolean(), + bounds: Joi.array().items(Joi.array().items(Joi.number()).min(2).required()).min(2), + default: Joi.boolean(), + }).default({ + default: true, + }), + }).default(), + regionmap: Joi.object({ + includeOpenSearchMapsService: Joi.boolean().default(true), + layers: Joi.array() + .items( + Joi.object({ + url: Joi.string(), + format: Joi.object({ + type: Joi.string().default('geojson'), + }).default({ + type: 'geojson', + }), + meta: Joi.object({ + feature_collection_path: Joi.string().default('data'), + }).default({ + feature_collection_path: 'data', + }), + attribution: Joi.string(), + name: Joi.string(), + fields: Joi.array().items( + Joi.object({ + name: Joi.string(), + description: Joi.string(), + }) + ), + }) + ) + .default([]), + }).default(), + manifestServiceUrl: Joi.string().default('').allow(''), + opensearchManifestServiceUrl: Joi.string().default( + 'https://maps.search-services.aws.a2z.com/v4/ap-southeast-1/manifest' + ), + emsFileApiUrl: Joi.string().default('https://vector.maps.elastic.co'), + emsTileApiUrl: Joi.string().default('https://tiles.maps.elastic.co'), + emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v7.9'), + emsFontLibraryUrl: Joi.string().default( + 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf' + ), + emsTileLayerId: Joi.object({ + bright: Joi.string().default('road_map'), + desaturated: Joi.string().default('road_map_desaturated'), + dark: Joi.string().default('dark_map'), + }).default({ + bright: 'road_map', + desaturated: 'road_map_desaturated', + dark: 'dark_map', + }), + }).default(), i18n: Joi.object({ locale: Joi.string().default('en'), diff --git a/src/plugins/maps_legacy/config.ts b/src/plugins/maps_legacy/config.ts index 4d9fe15a6a28..16022fb29b0a 100644 --- a/src/plugins/maps_legacy/config.ts +++ b/src/plugins/maps_legacy/config.ts @@ -40,6 +40,9 @@ export const configSchema = schema.object({ tilemap: tilemapSchema, regionmap: regionmapSchema, manifestServiceUrl: schema.string({ defaultValue: '' }), + opensearchManifestServiceUrl: schema.string({ + defaultValue: 'https://maps.search-services.aws.a2z.com/v4/us-east-1/manifest', + }), emsFileApiUrl: schema.string({ defaultValue: 'https://vector.maps.opensearch.org' }), emsTileApiUrl: schema.string({ defaultValue: 'https://tiles.maps.opensearch.org' }), emsLandingPageUrl: schema.string({ defaultValue: 'https://maps.opensearch.org/v7.10' }), diff --git a/src/plugins/maps_legacy/public/common/opensearch_maps_client.js b/src/plugins/maps_legacy/public/common/opensearch_maps_client.js new file mode 100644 index 000000000000..f3c001d280db --- /dev/null +++ b/src/plugins/maps_legacy/public/common/opensearch_maps_client.js @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +import { EMSClient } from '@elastic/ems-client'; + +export class OpenSearchMapsClient extends EMSClient { + constructor({ kbnVersion, manifestServiceUrl, language, landingPageUrl, fetchFunction }) { + super({ kbnVersion, manifestServiceUrl, language, landingPageUrl, fetchFunction }); + this._queryParams = { + kbn_version: kbnVersion, + opensearch_tos_agree: true, + }; + this._manifestServiceUrl = manifestServiceUrl; + } + + async isEnabled() { + let result; + try { + result = await this._fetchWithTimeout(this._manifestServiceUrl); + } catch (e) { + // silently ignoring the exception and returning false. + return false; + } + if (result.ok) { + const resultJson = await result.json(); + return resultJson.enabled; + } + return false; + } +} diff --git a/src/plugins/maps_legacy/public/map/map_messages.js b/src/plugins/maps_legacy/public/map/map_messages.js index 6d99c96dab77..7d2a909d8437 100644 --- a/src/plugins/maps_legacy/public/map/map_messages.js +++ b/src/plugins/maps_legacy/public/map/map_messages.js @@ -30,11 +30,65 @@ * GitHub history for details. */ -import React from 'react'; +import React, { Fragment } from 'react'; +import ReactDOM from 'react-dom'; import { FormattedMessage } from '@osd/i18n/react'; -import { EuiSpacer, EuiButtonEmpty } from '@elastic/eui'; +import { EuiSpacer, EuiButtonEmpty, EuiEmptyPrompt } from '@elastic/eui'; import { toMountPoint } from '../../../opensearch_dashboards_react/public'; +export const createRegionBlockedWarning = (function () { + class RegionBlockedWarningOverlay extends React.Component { + constructor(props) { + super(props); + } + + render() { + return ( + The default Web Map Service is currently not available in your region.} + titleSize="xs" + body={ + +

+ You can configure OpenSearch Dash to use a different map server for coordinate maps + by modifying the default WMS properties. +

+
+ } + /> + ); + } + } + return () => { + let messageBlock = document.getElementById('blocker-div'); + if (!messageBlock) { + messageBlock = document.createElement('div'); + messageBlock.id = 'blocker-div'; + messageBlock.setAttribute('class', 'visError leaflet-popup-pane'); + Array.prototype.forEach.call( + document.getElementsByClassName('leaflet-container'), + (leafletDom) => { + ReactDOM.render( + new RegionBlockedWarningOverlay().render(), + leafletDom.appendChild(messageBlock) + ); + } + ); + } + }; +})(); + +export const removeRegionBlockedWarning = (function () { + return () => { + const childEle = document.getElementById('blocker-div'); + if (childEle) { + childEle.parentNode.removeChild(childEle); + } + }; +})(); + export const createZoomWarningMsg = (function () { let disableZoomMsg = false; const setZoomMsg = (boolDisableMsg) => (disableZoomMsg = boolDisableMsg); @@ -59,6 +113,7 @@ export const createZoomWarningMsg = (function () { access to additional zoom levels for free through the {ems}. Or, you can configure your own map server. Please go to { wms } or { configSettings} for more information." + // TODO: [RENAMEME] Need valid URLs values={{ defaultDistribution: ( diff --git a/src/plugins/maps_legacy/public/map/opensearch_dashboards_map.js b/src/plugins/maps_legacy/public/map/opensearch_dashboards_map.js index c35ea372c2bc..c2c94e8bfa5d 100644 --- a/src/plugins/maps_legacy/public/map/opensearch_dashboards_map.js +++ b/src/plugins/maps_legacy/public/map/opensearch_dashboards_map.js @@ -31,7 +31,11 @@ */ import { EventEmitter } from 'events'; -import { createZoomWarningMsg } from './map_messages'; +import { + createZoomWarningMsg, + createRegionBlockedWarning, + removeRegionBlockedWarning, +} from './map_messages'; import $ from 'jquery'; import { get, isEqual, escape } from 'lodash'; import { zoomToPrecision } from './zoom_to_precision'; @@ -606,6 +610,11 @@ export class OpenSearchDashboardsMap extends EventEmitter { baseLayer.on('loading', () => { this.emit('baseLayer:loading'); }); + baseLayer.on('tileerror', () => { + if (baseLayer._url.includes('search-services.aws.a2z.com')) { + createRegionBlockedWarning(); + } + }); this._leafletBaseLayer = baseLayer; if (settings.options.showZoomMessage) { @@ -684,6 +693,7 @@ export class OpenSearchDashboardsMap extends EventEmitter { } _updateDesaturation() { + removeRegionBlockedWarning(); const tiles = $('img.leaflet-tile-loaded'); // Don't apply client-side styling to EMS basemaps if (get(this._baseLayerSettings, 'options.origin') === ORIGIN.EMS) { diff --git a/src/plugins/maps_legacy/public/map/service_settings.js b/src/plugins/maps_legacy/public/map/service_settings.js index da38d6fd0be9..5b256a10cfa4 100644 --- a/src/plugins/maps_legacy/public/map/service_settings.js +++ b/src/plugins/maps_legacy/public/map/service_settings.js @@ -33,6 +33,7 @@ import _ from 'lodash'; import MarkdownIt from 'markdown-it'; import { EMSClient } from '@elastic/ems-client'; +import { OpenSearchMapsClient } from '../common/opensearch_maps_client.js'; import { i18n } from '@osd/i18n'; import { getOpenSearchDashboardsVersion } from '../opensearch_dashboards_services'; import { ORIGIN } from '../common/constants/origin'; @@ -46,13 +47,15 @@ export class ServiceSettings { this._hasTmsConfigured = typeof tilemapsConfig.url === 'string' && tilemapsConfig.url !== ''; this._showZoomMessage = true; - this._emsClient = new EMSClient({ + this._emsClient = null; + this._opensearchMapsClient = new OpenSearchMapsClient({ language: i18n.getLocale(), appVersion: getOpenSearchDashboardsVersion(), appName: 'opensearch-dashboards', fileApiUrl: this._mapConfig.emsFileApiUrl, tileApiUrl: this._mapConfig.emsTileApiUrl, - landingPageUrl: this._mapConfig.emsLandingPageUrl, + landingPageUrl: '', + manifestServiceUrl: this._mapConfig.opensearchManifestServiceUrl, // Wrap to avoid errors passing window fetch fetchFunction: function (...args) { return fetch(...args); @@ -87,6 +90,7 @@ export class ServiceSettings { } __debugStubManifestCalls(manifestRetrieval) { + this._emsClient = this._opensearchMapsClient; const oldGetManifest = this._emsClient.getManifest; this._emsClient.getManifest = manifestRetrieval; return { @@ -118,11 +122,39 @@ export class ServiceSettings { }; }; + // anyone using this._emsClient should call this method before, to set the right client + async _setMapServices() { + // if client is not null, return immediately. + // Effectively, client creation will be called only once. + if (this._emsClient) { + return; + } + const useOpenSearchMaps = await this._opensearchMapsClient.isEnabled(); + if (useOpenSearchMaps) { + // using OpenSearch Maps. + this._emsClient = this._opensearchMapsClient; + } else { + // not using OpenSearch Maps, fallback to default maps. + this._emsClient = new EMSClient({ + language: i18n.getLocale(), + appVersion: getOpenSearchDashboardsVersion(), + appName: 'opensearch-dashboards', + fileApiUrl: this._mapConfig.emsFileApiUrl, + tileApiUrl: this._mapConfig.emsTileApiUrl, + landingPageUrl: this._mapConfig.emsLandingPageUrl, + fetchFunction: function (...args) { + return fetch(...args); + }, + }); + } + } + async getFileLayers() { if (!this._mapConfig.includeOpenSearchMapsService) { return []; } + await this._setMapServices(); const fileLayers = await this._emsClient.getFileLayers(); return fileLayers.map(this._backfillSettings); } @@ -141,6 +173,7 @@ export class ServiceSettings { allServices.push(tmsService); } + await this._setMapServices(); if (this._mapConfig.includeOpenSearchMapsService) { const servicesFromManifest = await this._emsClient.getTMSServices(); const strippedServiceFromManifest = await Promise.all( @@ -183,6 +216,7 @@ export class ServiceSettings { } async getEMSHotLink(fileLayerConfig) { + await this._setMapServices(); const layer = await this.getFileLayerFromConfig(fileLayerConfig); return layer ? layer.getEMSHotLink() : null; } @@ -193,6 +227,7 @@ export class ServiceSettings { } async _getAttributesForEMSTMSLayer(isDesaturated, isDarkMode) { + await this._setMapServices(); const tmsServices = await this._emsClient.getTMSServices(); const emsTileLayerId = this._mapConfig.emsTileLayerId; let serviceId; @@ -236,6 +271,7 @@ export class ServiceSettings { } async _getFileUrlFromEMS(fileLayerConfig) { + await this._setMapServices(); const fileLayers = await this._emsClient.getFileLayers(); const layer = fileLayers.find((fileLayer) => { const hasIdByName = fileLayer.hasId(fileLayerConfig.name); //legacy diff --git a/src/plugins/maps_legacy/server/index.ts b/src/plugins/maps_legacy/server/index.ts index be59c8230b93..bad2223e927d 100644 --- a/src/plugins/maps_legacy/server/index.ts +++ b/src/plugins/maps_legacy/server/index.ts @@ -43,6 +43,7 @@ export const config: PluginConfigDescriptor = { tilemap: true, regionmap: true, manifestServiceUrl: true, + opensearchManifestServiceUrl: true, emsFileApiUrl: true, emsTileApiUrl: true, emsLandingPageUrl: true, diff --git a/src/plugins/region_map/public/choropleth_layer.js b/src/plugins/region_map/public/choropleth_layer.js index 690c1bf1e82d..c6b15eeb7bb4 100644 --- a/src/plugins/region_map/public/choropleth_layer.js +++ b/src/plugins/region_map/public/choropleth_layer.js @@ -193,6 +193,10 @@ Make sure the file exists at that location.", values: { name: name }, } ); + } else if (e.config.url.includes('aws.a2z.com')) { + // AES Region Maps will throw CORS exception when accessed from Embargo Regions. + // OPTIONS will fail before GET. Thus CORS error. + errorMessage = 'The vector map ' + name + ' is not available.'; } else { errorMessage = i18n.translate( 'regionMap.choroplethLayer.downloadingVectorDataErrorMessage', diff --git a/src/plugins/region_map/public/components/region_map_options.tsx b/src/plugins/region_map/public/components/region_map_options.tsx index d9fd44b3fc22..315fd97e95d6 100644 --- a/src/plugins/region_map/public/components/region_map_options.tsx +++ b/src/plugins/region_map/public/components/region_map_options.tsx @@ -117,29 +117,6 @@ function RegionMapOptions(props: RegionMapOptionsProps) { label={i18n.translate('regionMap.visParams.vectorMapLabel', { defaultMessage: 'Vector map', })} - labelAppend={ - stateParams.emsHotLink && ( - - - {' '} - - - - ) - } options={vectorLayerOptions} paramName="selectedLayer" value={stateParams.selectedLayer && stateParams.selectedLayer.layerId}