mirror of
https://github.com/wazuh/wazuh-dashboard.git
synced 2025-12-10 00:31:06 -06:00
Enable users to select custom vector map for visualization (#1718)
enable users to select custom vector map for visualization Signed-off-by: Shivam Dhar <dhshivam@amazon.com>
This commit is contained in:
parent
2a159e88e0
commit
dfbfec47e3
@ -1,2 +1,2 @@
|
||||
build
|
||||
target
|
||||
target
|
||||
|
||||
@ -75,12 +75,7 @@ export default {
|
||||
coveragePathIgnorePatterns: ['/node_modules/', '.*\\.d\\.ts'],
|
||||
coverageReporters: ['lcov', 'text-summary'],
|
||||
moduleFileExtensions: ['js', 'mjs', 'json', 'ts', 'tsx', 'node'],
|
||||
modulePathIgnorePatterns: [
|
||||
'__fixtures__/',
|
||||
'target/',
|
||||
'<rootDir>/src/plugins/maps_legacy',
|
||||
'<rootDir>/src/plugins/region_map',
|
||||
],
|
||||
modulePathIgnorePatterns: ['__fixtures__/', 'target/', '<rootDir>/src/plugins/maps_legacy'],
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
testMatch: ['**/*.test.{js,mjs,ts,tsx}'],
|
||||
testPathIgnorePatterns: [
|
||||
|
||||
7
src/plugins/region_map/common/constants/shared.ts
Normal file
7
src/plugins/region_map/common/constants/shared.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export const DEFAULT_MAP_CHOICE = 'default';
|
||||
export const CUSTOM_MAP_CHOICE = 'custom';
|
||||
8
src/plugins/region_map/common/index.ts
Normal file
8
src/plugins/region_map/common/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { DEFAULT_MAP_CHOICE, CUSTOM_MAP_CHOICE } from './constants/shared';
|
||||
|
||||
export { DEFAULT_MAP_CHOICE, CUSTOM_MAP_CHOICE };
|
||||
@ -11,7 +11,8 @@
|
||||
"mapsLegacy",
|
||||
"opensearchDashboardsLegacy",
|
||||
"data",
|
||||
"share"
|
||||
"share",
|
||||
"opensearchDashboardsReact"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"opensearchDashboardsUtils",
|
||||
|
||||
@ -36,6 +36,8 @@ import * as topojson from 'topojson-client';
|
||||
import { getNotifications } from './opensearch_dashboards_services';
|
||||
import { colorUtil, OpenSearchDashboardsMapLayer } from '../../maps_legacy/public';
|
||||
import { truncatedColorMaps } from '../../charts/public';
|
||||
import { getServices } from './services';
|
||||
import { DEFAULT_MAP_CHOICE, CUSTOM_MAP_CHOICE } from '../common';
|
||||
|
||||
const EMPTY_STYLE = {
|
||||
weight: 1,
|
||||
@ -90,7 +92,9 @@ export class ChoroplethLayer extends OpenSearchDashboardsMapLayer {
|
||||
meta,
|
||||
layerConfig,
|
||||
serviceSettings,
|
||||
leaflet
|
||||
leaflet,
|
||||
layerChosenByUser,
|
||||
http
|
||||
) {
|
||||
super();
|
||||
this._serviceSettings = serviceSettings;
|
||||
@ -105,6 +109,9 @@ export class ChoroplethLayer extends OpenSearchDashboardsMapLayer {
|
||||
this._layerName = name;
|
||||
this._layerConfig = layerConfig;
|
||||
this._leaflet = leaflet;
|
||||
this._layerChosenByUser = layerChosenByUser;
|
||||
this._http = http;
|
||||
this._visParams = null;
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
this._leafletLayer = this._leaflet.geoJson(null, {
|
||||
@ -139,7 +146,14 @@ export class ChoroplethLayer extends OpenSearchDashboardsMapLayer {
|
||||
this._isJoinValid = false;
|
||||
this._whenDataLoaded = new Promise(async (resolve) => {
|
||||
try {
|
||||
const data = await this._makeJsonAjaxCall();
|
||||
let data;
|
||||
if (DEFAULT_MAP_CHOICE === this._layerChosenByUser) {
|
||||
data = await this._makeJsonAjaxCall();
|
||||
} else if (CUSTOM_MAP_CHOICE === this._layerChosenByUser) {
|
||||
data = await this._fetchCustomLayerData();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
let featureCollection;
|
||||
let formatType;
|
||||
if (typeof format === 'string') {
|
||||
@ -223,6 +237,29 @@ CORS configuration of the server permits requests from the OpenSearch Dashboards
|
||||
return this._serviceSettings.getJsonForRegionLayer(this._layerConfig);
|
||||
}
|
||||
|
||||
async _fetchCustomLayerData() {
|
||||
// fetch data from index and transform it to feature collection
|
||||
try {
|
||||
const services = getServices(this._http);
|
||||
const result = await services.getIndexData(this._layerName);
|
||||
|
||||
const finalResult = {
|
||||
type: 'FeatureCollection',
|
||||
features: [],
|
||||
};
|
||||
for (let featureCount = 0; featureCount < result.resp.hits.hits.length; featureCount++) {
|
||||
finalResult.features.push({
|
||||
geometry: result.resp.hits.hits[featureCount]._source.location,
|
||||
properties: removeKeys(result.resp.hits.hits[featureCount]._source),
|
||||
type: 'Feature',
|
||||
});
|
||||
}
|
||||
return finalResult;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_invalidateJoin() {
|
||||
this._isJoinValid = false;
|
||||
}
|
||||
@ -298,7 +335,9 @@ CORS configuration of the server permits requests from the OpenSearch Dashboards
|
||||
meta,
|
||||
layerConfig,
|
||||
serviceSettings,
|
||||
leaflet
|
||||
leaflet,
|
||||
layerChosenByUser,
|
||||
http
|
||||
) {
|
||||
const clonedLayer = new ChoroplethLayer(
|
||||
name,
|
||||
@ -308,7 +347,9 @@ CORS configuration of the server permits requests from the OpenSearch Dashboards
|
||||
meta,
|
||||
layerConfig,
|
||||
serviceSettings,
|
||||
leaflet
|
||||
leaflet,
|
||||
layerChosenByUser,
|
||||
http
|
||||
);
|
||||
clonedLayer.setJoinField(this._joinField);
|
||||
clonedLayer.setColorRamp(this._colorRamp);
|
||||
@ -335,6 +376,14 @@ CORS configuration of the server permits requests from the OpenSearch Dashboards
|
||||
return this._whenDataLoaded;
|
||||
}
|
||||
|
||||
setLayerChosenByUser(layerChosenByUser) {
|
||||
this._layerChosenByUser = layerChosenByUser;
|
||||
}
|
||||
|
||||
setVisParams(visParams) {
|
||||
this._visParams = visParams;
|
||||
}
|
||||
|
||||
setMetrics(metrics, fieldFormatter, metricTitle) {
|
||||
this._metrics = metrics.slice();
|
||||
this._valueFormatter = fieldFormatter;
|
||||
@ -520,3 +569,11 @@ function getChoroplethColor(value, min, max, colorRamp) {
|
||||
|
||||
return colorUtil.getColor(colorRamp, i);
|
||||
}
|
||||
|
||||
function removeKeys(myObj) {
|
||||
const array = ['id', 'location'];
|
||||
for (let index = 0; index < array.length; index++) {
|
||||
delete myObj[array[index]];
|
||||
}
|
||||
return myObj;
|
||||
}
|
||||
|
||||
1097
src/plugins/region_map/public/components/__snapshots__/region_map_options.test.tsx.snap
generated
Normal file
1097
src/plugins/region_map/public/components/__snapshots__/region_map_options.test.tsx.snap
generated
Normal file
File diff suppressed because it is too large
Load Diff
114
src/plugins/region_map/public/components/__snapshots__/style_options.test.tsx.snap
generated
Normal file
114
src/plugins/region_map/public/components/__snapshots__/style_options.test.tsx.snap
generated
Normal file
@ -0,0 +1,114 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`style_options renders the Style options comprising of style settings, color schema and border thickness 1`] = `
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingSmall euiPanel--borderRadiusMedium euiPanel--plain euiPanel--hasShadow"
|
||||
>
|
||||
<h2
|
||||
className="euiTitle euiTitle--xsmall"
|
||||
id="styleSettingTitleId"
|
||||
>
|
||||
<span>
|
||||
Style settings
|
||||
</span>
|
||||
</h2>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
<div
|
||||
className="euiFormRow euiFormRow--fullWidth euiFormRow--compressed"
|
||||
id="colorSchemaId-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="colorSchemaId"
|
||||
>
|
||||
Color schema
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout euiFormControlLayout--fullWidth euiFormControlLayout--compressed"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout__childrenWrapper"
|
||||
>
|
||||
<select
|
||||
className="euiSelect euiSelect--fullWidth euiSelect--compressed"
|
||||
id="colorSchemaId"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
value={Object {}}
|
||||
>
|
||||
<option
|
||||
disabled={true}
|
||||
hidden={true}
|
||||
value="EMPTY_VALUE"
|
||||
>
|
||||
|
||||
</option>
|
||||
</select>
|
||||
<div
|
||||
className="euiFormControlLayoutIcons euiFormControlLayoutIcons--right"
|
||||
>
|
||||
<span
|
||||
className="euiFormControlLayoutCustomIcon"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="euiFormControlLayoutCustomIcon__icon"
|
||||
data-euiicon-type="arrowDown"
|
||||
size="s"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow euiFormRow--fullWidth euiFormRow--compressed"
|
||||
id="generated-id-row"
|
||||
>
|
||||
<div
|
||||
className="euiFormRow__labelWrapper"
|
||||
>
|
||||
<label
|
||||
className="euiFormLabel euiFormRow__label"
|
||||
htmlFor="generated-id"
|
||||
>
|
||||
Border thickness
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
className="euiFormRow__fieldWrapper"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout euiFormControlLayout--fullWidth euiFormControlLayout--compressed"
|
||||
>
|
||||
<div
|
||||
className="euiFormControlLayout__childrenWrapper"
|
||||
>
|
||||
<input
|
||||
className="euiFieldNumber euiFieldNumber--fullWidth euiFieldNumber--compressed"
|
||||
id="generated-id"
|
||||
min={0}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="number"
|
||||
value={Object {}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
144
src/plugins/region_map/public/components/default_map_options.tsx
Normal file
144
src/plugins/region_map/public/components/default_map_options.tsx
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { EuiPanel, EuiSpacer, EuiTitle, EuiFlexItem, EuiFlexGroup } from '@elastic/eui';
|
||||
import { i18n } from '@osd/i18n';
|
||||
import { FormattedMessage } from '@osd/i18n/react';
|
||||
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
|
||||
import { FileLayerField, VectorLayer, IServiceSettings } from '../../../maps_legacy/public';
|
||||
import { SelectOption, SwitchOption } from '../../../charts/public';
|
||||
import { RegionMapVisParams } from '../../../maps_legacy/public';
|
||||
|
||||
const mapLayerForOption = ({ layerId, name }: VectorLayer) => ({
|
||||
text: name,
|
||||
value: layerId,
|
||||
});
|
||||
|
||||
const mapFieldForOption = ({ description, name }: FileLayerField) => ({
|
||||
text: description,
|
||||
value: name,
|
||||
});
|
||||
|
||||
export type DefaultMapOptionsProps = {
|
||||
getServiceSettings: () => Promise<IServiceSettings>;
|
||||
} & VisOptionsProps<RegionMapVisParams>;
|
||||
|
||||
function DefaultMapOptions(props: DefaultMapOptionsProps) {
|
||||
const { getServiceSettings, stateParams, vis, setValue } = props;
|
||||
|
||||
const vectorLayers = vis.type.editorConfig.collections.vectorLayers;
|
||||
const vectorLayerOptions = useMemo(() => vectorLayers.map(mapLayerForOption), [vectorLayers]);
|
||||
|
||||
const fieldOptions = useMemo(
|
||||
() =>
|
||||
((stateParams.selectedLayer && stateParams.selectedLayer.fields) || []).map(
|
||||
mapFieldForOption
|
||||
),
|
||||
[stateParams.selectedLayer]
|
||||
);
|
||||
|
||||
const setEmsHotLink = useCallback(
|
||||
async (layer: VectorLayer) => {
|
||||
const serviceSettings = await getServiceSettings();
|
||||
const emsHotLink = await serviceSettings.getEMSHotLink(layer);
|
||||
setValue('emsHotLink', emsHotLink);
|
||||
},
|
||||
[setValue, getServiceSettings]
|
||||
);
|
||||
|
||||
const setLayer = useCallback(
|
||||
async (paramName: 'selectedLayer', value: VectorLayer['layerId']) => {
|
||||
const newLayer = vectorLayers.find(({ layerId }: VectorLayer) => layerId === value);
|
||||
|
||||
if (newLayer) {
|
||||
setValue(paramName, newLayer);
|
||||
setValue('selectedJoinField', newLayer.fields[0]);
|
||||
setEmsHotLink(newLayer);
|
||||
}
|
||||
},
|
||||
[vectorLayers, setEmsHotLink, setValue]
|
||||
);
|
||||
|
||||
const setField = useCallback(
|
||||
(paramName: 'selectedJoinField', value: FileLayerField['name']) => {
|
||||
if (stateParams.selectedLayer) {
|
||||
setValue(
|
||||
paramName,
|
||||
stateParams.selectedLayer.fields.find((f) => f.name === value)
|
||||
);
|
||||
}
|
||||
},
|
||||
[setValue, stateParams.selectedLayer]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPanel paddingSize="s">
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="regionMap.visParams.layerSettingsTitle"
|
||||
defaultMessage="Layer settings"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup id="defaultMapSelection" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectOption
|
||||
id="regionMapOptionsSelectLayer"
|
||||
label={i18n.translate('regionMap.visParams.vectorMapLabel', {
|
||||
defaultMessage: 'Vector map',
|
||||
})}
|
||||
options={vectorLayerOptions}
|
||||
paramName="selectedLayer"
|
||||
value={stateParams.selectedLayer && stateParams.selectedLayer.layerId}
|
||||
setValue={setLayer}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectOption
|
||||
id="regionMapOptionsSelectJoinField"
|
||||
label={i18n.translate('regionMap.visParams.joinFieldLabel', {
|
||||
defaultMessage: 'Join field',
|
||||
})}
|
||||
options={fieldOptions}
|
||||
paramName="selectedJoinField"
|
||||
value={stateParams.selectedJoinField && stateParams.selectedJoinField.name}
|
||||
setValue={setField}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<SwitchOption
|
||||
label={i18n.translate('regionMap.visParams.displayWarningsLabel', {
|
||||
defaultMessage: 'Display warnings',
|
||||
})}
|
||||
tooltip={i18n.translate('regionMap.visParams.switchWarningsTipText', {
|
||||
defaultMessage:
|
||||
'Turns on/off warnings. When turned on, warning will be shown for each term that cannot be matched to a shape in the vector layer based on the join field. When turned off, these warnings will be turned off.',
|
||||
})}
|
||||
paramName="isDisplayWarning"
|
||||
value={stateParams.isDisplayWarning}
|
||||
setValue={setValue}
|
||||
/>
|
||||
|
||||
<SwitchOption
|
||||
label={i18n.translate('regionMap.visParams.showAllShapesLabel', {
|
||||
defaultMessage: 'Show all shapes',
|
||||
})}
|
||||
tooltip={i18n.translate('regionMap.visParams.turnOffShowingAllShapesTipText', {
|
||||
defaultMessage:
|
||||
'Turning this off only shows the shapes that were matched with a corresponding term.',
|
||||
})}
|
||||
paramName="showAllShapes"
|
||||
value={stateParams.showAllShapes}
|
||||
setValue={setValue}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
||||
export { DefaultMapOptions };
|
||||
@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
.mapChoiceGroup {
|
||||
font-size: small;
|
||||
}
|
||||
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { MapChoiceOptions } from './map_choice_options';
|
||||
import { screen, render } from '@testing-library/react';
|
||||
import { fireEvent, getByTestId } from '@testing-library/dom';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
describe('map_choice_options', () => {
|
||||
it('renders the MapChoiceOptions based on the props provided', async () => {
|
||||
const props = jest.mock;
|
||||
const vis = {
|
||||
type: {
|
||||
editorConfig: {
|
||||
collections: {
|
||||
colorSchemas: [],
|
||||
customVectorLayers: [],
|
||||
tmsLayers: [],
|
||||
vectorLayers: [
|
||||
{
|
||||
attribution:
|
||||
'<a rel="noreferrer noopener" href="http://www.naturalearthdata.com/about/terms-of-use">Made with NaturalEarth</a>',
|
||||
created_at: '2017-04-26T17:12:15.978370',
|
||||
format: 'geojson',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'id',
|
||||
description: 'description',
|
||||
},
|
||||
],
|
||||
id: 'sample',
|
||||
meta: undefined,
|
||||
name: 'sample',
|
||||
origin: 'user-upload',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const stateParams = {
|
||||
colorSchema: {},
|
||||
outlineWeight: {},
|
||||
wms: {},
|
||||
selectedLayer: {
|
||||
attribution:
|
||||
'<a rel="noreferrer noopener" href="http://www.naturalearthdata.com/about/terms-of-use">Made with NaturalEarth</a>',
|
||||
created_at: '2017-04-26T17:12:15.978370',
|
||||
format: 'geojson',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'id',
|
||||
description: 'description',
|
||||
},
|
||||
],
|
||||
id: 'sample',
|
||||
meta: undefined,
|
||||
name: 'sample',
|
||||
origin: 'user-upload',
|
||||
},
|
||||
selectedJoinField: {
|
||||
name: 'name',
|
||||
type: 'id',
|
||||
description: 'description',
|
||||
},
|
||||
};
|
||||
render(<MapChoiceOptions stateParams={stateParams} vis={vis} {...props} />);
|
||||
const defaultVectorSelection = screen.getByTestId('defaultVectorMap');
|
||||
const customVectorSelection = screen.getByTestId('customVectorMap');
|
||||
fireEvent.click(defaultVectorSelection);
|
||||
await expect(defaultVectorSelection).toBeChecked;
|
||||
await expect(customVectorSelection).not.toBeChecked;
|
||||
});
|
||||
|
||||
it('renders the MapChoiceOptions based on the props provided for custom selection', async () => {
|
||||
const props = jest.mock;
|
||||
const vis = {
|
||||
type: {
|
||||
editorConfig: {
|
||||
collections: {
|
||||
colorSchemas: [],
|
||||
customVectorLayers: [
|
||||
{
|
||||
attribution:
|
||||
'<a rel="noreferrer noopener" href="http://www.naturalearthdata.com/about/terms-of-use">Made with NaturalEarth</a>',
|
||||
created_at: '2017-04-26T17:12:15.978370',
|
||||
format: 'geojson',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'id',
|
||||
description: 'description',
|
||||
},
|
||||
],
|
||||
id: 'sample',
|
||||
meta: undefined,
|
||||
name: 'sample',
|
||||
origin: 'user-upload',
|
||||
},
|
||||
],
|
||||
tmsLayers: [],
|
||||
vectorLayers: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const stateParams = {
|
||||
colorSchema: {},
|
||||
outlineWeight: {},
|
||||
wms: {},
|
||||
selectedCustomLayer: {
|
||||
attribution:
|
||||
'<a rel="noreferrer noopener" href="http://www.naturalearthdata.com/about/terms-of-use">Made with NaturalEarth</a>',
|
||||
created_at: '2017-04-26T17:12:15.978370',
|
||||
format: 'geojson',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'id',
|
||||
description: 'description',
|
||||
},
|
||||
],
|
||||
id: 'sample',
|
||||
meta: undefined,
|
||||
name: 'sample',
|
||||
origin: 'user-upload',
|
||||
},
|
||||
selectedCustomJoinField: {
|
||||
name: 'name',
|
||||
type: 'id',
|
||||
description: 'description',
|
||||
},
|
||||
};
|
||||
render(<MapChoiceOptions stateParams={stateParams} vis={vis} {...props} />);
|
||||
const defaultVectorSelection = screen.getByTestId('defaultVectorMap');
|
||||
const customVectorSelection = screen.getByTestId('customVectorMap');
|
||||
fireEvent.click(customVectorSelection);
|
||||
await expect(customVectorSelection).toBeChecked;
|
||||
await expect(defaultVectorSelection).not.toBeChecked;
|
||||
});
|
||||
});
|
||||
265
src/plugins/region_map/public/components/map_choice_options.tsx
Normal file
265
src/plugins/region_map/public/components/map_choice_options.tsx
Normal file
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import './map_choice_options.scss';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
EuiTitle,
|
||||
EuiCheckableCard,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
EuiTextColor,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@osd/i18n';
|
||||
import { FormattedMessage } from '@osd/i18n/react';
|
||||
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
|
||||
import { FileLayerField, VectorLayer, IServiceSettings } from '../../../maps_legacy/public';
|
||||
import { SelectOption, SwitchOption } from '../../../charts/public';
|
||||
import { RegionMapVisParams } from '../../../maps_legacy/public';
|
||||
import { DEFAULT_MAP_CHOICE, CUSTOM_MAP_CHOICE } from '../../common';
|
||||
|
||||
const mapLayerForOption = ({ layerId, name }: VectorLayer) => ({
|
||||
text: name,
|
||||
value: layerId,
|
||||
});
|
||||
|
||||
const mapFieldForOption = ({ description, name }: FileLayerField) => ({
|
||||
text: description,
|
||||
value: name,
|
||||
});
|
||||
|
||||
const mapCustomJoinFieldForOption = ({ description, name }: FileLayerField) => ({
|
||||
label: name,
|
||||
text: description,
|
||||
value: name,
|
||||
});
|
||||
|
||||
export type MapChoiceOptionsProps = {
|
||||
getServiceSettings: () => Promise<IServiceSettings>;
|
||||
} & VisOptionsProps<RegionMapVisParams>;
|
||||
|
||||
function MapChoiceOptions(props: MapChoiceOptionsProps) {
|
||||
const { getServiceSettings, stateParams, vis, setValue } = props;
|
||||
const vectorLayers = vis.type.editorConfig.collections.vectorLayers;
|
||||
const customVectorLayers = vis.type.editorConfig.collections.customVectorLayers;
|
||||
const vectorLayerOptions = useMemo(() => vectorLayers.map(mapLayerForOption), [vectorLayers]);
|
||||
const customVectorLayerOptions = useMemo(() => customVectorLayers.map(mapLayerForOption), [
|
||||
customVectorLayers,
|
||||
]);
|
||||
|
||||
const fieldOptions = useMemo(
|
||||
() =>
|
||||
((stateParams.selectedLayer && stateParams.selectedLayer.fields) || []).map(
|
||||
mapFieldForOption
|
||||
),
|
||||
[stateParams.selectedLayer]
|
||||
);
|
||||
|
||||
const customFieldOptions = useMemo(
|
||||
() =>
|
||||
((stateParams.selectedCustomLayer && stateParams.selectedCustomLayer.fields) || []).map(
|
||||
mapCustomJoinFieldForOption
|
||||
),
|
||||
[stateParams.selectedCustomLayer]
|
||||
);
|
||||
|
||||
const selectDefaultVectorMap = () => {
|
||||
setValue('layerChosenByUser', DEFAULT_MAP_CHOICE);
|
||||
};
|
||||
|
||||
const selectCustomVectorMap = () => {
|
||||
setValue('layerChosenByUser', CUSTOM_MAP_CHOICE);
|
||||
};
|
||||
|
||||
const setEmsHotLink = useCallback(
|
||||
async (layer: VectorLayer) => {
|
||||
const serviceSettings = await getServiceSettings();
|
||||
const emsHotLink = await serviceSettings.getEMSHotLink(layer);
|
||||
setValue('emsHotLink', emsHotLink);
|
||||
},
|
||||
[setValue, getServiceSettings]
|
||||
);
|
||||
|
||||
const setLayer = useCallback(
|
||||
async (paramName: 'selectedLayer', value: VectorLayer['layerId']) => {
|
||||
const newLayer = vectorLayers.find(({ layerId }: VectorLayer) => layerId === value);
|
||||
|
||||
if (newLayer) {
|
||||
setValue(paramName, newLayer);
|
||||
setValue('selectedJoinField', newLayer.fields[0]);
|
||||
setEmsHotLink(newLayer);
|
||||
}
|
||||
},
|
||||
[vectorLayers, setEmsHotLink, setValue]
|
||||
);
|
||||
|
||||
const setCustomLayer = useCallback(
|
||||
async (paramName: 'selectedCustomLayer', value: VectorLayer['layerId']) => {
|
||||
const newLayer = customVectorLayers.find(({ layerId }: VectorLayer) => layerId === value);
|
||||
|
||||
if (newLayer) {
|
||||
setValue(paramName, newLayer);
|
||||
setValue('selectedJoinField', newLayer.fields[0]);
|
||||
}
|
||||
},
|
||||
[customVectorLayers, setValue]
|
||||
);
|
||||
|
||||
const setCustomJoinField = useCallback(
|
||||
async (paramName: 'selectedCustomJoinField', value) => {
|
||||
if (stateParams.selectedCustomLayer) {
|
||||
setValue(
|
||||
paramName,
|
||||
stateParams.selectedCustomLayer.fields.find((f) => f.name === value)
|
||||
);
|
||||
}
|
||||
},
|
||||
[setValue, stateParams.selectedCustomLayer]
|
||||
);
|
||||
|
||||
const setField = useCallback(
|
||||
(paramName: 'selectedJoinField', value: FileLayerField['name']) => {
|
||||
if (stateParams.selectedLayer) {
|
||||
setValue(
|
||||
paramName,
|
||||
stateParams.selectedLayer.fields.find((f) => f.name === value)
|
||||
);
|
||||
}
|
||||
},
|
||||
[setValue, stateParams.selectedLayer]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPanel paddingSize="s">
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="regionMap.visParams.layerSettingsTitle"
|
||||
defaultMessage="Layer settings"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiText size="xs">
|
||||
<strong>
|
||||
<EuiTextColor color="default">Choose a vector map layer</EuiTextColor>
|
||||
</strong>
|
||||
</EuiText>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup className="mapChoiceGroup">
|
||||
<EuiFlexItem>
|
||||
<EuiCheckableCard
|
||||
id="defaultVectorMap"
|
||||
data-test-subj="defaultVectorMap"
|
||||
label="Default vector map"
|
||||
name="defaultVectorMap"
|
||||
value="default"
|
||||
checked={DEFAULT_MAP_CHOICE === stateParams.layerChosenByUser}
|
||||
onChange={selectDefaultVectorMap}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCheckableCard
|
||||
id="customVectorMap"
|
||||
data-test-subj="customVectorMap"
|
||||
label="Custom vector map"
|
||||
name="customVectorMap"
|
||||
value="custom"
|
||||
checked={CUSTOM_MAP_CHOICE === stateParams.layerChosenByUser}
|
||||
onChange={selectCustomVectorMap}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
{DEFAULT_MAP_CHOICE === stateParams.layerChosenByUser ? (
|
||||
<EuiFlexGroup id="defaultMapSelection" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectOption
|
||||
id="regionMapOptionsSelectLayer"
|
||||
label={i18n.translate('regionMap.visParams.vectorMapLabel', {
|
||||
defaultMessage: 'Vector map',
|
||||
})}
|
||||
options={vectorLayerOptions}
|
||||
paramName="selectedLayer"
|
||||
value={stateParams.selectedLayer && stateParams.selectedLayer.layerId}
|
||||
setValue={setLayer}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectOption
|
||||
id="regionMapOptionsSelectJoinField"
|
||||
label={i18n.translate('regionMap.visParams.joinFieldLabel', {
|
||||
defaultMessage: 'Join field',
|
||||
})}
|
||||
options={fieldOptions}
|
||||
paramName="selectedJoinField"
|
||||
value={stateParams.selectedJoinField && stateParams.selectedJoinField.name}
|
||||
setValue={setField}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : (
|
||||
<EuiFlexGroup id="customMapSelection" direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectOption
|
||||
id="regionMapOptionsCustomSelectLayer"
|
||||
label="Vector map"
|
||||
options={customVectorLayerOptions}
|
||||
paramName="selectedCustomLayer"
|
||||
value={stateParams.selectedCustomLayer && stateParams.selectedCustomLayer.layerId}
|
||||
setValue={setCustomLayer}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SelectOption
|
||||
id="regionMapOptionsCustomSelectJoinField"
|
||||
label="Join field"
|
||||
options={customFieldOptions}
|
||||
paramName="selectedCustomJoinField"
|
||||
value={
|
||||
stateParams.selectedCustomJoinField && stateParams.selectedCustomJoinField.name
|
||||
}
|
||||
setValue={setCustomJoinField}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<SwitchOption
|
||||
label={i18n.translate('regionMap.visParams.displayWarningsLabel', {
|
||||
defaultMessage: 'Display warnings',
|
||||
})}
|
||||
tooltip={i18n.translate('regionMap.visParams.switchWarningsTipText', {
|
||||
defaultMessage:
|
||||
'Turns on/off warnings. When turned on, warning will be shown for each term that cannot be matched to a shape in the vector layer based on the join field. When turned off, these warnings will be turned off.',
|
||||
})}
|
||||
paramName="isDisplayWarning"
|
||||
value={stateParams.isDisplayWarning}
|
||||
setValue={setValue}
|
||||
/>
|
||||
|
||||
<SwitchOption
|
||||
label={i18n.translate('regionMap.visParams.showAllShapesLabel', {
|
||||
defaultMessage: 'Show all shapes',
|
||||
})}
|
||||
tooltip={i18n.translate('regionMap.visParams.turnOffShowingAllShapesTipText', {
|
||||
defaultMessage:
|
||||
'Turning this off only shows the shapes that were matched with a corresponding term.',
|
||||
})}
|
||||
paramName="showAllShapes"
|
||||
value={stateParams.showAllShapes}
|
||||
setValue={setValue}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
||||
export { MapChoiceOptions };
|
||||
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { RegionMapOptions } from './region_map_options';
|
||||
import renderer, { act } from 'react-test-renderer';
|
||||
|
||||
describe('region_map_options', () => {
|
||||
it('renders the RegionMapOptions with default option if no custom vector maps are found', async () => {
|
||||
const props = jest.mock;
|
||||
const vis = {
|
||||
type: {
|
||||
editorConfig: {
|
||||
collections: {
|
||||
colorSchemas: [],
|
||||
customVectorLayers: [],
|
||||
tmsLayers: [],
|
||||
vectorLayers: [
|
||||
{
|
||||
attribution:
|
||||
'<a rel="noreferrer noopener" href="http://www.naturalearthdata.com/about/terms-of-use">Made with NaturalEarth</a>',
|
||||
created_at: '2017-04-26T17:12:15.978370',
|
||||
format: 'geojson',
|
||||
fields: [],
|
||||
id: 'sample',
|
||||
meta: undefined,
|
||||
name: 'sample',
|
||||
origin: 'user-upload',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const stateParams = {
|
||||
colorSchema: {},
|
||||
outlineWeight: {},
|
||||
wms: {},
|
||||
selectedJoinField: {
|
||||
name: 'randomId',
|
||||
},
|
||||
selectedLayer: {
|
||||
layerId: 'name',
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'name',
|
||||
property: 'name',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
let tree;
|
||||
await act(async () => {
|
||||
tree = renderer.create(<RegionMapOptions stateParams={stateParams} vis={vis} {...props} />);
|
||||
});
|
||||
expect(tree.toJSON().props.id).toBe('defaultMapOption');
|
||||
expect(tree.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders the RegionMapOptions with custom option if custom vector maps are found', async () => {
|
||||
const props = jest.mock;
|
||||
const vis = {
|
||||
type: {
|
||||
editorConfig: {
|
||||
collections: {
|
||||
colorSchemas: [],
|
||||
customVectorLayers: [
|
||||
{
|
||||
attribution:
|
||||
'<a rel="noreferrer noopener" href="http://www.naturalearthdata.com/about/terms-of-use">Made with NaturalEarth</a>',
|
||||
created_at: '2017-04-26T17:12:15.978370',
|
||||
format: 'geojson',
|
||||
fields: [],
|
||||
id: 'sample',
|
||||
meta: undefined,
|
||||
name: 'sample',
|
||||
origin: 'user-upload',
|
||||
},
|
||||
],
|
||||
tmsLayers: [],
|
||||
vectorLayers: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const stateParams = {
|
||||
colorSchema: {},
|
||||
outlineWeight: {},
|
||||
wms: {},
|
||||
selectedJoinField: {
|
||||
name: 'randomId',
|
||||
},
|
||||
selectedCustomLayer: {
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
property: 'name',
|
||||
type: 'name',
|
||||
},
|
||||
],
|
||||
layerId: 'sample',
|
||||
},
|
||||
};
|
||||
|
||||
let tree;
|
||||
await act(async () => {
|
||||
tree = renderer.create(<RegionMapOptions stateParams={stateParams} vis={vis} {...props} />);
|
||||
});
|
||||
expect(tree.toJSON().props.id).toBe('customMapOption');
|
||||
expect(tree.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@ -28,176 +28,51 @@
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { EuiIcon, EuiLink, EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@osd/i18n';
|
||||
import { FormattedMessage } from '@osd/i18n/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
|
||||
import { FileLayerField, VectorLayer, IServiceSettings } from '../../../maps_legacy/public';
|
||||
import { NumberInputOption, SelectOption, SwitchOption } from '../../../charts/public';
|
||||
import { VectorLayer, IServiceSettings } from '../../../maps_legacy/public';
|
||||
import { RegionMapVisParams, WmsOptions } from '../../../maps_legacy/public';
|
||||
import { DefaultMapOptions } from './default_map_options';
|
||||
import { MapChoiceOptions } from './map_choice_options';
|
||||
import { StyleOptions } from './style_options';
|
||||
|
||||
const mapLayerForOption = ({ layerId, name }: VectorLayer) => ({
|
||||
text: name,
|
||||
value: layerId,
|
||||
});
|
||||
|
||||
const mapFieldForOption = ({ description, name }: FileLayerField) => ({
|
||||
text: description,
|
||||
value: name,
|
||||
});
|
||||
|
||||
export type RegionMapOptionsProps = {
|
||||
getServiceSettings: () => Promise<IServiceSettings>;
|
||||
} & VisOptionsProps<RegionMapVisParams>;
|
||||
|
||||
function RegionMapOptions(props: RegionMapOptionsProps) {
|
||||
const { getServiceSettings, stateParams, vis, setValue } = props;
|
||||
const { vectorLayers } = vis.type.editorConfig.collections;
|
||||
const vectorLayerOptions = useMemo(() => vectorLayers.map(mapLayerForOption), [vectorLayers]);
|
||||
const fieldOptions = useMemo(
|
||||
() =>
|
||||
((stateParams.selectedLayer && stateParams.selectedLayer.fields) || []).map(
|
||||
mapFieldForOption
|
||||
),
|
||||
[stateParams.selectedLayer]
|
||||
);
|
||||
const customVectorLayers = props.vis.type.editorConfig.collections.customVectorLayers;
|
||||
const customVectorLayerOptions = useMemo(() => customVectorLayers.map(mapLayerForOption), [
|
||||
customVectorLayers,
|
||||
]);
|
||||
|
||||
const setEmsHotLink = useCallback(
|
||||
async (layer: VectorLayer) => {
|
||||
const serviceSettings = await getServiceSettings();
|
||||
const emsHotLink = await serviceSettings.getEMSHotLink(layer);
|
||||
setValue('emsHotLink', emsHotLink);
|
||||
},
|
||||
[setValue, getServiceSettings]
|
||||
);
|
||||
|
||||
const setLayer = useCallback(
|
||||
async (paramName: 'selectedLayer', value: VectorLayer['layerId']) => {
|
||||
const newLayer = vectorLayers.find(({ layerId }: VectorLayer) => layerId === value);
|
||||
|
||||
if (newLayer) {
|
||||
setValue(paramName, newLayer);
|
||||
setValue('selectedJoinField', newLayer.fields[0]);
|
||||
setEmsHotLink(newLayer);
|
||||
}
|
||||
},
|
||||
[vectorLayers, setEmsHotLink, setValue]
|
||||
);
|
||||
|
||||
const setField = useCallback(
|
||||
(paramName: 'selectedJoinField', value: FileLayerField['name']) => {
|
||||
if (stateParams.selectedLayer) {
|
||||
setValue(
|
||||
paramName,
|
||||
stateParams.selectedLayer.fields.find((f) => f.name === value)
|
||||
);
|
||||
}
|
||||
},
|
||||
[setValue, stateParams.selectedLayer]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPanel paddingSize="s">
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="regionMap.visParams.layerSettingsTitle"
|
||||
defaultMessage="Layer settings"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
if (customVectorLayerOptions.length === 0) {
|
||||
return (
|
||||
<div id="defaultMapOption">
|
||||
<DefaultMapOptions {...props} />
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<SelectOption
|
||||
id="regionMapOptionsSelectLayer"
|
||||
label={i18n.translate('regionMap.visParams.vectorMapLabel', {
|
||||
defaultMessage: 'Vector map',
|
||||
})}
|
||||
options={vectorLayerOptions}
|
||||
paramName="selectedLayer"
|
||||
value={stateParams.selectedLayer && stateParams.selectedLayer.layerId}
|
||||
setValue={setLayer}
|
||||
/>
|
||||
|
||||
<SelectOption
|
||||
id="regionMapOptionsSelectJoinField"
|
||||
label={i18n.translate('regionMap.visParams.joinFieldLabel', {
|
||||
defaultMessage: 'Join field',
|
||||
})}
|
||||
options={fieldOptions}
|
||||
paramName="selectedJoinField"
|
||||
value={stateParams.selectedJoinField && stateParams.selectedJoinField.name}
|
||||
setValue={setField}
|
||||
/>
|
||||
|
||||
<SwitchOption
|
||||
label={i18n.translate('regionMap.visParams.displayWarningsLabel', {
|
||||
defaultMessage: 'Display warnings',
|
||||
})}
|
||||
tooltip={i18n.translate('regionMap.visParams.switchWarningsTipText', {
|
||||
defaultMessage:
|
||||
'Turns on/off warnings. When turned on, warning will be shown for each term that cannot be matched to a shape in the vector layer based on the join field. When turned off, these warnings will be turned off.',
|
||||
})}
|
||||
paramName="isDisplayWarning"
|
||||
value={stateParams.isDisplayWarning}
|
||||
setValue={setValue}
|
||||
/>
|
||||
|
||||
<SwitchOption
|
||||
label={i18n.translate('regionMap.visParams.showAllShapesLabel', {
|
||||
defaultMessage: 'Show all shapes',
|
||||
})}
|
||||
tooltip={i18n.translate('regionMap.visParams.turnOffShowingAllShapesTipText', {
|
||||
defaultMessage:
|
||||
'Turning this off only shows the shapes that were matched with a corresponding term.',
|
||||
})}
|
||||
paramName="showAllShapes"
|
||||
value={stateParams.showAllShapes}
|
||||
setValue={setValue}
|
||||
/>
|
||||
</EuiPanel>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<EuiPanel paddingSize="s">
|
||||
<EuiTitle size="xs">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="regionMap.visParams.styleSettingsLabel"
|
||||
defaultMessage="Style settings"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<StyleOptions {...props} />
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<SelectOption
|
||||
label={i18n.translate('regionMap.visParams.colorSchemaLabel', {
|
||||
defaultMessage: 'Color schema',
|
||||
})}
|
||||
options={vis.type.editorConfig.collections.colorSchemas}
|
||||
paramName="colorSchema"
|
||||
value={stateParams.colorSchema}
|
||||
setValue={setValue}
|
||||
/>
|
||||
|
||||
<NumberInputOption
|
||||
label={i18n.translate('regionMap.visParams.outlineWeightLabel', {
|
||||
defaultMessage: 'Border thickness',
|
||||
})}
|
||||
min={0}
|
||||
paramName="outlineWeight"
|
||||
value={stateParams.outlineWeight}
|
||||
setValue={setValue}
|
||||
/>
|
||||
</EuiPanel>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<WmsOptions {...props} />
|
||||
</>
|
||||
);
|
||||
<WmsOptions {...props} />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div id="customMapOption">
|
||||
<MapChoiceOptions {...props} />
|
||||
<EuiSpacer size="s" />
|
||||
<StyleOptions {...props} />
|
||||
<EuiSpacer size="s" />
|
||||
<WmsOptions {...props} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { RegionMapOptions };
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { StyleOptions } from './style_options';
|
||||
import renderer, { act } from 'react-test-renderer';
|
||||
|
||||
describe('style_options', () => {
|
||||
it('renders the Style options comprising of style settings, color schema and border thickness', async () => {
|
||||
const props = jest.mock;
|
||||
const vis = {
|
||||
type: {
|
||||
editorConfig: {
|
||||
collections: {
|
||||
colorSchemas: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const stateParams = {
|
||||
colorSchema: {},
|
||||
outlineWeight: {},
|
||||
};
|
||||
|
||||
let tree;
|
||||
await act(async () => {
|
||||
tree = renderer.create(<StyleOptions stateParams={stateParams} vis={vis} {...props} />);
|
||||
});
|
||||
expect(tree.toJSON().children[0].props.id).toBe('styleSettingTitleId');
|
||||
expect(tree.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
58
src/plugins/region_map/public/components/style_options.tsx
Normal file
58
src/plugins/region_map/public/components/style_options.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
|
||||
import { i18n } from '@osd/i18n';
|
||||
import { FormattedMessage } from '@osd/i18n/react';
|
||||
import { VisOptionsProps } from 'src/plugins/vis_default_editor/public';
|
||||
import { IServiceSettings } from '../../../maps_legacy/public';
|
||||
import { NumberInputOption, SelectOption } from '../../../charts/public';
|
||||
import { RegionMapVisParams } from '../../../maps_legacy/public';
|
||||
|
||||
export type StyleOptionsProps = {
|
||||
getServiceSettings: () => Promise<IServiceSettings>;
|
||||
} & VisOptionsProps<RegionMapVisParams>;
|
||||
|
||||
function StyleOptions(props: StyleOptionsProps) {
|
||||
const { stateParams, vis, setValue } = props;
|
||||
return (
|
||||
<EuiPanel paddingSize="s">
|
||||
<EuiTitle size="xs" id="styleSettingTitleId">
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="regionMap.visParams.styleSettingsLabel"
|
||||
defaultMessage="Style settings"
|
||||
/>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<SelectOption
|
||||
label={i18n.translate('regionMap.visParams.colorSchemaLabel', {
|
||||
defaultMessage: 'Color schema',
|
||||
})}
|
||||
options={vis.type.editorConfig.collections.colorSchemas}
|
||||
paramName="colorSchema"
|
||||
value={stateParams.colorSchema}
|
||||
setValue={setValue}
|
||||
id="colorSchemaId"
|
||||
/>
|
||||
|
||||
<NumberInputOption
|
||||
label={i18n.translate('regionMap.visParams.outlineWeightLabel', {
|
||||
defaultMessage: 'Border thickness',
|
||||
})}
|
||||
min={0}
|
||||
paramName="outlineWeight"
|
||||
value={stateParams.outlineWeight}
|
||||
setValue={setValue}
|
||||
id="borderThicknessId"
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
}
|
||||
|
||||
export { StyleOptions };
|
||||
@ -60,7 +60,6 @@ import { SharePluginStart } from '../../share/public';
|
||||
/** @private */
|
||||
export interface RegionMapVisualizationDependencies {
|
||||
http: CoreStart['http'];
|
||||
notifications: CoreStart['notifications'];
|
||||
uiSettings: IUiSettingsClient;
|
||||
regionmapsConfig: RegionMapsConfig;
|
||||
getServiceSettings: () => Promise<IServiceSettings>;
|
||||
@ -123,9 +122,9 @@ export class RegionMapPlugin implements Plugin<RegionMapPluginSetup, RegionMapPl
|
||||
// ideally constrain regionmap config updates to occur only from this plugin
|
||||
...mapsLegacy.config.regionmap,
|
||||
};
|
||||
|
||||
const visualizationDependencies: Readonly<RegionMapVisualizationDependencies> = {
|
||||
http: core.http,
|
||||
notifications: core.notifications,
|
||||
uiSettings: core.uiSettings,
|
||||
regionmapsConfig: config as RegionMapsConfig,
|
||||
getServiceSettings: mapsLegacy.getServiceSettings,
|
||||
|
||||
@ -47,7 +47,6 @@ export const createRegionMapFn = () => ({
|
||||
},
|
||||
fn(context, args) {
|
||||
const visConfig = JSON.parse(args.visConfig);
|
||||
|
||||
return {
|
||||
type: 'render',
|
||||
as: 'visualization',
|
||||
|
||||
@ -36,10 +36,67 @@ import { RegionMapOptions } from './components/region_map_options';
|
||||
import { truncatedColorSchemas } from '../../charts/public';
|
||||
import { Schemas } from '../../vis_default_editor/public';
|
||||
import { ORIGIN } from '../../maps_legacy/public';
|
||||
import { getServices } from './services';
|
||||
import { DEFAULT_MAP_CHOICE } from '../common';
|
||||
|
||||
export function createRegionMapTypeDefinition(dependencies) {
|
||||
const { uiSettings, regionmapsConfig, getServiceSettings, additionalOptions } = dependencies;
|
||||
const {
|
||||
http,
|
||||
uiSettings,
|
||||
regionmapsConfig,
|
||||
getServiceSettings,
|
||||
additionalOptions,
|
||||
} = dependencies;
|
||||
|
||||
const services = getServices(http);
|
||||
const visualization = createRegionMapVisualization(dependencies);
|
||||
const diffArray = (arr1, arr2) => {
|
||||
return arr1.concat(arr2).filter((item) => !arr1.includes(item) || !arr2.includes(item));
|
||||
};
|
||||
|
||||
const getCustomIndices = async () => {
|
||||
try {
|
||||
const result = await services.getCustomIndices();
|
||||
return result.resp;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const getJoinFields = async (indexName) => {
|
||||
try {
|
||||
const result = await services.getIndexMapping(indexName);
|
||||
const properties = diffArray(Object.keys(result.resp[indexName].mappings.properties), [
|
||||
'location',
|
||||
]);
|
||||
return properties.map(function (property) {
|
||||
return {
|
||||
type: 'id',
|
||||
name: property,
|
||||
description: property,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const addSchemaToCustomLayer = async (customlayer) => {
|
||||
const joinFields = await getJoinFields(customlayer.index);
|
||||
const customLayerWithSchema = {
|
||||
attribution:
|
||||
'<a rel="noreferrer noopener" href="http://www.naturalearthdata.com/about/terms-of-use">Made with NaturalEarth</a>',
|
||||
created_at: '2017-04-26T17:12:15.978370',
|
||||
format: 'geojson',
|
||||
fields: joinFields,
|
||||
id: customlayer.index,
|
||||
meta: undefined,
|
||||
name: customlayer.index,
|
||||
origin: 'user-upload',
|
||||
};
|
||||
|
||||
return customLayerWithSchema;
|
||||
};
|
||||
|
||||
return {
|
||||
name: 'region_map',
|
||||
@ -52,6 +109,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||
icon: 'visMapRegion',
|
||||
visConfig: {
|
||||
defaults: {
|
||||
layerChosenByUser: DEFAULT_MAP_CHOICE,
|
||||
legendPosition: 'bottomright',
|
||||
addTooltip: true,
|
||||
colorSchema: 'Yellow to Red',
|
||||
@ -86,6 +144,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||
collections: {
|
||||
colorSchemas: truncatedColorSchemas,
|
||||
vectorLayers: [],
|
||||
customVectorLayers: [],
|
||||
tmsLayers: [],
|
||||
},
|
||||
schemas: new Schemas([
|
||||
@ -127,6 +186,7 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||
setup: async (vis) => {
|
||||
const serviceSettings = await getServiceSettings();
|
||||
const tmsLayers = await serviceSettings.getTMSServices();
|
||||
|
||||
vis.type.editorConfig.collections.tmsLayers = tmsLayers;
|
||||
if (!vis.params.wms.selectedTmsLayer && tmsLayers.length) {
|
||||
vis.params.wms.selectedTmsLayer = tmsLayers[0];
|
||||
@ -135,8 +195,16 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||
const vectorLayers = regionmapsConfig.layers.map(
|
||||
mapToLayerWithId.bind(null, ORIGIN.OPENSEARCH_DASHBOARDS_YML)
|
||||
);
|
||||
const customVectorLayers = regionmapsConfig.layers.map(
|
||||
mapToLayerWithId.bind(null, ORIGIN.OPENSEARCH_DASHBOARDS_YML)
|
||||
);
|
||||
const customIndices = await getCustomIndices();
|
||||
|
||||
let selectedLayer = vectorLayers[0];
|
||||
let selectedCustomLayer = customVectorLayers[0];
|
||||
let selectedJoinField = selectedLayer ? selectedLayer.fields[0] : null;
|
||||
const selectedCustomJoinField = selectedCustomLayer ? selectedCustomLayer.fields[0] : null;
|
||||
|
||||
if (regionmapsConfig.includeOpenSearchMapsService) {
|
||||
const layers = await serviceSettings.getFileLayers();
|
||||
const newLayers = layers
|
||||
@ -144,6 +212,8 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||
.filter(
|
||||
(layer) => !vectorLayers.some((vectorLayer) => vectorLayer.layerId === layer.layerId)
|
||||
);
|
||||
const promises = customIndices.map(addSchemaToCustomLayer);
|
||||
const newCustomLayers = await Promise.all(promises);
|
||||
|
||||
// backfill v1 manifest for now
|
||||
newLayers.forEach((layer) => {
|
||||
@ -154,9 +224,27 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||
}
|
||||
});
|
||||
|
||||
newCustomLayers.forEach((layer) => {
|
||||
if (layer.format === 'geojson') {
|
||||
layer.format = {
|
||||
type: 'geojson',
|
||||
};
|
||||
layer.isEMS = false;
|
||||
layer.layerId = layer.origin + '.' + layer.name;
|
||||
}
|
||||
});
|
||||
|
||||
vis.type.editorConfig.collections.vectorLayers = [...vectorLayers, ...newLayers];
|
||||
vis.type.editorConfig.collections.customVectorLayers = [
|
||||
...customVectorLayers,
|
||||
...newCustomLayers,
|
||||
];
|
||||
|
||||
[selectedLayer] = vis.type.editorConfig.collections.vectorLayers;
|
||||
[selectedCustomLayer] = vis.type.editorConfig.collections.customVectorLayers;
|
||||
vis.params.selectedCustomLayer = selectedCustomLayer;
|
||||
vis.params.selectedCustomJoinField = selectedCustomJoinField;
|
||||
|
||||
selectedJoinField = selectedLayer ? selectedLayer.fields[0] : null;
|
||||
|
||||
if (selectedLayer && !vis.params.selectedLayer && selectedLayer.isEMS) {
|
||||
@ -169,6 +257,10 @@ provided base maps, or add your own. Darker colors represent higher values.',
|
||||
vis.params.selectedJoinField = selectedJoinField;
|
||||
}
|
||||
|
||||
vis.params.layerChosenByUser = vis.params.layerChosenByUser
|
||||
? vis.params.layerChosenByUser
|
||||
: DEFAULT_MAP_CHOICE;
|
||||
|
||||
return vis;
|
||||
},
|
||||
};
|
||||
|
||||
@ -37,8 +37,10 @@ import {
|
||||
import { truncatedColorMaps } from '../../charts/public';
|
||||
import { tooltipFormatter } from './tooltip_formatter';
|
||||
import { mapTooltipProvider, ORIGIN, lazyLoadMapsLegacyModules } from '../../maps_legacy/public';
|
||||
import { DEFAULT_MAP_CHOICE } from '../common';
|
||||
|
||||
export function createRegionMapVisualization({
|
||||
http,
|
||||
regionmapsConfig,
|
||||
uiSettings,
|
||||
BaseMapsVisualization,
|
||||
@ -54,8 +56,14 @@ export function createRegionMapVisualization({
|
||||
|
||||
async render(opensearchResponse, visParams) {
|
||||
getOpenSearchDashboardsLegacy().loadFontAwesome();
|
||||
this._choroplethLayer?.setLayerChosenByUser(visParams.layerChosenByUser);
|
||||
this._choroplethLayer?.setVisParams(visParams);
|
||||
await super.render(opensearchResponse, visParams);
|
||||
|
||||
// fetches geojson data
|
||||
if (this._choroplethLayer) {
|
||||
this._choroplethLayer.setLayerChosenByUser(visParams.layerChosenByUser);
|
||||
this._choroplethLayer.setVisParams(visParams);
|
||||
await this._choroplethLayer.whenDataLoaded();
|
||||
}
|
||||
}
|
||||
@ -75,7 +83,15 @@ export function createRegionMapVisualization({
|
||||
});
|
||||
}
|
||||
|
||||
const selectedLayer = await this._loadConfig(this._params.selectedLayer);
|
||||
let selectedLayer;
|
||||
if (DEFAULT_MAP_CHOICE === this._params.layerChosenByUser) {
|
||||
selectedLayer = await this._loadConfig(this._params.selectedLayer);
|
||||
this._params.selectedJoinField = selectedLayer.fields[0];
|
||||
} else {
|
||||
selectedLayer = this._params.selectedCustomLayer;
|
||||
this._params.selectedJoinField = this._params.selectedCustomJoinField;
|
||||
}
|
||||
|
||||
if (!this._params.selectedJoinField && selectedLayer) {
|
||||
this._params.selectedJoinField = selectedLayer.fields[0];
|
||||
}
|
||||
@ -112,7 +128,6 @@ export function createRegionMapVisualization({
|
||||
// These settings are stored in the URL and can be used to inject dirty display content.
|
||||
|
||||
const { escape } = await import('lodash');
|
||||
|
||||
if (
|
||||
fileLayerConfig &&
|
||||
(fileLayerConfig.isEMS || //Hosted by EMS. Metadata needs to be resolved through EMS
|
||||
@ -140,8 +155,14 @@ export function createRegionMapVisualization({
|
||||
|
||||
async _updateParams() {
|
||||
await super._updateParams();
|
||||
|
||||
const selectedLayer = await this._loadConfig(this._params.selectedLayer);
|
||||
let selectedLayer;
|
||||
if (DEFAULT_MAP_CHOICE === this._params.layerChosenByUser) {
|
||||
selectedLayer = await this._loadConfig(this._params.selectedLayer);
|
||||
this._params.selectedJoinField = selectedLayer.fields[0];
|
||||
} else {
|
||||
selectedLayer = this._params.selectedCustomLayer;
|
||||
this._params.selectedJoinField = this._params.selectedCustomJoinField;
|
||||
}
|
||||
|
||||
if (!this._params.selectedJoinField && selectedLayer) {
|
||||
this._params.selectedJoinField = selectedLayer.fields[0];
|
||||
@ -159,6 +180,8 @@ export function createRegionMapVisualization({
|
||||
|
||||
const metricFieldFormatter = getFormatService().deserialize(this._params.metric.format);
|
||||
|
||||
this._choroplethLayer.setLayerChosenByUser(this._params.layerChosenByUser);
|
||||
|
||||
this._choroplethLayer.setJoinField(this._params.selectedJoinField.name);
|
||||
this._choroplethLayer.setColorRamp(truncatedColorMaps[this._params.colorSchema].value);
|
||||
this._choroplethLayer.setLineWeight(this._params.outlineWeight);
|
||||
@ -170,6 +193,7 @@ export function createRegionMapVisualization({
|
||||
}
|
||||
|
||||
async _updateChoroplethLayerForNewMetrics(name, attribution, showAllData, newMetrics) {
|
||||
this._choroplethLayer.setLayerChosenByUser(this._params.layerChosenByUser);
|
||||
if (
|
||||
this._choroplethLayer &&
|
||||
this._choroplethLayer.canReuseInstanceForNewMetrics(name, showAllData, newMetrics)
|
||||
@ -187,7 +211,10 @@ export function createRegionMapVisualization({
|
||||
}
|
||||
|
||||
async _recreateChoroplethLayer(name, attribution, showAllData) {
|
||||
const selectedLayer = await this._loadConfig(this._params.selectedLayer);
|
||||
const selectedLayer =
|
||||
DEFAULT_MAP_CHOICE === this._params.layerChosenByUser
|
||||
? await this._loadConfig(this._params.selectedLayer)
|
||||
: this._params.selectedCustomLayer;
|
||||
this._opensearchDashboardsMap.removeLayer(this._choroplethLayer);
|
||||
|
||||
if (this._choroplethLayer) {
|
||||
@ -199,7 +226,9 @@ export function createRegionMapVisualization({
|
||||
selectedLayer.meta,
|
||||
selectedLayer,
|
||||
await getServiceSettings(),
|
||||
(await lazyLoadMapsLegacyModules()).L
|
||||
(await lazyLoadMapsLegacyModules()).L,
|
||||
this._params.layerChosenByUser,
|
||||
http
|
||||
);
|
||||
} else {
|
||||
const { ChoroplethLayer } = await import('./choropleth_layer');
|
||||
@ -211,9 +240,12 @@ export function createRegionMapVisualization({
|
||||
selectedLayer.meta,
|
||||
selectedLayer,
|
||||
await getServiceSettings(),
|
||||
(await lazyLoadMapsLegacyModules()).L
|
||||
(await lazyLoadMapsLegacyModules()).L,
|
||||
this._params.layerChosenByUser,
|
||||
http
|
||||
);
|
||||
}
|
||||
this._choroplethLayer.setLayerChosenByUser(this._params.layerChosenByUser);
|
||||
|
||||
this._choroplethLayer.on('select', (event) => {
|
||||
const { rows, columns } = this._chartData;
|
||||
|
||||
53
src/plugins/region_map/public/services.ts
Normal file
53
src/plugins/region_map/public/services.ts
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { CoreStart, HttpFetchError } from 'opensearch-dashboards/public';
|
||||
|
||||
export interface Services {
|
||||
getCustomIndices: () => Promise<undefined | HttpFetchError>;
|
||||
getIndexData: (indexName: string) => Promise<undefined | HttpFetchError>;
|
||||
getIndexMapping: (indexName: string) => Promise<undefined | HttpFetchError>;
|
||||
}
|
||||
|
||||
export function getServices(http: CoreStart['http']): Services {
|
||||
return {
|
||||
getCustomIndices: async () => {
|
||||
try {
|
||||
const response = await http.post('../api/geospatial/_indices', {
|
||||
body: JSON.stringify({
|
||||
index: '*-map',
|
||||
}),
|
||||
});
|
||||
return response;
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
},
|
||||
getIndexData: async (indexName: string) => {
|
||||
try {
|
||||
const response = await http.post('../api/geospatial/_search', {
|
||||
body: JSON.stringify({
|
||||
index: indexName,
|
||||
}),
|
||||
});
|
||||
return response;
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
},
|
||||
getIndexMapping: async (indexName: string) => {
|
||||
try {
|
||||
const response = await http.post('../api/geospatial/_mappings', {
|
||||
body: JSON.stringify({
|
||||
index: indexName,
|
||||
}),
|
||||
});
|
||||
return response;
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -29,9 +29,9 @@
|
||||
*/
|
||||
|
||||
import { PluginConfigDescriptor } from 'opensearch-dashboards/server';
|
||||
import { CoreSetup } from 'src/core/server';
|
||||
import { PluginInitializerContext } from 'src/core/server';
|
||||
import { configSchema, ConfigSchema } from '../config';
|
||||
import { getUiSettings } from './ui_settings';
|
||||
import { RegionMapPlugin } from './plugin';
|
||||
|
||||
export const config: PluginConfigDescriptor<ConfigSchema> = {
|
||||
exposeToBrowser: {
|
||||
@ -47,10 +47,8 @@ export const config: PluginConfigDescriptor<ConfigSchema> = {
|
||||
],
|
||||
};
|
||||
|
||||
export const plugin = () => ({
|
||||
setup(core: CoreSetup) {
|
||||
core.uiSettings.register(getUiSettings());
|
||||
},
|
||||
export function plugin(initializerContext: PluginInitializerContext) {
|
||||
return new RegionMapPlugin(initializerContext);
|
||||
}
|
||||
|
||||
start() {},
|
||||
});
|
||||
export { RegionMapPluginSetup, RegionMapPluginStart } from './types';
|
||||
|
||||
34
src/plugins/region_map/server/plugin.js
Normal file
34
src/plugins/region_map/server/plugin.js
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { OpensearchService } from './services';
|
||||
import { opensearch } from '../server/routes';
|
||||
import { getUiSettings } from './ui_settings';
|
||||
|
||||
export class RegionMapPlugin {
|
||||
constructor(initializerContext) {
|
||||
this.logger = initializerContext.logger.get();
|
||||
}
|
||||
|
||||
async setup(core) {
|
||||
const opensearchClient = core.opensearch.legacy.createClient('opensearch');
|
||||
|
||||
// Initialize services
|
||||
const opensearchService = new OpensearchService(opensearchClient);
|
||||
|
||||
// Register server side APIs
|
||||
const router = core.http.createRouter();
|
||||
core.uiSettings.register(getUiSettings());
|
||||
opensearch(opensearchService, router);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
async start() {
|
||||
return {};
|
||||
}
|
||||
|
||||
async stop() {}
|
||||
}
|
||||
8
src/plugins/region_map/server/routes/index.ts
Normal file
8
src/plugins/region_map/server/routes/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import opensearch from './opensearch';
|
||||
|
||||
export { opensearch };
|
||||
45
src/plugins/region_map/server/routes/opensearch.ts
Normal file
45
src/plugins/region_map/server/routes/opensearch.ts
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { schema } from '@osd/config-schema';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function (services, router) {
|
||||
router.post(
|
||||
{
|
||||
path: '/api/geospatial/_indices',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
index: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
services.getIndex
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/api/geospatial/_search',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
index: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
services.search
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/api/geospatial/_mappings',
|
||||
validate: {
|
||||
body: schema.object({
|
||||
index: schema.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
services.getMappings
|
||||
);
|
||||
}
|
||||
8
src/plugins/region_map/server/services/index.js
Normal file
8
src/plugins/region_map/server/services/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import OpensearchService from './opensearch_service';
|
||||
|
||||
export { OpensearchService };
|
||||
88
src/plugins/region_map/server/services/opensearch_service.js
Normal file
88
src/plugins/region_map/server/services/opensearch_service.js
Normal file
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
export default class OpensearchService {
|
||||
constructor(esDriver) {
|
||||
this.esDriver = esDriver;
|
||||
}
|
||||
|
||||
getMappings = async (context, req, res) => {
|
||||
try {
|
||||
const { index } = req.body;
|
||||
const { callAsCurrentUser } = this.esDriver.asScoped(req);
|
||||
const mappings = await callAsCurrentUser('indices.getMapping', { index });
|
||||
return res.ok({
|
||||
body: {
|
||||
ok: true,
|
||||
resp: mappings,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return res.ok({
|
||||
body: {
|
||||
ok: false,
|
||||
resp: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
search = async (context, req, res) => {
|
||||
try {
|
||||
const { query, index, size } = req.body;
|
||||
const params = { index, size, body: query };
|
||||
const { callAsCurrentUser } = this.esDriver.asScoped(req);
|
||||
const results = await callAsCurrentUser('search', params);
|
||||
return res.ok({
|
||||
body: {
|
||||
ok: true,
|
||||
resp: results,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
return res.ok({
|
||||
body: {
|
||||
ok: false,
|
||||
resp: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getIndex = async (context, req, res) => {
|
||||
try {
|
||||
const { index } = req.body;
|
||||
const { callAsCurrentUser } = this.esDriver.asScoped(req);
|
||||
const indices = await callAsCurrentUser('cat.indices', {
|
||||
index,
|
||||
format: 'json',
|
||||
h: 'health,index,status',
|
||||
});
|
||||
return res.ok({
|
||||
body: {
|
||||
ok: true,
|
||||
resp: indices,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
// Opensearch throws an index_not_found_exception which we'll treat as a success
|
||||
if (err.statusCode === 404) {
|
||||
return res.ok({
|
||||
body: {
|
||||
ok: false,
|
||||
resp: [],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return res.ok({
|
||||
body: {
|
||||
ok: false,
|
||||
resp: err.message,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
9
src/plugins/region_map/server/types.ts
Normal file
9
src/plugins/region_map/server/types.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Copyright OpenSearch Contributors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface RegionMapPluginSetup {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface RegionMapPluginStart {}
|
||||
Loading…
x
Reference in New Issue
Block a user