diff --git a/DEVELOPER_GUIDE.md b/DEVELOPER_GUIDE.md
index 8b94c925..4e1ef1ac 100644
--- a/DEVELOPER_GUIDE.md
+++ b/DEVELOPER_GUIDE.md
@@ -33,7 +33,7 @@ echo 'src/plugins/custom_import_map/*' >> .git/info/sparse-checkout
git config core.sparseCheckout true
git checkout main
```
-6. Run `yarn osd bootstrap` inside `OpenSearch-Dashboards/plugins/custom_import_map`.
+6. Run `yarn osd bootstrap` inside `OpenSearch-Dashboards/plugins/src/plugins/custom_import_map`.
Ultimately, your directory structure should look like this:
diff --git a/README.md b/README.md
index 56de15ef..04b58295 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ Dashboards-maps is a frontend plugin that helps you in uploading custom GeoJSON
* [Project Website](https://opensearch.org/)
* [Downloads](https://opensearch.org/downloads.html)
* [Documentation](https://opensearch.org/docs/latest/)
+* [Developer Guide](DEVELOPER_GUIDE.md)
* Need help? Try [Forums](https://discuss.opendistrocommunity.dev/)
* [Project Principles](https://opensearch.org/#principles)
* [Contributing to OpenSearch](CONTRIBUTING.md)
diff --git a/src/plugins/custom_import_map/babel.config.js b/src/plugins/custom_import_map/babel.config.js
index e5f3822c..fa274ac8 100644
--- a/src/plugins/custom_import_map/babel.config.js
+++ b/src/plugins/custom_import_map/babel.config.js
@@ -4,15 +4,15 @@
*/
module.exports = {
- presets: [
- require('@babel/preset-env'),
- require('@babel/preset-react'),
- require('@babel/preset-typescript'),
- ],
- plugins: [
- require('@babel/plugin-proposal-class-properties'),
- require('@babel/plugin-proposal-object-rest-spread'),
- ['@babel/plugin-transform-modules-commonjs', { allowTopLevelThis: true }],
- [require('@babel/plugin-transform-runtime'), { regenerator: true }],
- ],
- };
+ presets: [
+ require('@babel/preset-env'),
+ require('@babel/preset-react'),
+ require('@babel/preset-typescript'),
+ ],
+ plugins: [
+ require('@babel/plugin-proposal-class-properties'),
+ require('@babel/plugin-proposal-object-rest-spread'),
+ ['@babel/plugin-transform-modules-commonjs', { allowTopLevelThis: true }],
+ [require('@babel/plugin-transform-runtime'), { regenerator: true }],
+ ],
+};
diff --git a/src/plugins/custom_import_map/common/index.ts b/src/plugins/custom_import_map/common/index.ts
index 82114374..109ca6e7 100644
--- a/src/plugins/custom_import_map/common/index.ts
+++ b/src/plugins/custom_import_map/common/index.ts
@@ -3,7 +3,18 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { fromMBtoBytes } from "./util";
-import { MAX_FILE_PAYLOAD_SIZE, MAX_FILE_PAYLOAD_SIZE_IN_MB, PLUGIN_ID, PLUGIN_NAME } from "./constants/shared";
+import { fromMBtoBytes } from './util';
+import {
+ MAX_FILE_PAYLOAD_SIZE,
+ MAX_FILE_PAYLOAD_SIZE_IN_MB,
+ PLUGIN_ID,
+ PLUGIN_NAME,
+} from './constants/shared';
-export { fromMBtoBytes, MAX_FILE_PAYLOAD_SIZE, MAX_FILE_PAYLOAD_SIZE_IN_MB, PLUGIN_ID, PLUGIN_NAME };
+export {
+ fromMBtoBytes,
+ MAX_FILE_PAYLOAD_SIZE,
+ MAX_FILE_PAYLOAD_SIZE_IN_MB,
+ PLUGIN_ID,
+ PLUGIN_NAME,
+};
diff --git a/src/plugins/custom_import_map/common/util.ts b/src/plugins/custom_import_map/common/util.ts
index dc898855..afc2ef97 100644
--- a/src/plugins/custom_import_map/common/util.ts
+++ b/src/plugins/custom_import_map/common/util.ts
@@ -4,5 +4,5 @@
*/
export const fromMBtoBytes = (sizeInMB: number) => {
- return sizeInMB * 1024 * 1024;
-}
+ return sizeInMB * 1024 * 1024;
+};
diff --git a/src/plugins/custom_import_map/package.json b/src/plugins/custom_import_map/package.json
index 00ecf7f9..ed63c87b 100644
--- a/src/plugins/custom_import_map/package.json
+++ b/src/plugins/custom_import_map/package.json
@@ -5,7 +5,9 @@
"build": "yarn plugin-helpers build",
"plugin-helpers": "node ../../../../scripts/plugin_helpers",
"osd": "node ../../../../scripts/osd",
- "lint": "node ../../../../scripts/eslint .",
+ "lint": "yarn run lint:es && yarn run lint:style",
+ "lint:es": "node ../../../../scripts/eslint",
+ "lint:style": "node ../../../../scripts/stylelint",
"test:jest": "TZ=UTC ../../../../node_modules/.bin/jest --config ./test/jest.config.js"
},
"husky": {
diff --git a/src/plugins/custom_import_map/public/components/__snapshots__/show_error_modal.test.tsx.snap b/src/plugins/custom_import_map/public/components/__snapshots__/show_error_modal.test.tsx.snap
new file mode 100644
index 00000000..29791d94
--- /dev/null
+++ b/src/plugins/custom_import_map/public/components/__snapshots__/show_error_modal.test.tsx.snap
@@ -0,0 +1,31 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`display error modal renders the error modal based on the props passed 1`] = `
+
+
+
+`;
diff --git a/src/plugins/custom_import_map/public/components/show_error_modal.test.tsx b/src/plugins/custom_import_map/public/components/show_error_modal.test.tsx
new file mode 100644
index 00000000..10a0d2b4
--- /dev/null
+++ b/src/plugins/custom_import_map/public/components/show_error_modal.test.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import * as React from 'react';
+import { ShowErrorModal } from './show_error_modal';
+import renderer, { act } from 'react-test-renderer';
+import { fireEvent } from '@testing-library/dom';
+import { screen, render } from '@testing-library/react';
+
+describe('display error modal', () => {
+ const props = {
+ modalTitle: 'testModalTitle',
+ modalBody: 'testModalBody',
+ buttonText: 'testButtonText',
+ };
+
+ it('renders the error modal based on the props passed', async () => {
+ let tree;
+ await act(async () => {
+ tree = renderer.create();
+ });
+ expect(tree.toJSON().props.id).toBe('showModalOption');
+ expect(tree.toJSON()).toMatchSnapshot();
+ });
+
+ it('renders the error modal when showModal button is clicked', () => {
+ render();
+ const button = screen.getByRole('button');
+ fireEvent.click(button);
+ const closeButton = screen.getByTestId('closeModal');
+ fireEvent.click(closeButton);
+ });
+});
diff --git a/src/plugins/custom_import_map/public/components/show_error_modal.tsx b/src/plugins/custom_import_map/public/components/show_error_modal.tsx
new file mode 100644
index 00000000..bfeed956
--- /dev/null
+++ b/src/plugins/custom_import_map/public/components/show_error_modal.tsx
@@ -0,0 +1,67 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React, { useState } from 'react';
+import {
+ EuiButton,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+ EuiCodeBlock,
+} from '@elastic/eui';
+
+export interface ShowErrorModalProps {
+ modalTitle: string;
+ modalBody: string;
+ buttonText: string;
+}
+
+const ShowErrorModal = (props: ShowErrorModalProps) => {
+ const [isModalVisible, setIsModalVisible] = useState(false);
+
+ const closeModal = () => setIsModalVisible(false);
+ const showModal = () => setIsModalVisible(true);
+
+ let modal;
+
+ if (isModalVisible) {
+ modal = (
+
+
+
+ {props.modalTitle}
+
+
+
+
+ {props.modalBody}
+
+
+
+
+ Close
+
+
+
+ );
+ }
+
+ return (
+
+ {props.buttonText}
+ {modal}
+
+ );
+};
+
+export { ShowErrorModal };
diff --git a/src/plugins/custom_import_map/public/components/vector_upload_options.test.tsx b/src/plugins/custom_import_map/public/components/vector_upload_options.test.tsx
index c4080182..86439e30 100644
--- a/src/plugins/custom_import_map/public/components/vector_upload_options.test.tsx
+++ b/src/plugins/custom_import_map/public/components/vector_upload_options.test.tsx
@@ -13,17 +13,21 @@ import * as serviceApiCalls from '../services';
jest.mock('../../../../../../src/plugins/opensearch_dashboards_react/public', () => ({
useOpenSearchDashboards: jest.fn().mockReturnValue({
services: {
- http: { post: () => {Promise.resolve({});} },
- notifications: { toasts: { addSuccess: jest.fn(), addDanger: jest.fn(), addWarning: jest.fn() } },
+ http: {
+ post: () => {
+ Promise.resolve({});
+ },
+ },
+ notifications: {
+ toasts: { addSuccess: jest.fn(), addDanger: jest.fn(), addWarning: jest.fn() },
+ },
},
}),
toMountPoint: jest.fn().mockReturnValue({}),
}));
-
describe('vector_upload_options', () => {
- const props = {
- };
+ const props = {};
const getIndexResponseWhenIndexIsNotPresent = {
ok: false,
@@ -104,7 +108,7 @@ describe('vector_upload_options', () => {
await expect(tree.findAllByText(message)).toBeTruthy();
};
- const addUserInputToDOM = async () => {
+ const addUserInputToDOM = async () => {
const jsonData = {
type: 'FeatureCollection',
name: 'sample',
@@ -120,7 +124,7 @@ describe('vector_upload_options', () => {
const indexName = screen.getByTestId('customIndex');
fireEvent.change(indexName, { target: { value: 'sample' } });
const uploader = getByTestId('filePicker');
-
+
const str = JSON.stringify([jsonData]);
const blob = new Blob([str]);
const file = new File([blob], 'sample.json', { type: 'application/JSON' });
@@ -152,25 +156,34 @@ describe('vector_upload_options', () => {
it('renders the VectorUploadOptions component with error message when index name has upper case letters', async () => {
try {
- await vectorUploadOptionsWithIndexNameRendererUtil('ABC', 'Upper case letters are not allowed');
+ await vectorUploadOptionsWithIndexNameRendererUtil(
+ 'ABC',
+ 'Upper case letters are not allowed'
+ );
} catch (err) {}
});
it('renders the VectorUploadOptions component with error message when index name has special characters', async () => {
try {
- await vectorUploadOptionsWithIndexNameRendererUtil('a#bc', 'Special characters are not allowed.');
+ await vectorUploadOptionsWithIndexNameRendererUtil(
+ 'a#bc',
+ 'Special characters are not allowed.'
+ );
} catch (err) {}
});
it('renders the VectorUploadOptions component with error message when index name has -map as suffix', async () => {
try {
- await vectorUploadOptionsWithIndexNameRendererUtil('sample-map', "Map name can't end with -map.");
+ await vectorUploadOptionsWithIndexNameRendererUtil(
+ 'sample-map',
+ "Map name can't end with -map."
+ );
} catch (err) {}
});
it('renders the VectorUploadOptions component when we have successfully indexed all the data', async () => {
addUserInputToDOM();
- console.log("test case for successfully indexed file data");
+ console.log('test case for successfully indexed file data');
const button = screen.getByRole('button', { name: 'import-file-button' });
jest.spyOn(serviceApiCalls, 'getIndex').mockImplementation(() => {
return Promise.resolve(getIndexResponseWhenIndexIsNotPresent);
@@ -185,7 +198,7 @@ describe('vector_upload_options', () => {
it('renders the VectorUploadOptions component when we have partial failures during indexing', async () => {
addUserInputToDOM();
- console.log("test case for partial failures during indexing");
+ console.log('test case for partial failures during indexing');
const button = screen.getByRole('button', { name: 'import-file-button' });
jest.spyOn(serviceApiCalls, 'getIndex').mockImplementation(() => {
return Promise.resolve(getIndexResponseWhenIndexIsNotPresent);
@@ -200,7 +213,7 @@ describe('vector_upload_options', () => {
it('renders the VectorUploadOptions component when all the documents fail to index', async () => {
addUserInputToDOM();
- console.log("test case for failed documents");
+ console.log('test case for failed documents');
const button = screen.getByRole('button', { name: 'import-file-button' });
jest.spyOn(serviceApiCalls, 'getIndex').mockImplementation(() => {
return Promise.resolve(getIndexResponseWhenIndexIsNotPresent);
@@ -215,7 +228,7 @@ describe('vector_upload_options', () => {
it('renders the VectorUploadOptions component when postGeojson call fails', async () => {
addUserInputToDOM();
- console.log("test case for call failure to postGeojson");
+ console.log('test case for call failure to postGeojson');
const button = screen.getByRole('button', { name: 'import-file-button' });
jest.spyOn(serviceApiCalls, 'getIndex').mockImplementation(() => {
return Promise.resolve(getIndexResponseWhenIndexIsNotPresent);
@@ -230,7 +243,7 @@ describe('vector_upload_options', () => {
it('renders the VectorUploadOptions component when getIndex returns a duplicate index', async () => {
addUserInputToDOM();
- console.log("test case for duplicate index check");
+ console.log('test case for duplicate index check');
const button = screen.getByRole('button', { name: 'import-file-button' });
jest.spyOn(serviceApiCalls, 'getIndex').mockImplementation(() => {
return Promise.resolve(getIndexResponseWhenIndexIsPresent);
diff --git a/src/plugins/custom_import_map/public/components/vector_upload_options.tsx b/src/plugins/custom_import_map/public/components/vector_upload_options.tsx
index 9e839de6..6f573f39 100644
--- a/src/plugins/custom_import_map/public/components/vector_upload_options.tsx
+++ b/src/plugins/custom_import_map/public/components/vector_upload_options.tsx
@@ -18,8 +18,12 @@ import {
EuiFormRow,
} from '@elastic/eui';
import { getIndex, postGeojson } from '../services';
+import { ShowErrorModal } from './show_error_modal';
import { MAX_FILE_PAYLOAD_SIZE, MAX_FILE_PAYLOAD_SIZE_IN_MB } from '../../common';
-import { toMountPoint, useOpenSearchDashboards } from '../../../../../../src/plugins/opensearch_dashboards_react/public';
+import {
+ toMountPoint,
+ useOpenSearchDashboards,
+} from '../../../../../../src/plugins/opensearch_dashboards_react/public';
import { RegionMapOptionsProps } from '../../../../../../src/plugins/region_map/public';
const VectorUploadOptions = (props: RegionMapOptionsProps) => {
@@ -56,7 +60,7 @@ const VectorUploadOptions = (props: RegionMapOptionsProps) => {
};
const validateIndexName = (typedIndexName: string, isIndexNameWithSuffix: boolean) => {
- let error = [];
+ const error = [];
const errorIndexNameDiv = fetchElementByName('errorIndexName');
// check for presence of index name entered by the user
@@ -109,7 +113,7 @@ const VectorUploadOptions = (props: RegionMapOptionsProps) => {
);
setLoading(false);
return false;
- }
+ }
if (files[0].size === 0) {
notifications.toasts.addDanger(
'Error. File does not contain valid features. Check your json format.'
@@ -130,7 +134,7 @@ const VectorUploadOptions = (props: RegionMapOptionsProps) => {
fileData = await files[0].text();
}
return fileData;
- }
+ };
const handleSubmit = async () => {
// show import button as loading
@@ -164,6 +168,7 @@ const VectorUploadOptions = (props: RegionMapOptionsProps) => {
const successfullyIndexedRecordCount = result.resp.success;
const failedToIndexRecordCount = result.resp.failure;
const totalRecords = result.resp.total;
+
if (successfullyIndexedRecordCount === totalRecords) {
notifications.toasts.addSuccess(
'Successfully added ' + successfullyIndexedRecordCount + ' features to ' + indexName
@@ -172,17 +177,27 @@ const VectorUploadOptions = (props: RegionMapOptionsProps) => {
}
if (successfullyIndexedRecordCount > 0 && failedToIndexRecordCount > 0) {
- const title = 'Partially indexed ' + successfullyIndexedRecordCount + ' of ' +
- totalRecords + ' features in ' + indexName;
+ const title =
+ 'Partially indexed ' +
+ successfullyIndexedRecordCount +
+ ' of ' +
+ totalRecords +
+ ' features in ' +
+ indexName;
+ const showModalProps = {
+ modalTitle: 'Error Details',
+ modalBody: JSON.stringify(result.resp.failures),
+ buttonText: 'View error details',
+ };
notifications.toasts.addDanger({
- title: title,
+ title,
iconType: 'alert',
text: toMountPoint(
There were {failedToIndexRecordCount} errors processing the custom map.
- View error details
+
@@ -195,7 +210,7 @@ const VectorUploadOptions = (props: RegionMapOptionsProps) => {
'Error. File does not contain valid features. Check your json format.'
);
}
- }
+ };
const uploadGeojson = async (indexName: string, fileData: object) => {
const bodyData = {
@@ -253,12 +268,8 @@ const VectorUploadOptions = (props: RegionMapOptionsProps) => {
-
- Formats accepted: .json, .geojson
-
-
- Max size: 25 MB
-
+ Formats accepted: .json, .geojson
+ Max size: 25 MB
Coordinates must be in EPSG:4326 coordinate reference system.
diff --git a/src/plugins/custom_import_map/public/plugin.tsx b/src/plugins/custom_import_map/public/plugin.tsx
index c137883a..068e0602 100644
--- a/src/plugins/custom_import_map/public/plugin.tsx
+++ b/src/plugins/custom_import_map/public/plugin.tsx
@@ -9,28 +9,25 @@ import { CoreSetup, CoreStart, Plugin } from '../../../../../src/core/public';
import {
CustomImportMapPluginSetup,
CustomImportMapPluginStart,
- AppPluginSetupDependencies
+ AppPluginSetupDependencies,
} from './types';
import { RegionMapVisualizationDependencies } from '../../../../../src/plugins/region_map/public';
import { VectorUploadOptions } from './components/vector_upload_options';
export class CustomImportMapPlugin
implements Plugin {
- public setup(core: CoreSetup, { regionMap }: AppPluginSetupDependencies): CustomImportMapPluginSetup {
-
+ public setup(
+ core: CoreSetup,
+ { regionMap }: AppPluginSetupDependencies
+ ): CustomImportMapPluginSetup {
regionMap.addOptionTab({
name: 'controls',
- title: i18n.translate(
- 'regionMap.mapVis.regionMapEditorConfig.controlTabs.controlsTitle',
- {
- defaultMessage: 'Import Vector Map',
- }
- ),
- editor: (props : RegionMapVisualizationDependencies) => (
-
- ),
- })
-
+ title: i18n.translate('regionMap.mapVis.regionMapEditorConfig.controlTabs.controlsTitle', {
+ defaultMessage: 'Import Vector Map',
+ }),
+ editor: (props: RegionMapVisualizationDependencies) => ,
+ });
+
// Return methods that should be available to other plugins
return {};
}
diff --git a/src/plugins/custom_import_map/public/services.ts b/src/plugins/custom_import_map/public/services.ts
index aeedf84b..f2312fc8 100644
--- a/src/plugins/custom_import_map/public/services.ts
+++ b/src/plugins/custom_import_map/public/services.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { CoreStart } from "../../../../../src/core/public";
+import { CoreStart } from '../../../../../src/core/public';
export const postGeojson = async (requestBody: any, http: CoreStart['http']) => {
try {
diff --git a/src/plugins/custom_import_map/public/types.ts b/src/plugins/custom_import_map/public/types.ts
index f1266b7d..593f1b10 100644
--- a/src/plugins/custom_import_map/public/types.ts
+++ b/src/plugins/custom_import_map/public/types.ts
@@ -13,9 +13,9 @@ export interface CustomImportMapPluginSetup {}
export interface CustomImportMapPluginStart {}
export interface AppPluginStartDependencies {
- navigation: NavigationPublicPluginStart;
+ navigation: NavigationPublicPluginStart;
}
export interface AppPluginSetupDependencies {
- regionMap: RegionMapPluginSetup;
+ regionMap: RegionMapPluginSetup;
}
diff --git a/src/plugins/custom_import_map/server/plugin.ts b/src/plugins/custom_import_map/server/plugin.ts
index 80eabc91..b094bb6c 100644
--- a/src/plugins/custom_import_map/server/plugin.ts
+++ b/src/plugins/custom_import_map/server/plugin.ts
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import { first } from 'rxjs/operators';
import {
PluginInitializerContext,
CoreSetup,
@@ -12,7 +13,6 @@ import {
} from '../../../../../src/core/server';
import { CustomImportMapPluginSetup, CustomImportMapPluginStart } from './types';
-import { first } from 'rxjs/operators';
import { createGeospatialCluster } from './clusters';
import { GeospatialService, OpensearchService } from './services';
import { geospatial, opensearch } from '../server/routes';