Skip to content

Commit e475830

Browse files
Feature: Dev Tools Query Export/Import (#3810) (#4987)
* feat: Add export functionality for dev tool queries * feat: Add import functionality for dev tool queries * update: changelog. * feat: updated license headers for new file. * fix: typo:ImportFlyout and removed ImportModeControl prop (isLegacyFile) * fix: default query export * fix: added basic text validation for imported file. * update: removed legacy variable export. * test: Add unit tests for OverwriteModal component * test: Add unit tests for ImportModeControl component * test: Add unit tests for ImportFlyout component * test: updated unit tests for ImportFlyout component * test: Enhance async operation handling and complete async cycle in Enzyme test suites. * test: Enhanced test coverage for ImportFlyout component unit tests * fix: Add 'http' property to serviceContextMock and update test expectations --------- (cherry picked from commit 56dd85b) Signed-off-by: kishor82 <kishorrathva8298@gmail.com> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent a53e8a2 commit e475830

File tree

18 files changed

+1440
-9
lines changed

18 files changed

+1440
-9
lines changed

src/plugins/console/public/application/components/__snapshots__/import_flyout.test.tsx.snap

Lines changed: 489 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/plugins/console/public/application/components/__snapshots__/import_mode_control.test.tsx.snap

Lines changed: 34 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/plugins/console/public/application/components/__snapshots__/overwrite_modal.test.tsx.snap

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import React from 'react';
7+
import { act } from 'react-dom/test-utils';
8+
import { ImportFlyout } from './import_flyout';
9+
import { ContextValue, ServicesContextProvider } from '../contexts';
10+
import { serviceContextMock } from '../contexts/services_context.mock';
11+
import { wrapWithIntl, nextTick } from 'test_utils/enzyme_helpers';
12+
import { ReactWrapper, mount } from 'enzyme';
13+
14+
const mockFile = new File(['{"text":"Sample JSON data"}'], 'sample.json', {
15+
type: 'application/json',
16+
});
17+
const mockFile1 = new File(['{"text":"Sample JSON data1"}'], 'sample.json', {
18+
type: 'application/json',
19+
});
20+
21+
const mockInvalidFile = new File(['Some random data'], 'sample.json', {
22+
type: 'application/json',
23+
});
24+
25+
const filePickerIdentifier = '[data-test-subj="queryFilePicker"]';
26+
const confirmBtnIdentifier = '[data-test-subj="importQueriesConfirmBtn"]';
27+
const cancelBtnIdentifier = '[data-test-subj="importQueriesCancelBtn"]';
28+
const confirmModalConfirmButton = '[data-test-subj="confirmModalConfirmButton"]';
29+
const confirmModalCancelButton = '[data-test-subj="confirmModalCancelButton"]';
30+
const importErrorTextIdentifier = '[data-test-subj="importSenseObjectsErrorText"]';
31+
const mergeOptionIdentifier = '[id="overwriteDisabled"]';
32+
const overwriteOptionIdentifier = '[id="overwriteEnabled"]';
33+
const callOutIdentifier = 'EuiCallOut';
34+
35+
const invalidFileError = 'The selected file is not valid. Please select a valid JSON file.';
36+
37+
const defaultQuery = {
38+
id: '43461752-1fd5-472e-8487-b47ff7fccbc8',
39+
createdAt: 1690958998493,
40+
updatedAt: 1690958998493,
41+
text: 'GET _search\n{\n "query": {\n "match_all": {}\n }\n}',
42+
};
43+
44+
describe('ImportFlyout Component', () => {
45+
let mockedAppContextValue: ContextValue;
46+
const mockClose = jest.fn();
47+
const mockRefresh = jest.fn();
48+
const mockFindAll = jest.fn();
49+
const mockCreate = jest.fn();
50+
const mockUpdate = jest.fn();
51+
52+
let component: ReactWrapper<any, Readonly<{}>, React.Component<{}, {}, any>>;
53+
54+
beforeEach(async () => {
55+
jest.clearAllMocks();
56+
57+
mockedAppContextValue = serviceContextMock.create();
58+
59+
mockedAppContextValue.services.objectStorageClient.text = {
60+
create: mockCreate,
61+
update: mockUpdate,
62+
findAll: mockFindAll,
63+
};
64+
65+
mockFindAll.mockResolvedValue([defaultQuery]);
66+
67+
await act(async () => {
68+
component = mount(wrapWithIntl(<ImportFlyout close={mockClose} refresh={mockRefresh} />), {
69+
wrappingComponent: ServicesContextProvider,
70+
wrappingComponentProps: {
71+
value: mockedAppContextValue,
72+
},
73+
});
74+
});
75+
await nextTick();
76+
component.update();
77+
});
78+
79+
it('renders correctly', () => {
80+
expect(component).toMatchSnapshot();
81+
});
82+
83+
it('should enable confirm button when select file', async () => {
84+
// Confirm button should be disable if no file selected
85+
expect(component.find(confirmBtnIdentifier).first().props().disabled).toBe(true);
86+
component.update();
87+
88+
await act(async () => {
89+
const nodes = component.find(filePickerIdentifier);
90+
// @ts-ignore
91+
nodes.first().props().onChange([mockFile1]);
92+
await new Promise((r) => setTimeout(r, 1));
93+
});
94+
await nextTick();
95+
component.update();
96+
97+
// Confirm button should be enable after importing file
98+
expect(component.find(confirmBtnIdentifier).first().props().disabled).toBe(false);
99+
});
100+
101+
it('should handle import process with default import mode', async () => {
102+
await act(async () => {
103+
const nodes = component.find(filePickerIdentifier);
104+
// @ts-ignore
105+
nodes.first().props().onChange([mockFile]);
106+
// Applied a timeout after FileReader.onload event to resolve side effect issue.
107+
await new Promise((r) => setTimeout(r, 10));
108+
});
109+
110+
await nextTick();
111+
component.update();
112+
113+
await act(async () => {
114+
await nextTick();
115+
component.find(confirmBtnIdentifier).first().simulate('click');
116+
});
117+
118+
component.update();
119+
120+
// default option "Merge with existing queries" should be checked by default
121+
expect(component.find(mergeOptionIdentifier).first().props().checked).toBe(true);
122+
expect(component.find(overwriteOptionIdentifier).first().props().checked).toBe(false);
123+
124+
// should update existing query
125+
expect(mockUpdate).toBeCalledTimes(1);
126+
expect(mockClose).toBeCalledTimes(1);
127+
expect(mockRefresh).toBeCalledTimes(1);
128+
});
129+
130+
it('should handle errors during import', async () => {
131+
await act(async () => {
132+
const nodes = component.find(filePickerIdentifier);
133+
// @ts-ignore
134+
nodes.first().props().onChange([mockInvalidFile]);
135+
});
136+
137+
component.update();
138+
139+
await act(async () => {
140+
await nextTick();
141+
component.find(confirmBtnIdentifier).first().simulate('click');
142+
});
143+
144+
component.update();
145+
146+
expect(component.find(callOutIdentifier).exists()).toBe(true);
147+
148+
expect(component.find(importErrorTextIdentifier).text()).toEqual(invalidFileError);
149+
});
150+
151+
it('should handle internal errors', async () => {
152+
const errorMessage = 'some internal error';
153+
mockFindAll.mockRejectedValue(new Error(errorMessage));
154+
await act(async () => {
155+
const nodes = component.find(filePickerIdentifier);
156+
// @ts-ignore
157+
nodes.first().props().onChange([mockFile]);
158+
await new Promise((r) => setTimeout(r, 1));
159+
});
160+
161+
component.update();
162+
163+
await act(async () => {
164+
await nextTick();
165+
component.find(confirmBtnIdentifier).first().simulate('click');
166+
});
167+
168+
component.update();
169+
170+
expect(component.find(callOutIdentifier).exists()).toBe(true);
171+
172+
expect(component.find(importErrorTextIdentifier).text()).toEqual(
173+
`The file could not be processed due to error: "${errorMessage}"`
174+
);
175+
});
176+
177+
it('should cancel button work normally', async () => {
178+
act(() => {
179+
component.find(cancelBtnIdentifier).first().simulate('click');
180+
});
181+
182+
expect(mockClose).toBeCalledTimes(1);
183+
});
184+
185+
describe('OverwriteModal', () => {
186+
beforeEach(async () => {
187+
jest.clearAllMocks();
188+
// Select a file
189+
await act(async () => {
190+
const nodes = component.find(filePickerIdentifier);
191+
// @ts-ignore
192+
nodes.first().props().onChange([mockFile]);
193+
await new Promise((r) => setTimeout(r, 1));
194+
});
195+
component.update();
196+
197+
// change import mode to overwrite
198+
await act(async () => {
199+
await nextTick();
200+
component.find(overwriteOptionIdentifier).last().simulate('change');
201+
});
202+
203+
component.update();
204+
205+
// import selected file
206+
await act(async () => {
207+
await nextTick();
208+
component.find(confirmBtnIdentifier).first().simulate('click');
209+
});
210+
211+
component.update();
212+
});
213+
it('should handle overwrite confirmation', async () => {
214+
// Check confirm overwrite modal exist before confirmation
215+
expect(component.find('OverwriteModal').exists()).toBe(true);
216+
217+
// confirm overwrite
218+
await act(async () => {
219+
await nextTick();
220+
component.find(confirmModalConfirmButton).first().simulate('click');
221+
});
222+
223+
component.update();
224+
225+
// should update existing query
226+
expect(mockUpdate).toBeCalledTimes(1);
227+
expect(mockClose).toBeCalledTimes(1);
228+
expect(mockRefresh).toBeCalledTimes(1);
229+
230+
// confirm overwrite modal should close after confirmation.
231+
expect(component.find('OverwriteModal').exists()).toBe(false);
232+
});
233+
234+
it('should handle overwrite skip', async () => {
235+
// Check confirm overwrite modal exist before skip
236+
expect(component.find('OverwriteModal').exists()).toBe(true);
237+
238+
// confirm overwrite
239+
act(() => {
240+
component.find(confirmModalCancelButton).first().simulate('click');
241+
});
242+
await nextTick();
243+
component.update();
244+
245+
// confirm overwrite modal should close after cancel.
246+
expect(component.find('OverwriteModal').exists()).toBe(false);
247+
});
248+
249+
it('should create storage text when storage client returns empty result with overwrite import mode', async () => {
250+
mockFindAll.mockResolvedValue([]);
251+
252+
// confirm overwrite
253+
await act(async () => {
254+
await nextTick();
255+
component.find(confirmModalConfirmButton).first().simulate('click');
256+
});
257+
258+
component.update();
259+
260+
expect(component.find(overwriteOptionIdentifier).first().props().checked).toBe(true);
261+
262+
// should create new query
263+
expect(mockCreate).toBeCalledTimes(1);
264+
expect(mockClose).toBeCalledTimes(1);
265+
expect(mockRefresh).toBeCalledTimes(1);
266+
});
267+
});
268+
});

0 commit comments

Comments
 (0)