-
+
+ {inDashboardMode ? null : (
+
+ )}
+
{
maplibreRef={maplibreRef}
mapState={mapState}
mapConfig={mapConfig}
+ inDashboardMode={inDashboardMode}
+ timeRange={timeRange}
+ refreshConfig={refreshConfig}
+ filters={filters}
+ query={query}
/>
);
};
+
+export const MapPage = ({ mapConfig }: MapPageProps) => {
+ const { id: mapId } = useParams<{ id: string }>();
+ return (
+
+ );
+};
diff --git a/public/components/map_top_nav/get_top_nav_config.tsx b/public/components/map_top_nav/get_top_nav_config.tsx
index 4269c356..d69f8fc6 100644
--- a/public/components/map_top_nav/get_top_nav_config.tsx
+++ b/public/components/map_top_nav/get_top_nav_config.tsx
@@ -7,15 +7,14 @@ import React from 'react';
import { i18n } from '@osd/i18n';
import { TopNavMenuData } from '../../../../../src/plugins/navigation/public';
import {
- OnSaveProps,
SavedObjectSaveModalOrigin,
showSaveModal,
checkForDuplicateTitle,
+ SavedObjectSaveOpts,
} from '../../../../../src/plugins/saved_objects/public';
import { MapServices } from '../../types';
import { MapState } from '../../model/mapState';
-
-const SAVED_OBJECT_TYPE = 'map';
+import { MAP_SAVED_OBJECT_TYPE } from '../../../common';
interface GetTopNavConfigParams {
mapIdFromUrl: string;
@@ -25,16 +24,11 @@ interface GetTopNavConfigParams {
setTitle: (title: string) => void;
setDescription: (description: string) => void;
mapState: MapState;
+ originatingApp?: string;
}
export const getTopNavConfig = (
- {
- notifications: { toasts },
- i18n: { Context: I18nContext },
- savedObjects: { client: savedObjectsClient },
- history,
- overlays,
- }: MapServices,
+ services: MapServices,
{
mapIdFromUrl,
layers,
@@ -43,8 +37,15 @@ export const getTopNavConfig = (
setTitle,
setDescription,
mapState,
+ originatingApp,
}: GetTopNavConfigParams
) => {
+ const {
+ embeddable,
+ i18n: { Context: I18nContext },
+ scopedHistory,
+ } = services;
+ const stateTransfer = embeddable.getStateTransfer(scopedHistory);
const topNavConfig: TopNavMenuData[] = [
{
iconType: 'save',
@@ -53,65 +54,8 @@ export const getTopNavConfig = (
label: i18n.translate('maps.topNav.saveMapButtonLabel', {
defaultMessage: `Save`,
}),
- run: (_anchorElement) => {
- const onModalSave = async ({ newTitle, newDescription, onTitleDuplicate }: OnSaveProps) => {
- let newlySavedMap;
- const saveAttributes = {
- title: newTitle,
- description: newDescription,
- layerList: JSON.stringify(layers),
- mapState: JSON.stringify(mapState),
- };
- try {
- await checkForDuplicateTitle(
- {
- title: newTitle,
- lastSavedTitle: title,
- copyOnSave: false,
- getDisplayName: () => SAVED_OBJECT_TYPE,
- getOpenSearchType: () => SAVED_OBJECT_TYPE,
- },
- false,
- onTitleDuplicate,
- {
- savedObjectsClient,
- overlays,
- }
- );
- } catch (_error) {
- return {};
- }
- if (mapIdFromUrl) {
- // edit existing map
- newlySavedMap = await savedObjectsClient.update(
- SAVED_OBJECT_TYPE,
- mapIdFromUrl,
- saveAttributes
- );
- } else {
- // save new map
- newlySavedMap = await savedObjectsClient.create(SAVED_OBJECT_TYPE, saveAttributes);
- }
- const id = newlySavedMap.id;
- if (id) {
- history.push({
- ...history.location,
- pathname: `${id}`,
- });
- setTitle(newTitle);
- setDescription(newDescription);
- toasts.addSuccess({
- title: i18n.translate('map.topNavMenu.saveMap.successNotificationText', {
- defaultMessage: `Saved ${newTitle}`,
- values: {
- visTitle: newTitle,
- },
- }),
- });
- }
- return { id };
- };
-
+ testId: 'mapSaveButton',
+ run: (_anchorElement: any) => {
const documentInfo = {
title,
description,
@@ -120,9 +64,20 @@ export const getTopNavConfig = (
const saveModal = (
{}}
+ originatingApp={originatingApp}
+ getAppNameFromId={stateTransfer.getAppNameFromId}
/>
);
showSaveModal(saveModal, I18nContext);
@@ -131,3 +86,121 @@ export const getTopNavConfig = (
];
return topNavConfig;
};
+
+export const onGetSave = (
+ title: string,
+ originatingApp: string | undefined,
+ mapIdFromUrl: string,
+ services: MapServices,
+ layers: any,
+ mapState: MapState,
+ setTitle: (title: string) => void,
+ setDescription: (description: string) => void
+) => {
+ const onSave = async ({
+ newTitle,
+ newDescription,
+ onTitleDuplicate,
+ returnToOrigin,
+ }: SavedObjectSaveOpts & {
+ newTitle: string;
+ newCopyOnSave: boolean;
+ returnToOrigin: boolean;
+ newDescription?: string;
+ }) => {
+ const {
+ savedObjects: { client: savedObjectsClient },
+ history,
+ toastNotifications,
+ overlays,
+ embeddable,
+ application,
+ } = services;
+ const stateTransfer = embeddable.getStateTransfer();
+ let newlySavedMap;
+ const saveAttributes = {
+ title: newTitle,
+ description: newDescription,
+ layerList: JSON.stringify(layers),
+ mapState: JSON.stringify(mapState),
+ };
+ try {
+ await checkForDuplicateTitle(
+ {
+ title: newTitle,
+ lastSavedTitle: title,
+ copyOnSave: false,
+ getDisplayName: () => MAP_SAVED_OBJECT_TYPE,
+ getOpenSearchType: () => MAP_SAVED_OBJECT_TYPE,
+ },
+ false,
+ onTitleDuplicate,
+ {
+ savedObjectsClient,
+ overlays,
+ }
+ );
+ } catch (_error) {
+ return {};
+ }
+ try {
+ if (mapIdFromUrl) {
+ // edit existing map
+ newlySavedMap = await savedObjectsClient.update(
+ MAP_SAVED_OBJECT_TYPE,
+ mapIdFromUrl,
+ saveAttributes
+ );
+ } else {
+ // save new map
+ newlySavedMap = await savedObjectsClient.create(MAP_SAVED_OBJECT_TYPE, saveAttributes);
+ }
+ const id = newlySavedMap.id;
+ if (id) {
+ history.push({
+ ...history.location,
+ pathname: `${id}`,
+ });
+ setTitle(newTitle);
+ if (newDescription) {
+ setDescription(newDescription);
+ }
+ toastNotifications.addSuccess({
+ title: i18n.translate('map.topNavMenu.saveMap.successNotificationText', {
+ defaultMessage: `Saved ${newTitle}`,
+ values: {
+ visTitle: newTitle,
+ },
+ }),
+ });
+ if (originatingApp && returnToOrigin) {
+ // create or edit map directly from another app, such as `dashboard`
+ if (!mapIdFromUrl && stateTransfer) {
+ // create new embeddable to transfer to originatingApp
+ await stateTransfer.navigateToWithEmbeddablePackage(originatingApp, {
+ state: { type: MAP_SAVED_OBJECT_TYPE, input: { savedObjectId: id } },
+ });
+ return { id };
+ } else {
+ // update an existing visBuilder from another app
+ application.navigateToApp(originatingApp);
+ }
+ }
+ }
+ return { id };
+ } catch (error: any) {
+ toastNotifications.addDanger({
+ title: i18n.translate('maps.topNavMenu.saveVisualization.failureNotificationText', {
+ defaultMessage: `Error on saving ${newTitle}`,
+ values: {
+ visTitle: newTitle,
+ },
+ }),
+ text: error.message,
+ 'data-test-subj': 'saveMapError',
+ });
+ return { error };
+ }
+ };
+ return onSave;
+};
diff --git a/public/components/map_top_nav/top_nav_menu.tsx b/public/components/map_top_nav/top_nav_menu.tsx
index 15b2fbe3..b5b9a184 100644
--- a/public/components/map_top_nav/top_nav_menu.tsx
+++ b/public/components/map_top_nav/top_nav_menu.tsx
@@ -3,10 +3,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import React, { useCallback, useEffect, useState } from 'react';
-import { SimpleSavedObject } from 'opensearch-dashboards/public';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { SimpleSavedObject } from '../../../../../src/core/public';
import { IndexPattern, Query, TimeRange } from '../../../../../src/plugins/data/public';
-import { DASHBOARDS_MAPS_LAYER_TYPE, PLUGIN_NAVIGATION_BAR_ID } from '../../../common';
+import { DASHBOARDS_MAPS_LAYER_TYPE, MAPS_APP_ID } from '../../../common';
import { getTopNavConfig } from './get_top_nav_config';
import { useOpenSearchDashboards } from '../../../../../src/plugins/opensearch_dashboards_react/public';
import { MapServices } from '../../types';
@@ -24,11 +24,16 @@ interface MapTopNavMenuProps {
maplibreRef: any;
mapState: MapState;
setMapState: (mapState: MapState) => void;
+ inDashboardMode: boolean;
+ timeRange?: TimeRange;
+ originatingApp?: string;
}
export const MapTopNavMenu = ({
mapIdFromUrl,
savedMapObject,
+ inDashboardMode,
+ timeRange,
layers,
layersIndexPatterns,
maplibreRef,
@@ -43,6 +48,8 @@ export const MapTopNavMenu = ({
},
chrome,
application: { navigateToApp },
+ embeddable,
+ scopedHistory,
} = services;
const [title, setTitle] = useState('');
@@ -52,6 +59,7 @@ export const MapTopNavMenu = ({
const [queryConfig, setQueryConfig] = useState({ query: '', language: 'kuery' });
const [refreshIntervalValue, setRefreshIntervalValue] = useState(60000);
const [isRefreshPaused, setIsRefreshPaused] = useState(false);
+ const [originatingApp, setOriginatingApp] = useState();
const changeTitle = useCallback(
(newTitle: string) => {
chrome.setBreadcrumbs(getSavedMapBreadcrumbs(newTitle, navigateToApp));
@@ -60,6 +68,14 @@ export const MapTopNavMenu = ({
[chrome, navigateToApp]
);
+ useEffect(() => {
+ const { originatingApp: value } =
+ embeddable
+ .getStateTransfer(scopedHistory)
+ .getIncomingEditorState({ keysToRemoveAfterFetch: ['id', 'input'] }) || {};
+ setOriginatingApp(value);
+ }, [embeddable, scopedHistory]);
+
useEffect(() => {
if (savedMapObject) {
setTitle(savedMapObject.attributes.title);
@@ -73,10 +89,9 @@ export const MapTopNavMenu = ({
const refreshDataLayerRender = () => {
layers.forEach((layer: MapLayerSpecification) => {
- if (layer.type === DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP) {
- return;
+ if (layer.type === DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS) {
+ handleDataLayerRender(layer, mapState, services, maplibreRef, undefined);
}
- handleDataLayerRender(layer, mapState, services, maplibreRef, undefined);
});
};
@@ -90,13 +105,17 @@ export const MapTopNavMenu = ({
};
useEffect(() => {
- setDateFrom(mapState.timeRange.from);
- setDateTo(mapState.timeRange.to);
+ if (!inDashboardMode) {
+ setDateFrom(mapState.timeRange.from);
+ setDateTo(mapState.timeRange.to);
+ } else {
+ setDateFrom(timeRange!.from);
+ setDateTo(timeRange!.to);
+ }
setQueryConfig(mapState.query);
setIsRefreshPaused(mapState.refreshInterval.pause);
setRefreshIntervalValue(mapState.refreshInterval.value);
- refreshDataLayerRender();
- }, [mapState]);
+ }, [mapState, timeRange]);
const onRefreshChange = useCallback(
({ isPaused, refreshInterval }: { isPaused: boolean; refreshInterval: number }) => {
@@ -106,23 +125,28 @@ export const MapTopNavMenu = ({
[]
);
+ const config = useMemo(() => {
+ return getTopNavConfig(services, {
+ mapIdFromUrl,
+ layers,
+ title,
+ description,
+ setTitle,
+ setDescription,
+ mapState,
+ originatingApp,
+ });
+ }, [services, mapIdFromUrl, layers, title, description, mapState, originatingApp]);
+
return (
{
const {
@@ -39,7 +39,7 @@ export const MapsList = () => {
}, [docTitle, navigateToApp, setBreadcrumbs]);
const navigateToSavedMapPage = (id: string) => {
- navigateToApp(PLUGIN_NAVIGATION_BAR_ID, { path: `/${id}` });
+ navigateToApp(MAPS_APP_ID, { path: `/${id}` });
};
const tableColumns = [
@@ -70,7 +70,7 @@ export const MapsList = () => {
];
const navigateToCreateMapPage = () => {
- navigateToApp(PLUGIN_NAVIGATION_BAR_ID, { path: APP_PATH.CREATE_MAP });
+ navigateToApp(MAPS_APP_ID, { path: APP_PATH.CREATE_MAP });
};
const fetchMaps = useCallback(async (): Promise<{
diff --git a/public/embeddable/index.ts b/public/embeddable/index.ts
new file mode 100644
index 00000000..9687b6ce
--- /dev/null
+++ b/public/embeddable/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+export * from './map_embeddable';
+export * from './map_embeddable_factory';
diff --git a/public/embeddable/map_component.tsx b/public/embeddable/map_component.tsx
new file mode 100644
index 00000000..a2fb58f4
--- /dev/null
+++ b/public/embeddable/map_component.tsx
@@ -0,0 +1,57 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useEffect, useState } from 'react';
+import {
+ withEmbeddableSubscription,
+ EmbeddableOutput,
+} from '../../../../src/plugins/embeddable/public';
+import { MapEmbeddable, MapInput } from './map_embeddable';
+import { MapComponent } from '../components/map_page/';
+import { OpenSearchDashboardsContextProvider } from '../../../../src/plugins/opensearch_dashboards_react/public';
+import { MapServices } from '../types';
+import { TimeRange } from '../../../../src/plugins/data/common';
+
+interface Props {
+ embeddable: MapEmbeddable;
+ input: MapInput;
+ output: EmbeddableOutput;
+}
+export function MapEmbeddableComponentInner({ embeddable, input }: Props) {
+ const [timeRange, setTimeRange] = useState(input.timeRange);
+ const [refreshConfig, setRefreshConfig] = useState(input.refreshConfig);
+ const [filters, setFilters] = useState(input.filters);
+ const [query, setQuery] = useState(input.query);
+ const services: MapServices = {
+ ...embeddable.getServiceSettings(),
+ };
+
+ useEffect(() => {
+ setTimeRange(input.timeRange);
+ setRefreshConfig(input.refreshConfig);
+ setFilters(input.filters);
+ setQuery(input.query);
+ }, [input.refreshConfig, input.timeRange, input.filters, input.query]);
+
+ return (
+
+
+
+ );
+}
+
+export const MapEmbeddableComponent = withEmbeddableSubscription<
+ MapInput,
+ EmbeddableOutput,
+ MapEmbeddable
+>(MapEmbeddableComponentInner);
diff --git a/public/embeddable/map_embeddable.tsx b/public/embeddable/map_embeddable.tsx
new file mode 100644
index 00000000..72e03d0b
--- /dev/null
+++ b/public/embeddable/map_embeddable.tsx
@@ -0,0 +1,97 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { Subscription } from 'rxjs';
+import { MAP_SAVED_OBJECT_TYPE, MAPS_APP_ID } from '../../common';
+import {
+ Embeddable,
+ EmbeddableInput,
+ EmbeddableOutput,
+ IContainer,
+} from '../../../../src/plugins/embeddable/public';
+import { MapEmbeddableComponent } from './map_component';
+import { ConfigSchema } from '../../common/config';
+import { MapSavedObjectAttributes } from '../../common/map_saved_object_attributes';
+import { RefreshInterval } from '../../../../src/plugins/data/public';
+
+export const MAP_EMBEDDABLE = MAP_SAVED_OBJECT_TYPE;
+
+export interface MapInput extends EmbeddableInput {
+ savedObjectId: string;
+ refreshConfig?: RefreshInterval;
+}
+
+export type MapOutput = EmbeddableOutput;
+
+function getOutput(input: MapInput, editUrl: string, tittle: string): MapOutput {
+ return {
+ editable: true,
+ editUrl,
+ defaultTitle: tittle,
+ editApp: MAPS_APP_ID,
+ editPath: input.savedObjectId,
+ };
+}
+
+export class MapEmbeddable extends Embeddable {
+ public readonly type = MAP_EMBEDDABLE;
+ private subscription: Subscription;
+ private node?: HTMLElement;
+ private readonly mapConfig: ConfigSchema;
+ private readonly services: any;
+ constructor(
+ initialInput: MapInput,
+ {
+ parent,
+ services,
+ mapConfig,
+ editUrl,
+ savedMapAttributes,
+ }: {
+ parent?: IContainer;
+ services: any;
+ mapConfig: ConfigSchema;
+ editUrl: string;
+ savedMapAttributes: MapSavedObjectAttributes;
+ }
+ ) {
+ super(initialInput, getOutput(initialInput, editUrl, savedMapAttributes.title), parent);
+ this.mapConfig = mapConfig;
+ this.services = services;
+ this.subscription = this.getInput$().subscribe(() => {
+ this.updateOutput(getOutput(this.input, editUrl, savedMapAttributes.title));
+ });
+ }
+
+ public render(node: HTMLElement) {
+ this.node = node;
+ if (this.node) {
+ ReactDOM.unmountComponentAtNode(this.node);
+ }
+ ReactDOM.render(, node);
+ }
+
+ public reload() {
+ if (this.node) {
+ this.render(this.node);
+ }
+ }
+
+ public destroy() {
+ super.destroy();
+ this.subscription.unsubscribe();
+ if (this.node) {
+ ReactDOM.unmountComponentAtNode(this.node);
+ }
+ }
+ public getServiceSettings() {
+ return this.services;
+ }
+ public getMapConfig() {
+ return this.mapConfig;
+ }
+}
diff --git a/public/embeddable/map_embeddable_factory.tsx b/public/embeddable/map_embeddable_factory.tsx
new file mode 100644
index 00000000..b1534687
--- /dev/null
+++ b/public/embeddable/map_embeddable_factory.tsx
@@ -0,0 +1,99 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { i18n } from '@osd/i18n';
+import {
+ IContainer,
+ EmbeddableFactoryDefinition,
+ ErrorEmbeddable,
+ SavedObjectEmbeddableInput,
+} from '../../../../src/plugins/embeddable/public';
+import { MAP_EMBEDDABLE, MapInput, MapOutput, MapEmbeddable } from './map_embeddable';
+import { MAPS_APP_ICON, MAPS_APP_ID } from '../../common';
+import { ConfigSchema } from '../../common/config';
+import { MapSavedObjectAttributes } from '../../common/map_saved_object_attributes';
+import { MAPS_APP_DISPLAY_NAME } from '../../common/constants/shared';
+import { getTimeFilter } from '../services';
+
+interface StartServices {
+ services: {
+ application: {
+ getUrlForApp: (appId: string, options?: { path?: string }) => string;
+ navigateToApp: (appId: string, options?: { path?: string }) => Promise;
+ };
+ savedObjects: {
+ client: {
+ get: (type: string, id: string) => Promise;
+ };
+ };
+ };
+ mapConfig: ConfigSchema;
+}
+
+export class MapEmbeddableFactoryDefinition
+ implements EmbeddableFactoryDefinition
+{
+ public readonly type = MAP_EMBEDDABLE;
+
+ public readonly savedObjectMetaData = {
+ name: MAPS_APP_DISPLAY_NAME,
+ type: MAP_EMBEDDABLE,
+ getIconForSavedObject: () => MAPS_APP_ICON,
+ };
+
+ constructor(private getStartServices: () => Promise) {}
+
+ public async isEditable() {
+ return true;
+ }
+
+ // Maps app will be created from visualization list
+ public canCreateNew() {
+ return false;
+ }
+
+ public createFromSavedObject = async (
+ savedObjectId: string,
+ input: Partial & { id: string },
+ parent?: IContainer
+ ): Promise => {
+ try {
+ const { services, mapConfig } = await this.getStartServices();
+ const url = services.application.getUrlForApp(MAPS_APP_ID, {
+ path: savedObjectId,
+ });
+ const timeFilter = getTimeFilter();
+ const savedMap = await services.savedObjects.client.get(MAP_EMBEDDABLE, savedObjectId);
+ const savedMapAttributes = savedMap.attributes as MapSavedObjectAttributes;
+ return new MapEmbeddable(
+ {
+ ...input,
+ savedObjectId,
+ title: savedMapAttributes.title,
+ },
+ {
+ parent,
+ services,
+ mapConfig,
+ editUrl: url,
+ savedMapAttributes,
+ timeFilter,
+ }
+ );
+ } catch (error) {
+ return new ErrorEmbeddable(error.message, input);
+ }
+ };
+
+ public async create(initialInput: MapInput, parent?: IContainer) {
+ return undefined;
+ }
+
+ public getDisplayName() {
+ return i18n.translate('maps.displayName', {
+ defaultMessage: MAPS_APP_DISPLAY_NAME,
+ });
+ }
+}
diff --git a/public/model/layerRenderController.ts b/public/model/layerRenderController.ts
index 7c11bb2d..cf160535 100644
--- a/public/model/layerRenderController.ts
+++ b/public/model/layerRenderController.ts
@@ -14,11 +14,13 @@ import {
getTime,
IOpenSearchDashboardsSearchResponse,
isCompleteResponse,
+ TimeRange,
+ Query,
} from '../../../../src/plugins/data/common';
import { layersFunctionMap } from './layersFunctions';
import { MapServices } from '../types';
import { MapState } from './mapState';
-import {GeoBounds, getBounds} from './map/boundary';
+import { GeoBounds, getBounds } from './map/boundary';
import { buildBBoxFilter } from './geo/filter';
interface MaplibreRef {
@@ -28,8 +30,10 @@ interface MaplibreRef {
export const prepareDataLayerSource = (
layer: MapLayerSpecification,
mapState: MapState,
- { data, notifications }: MapServices,
- filters: Filter[] = []
+ { data, toastNotifications }: MapServices,
+ filters: Filter[] = [],
+ timeRange?: TimeRange,
+ query?: Query
): Promise => {
return new Promise(async (resolve, reject) => {
if (layer.type === DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS) {
@@ -42,17 +46,19 @@ export const prepareDataLayerSource = (
sourceFields.push(...sourceConfig.tooltipFields);
}
let buildQuery;
+ let selectedTimeRange;
if (indexPattern) {
- const timeFilters = getTime(indexPattern, mapState.timeRange);
- buildQuery = buildOpenSearchQuery(
- indexPattern,
- [],
- [
- ...filters,
- ...(layer.source.filters ? layer.source.filters : []),
- ...(timeFilters ? [timeFilters] : []),
- ]
- );
+ if (timeRange) {
+ selectedTimeRange = timeRange;
+ } else {
+ selectedTimeRange = mapState.timeRange;
+ }
+ const timeFilters = getTime(indexPattern, selectedTimeRange);
+ buildQuery = buildOpenSearchQuery(indexPattern, query ? [query] : [], [
+ ...filters,
+ ...(layer.source.filters ? layer.source.filters : []),
+ ...(timeFilters ? [timeFilters] : []),
+ ]);
}
const request = {
params: {
@@ -72,7 +78,7 @@ export const prepareDataLayerSource = (
search$.unsubscribe();
resolve({ dataSource, layer });
} else {
- notifications.toasts.addWarning('An error has occurred when query dataSource');
+ toastNotifications.addWarning('An error has occurred when query dataSource');
search$.unsubscribe();
reject();
}
@@ -90,9 +96,14 @@ export const handleDataLayerRender = (
mapState: MapState,
services: MapServices,
maplibreRef: MaplibreRef,
- beforeLayerId: string | undefined
+ beforeLayerId: string | undefined,
+ timeRange?: TimeRange,
+ filtersFromDashboard?: Filter[],
+ query?: Query
) => {
+ // filters are passed from dashboard filters and geo bounding box filters
const filters: Filter[] = [];
+ filters.push(...(filtersFromDashboard ? filtersFromDashboard : []));
const geoField = mapLayer.source.geoFieldName;
const geoFieldType = mapLayer.source.geoFieldType;
@@ -106,12 +117,14 @@ export const handleDataLayerRender = (
const geoBoundingBoxFilter: GeoBoundingBoxFilter = buildBBoxFilter(geoField, mapBounds, meta);
filters.push(geoBoundingBoxFilter);
- return prepareDataLayerSource(mapLayer, mapState, services, filters).then((result) => {
- const { layer, dataSource } = result;
- if (layer.type === DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS) {
- layersFunctionMap[layer.type].render(maplibreRef, layer, dataSource, beforeLayerId);
+ return prepareDataLayerSource(mapLayer, mapState, services, filters, timeRange, query).then(
+ (result) => {
+ const { layer, dataSource } = result;
+ if (layer.type === DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS) {
+ layersFunctionMap[layer.type].render(maplibreRef, layer, dataSource, beforeLayerId);
+ }
}
- });
+ );
};
export const handleReferenceLayerRender = (
diff --git a/public/plugin.tsx b/public/plugin.tsx
index ee892f07..ce777fdd 100644
--- a/public/plugin.tsx
+++ b/public/plugin.tsx
@@ -19,32 +19,42 @@ import {
} from './types';
import {
PLUGIN_NAME,
- PLUGIN_NAVIGATION_BAR_ID,
- PLUGIN_NAVIGATION_BAR_TILE,
+ MAPS_APP_ID,
+ MAPS_APP_DISPLAY_NAME,
+ PLUGIN_ID,
} from '../common/constants/shared';
import { ConfigSchema } from '../common/config';
import { AppPluginSetupDependencies } from './types';
import { RegionMapVisualizationDependencies } from '../../../src/plugins/region_map/public';
import { VectorUploadOptions } from './components/vector_upload_options';
import { OpenSearchDashboardsContextProvider } from '../../../src/plugins/opensearch_dashboards_react/public';
+import {
+ MAPS_APP_ICON,
+ MAP_SAVED_OBJECT_TYPE,
+ APP_PATH,
+ MAPS_VISUALIZATION_DESCRIPTION,
+} from '../common';
+import { MapEmbeddableFactoryDefinition } from './embeddable';
+import { setTimeFilter } from './services';
export class CustomImportMapPlugin
- implements Plugin {
+ implements Plugin
+{
readonly _initializerContext: PluginInitializerContext;
constructor(initializerContext: PluginInitializerContext) {
this._initializerContext = initializerContext;
}
public setup(
core: CoreSetup,
- { regionMap }: AppPluginSetupDependencies
+ { regionMap, embeddable, visualizations }: AppPluginSetupDependencies
): CustomImportMapPluginSetup {
const mapConfig: ConfigSchema = {
...this._initializerContext.config.get(),
};
// Register an application into the side navigation menu
core.application.register({
- id: PLUGIN_NAVIGATION_BAR_ID,
- title: PLUGIN_NAVIGATION_BAR_TILE,
+ id: MAPS_APP_ID,
+ title: MAPS_APP_DISPLAY_NAME,
order: 5100,
category: {
id: 'opensearch',
@@ -56,7 +66,11 @@ export class CustomImportMapPlugin
const { renderApp } = await import('./application');
// Get start services as specified in opensearch_dashboards.json
const [coreStart, depsStart] = await core.getStartServices();
- const { navigation, data } = depsStart as AppPluginStartDependencies;
+ const {
+ navigation,
+ data,
+ embeddable: useEmbeddable,
+ } = depsStart as AppPluginStartDependencies;
// make sure the index pattern list is up-to-date
data.indexPatterns.clearCache();
@@ -73,12 +87,57 @@ export class CustomImportMapPlugin
toastNotifications: coreStart.notifications.toasts,
history: params.history,
data,
+ embeddable: useEmbeddable,
+ scopedHistory: params.history,
};
+ params.element.classList.add('mapAppContainer');
// Render the application
return renderApp(params, services, mapConfig);
},
});
+ const mapEmbeddableFactory = new MapEmbeddableFactoryDefinition(async () => {
+ const [coreStart, depsStart] = await core.getStartServices();
+ const { navigation, data: useData } = depsStart as AppPluginStartDependencies;
+ return {
+ mapConfig,
+ services: {
+ ...coreStart,
+ navigation,
+ data: useData,
+ toastNotifications: coreStart.notifications.toasts,
+ },
+ };
+ });
+ embeddable.registerEmbeddableFactory(MAP_SAVED_OBJECT_TYPE, mapEmbeddableFactory as any);
+
+ visualizations.registerAlias({
+ name: PLUGIN_ID,
+ title: MAPS_APP_DISPLAY_NAME,
+ description: MAPS_VISUALIZATION_DESCRIPTION,
+ icon: MAPS_APP_ICON,
+ aliasApp: MAPS_APP_ID,
+ aliasPath: APP_PATH.CREATE_MAP,
+ stage: 'production',
+ appExtensions: {
+ visualizations: {
+ docTypes: [MAP_SAVED_OBJECT_TYPE],
+ toListItem: ({ id, attributes, updated_at: updatedAt }) => ({
+ description: attributes?.description,
+ editApp: MAPS_APP_ID,
+ editUrl: `${encodeURIComponent(id)}`,
+ icon: MAPS_APP_ICON,
+ id,
+ savedObjectType: MAP_SAVED_OBJECT_TYPE,
+ title: attributes?.title,
+ typeTitle: MAPS_APP_DISPLAY_NAME,
+ stage: 'production',
+ updated_at: updatedAt,
+ }),
+ },
+ },
+ });
+
const customSetup = async () => {
const [coreStart] = await core.getStartServices();
regionMap.addOptionTab({
@@ -108,7 +167,8 @@ export class CustomImportMapPlugin
};
}
- public start(core: CoreStart): CustomImportMapPluginStart {
+ public start(core: CoreStart, { data }: AppPluginStartDependencies): CustomImportMapPluginStart {
+ setTimeFilter(data.query.timefilter.timefilter);
return {};
}
diff --git a/public/services.ts b/public/services.ts
index 25166d39..da4ce07b 100644
--- a/public/services.ts
+++ b/public/services.ts
@@ -4,6 +4,8 @@
*/
import { CoreStart } from '../../../src/core/public';
+import { createGetterSetter } from '../../../src/plugins/opensearch_dashboards_utils/common';
+import { TimefilterContract } from '../../../src/plugins/data/public';
export const postGeojson = async (requestBody: any, http: CoreStart['http']) => {
try {
@@ -28,3 +30,5 @@ export const getIndex = async (indexName: string, http: CoreStart['http']) => {
return e;
}
};
+
+export const [getTimeFilter, setTimeFilter] = createGetterSetter('TimeFilter');
diff --git a/public/types.ts b/public/types.ts
index d34f7273..657d6c4c 100644
--- a/public/types.ts
+++ b/public/types.ts
@@ -8,16 +8,19 @@ import {
CoreStart,
SavedObjectsClient,
ToastsStart,
-} from 'opensearch-dashboards/public';
+ ScopedHistory,
+} from '../../../src/core/public';
import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public';
-import { DataPublicPluginStart } from '../../../src/plugins/data/public';
-
+import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../src/plugins/data/public';
import { RegionMapPluginSetup } from '../../../src/plugins/region_map/public';
+import { EmbeddableSetup, EmbeddableStart } from '../../../src/plugins/embeddable/public';
+import { VisualizationsSetup } from '../../../src/plugins/visualizations/public';
export interface AppPluginStartDependencies {
navigation: NavigationPublicPluginStart;
savedObjects: SavedObjectsClient;
data: DataPublicPluginStart;
+ embeddable: EmbeddableStart;
}
export interface MapServices extends CoreStart {
@@ -28,20 +31,24 @@ export interface MapServices extends CoreStart {
toastNotifications: ToastsStart;
history: AppMountParameters['history'];
data: DataPublicPluginStart;
+ application: CoreStart['application'];
+ i18n: CoreStart['i18n'];
+ savedObjects: CoreStart['savedObjects'];
+ overlays: CoreStart['overlays'];
+ embeddable: EmbeddableStart;
+ scopedHistory: ScopedHistory;
+ chrome: CoreStart['chrome'];
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
-export interface CustomImportMapPluginSetup {
- getGreeting: () => string;
-}
+export interface CustomImportMapPluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CustomImportMapPluginStart {}
-export interface AppPluginStartDependencies {
- navigation: NavigationPublicPluginStart;
-}
-
export interface AppPluginSetupDependencies {
regionMap: RegionMapPluginSetup;
+ embeddable: EmbeddableSetup;
+ visualizations: VisualizationsSetup;
+ data: DataPublicPluginSetup;
}
diff --git a/public/utils/breadcrumbs.ts b/public/utils/breadcrumbs.ts
index daa69ea2..40308ce0 100644
--- a/public/utils/breadcrumbs.ts
+++ b/public/utils/breadcrumbs.ts
@@ -4,7 +4,7 @@
*/
import { i18n } from '@osd/i18n';
-import {PLUGIN_NAVIGATION_BAR_ID} from '../../common';
+import { MAPS_APP_ID } from '../../common';
export function getMapsLandingBreadcrumbs(navigateToApp: any) {
return [
@@ -12,7 +12,7 @@ export function getMapsLandingBreadcrumbs(navigateToApp: any) {
text: i18n.translate('maps.listing.breadcrumb', {
defaultMessage: 'Maps',
}),
- onClick: () => navigateToApp(PLUGIN_NAVIGATION_BAR_ID),
+ onClick: () => navigateToApp(MAPS_APP_ID),
},
];
}
diff --git a/yarn.lock b/yarn.lock
index f21443e3..7b5d9661 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -127,6 +127,32 @@
resolved "https://registry.yarnpkg.com/@types/pbf/-/pbf-3.0.2.tgz#8d291ad68b4b8c533e96c174a2e3e6399a59ed61"
integrity sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ==
+"@types/prop-types@*":
+ version "15.7.5"
+ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
+ integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
+
+"@types/react-test-renderer@^18.0.0":
+ version "18.0.0"
+ resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-18.0.0.tgz#7b7f69ca98821ea5501b21ba24ea7b6139da2243"
+ integrity sha512-C7/5FBJ3g3sqUahguGi03O79b8afNeSD6T8/GU50oQrJCU0bVCCGQHaGKUbg2Ce8VQEEqTw8/HiS6lXHHdgkdQ==
+ dependencies:
+ "@types/react" "*"
+
+"@types/react@*":
+ version "18.0.28"
+ resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.28.tgz#accaeb8b86f4908057ad629a26635fe641480065"
+ integrity sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==
+ dependencies:
+ "@types/prop-types" "*"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
+
+"@types/scheduler@*":
+ version "0.16.2"
+ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
+ integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
+
"@types/sinonjs__fake-timers@8.1.1":
version "8.1.1"
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz#b49c2c70150141a15e0fa7e79cf1f92a72934ce3"
@@ -405,6 +431,11 @@ csscolorparser@~1.0.3:
resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b"
integrity sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==
+csstype@^3.0.2:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9"
+ integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==
+
cypress-file-upload@^5.0.8:
version "5.0.8"
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1"