Skip to content

Commit

Permalink
Support multi data source display in Maps app (#611)
Browse files Browse the repository at this point in the history
* Support multi data source display in Maps app

Signed-off-by: Junqiu Lei <junqiu@amazon.com>

(cherry picked from commit f325212)
Signed-off-by: Junqiu Lei <junqiu@amazon.com>
  • Loading branch information
junqiu-lei committed Apr 22, 2024
1 parent 99cd794 commit ec87789
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 70 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased 2.x](https://github.com/opensearch-project/dashboards-maps/compare/2.13...2.x)
### Features
Support multi data source display in Maps app([#611](https://github.com/opensearch-project/dashboards-maps/pull/611))
### Enhancements
### Bug Fixes
* Fix zoom level type error in custom layer ([#605](https://github.com/opensearch-project/dashboards-maps/pull/605))
Expand Down
6 changes: 4 additions & 2 deletions opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"visualizations"
],
"optionalPlugins": [
"home"
"home",
"dataSource",
"dataSourceManagement"
]
}
}
6 changes: 5 additions & 1 deletion public/application.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import { MapServices } from './types';
import { MapsDashboardsApp } from './components/app';
import { OpenSearchDashboardsContextProvider } from '../../../src/plugins/opensearch_dashboards_react/public';

export const renderApp = ({ element }: AppMountParameters, services: MapServices) => {
export const renderApp = (
{ element }: AppMountParameters,
services: MapServices,
dataSourceManagementEnabled: boolean
) => {
ReactDOM.render(
<OpenSearchDashboardsContextProvider services={services}>
<MapsDashboardsApp />
Expand Down
6 changes: 5 additions & 1 deletion public/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ export const MapsDashboardsApp = () => {
<I18nProvider>
<Switch>
<Route path={[APP_PATH.CREATE_MAP, APP_PATH.EDIT_MAP]} render={() => <MapPage />} />
<Route exact path={APP_PATH.LANDING_PAGE_PATH} render={() => <MapsList />} />
<Route
exact
path={APP_PATH.LANDING_PAGE_PATH}
render={() => <MapsList />}
/>
</Switch>
</I18nProvider>
</Router>
Expand Down
33 changes: 32 additions & 1 deletion public/components/layer_control_panel/layer_control_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { IndexPattern } from '../../../../../src/plugins/data/public';
import { AddLayerPanel } from '../add_layer_panel';
import { LayerConfigPanel } from '../layer_config';
import { MapLayerSpecification } from '../../model/mapLayerType';
import { LAYER_ICON_TYPE_MAP } from '../../../common';
import { DASHBOARDS_MAPS_LAYER_TYPE, LAYER_ICON_TYPE_MAP } from '../../../common';
import { useOpenSearchDashboards } from '../../../../../src/plugins/opensearch_dashboards_react/public';
import { MapServices } from '../../types';
import { MapState } from '../../model/mapState';
Expand All @@ -49,18 +49,22 @@ interface Props {
selectedLayerConfig: MapLayerSpecification | undefined;
setSelectedLayerConfig: (layerConfig: MapLayerSpecification | undefined) => void;
setIsUpdatingLayerRender: (isUpdatingLayerRender: boolean) => void;
setDataSourceRefIds: (dataSourceRefIds: string[]) => void;
}

export const LayerControlPanel = memo(
({
maplibreRef,
setLayers,
layers,
layersIndexPatterns,
setLayersIndexPatterns,
zoom,
isReadOnlyMode,
selectedLayerConfig,
setSelectedLayerConfig,
setIsUpdatingLayerRender,
setDataSourceRefIds,
}: Props) => {
const { services } = useOpenSearchDashboards<MapServices>();

Expand Down Expand Up @@ -205,12 +209,39 @@ export const LayerControlPanel = memo(
setIsDeleteLayerModalVisible(true);
};

const removeDataLayerDataSource = (layer: MapLayerSpecification) => {
if (layer.type === DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS) {
const indexPatternId = layer.source.indexPatternId;
const indexPattern = layersIndexPatterns.find((idp) => idp.id === indexPatternId);
if (indexPattern) {
const indexPatternClone = [...layersIndexPatterns];
const index = indexPatternClone.findIndex((idp) => idp.id === indexPatternId);
if (index > -1) {
indexPatternClone.splice(index, 1);
setLayersIndexPatterns(indexPatternClone);
}
// remove duplicate dataSourceRefIds
const updatedDataSourceRefIds : string[] = [];
indexPatternClone.forEach((ip) => {
if (ip.dataSourceRef && !updatedDataSourceRefIds.includes(ip.dataSourceRef.id)) {
updatedDataSourceRefIds.push(ip.dataSourceRef.id);
} else if (!ip.dataSourceRef && !updatedDataSourceRefIds.includes('')) {
updatedDataSourceRefIds.push('');
}
});

setDataSourceRefIds(updatedDataSourceRefIds);
}
}
};

const onDeleteLayerConfirm = () => {
if (selectedDeleteLayer) {
removeLayers(maplibreRef.current!, selectedDeleteLayer.id, true);
removeLayer(selectedDeleteLayer.id);
setIsDeleteLayerModalVisible(false);
setSelectedDeleteLayer(undefined);
removeDataLayerDataSource(selectedDeleteLayer);
}
};

Expand Down
14 changes: 14 additions & 0 deletions public/components/map_container/map_container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ interface MapContainerProps {
isUpdatingLayerRender: boolean;
setIsUpdatingLayerRender: (isUpdatingLayerRender: boolean) => void;
addSpatialFilter: (shape: ShapeFilter, label: string | null, relation: GeoShapeRelation) => void;
dataSourceRefIds: string[];
setDataSourceRefIds: (refIds: string[]) => void;
}

export class MapsServiceError extends Error {
Expand All @@ -67,6 +69,7 @@ export const MapContainer = ({
isUpdatingLayerRender,
setIsUpdatingLayerRender,
addSpatialFilter,
setDataSourceRefIds,
}: MapContainerProps) => {
const { services } = useOpenSearchDashboards<MapServices>();

Expand Down Expand Up @@ -245,6 +248,16 @@ export const MapContainer = ({
);
const cloneLayersIndexPatterns = [...layersIndexPatterns, newIndexPattern];
setLayersIndexPatterns(cloneLayersIndexPatterns);
const updatedDataSourceRefIds: string[] = [];
cloneLayersIndexPatterns.forEach((ip) => {
if (ip.dataSourceRef && !updatedDataSourceRefIds.includes(ip.dataSourceRef.id)) {
updatedDataSourceRefIds.push(ip.dataSourceRef.id);
} else if (!ip.dataSourceRef && !updatedDataSourceRefIds.includes('')) {
// If index pattern of the layer doesn't have reference to a data source, it is using local cluster
updatedDataSourceRefIds.push('');
}
});
setDataSourceRefIds(updatedDataSourceRefIds);
}
};

Expand All @@ -271,6 +284,7 @@ export const MapContainer = ({
selectedLayerConfig={selectedLayerConfig}
setSelectedLayerConfig={setSelectedLayerConfig}
setIsUpdatingLayerRender={setIsUpdatingLayerRender}
setDataSourceRefIds={setDataSourceRefIds}
/>
)}
{mounted && tooltipState === TOOLTIP_STATE.DISPLAY_FEATURES && maplibreRef.current && (
Expand Down
70 changes: 48 additions & 22 deletions public/components/map_page/map_page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,16 @@ export const MapComponent = ({ mapIdFromSavedObject, dashboardProps }: MapCompon
savedObjects: { client: savedObjectsClient },
} = services;
const [layers, setLayers] = useState<MapLayerSpecification[]>([]);
const [savedMapObject, setSavedMapObject] =
useState<SimpleSavedObject<MapSavedObjectAttributes> | null>();
const [savedMapObject, setSavedMapObject] = useState<SimpleSavedObject<
MapSavedObjectAttributes
> | null>();
const [layersIndexPatterns, setLayersIndexPatterns] = useState<IndexPattern[]>([]);
const maplibreRef = useRef<Maplibre | null>(null);
const [mapState, setMapState] = useState<MapState>(getInitialMapState());
const [isUpdatingLayerRender, setIsUpdatingLayerRender] = useState(true);
const isReadOnlyMode = !!dashboardProps;
const [dataSourceRefIds, setDataSourceRefIds] = useState<string[]>([]);
const [dataLoadReady, setDataLoadReady] = useState(false);

useEffect(() => {
if (mapIdFromSavedObject) {
Expand All @@ -68,21 +71,39 @@ export const MapComponent = ({ mapIdFromSavedObject, dashboardProps }: MapCompon
setMapState(savedMapState);
setLayers(layerList);
const savedIndexPatterns: IndexPattern[] = [];
layerList.forEach(async (layer: MapLayerSpecification) => {
if (layer.type === DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS) {
const indexPatternId = layer.source.indexPatternId;
const indexPattern = await services.data.indexPatterns.get(indexPatternId);
savedIndexPatterns.push(indexPattern);
}
});
setLayersIndexPatterns(savedIndexPatterns);
const remoteDataSourceIds: string[] = [];

const fetchDataLayer = async () => {
const requests = layerList
.filter((layer) => layer.type === DASHBOARDS_MAPS_LAYER_TYPE.DOCUMENTS)
.map((layer) => services.data.indexPatterns.get(layer.source.indexPatternId));

const resp = await Promise.all(requests);
resp.forEach((response: IndexPattern) => {
savedIndexPatterns.push(response);
if (response.dataSourceRef && !dataSourceRefIds.includes(response.dataSourceRef.id)) {
remoteDataSourceIds.push(response.dataSourceRef.id);
} else if (!response.dataSourceRef && !remoteDataSourceIds.includes('')) {
// If index pattern of the layer doesn't have reference to a data source, it is using local cluster
remoteDataSourceIds.push('');
}
});

setLayers(layerList);
setLayersIndexPatterns(savedIndexPatterns);
setDataSourceRefIds(remoteDataSourceIds);
setDataLoadReady(true);
};

fetchDataLayer();
});
} else {
const initialDefaultLayer: MapLayerSpecification = getLayerConfigMap()[
OPENSEARCH_MAP_LAYER.type
] as MapLayerSpecification;
initialDefaultLayer.name = MAP_LAYER_DEFAULT_NAME;
setLayers([initialDefaultLayer]);
setDataLoadReady(true);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand Down Expand Up @@ -114,18 +135,21 @@ export const MapComponent = ({ mapIdFromSavedObject, dashboardProps }: MapCompon

return (
<div className="map-page">
{isReadOnlyMode ? null : (
<MapTopNavMenu
mapIdFromUrl={mapIdFromSavedObject}
savedMapObject={savedMapObject}
layers={layers}
layersIndexPatterns={layersIndexPatterns}
maplibreRef={maplibreRef}
mapState={mapState}
setMapState={setMapState}
setIsUpdatingLayerRender={setIsUpdatingLayerRender}
/>
)}
{isReadOnlyMode
? null
: dataLoadReady && (
<MapTopNavMenu
mapIdFromUrl={mapIdFromSavedObject}
savedMapObject={savedMapObject}
layers={layers}
layersIndexPatterns={layersIndexPatterns}
maplibreRef={maplibreRef}
mapState={mapState}
setMapState={setMapState}
setIsUpdatingLayerRender={setIsUpdatingLayerRender}
dataSourceRefIds={dataSourceRefIds}
/>
)}
{!isReadOnlyMode && !!mapState.spatialMetaFilters?.length && (
<div id="SpatiallFilterGroup" className="globalQueryBar">
<div className={filterGroupClasses}>
Expand All @@ -149,6 +173,8 @@ export const MapComponent = ({ mapIdFromSavedObject, dashboardProps }: MapCompon
isUpdatingLayerRender={isUpdatingLayerRender}
setIsUpdatingLayerRender={setIsUpdatingLayerRender}
addSpatialFilter={addSpatialFilter}
dataSourceRefIds={dataSourceRefIds}
setDataSourceRefIds={setDataSourceRefIds}
/>
</div>
);
Expand Down
60 changes: 40 additions & 20 deletions public/components/map_top_nav/top_nav_menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface MapTopNavMenuProps {
setMapState: (mapState: MapState) => void;
originatingApp?: string;
setIsUpdatingLayerRender: (isUpdatingLayerRender: boolean) => void;
dataSourceRefIds: string[];
}

export const MapTopNavMenu = ({
Expand All @@ -37,6 +38,7 @@ export const MapTopNavMenu = ({
mapState,
setMapState,
setIsUpdatingLayerRender,
dataSourceRefIds,
}: MapTopNavMenuProps) => {
const { services } = useOpenSearchDashboards<MapServices>();
const {
Expand All @@ -48,6 +50,9 @@ export const MapTopNavMenu = ({
application: { navigateToApp },
embeddable,
scopedHistory,
dataSourceManagement,
savedObjects: { client: savedObjectsClient },
notifications,
} = services;

const [title, setTitle] = useState<string>('');
Expand Down Expand Up @@ -132,27 +137,42 @@ export const MapTopNavMenu = ({
});
}, [services, mapIdFromUrl, layers, title, description, mapState, originatingApp]);

const dataSourceManagementEnabled: boolean = !!dataSourceManagement;

return (
// @ts-ignore
<TopNavMenu
appName={MAPS_APP_ID}
config={config}
setMenuMountPoint={setHeaderActionMenu}
indexPatterns={layersIndexPatterns || []}
showSearchBar={true}
showFilterBar={false}
showDatePicker={true}
showQueryBar={true}
showSaveQuery={true}
showQueryInput={true}
onQuerySubmit={handleQuerySubmit}
dateRangeFrom={dateFrom}
dateRangeTo={dateTo}
query={queryConfig}
isRefreshPaused={isRefreshPaused}
refreshInterval={refreshIntervalValue}
onRefresh={refreshDataLayerRender}
onRefreshChange={onRefreshChange}
/>
<>
<TopNavMenu
appName={MAPS_APP_ID}
config={config}
setMenuMountPoint={setHeaderActionMenu}
indexPatterns={layersIndexPatterns || []}
showSearchBar={true}
showFilterBar={false}
showDatePicker={true}
showQueryBar={true}
showSaveQuery={true}
showQueryInput={true}
onQuerySubmit={handleQuerySubmit}
dateRangeFrom={dateFrom}
dateRangeTo={dateTo}
query={queryConfig}
isRefreshPaused={isRefreshPaused}
refreshInterval={refreshIntervalValue}
onRefresh={refreshDataLayerRender}
onRefreshChange={onRefreshChange}
showDataSourceMenu={dataSourceManagementEnabled}
dataSourceMenuConfig={{
componentType: 'DataSourceAggregatedView',
componentConfig: {
activeDataSourceIds: dataSourceRefIds,
savedObjects: savedObjectsClient,
notifications,
fullWidth: true,
displayAllCompatibleDataSources: false,
},
}}
/>
</>
);
};
Loading

0 comments on commit ec87789

Please sign in to comment.