Skip to content

Commit

Permalink
Merge branch 'main' of github.com:opensearch-project/dashboards-maps …
Browse files Browse the repository at this point in the history
…into binary-install
  • Loading branch information
derek-ho committed May 1, 2024
2 parents 38878d1 + d53bc2d commit 91ea3ad
Show file tree
Hide file tree
Showing 31 changed files with 588 additions and 125 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Maintenance
### Refactoring

## [Unreleased 2.x](https://github.com/opensearch-project/dashboards-maps/compare/2.12...2.x)
## [Unreleased 2.x](https://github.com/opensearch-project/dashboards-maps/compare/2.14...2.x)
### Features
### Enhancements
### Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Ultimately, your directory structure should look like this:
From OpenSearch-Dashboards repo (root folder), the following commands start OpenSearch Dashboards and includes this plugin.

```
yarn osd bootstrap
yarn osd bootstrap --single-version=loose
yarn start --no-base-path
```

Expand Down
4 changes: 2 additions & 2 deletions cypress/e2e/documentsLayer.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ describe('Documents layer', () => {
cy.contains('Create map').click();
cy.get("button[data-test-subj='addLayerButton']").click();
cy.contains('Documents').click();
cy.contains('Select data source', { timeout: 60000 }).click({ force: true });
cy.wait(5000).contains('Select index pattern', { timeout: 60000 }).click({ force: true });
cy.contains('opensearch_dashboards_sample_data_flights').click();
cy.contains('Select data field', { timeout: 60000 }).click({ force: true });
cy.wait(5000).contains('Select data field', { timeout: 60000 }).click({ force: true });
cy.contains('DestLocation').click();
cy.get('[data-test-subj="indexPatternSelect"]').should(
'contain',
Expand Down
2 changes: 1 addition & 1 deletion opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"server": true,
"ui": true,
"requiredPlugins": ["regionMap", "opensearchDashboardsReact", "opensearchDashboardsUtils", "navigation", "savedObjects", "data", "embeddable", "visualizations"],
"optionalPlugins": ["home"]
"optionalPlugins": ["home", "dataSource", "dataSourceManagement"]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"dependencies": {
"@cypress/skip-test": "^2.6.1",
"@mapbox/mapbox-gl-draw": "^1.4.0",
"@opensearch-dashboards-test/opensearch-dashboards-test-library": "git+https://github.com/opensearch-project/opensearch-dashboards-test-library.git#main",
"@opensearch-dashboards-test/opensearch-dashboards-test-library": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.6.tar.gz",
"@types/mapbox__mapbox-gl-draw": "^1.3.3",
"@types/wellknown": "^0.5.4",
"cypress-file-upload": "^5.0.8",
Expand Down
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
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ export const DocumentLayerSource = ({
<EuiFlexGrid columns={1}>
<EuiFlexItem>
<EuiFormRow
label="Data source"
label="Index pattern"
isInvalid={!indexPattern}
error={errorsMap.datasource}
data-test-subj={'indexPatternSelect'}
Expand All @@ -273,7 +273,7 @@ export const DocumentLayerSource = ({
<IndexPatternSelect
savedObjectsClient={savedObjectsClient}
placeholder={i18n.translate('documentLayer.selectDataSourcePlaceholder', {
defaultMessage: 'Select data source',
defaultMessage: 'Select index pattern',
})}
indexPatternId={indexPattern?.id || ''}
onChange={async (newIndexPatternId: any) => {
Expand Down
113 changes: 113 additions & 0 deletions public/components/layer_config/layer_basic_settings.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { LayerBasicSettings } from './layer_basic_settings';
import { MapLayerSpecification } from '../../model/mapLayerType';
import { DASHBOARDS_MAPS_LAYER_TYPE, MAX_LAYER_NAME_LIMIT } from '../../../common';
import { shallow } from 'enzyme';
import { EuiDualRange, EuiFieldText, EuiFormRow, EuiRange, EuiTextArea } from '@elastic/eui';

describe('LayerBasicSettings', () => {
let wrapper: any;
const mockLayerConfig: MapLayerSpecification = {
name: 'Test Layer',
id: 'test-layer',
type: DASHBOARDS_MAPS_LAYER_TYPE.OPENSEARCH_MAP,
description: 'Some description',
zoomRange: [1, 20],
visibility: 'visible',
opacity: 80,
};

const mockProps = {
selectedLayerConfig: mockLayerConfig,
setSelectedLayerConfig: jest.fn(),
setIsUpdateDisabled: jest.fn(),
isLayerExists: jest.fn(),
};

beforeEach(() => {
wrapper = shallow(<LayerBasicSettings {...mockProps} />);
});

it('should render with correct props', () => {
expect(wrapper.find(EuiFieldText).prop('value')).toBe(mockLayerConfig.name);
expect(wrapper.find(EuiTextArea).prop('value')).toBe(mockLayerConfig.description);
expect(wrapper.find(EuiDualRange).prop('value')).toEqual(mockLayerConfig.zoomRange);
expect(wrapper.find(EuiRange).prop('value')).toEqual(mockLayerConfig.opacity);
});

it('should call setSelectedLayerConfig with updated zoom range on zoom level change', () => {
const newZoomRange = [2, 15];
wrapper.find(EuiDualRange).simulate('change', newZoomRange);

expect(mockProps.setSelectedLayerConfig).toHaveBeenCalledWith({
...mockLayerConfig,
zoomRange: newZoomRange,
});
});

it('should call setSelectedLayerConfig with updated opacity on opacity change', () => {
const newOpacity = 50;
wrapper.find(EuiRange).simulate('change', { target: { value: newOpacity.toString() } });

expect(mockProps.setSelectedLayerConfig).toHaveBeenCalledWith({
...mockLayerConfig,
opacity: newOpacity,
});
});

it('should call setSelectedLayerConfig with updated name on name change', () => {
const newName = 'Updated Test Layer';
wrapper.find(EuiFieldText).simulate('change', { target: { value: newName } });

expect(mockProps.setSelectedLayerConfig).toHaveBeenCalledWith({
...mockLayerConfig,
name: newName,
});
});

it('should call setSelectedLayerConfig with updated description on description change', () => {
const newDescription = 'Updated description';
wrapper.find(EuiTextArea).simulate('change', { target: { value: newDescription } });

expect(mockProps.setSelectedLayerConfig).toHaveBeenCalledWith({
...mockLayerConfig,
description: newDescription,
});
});

it('should display an error for an empty name', () => {
mockProps.isLayerExists.mockReturnValue(false); // Ensure this returns false for this test
const emptyName = '';
wrapper.find(EuiFieldText).simulate('change', { target: { value: emptyName } });

wrapper.update();

expect(wrapper.find(EuiFormRow).first().prop('error')).toContain('Name cannot be empty');
});

it('should display an error for a name exceeding the maximum length', () => {
const longName = 'a'.repeat(MAX_LAYER_NAME_LIMIT + 1); // Create a name longer than MAX_LAYER_NAME_LIMIT
wrapper.find(EuiFieldText).simulate('change', { target: { value: longName } });

wrapper.update();

expect(wrapper.find(EuiFormRow).first().prop('error')).toContain(
`Name should be less than ${MAX_LAYER_NAME_LIMIT} characters`
);
});

it('should display an error for a name that already exists', () => {
mockProps.isLayerExists.mockReturnValue(true); // Simulate that the name already exists
const existingName = 'Existing Layer Name';
wrapper.find(EuiFieldText).simulate('change', { target: { value: existingName } });

wrapper.update();

expect(wrapper.find(EuiFormRow).first().prop('error')).toContain('Name already exists');
});
});
14 changes: 11 additions & 3 deletions public/components/layer_config/layer_basic_settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
MAP_LAYER_DEFAULT_OPACITY_STEP,
MAX_LAYER_NAME_LIMIT,
} from '../../../common';
import { ValueMember } from '../../../../../src/plugins/opensearch_dashboards_react/public/validated_range/validated_dual_range';

interface Props {
selectedLayerConfig: MapLayerSpecification;
Expand All @@ -41,6 +42,7 @@ export const LayerBasicSettings = ({
}: Props) => {
const [invalid, setInvalid] = useState<boolean>(selectedLayerConfig.name.length === 0);
const [errors, setErrors] = useState<string[]>([]);
const [zoomLevel, setZoomLevel] = useState<[ValueMember, ValueMember]>(selectedLayerConfig.zoomRange as [ValueMember, ValueMember]);

const validateName = (name: string) => {
if (name?.length === 0) {
Expand Down Expand Up @@ -73,8 +75,14 @@ export const LayerBasicSettings = ({
const newLayerConfig = { ...selectedLayerConfig, [key]: value };
setSelectedLayerConfig(newLayerConfig);
};
const onZoomChange = (value: number[]) => {
commonUpdate('zoomRange', value);

const onZoomChange = (value: [ValueMember, ValueMember]) => {
// if value is not empty string, then update the zoom level to layer config
if (value[0] !== '' && value[1] !== '') {
// change value to number type
commonUpdate('zoomRange', value.map((zoomLevel) => Number(zoomLevel)));
}
setZoomLevel(value);
};

const onOpacityChange = (e: any) => {
Expand Down Expand Up @@ -121,7 +129,7 @@ export const LayerBasicSettings = ({
<EuiDualRange
min={MAP_DEFAULT_MIN_ZOOM}
max={MAP_DEFAULT_MAX_ZOOM}
value={selectedLayerConfig.zoomRange}
value={zoomLevel}
showInput
minInputProps={{ 'aria-label': 'Min value' }}
maxInputProps={{ 'aria-label': 'Max value' }}
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
Loading

0 comments on commit 91ea3ad

Please sign in to comment.