[Maps] convert EMS TMS source to typescript (#116508)

* [Maps] convert EMS TMS source to typescript

* eslint

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Nathan Reese 2021-10-29 11:33:12 -06:00 committed by GitHub
parent 0a3cd8ebab
commit 7c73e227eb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 110 additions and 159 deletions

View file

@ -351,7 +351,7 @@ When `includeElasticMapsService` is turned off, only tile layer configured by <<
| Specifies the URL of a self hosted <<elastic-maps-server,{hosted-ems}>>
| [[tilemap-settings]] `map.tilemap.options.attribution:` {ess-icon}
| The map attribution string.
| The map attribution string. Provide attributions in markdown and use '|' to delimit attributions, for example: `"[attribution 1](https://www.attribution1)|[attribution 2](https://www.attribution2)"`.
*Default: `"© [Elastic Maps Service](https://www.elastic.co/elastic-maps-service)"`*
| [[tilemap-max-zoom]] `map.tilemap.options.maxZoom:` {ess-icon}

View file

@ -13,7 +13,6 @@ import { getEMSSettings } from '../../kibana_services';
import { KibanaTilemapSource } from '../sources/kibana_tilemap_source';
import { TileLayer } from './tile_layer/tile_layer';
import { VectorTileLayer } from './vector_tile_layer/vector_tile_layer';
// @ts-expect-error
import { EMSTMSSource } from '../sources/ems_tms_source';
export function createBasemapLayerDescriptor(): LayerDescriptor | null {

View file

@ -8,7 +8,8 @@
import { ITileLayerArguments, TileLayer } from './tile_layer';
import { SOURCE_TYPES } from '../../../../common/constants';
import { XYZTMSSourceDescriptor } from '../../../../common/descriptor_types';
import { ITMSSource, AbstractTMSSource } from '../../sources/tms_source';
import { AbstractSource } from '../../sources/source';
import { ITMSSource } from '../../sources/tms_source';
import { ILayer } from '../layer';
const sourceDescriptor: XYZTMSSourceDescriptor = {
@ -17,7 +18,7 @@ const sourceDescriptor: XYZTMSSourceDescriptor = {
id: 'foobar',
};
class MockTileSource extends AbstractTMSSource implements ITMSSource {
class MockTileSource extends AbstractSource implements ITMSSource {
readonly _descriptor: XYZTMSSourceDescriptor;
constructor(descriptor: XYZTMSSourceDescriptor) {
super(descriptor, {});

View file

@ -8,7 +8,8 @@
import { ITileLayerArguments } from '../tile_layer/tile_layer';
import { SOURCE_TYPES } from '../../../../common/constants';
import { DataFilters, XYZTMSSourceDescriptor } from '../../../../common/descriptor_types';
import { ITMSSource, AbstractTMSSource } from '../../sources/tms_source';
import { AbstractSource } from '../../sources/source';
import { ITMSSource } from '../../sources/tms_source';
import { ILayer } from '../layer';
import { VectorTileLayer } from './vector_tile_layer';
import { DataRequestContext } from '../../../actions';
@ -19,7 +20,7 @@ const sourceDescriptor: XYZTMSSourceDescriptor = {
id: 'mockSourceId',
};
class MockTileSource extends AbstractTMSSource implements ITMSSource {
class MockTileSource extends AbstractSource implements ITMSSource {
readonly _descriptor: XYZTMSSourceDescriptor;
constructor(descriptor: XYZTMSSourceDescriptor) {
super(descriptor, {});

View file

@ -21,7 +21,7 @@ export class CreateSourceEditor extends Component<Props, State> {
state: State = {};
componentDidMount() {
this._onTileSelect({ id: null, isAutoSelect: true });
this._onTileSelect({ isAutoSelect: true });
}
_onTileSelect = (config: EmsTmsSourceConfig) => {

View file

@ -1,70 +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.
*/
jest.mock('../../../kibana_services', () => {
return {
getEmsTileLayerId: () => {
return {
bright: 'road_map',
desaturated: 'road_map_desaturated',
dark: 'dark_map',
};
},
};
});
jest.mock('../../../util', () => {
return {
getEmsTmsServices: () => {
class MockTMSService {
constructor(config) {
this._config = config;
}
getMarkdownAttribution() {
return this._config.attributionMarkdown;
}
getId() {
return this._config.id;
}
}
return [
new MockTMSService({
id: 'road_map',
attributionMarkdown: '[foobar](http://foobar.org) | [foobaz](http://foobaz.org)',
}),
new MockTMSService({
id: 'satellite',
attributionMarkdown: '[satellite](http://satellite.org)',
}),
];
},
};
});
import { EMSTMSSource } from './ems_tms_source';
describe('EMSTMSSource', () => {
it('should get attribution from markdown (tiles v2 legacy format)', async () => {
const emsTmsSource = new EMSTMSSource({
id: 'road_map',
});
const attributionProvider = emsTmsSource.getAttributionProvider();
const attributions = await attributionProvider();
expect(attributions).toEqual([
{
label: 'foobar',
url: 'http://foobar.org',
},
{
label: 'foobaz',
url: 'http://foobaz.org',
},
]);
});
});

View file

@ -6,18 +6,21 @@
*/
import React from 'react';
import { AbstractTMSSource } from '../tms_source';
import { Adapters } from 'src/plugins/inspector/public';
import { i18n } from '@kbn/i18n';
import { AbstractSource, SourceEditorArgs } from '../source';
import { ITMSSource } from '../tms_source';
import { getEmsTmsServices } from '../../../util';
import { UpdateSourceEditor } from './update_source_editor';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import { SOURCE_TYPES } from '../../../../common/constants';
import { EMSTMSSourceDescriptor } from '../../../../common/descriptor_types';
import { getEmsTileLayerId, getIsDarkMode, getEMSSettings } from '../../../kibana_services';
import { registerSource } from '../source_registry';
import { getEmsUnavailableMessage } from '../../../components/ems_unavailable_message';
import { LICENSED_FEATURES } from '../../../licensed_features';
function getErrorInfo(emsTileLayerId) {
function getErrorInfo(emsTileLayerId: string) {
return i18n.translate('xpack.maps.source.emsTile.unableToFindTileIdErrorMessage', {
defaultMessage: `Unable to find EMS tile configuration for id: {id}. {info}`,
values: { id: emsTileLayerId, info: getEmsUnavailableMessage() },
@ -37,8 +40,8 @@ export function getSourceTitle() {
}
}
export class EMSTMSSource extends AbstractTMSSource {
static createDescriptor(descriptor) {
export class EMSTMSSource extends AbstractSource implements ITMSSource {
static createDescriptor(descriptor: Partial<EMSTMSSourceDescriptor>): EMSTMSSourceDescriptor {
return {
type: SOURCE_TYPES.EMS_TMS,
id: descriptor.id,
@ -51,12 +54,15 @@ export class EMSTMSSource extends AbstractTMSSource {
};
}
constructor(descriptor, inspectorAdapters) {
descriptor = EMSTMSSource.createDescriptor(descriptor);
super(descriptor, inspectorAdapters);
readonly _descriptor: EMSTMSSourceDescriptor;
constructor(descriptor: Partial<EMSTMSSourceDescriptor>, inspectorAdapters?: Adapters) {
const emsTmsDescriptor = EMSTMSSource.createDescriptor(descriptor);
super(emsTmsDescriptor, inspectorAdapters);
this._descriptor = emsTmsDescriptor;
}
renderSourceSettingsEditor({ onChange }) {
renderSourceSettingsEditor({ onChange }: SourceEditorArgs) {
return <UpdateSourceEditor onChange={onChange} config={this._descriptor} />;
}
@ -100,12 +106,12 @@ export class EMSTMSSource extends AbstractTMSSource {
} catch (e) {
throw new Error(`${getErrorInfo(emsTileLayerId)} - ${e.message}`);
}
const tmsService = emsTMSServices.find((tmsService) => tmsService.getId() === emsTileLayerId);
if (tmsService) {
return tmsService;
const tmsService = emsTMSServices.find((service) => service.getId() === emsTileLayerId);
if (!tmsService) {
throw new Error(getErrorInfo(emsTileLayerId));
}
throw new Error(getErrorInfo(emsTileLayerId));
return tmsService;
}
async getDisplayName() {
@ -120,15 +126,11 @@ export class EMSTMSSource extends AbstractTMSSource {
getAttributionProvider() {
return async () => {
const emsTMSService = await this._getEMSTMSService();
const markdown = emsTMSService.getMarkdownAttribution();
if (!markdown) {
return [];
}
return this.convertMarkdownLinkToObjectArr(markdown);
return emsTMSService.getAttributions();
};
}
async getUrlTemplate() {
async getUrlTemplate(): Promise<string> {
const emsTMSService = await this._getEMSTMSService();
return await emsTMSService.getUrlTemplate();
}
@ -137,13 +139,13 @@ export class EMSTMSSource extends AbstractTMSSource {
return 'ems/' + this.getTileLayerId();
}
async getVectorStyleSheetAndSpriteMeta(isRetina) {
async getVectorStyleSheetAndSpriteMeta(isRetina: boolean) {
const emsTMSService = await this._getEMSTMSService();
const styleSheet = await emsTMSService.getVectorStyleSheet();
const spriteMeta = await emsTMSService.getSpriteSheetMeta(isRetina);
return {
vectorStyleSheet: styleSheet,
spriteMeta: spriteMeta,
spriteMeta,
};
}

View file

@ -9,15 +9,13 @@ import React, { ChangeEvent, Component } from 'react';
import { EuiSelect, EuiSelectOption, EuiFormRow } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EMSTMSSourceDescriptor } from '../../../../common/descriptor_types';
import { getEmsTmsServices } from '../../../util';
import { getEmsUnavailableMessage } from '../../../components/ems_unavailable_message';
const AUTO_SELECT = 'auto_select';
export interface EmsTmsSourceConfig {
id: string | null;
isAutoSelect: boolean;
}
export type EmsTmsSourceConfig = Pick<EMSTMSSourceDescriptor, 'id' | 'isAutoSelect'>;
interface Props {
config?: EmsTmsSourceConfig;
@ -72,7 +70,7 @@ export class TileServiceSelect extends Component<Props, State> {
const value = e.target.value;
const isAutoSelect = value === AUTO_SELECT;
this.props.onTileSelect({
id: isAutoSelect ? null : value,
id: isAutoSelect ? undefined : value,
isAutoSelect,
});
};

View file

@ -8,10 +8,16 @@
import React, { Fragment } from 'react';
import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { TileServiceSelect } from './tile_service_select';
import { EmsTmsSourceConfig, TileServiceSelect } from './tile_service_select';
import { OnSourceChangeArgs } from '../source';
export function UpdateSourceEditor({ onChange, config }) {
const _onTileSelect = ({ id, isAutoSelect }) => {
interface Props {
onChange: (...args: OnSourceChangeArgs[]) => Promise<void>;
config: EmsTmsSourceConfig;
}
export function UpdateSourceEditor({ onChange, config }: Props) {
const _onTileSelect = ({ id, isAutoSelect }: EmsTmsSourceConfig) => {
onChange({ propName: 'id', value: id });
onChange({ propName: 'isAutoSelect', value: isAutoSelect });
};

View file

@ -0,0 +1,28 @@
/*
* 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 { extractAttributions } from './extract_attributions';
test('Should extract attributions from markdown', () => {
const markdown =
'[OpenStreetMap contributors](https://www.openstreetmap.org/copyright)|[OpenMapTiles](https://openmaptiles.org)|[Elastic Maps Service](https://www.elastic.co/elastic-maps-service)';
const attributions = extractAttributions(markdown);
expect(attributions).toEqual([
{
label: 'OpenStreetMap contributors',
url: 'https://www.openstreetmap.org/copyright',
},
{
label: 'OpenMapTiles',
url: 'https://openmaptiles.org',
},
{
label: 'Elastic Maps Service',
url: 'https://www.elastic.co/elastic-maps-service',
},
]);
});

View file

@ -0,0 +1,25 @@
/*
* 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 { Attribution } from '../../../../common/descriptor_types';
export function extractAttributions(markdown: string): Attribution[] {
const attributions: Attribution[] = [];
markdown.split('|').forEach((attribution: string) => {
attribution = attribution.trim();
// this assumes attribution is plain markdown link
const extractLink = /\[(.*)\]\((.*)\)/;
const result = extractLink.exec(attribution);
if (result && result?.length >= 3 && result[1] && result[2]) {
attributions.push({
label: result[1],
url: result[2],
});
}
});
return attributions;
}

View file

@ -5,19 +5,20 @@
* 2.0.
*/
import { AbstractTMSSource } from '../tms_source';
import { AbstractSource } from '../source';
import { getKibanaTileMap } from '../../../util';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel } from '../../../../common/i18n_getters';
import _ from 'lodash';
import { SOURCE_TYPES } from '../../../../common/constants';
import { registerSource } from '../source_registry';
import { extractAttributions } from './extract_attributions';
export const sourceTitle = i18n.translate('xpack.maps.source.kbnTMSTitle', {
defaultMessage: 'Configured Tile Map Service',
});
export class KibanaTilemapSource extends AbstractTMSSource {
export class KibanaTilemapSource extends AbstractSource {
static type = SOURCE_TYPES.KIBANA_TILEMAP;
static createDescriptor() {
@ -57,8 +58,7 @@ export class KibanaTilemapSource extends AbstractTMSSource {
return async () => {
const tilemap = getKibanaTileMap();
const markdown = _.get(tilemap, 'options.attribution', '');
const objArr = this.convertMarkdownLinkToObjectArr(markdown);
return objArr;
return extractAttributions(markdown);
};
}

View file

@ -5,4 +5,8 @@
* 2.0.
*/
export * from './tms_source';
import { ISource } from '../source';
export interface ITMSSource extends ISource {
getUrlTemplate(): Promise<string>;
}

View file

@ -1,16 +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 { AbstractSource, ISource } from '../source';
export interface ITMSSource extends ISource {
getUrlTemplate(): Promise<string>;
}
export class AbstractTMSSource extends AbstractSource implements ITMSSource {
getUrlTemplate(): Promise<string>;
}

View file

@ -1,27 +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 { AbstractSource } from '../source';
export class AbstractTMSSource extends AbstractSource {
async getUrlTemplate() {
throw new Error('Should implement TMSSource#getUrlTemplate');
}
convertMarkdownLinkToObjectArr(markdown) {
return markdown.split('|').map((attribution) => {
attribution = attribution.trim();
//this assumes attribution is plain markdown link
const extractLink = /\[(.*)\]\((.*)\)/;
const result = extractLink.exec(attribution);
return {
label: result ? result[1] : null,
url: result ? result[2] : null,
};
});
}
}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { AbstractTMSSource } from '../tms_source';
import { AbstractSource } from '../source';
import { i18n } from '@kbn/i18n';
import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters';
import { WmsClient } from './wms_client';
@ -16,7 +16,7 @@ export const sourceTitle = i18n.translate('xpack.maps.source.wmsTitle', {
defaultMessage: 'Web Map Service',
});
export class WMSSource extends AbstractTMSSource {
export class WMSSource extends AbstractSource {
static type = SOURCE_TYPES.WMS;
static createDescriptor({ serviceUrl, layers, styles }) {

View file

@ -9,16 +9,16 @@ import { i18n } from '@kbn/i18n';
import { getDataSourceLabel, getUrlLabel } from '../../../../common/i18n_getters';
import { SOURCE_TYPES } from '../../../../common/constants';
import { registerSource } from '../source_registry';
import { AbstractTMSSource } from '../tms_source';
import { ITMSSource } from '../tms_source';
import { XYZTMSSourceDescriptor } from '../../../../common/descriptor_types';
import { ImmutableSourceProperty } from '../source';
import { AbstractSource, ImmutableSourceProperty } from '../source';
import { XYZTMSSourceConfig } from './xyz_tms_editor';
export const sourceTitle = i18n.translate('xpack.maps.source.ems_xyzTitle', {
defaultMessage: 'Tile Map Service',
});
export class XYZTMSSource extends AbstractTMSSource {
export class XYZTMSSource extends AbstractSource implements ITMSSource {
static type = SOURCE_TYPES.EMS_XYZ;
readonly _descriptor: XYZTMSSourceDescriptor;