, React.Component<{}, {}, any>>;
+ const mockOnConfirm = jest.fn();
+ const mockOnSkip = jest.fn();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ act(() => {
+ component = shallowWithIntl();
+ });
+
+ component.update();
+ });
+
+ it('should render correclty', () => {
+ expect(component).toMatchSnapshot();
+ });
+
+ it('should call onConfirm when clicking the "Overwrite" button', () => {
+ act(() => {
+ // @ts-ignore
+ component.find(confirmModalIdentifier).first().props().onConfirm();
+ });
+
+ component.update();
+
+ // Expect that the onConfirm function has been called
+ expect(mockOnConfirm).toHaveBeenCalled();
+ });
+
+ it('should call onSkip when clicking the "Skip" button', () => {
+ act(() => {
+ // @ts-ignore
+ component.find(confirmModalIdentifier).first().props().onCancel();
+ });
+
+ component.update();
+
+ // Expect that the onSkip function has been called
+ expect(mockOnSkip).toHaveBeenCalled();
+ });
+
+ it('should display the correct title and body text', () => {
+ // Find the title and body text elements
+ const componentProps = component.find(confirmModalIdentifier).first().props();
+ // Find the element inside the component
+ const paragraphElement = component.find('p');
+
+ // Expect the correct translations for title and body text
+ expect(componentProps.title).toBe('Confirm Overwrite');
+
+ // Check the text content of the
element
+ const expectedText =
+ 'Are you sure you want to overwrite the existing queries? This action cannot be undone. All existing queries will be deleted and replaced with the imported queries. If you are unsure, please choose the "Merge with existing queries" option instead';
+ expect(paragraphElement.text()).toEqual(expectedText);
+ });
+});
diff --git a/src/plugins/console/public/application/components/overwrite_modal.tsx b/src/plugins/console/public/application/components/overwrite_modal.tsx
new file mode 100644
index 000000000000..5432a0fc86c2
--- /dev/null
+++ b/src/plugins/console/public/application/components/overwrite_modal.tsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import React from 'react';
+import { i18n } from '@osd/i18n';
+import { EUI_MODAL_CONFIRM_BUTTON, EuiConfirmModal } from '@elastic/eui';
+
+export interface OverwriteModalProps {
+ onSkip: () => void;
+ onConfirm: () => void;
+}
+export const OverwriteModal = ({ onSkip, onConfirm }: OverwriteModalProps) => {
+ return (
+
+
+ {i18n.translate('console.overwriteModal.body.conflict', {
+ defaultMessage:
+ 'Are you sure you want to overwrite the existing queries? This action cannot be undone. All existing queries will be deleted and replaced with the imported queries. If you are unsure, please choose the "{option}" option instead',
+ values: { option: 'Merge with existing queries' },
+ })}
+
+
+ );
+};
diff --git a/src/plugins/console/public/application/containers/main/get_top_nav.ts b/src/plugins/console/public/application/containers/main/get_top_nav.ts
index e7eba5c580ac..cd21321993bb 100644
--- a/src/plugins/console/public/application/containers/main/get_top_nav.ts
+++ b/src/plugins/console/public/application/containers/main/get_top_nav.ts
@@ -34,9 +34,17 @@ interface Props {
onClickHistory: () => void;
onClickSettings: () => void;
onClickHelp: () => void;
+ onClickExport: () => void;
+ onClickImport: () => void;
}
-export function getTopNavConfig({ onClickHistory, onClickSettings, onClickHelp }: Props) {
+export function getTopNavConfig({
+ onClickHistory,
+ onClickSettings,
+ onClickHelp,
+ onClickExport,
+ onClickImport,
+}: Props) {
return [
{
id: 'history',
@@ -77,5 +85,31 @@ export function getTopNavConfig({ onClickHistory, onClickSettings, onClickHelp }
},
testId: 'consoleHelpButton',
},
+ {
+ id: 'export',
+ label: i18n.translate('console.topNav.exportTabLabel', {
+ defaultMessage: 'Export',
+ }),
+ description: i18n.translate('console.topNav.exportTabDescription', {
+ defaultMessage: 'Export',
+ }),
+ onClick: () => {
+ onClickExport();
+ },
+ testId: 'consoleExportButton',
+ },
+ {
+ id: 'import',
+ label: i18n.translate('console.topNav.importTabLabel', {
+ defaultMessage: 'Import',
+ }),
+ description: i18n.translate('console.topNav.importTabDescription', {
+ defaultMessage: 'Import',
+ }),
+ onClick: () => {
+ onClickImport();
+ },
+ testId: 'consoleImportButton',
+ },
];
}
diff --git a/src/plugins/console/public/application/containers/main/main.tsx b/src/plugins/console/public/application/containers/main/main.tsx
index 1967c14615bb..bbe5bd9856eb 100644
--- a/src/plugins/console/public/application/containers/main/main.tsx
+++ b/src/plugins/console/public/application/containers/main/main.tsx
@@ -28,8 +28,10 @@
* under the License.
*/
-import React, { useEffect, useState } from 'react';
+import React, { useState } from 'react';
import { i18n } from '@osd/i18n';
+// @ts-expect-error
+import { saveAs } from '@elastic/filesaver';
import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiPageContent } from '@elastic/eui';
import { ConsoleHistory } from '../console_history';
import { Editor } from '../editor';
@@ -41,6 +43,7 @@ import {
HelpPanel,
SomethingWentWrongCallout,
NetworkRequestStatusBar,
+ ImportFlyout,
} from '../../components';
import { useServicesContext, useEditorReadContext, useRequestReadContext } from '../../contexts';
@@ -54,7 +57,7 @@ interface MainProps {
export function Main({ dataSourceId }: MainProps) {
const {
- services: { storage },
+ services: { storage, objectStorageClient },
} = useServicesContext();
const { ready: editorsReady } = useEditorReadContext();
@@ -71,6 +74,14 @@ export function Main({ dataSourceId }: MainProps) {
const [showingHistory, setShowHistory] = useState(false);
const [showSettings, setShowSettings] = useState(false);
const [showHelp, setShowHelp] = useState(false);
+ const [showImportFlyout, setShowImportFlyout] = useState(false);
+
+ const onExport = async () => {
+ const results = await objectStorageClient.text.findAll();
+ const senseData = results.sort((a, b) => a.createdAt - b.createdAt)[0];
+ const blob = new Blob([JSON.stringify(senseData || {})], { type: 'application/json' });
+ saveAs(blob, 'sense.json');
+ };
const renderConsoleHistory = () => {
return editorsReady ? setShowHistory(false)} /> : null;
@@ -111,6 +122,8 @@ export function Main({ dataSourceId }: MainProps) {
onClickHistory: () => setShowHistory(!showingHistory),
onClickSettings: () => setShowSettings(true),
onClickHelp: () => setShowHelp(!showHelp),
+ onClickExport: () => onExport(),
+ onClickImport: () => setShowImportFlyout(!showImportFlyout),
})}
/>
@@ -152,6 +165,10 @@ export function Main({ dataSourceId }: MainProps) {
) : null}
{showHelp ? setShowHelp(false)} /> : null}
+
+ {showImportFlyout ? (
+ setShowImportFlyout(false)} />
+ ) : null}
);
}
diff --git a/src/plugins/console/public/application/contexts/services_context.mock.ts b/src/plugins/console/public/application/contexts/services_context.mock.ts
index 5e39565aca8a..bf12961989bc 100644
--- a/src/plugins/console/public/application/contexts/services_context.mock.ts
+++ b/src/plugins/console/public/application/contexts/services_context.mock.ts
@@ -28,8 +28,11 @@
* under the License.
*/
-import { notificationServiceMock } from '../../../../../core/public/mocks';
-import { httpServiceMock } from '../../../../../core/public/mocks';
+import {
+ notificationServiceMock,
+ uiSettingsServiceMock,
+ httpServiceMock,
+} from '../../../../../core/public/mocks';
import { HistoryMock } from '../../services/history.mock';
import { SettingsMock } from '../../services/settings.mock';
@@ -53,7 +56,9 @@ export const serviceContextMock = {
settings: new SettingsMock(storage),
history: new HistoryMock(storage),
notifications: notificationServiceMock.createSetupContract(),
+ uiSettings: uiSettingsServiceMock.createSetupContract(),
objectStorageClient: {} as any,
+ http,
},
docLinkVersion: 'NA',
};
diff --git a/src/plugins/console/public/application/contexts/services_context.tsx b/src/plugins/console/public/application/contexts/services_context.tsx
index fc9ab157f783..0e8398ea8b83 100644
--- a/src/plugins/console/public/application/contexts/services_context.tsx
+++ b/src/plugins/console/public/application/contexts/services_context.tsx
@@ -29,7 +29,7 @@
*/
import React, { createContext, useContext, useEffect } from 'react';
-import { HttpSetup, NotificationsSetup } from 'opensearch-dashboards/public';
+import { HttpSetup, IUiSettingsClient, NotificationsSetup } from 'opensearch-dashboards/public';
import { History, Settings, Storage } from '../../services';
import { ObjectStorageClient } from '../../../common/types';
import { MetricsTracker } from '../../types';
@@ -44,6 +44,7 @@ interface ContextServices {
trackUiMetric: MetricsTracker;
opensearchHostService: OpenSearchHostService;
http: HttpSetup;
+ uiSettings: IUiSettingsClient;
}
export interface ContextValue {
diff --git a/src/plugins/console/public/application/hooks/use_data_init/use_data_init.ts b/src/plugins/console/public/application/hooks/use_data_init/use_data_init.ts
index b9be1c56d912..9c6ee873806e 100644
--- a/src/plugins/console/public/application/hooks/use_data_init/use_data_init.ts
+++ b/src/plugins/console/public/application/hooks/use_data_init/use_data_init.ts
@@ -32,6 +32,13 @@ import { useCallback, useEffect, useState } from 'react';
import { migrateToTextObjects } from './data_migration';
import { useEditorActionContext, useServicesContext } from '../../contexts';
+const DEFAULT_INPUT_VALUE = `GET _search
+{
+ "query": {
+ "match_all": {}
+ }
+}`;
+
export const useDataInit = () => {
const [error, setError] = useState(null);
const [done, setDone] = useState(false);
@@ -58,7 +65,7 @@ export const useDataInit = () => {
const newObject = await objectStorageClient.text.create({
createdAt: Date.now(),
updatedAt: Date.now(),
- text: '',
+ text: DEFAULT_INPUT_VALUE,
});
dispatch({ type: 'setCurrentTextObject', payload: newObject });
} else {
diff --git a/src/plugins/console/public/application/hooks/use_send_current_request_to_opensearch/use_send_current_request_to_opensearch.test.tsx b/src/plugins/console/public/application/hooks/use_send_current_request_to_opensearch/use_send_current_request_to_opensearch.test.tsx
index cc7be7e444f0..8955972d27a0 100644
--- a/src/plugins/console/public/application/hooks/use_send_current_request_to_opensearch/use_send_current_request_to_opensearch.test.tsx
+++ b/src/plugins/console/public/application/hooks/use_send_current_request_to_opensearch/use_send_current_request_to_opensearch.test.tsx
@@ -74,7 +74,10 @@ describe('useSendCurrentRequestToOpenSearch', () => {
const { result } = renderHook(() => useSendCurrentRequestToOpenSearch(), { wrapper: contexts });
await act(() => result.current());
- expect(sendRequestToOpenSearch).toHaveBeenCalledWith({ requests: ['test'] });
+ expect(sendRequestToOpenSearch).toHaveBeenCalledWith({
+ requests: ['test'],
+ http: mockContextValue.services.http,
+ });
// Second call should be the request success
const [, [requestSucceededCall]] = (dispatch as jest.Mock).mock.calls;
diff --git a/src/plugins/console/public/application/index.tsx b/src/plugins/console/public/application/index.tsx
index c1a107ac500a..ac32909735b2 100644
--- a/src/plugins/console/public/application/index.tsx
+++ b/src/plugins/console/public/application/index.tsx
@@ -30,7 +30,7 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
-import { HttpSetup, NotificationsSetup } from 'src/core/public';
+import { HttpSetup, IUiSettingsClient, NotificationsSetup } from 'src/core/public';
import { ServicesContextProvider, EditorContextProvider, RequestContextProvider } from './contexts';
import { Main } from './containers';
import { createStorage, createHistory, createSettings } from '../services';
@@ -47,6 +47,7 @@ export interface BootDependencies {
usageCollection?: UsageCollectionSetup;
element: HTMLElement;
dataSourceId?: string;
+ uiSettings: IUiSettingsClient;
}
export function renderApp({
@@ -57,6 +58,7 @@ export function renderApp({
element,
http,
dataSourceId,
+ uiSettings,
}: BootDependencies) {
const trackUiMetric = createUsageTracker(usageCollection);
trackUiMetric.load('opened_app');
@@ -85,6 +87,7 @@ export function renderApp({
trackUiMetric,
objectStorageClient,
http,
+ uiSettings,
},
}}
>
diff --git a/src/plugins/console/public/plugin.ts b/src/plugins/console/public/plugin.ts
index 5e1478875ec6..300d57d4b75d 100644
--- a/src/plugins/console/public/plugin.ts
+++ b/src/plugins/console/public/plugin.ts
@@ -68,6 +68,7 @@ export class ConsoleUIPlugin implements Plugin