-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Console] Fix load_from param (#196836)
Fixes #195877 Addresses #179658 ## Summary This PR fixes the bug in Console where using the `load_from` param in the URL made Console uneditable because every re-render reset the initial value in the editor. This is fixed by restricting the hook to only set the initial value once. This PR also adds some unit tests for the hook, as I realized that this was a long-standing improvement. ### How to test: Try loading the following URL (making the necessary replacement in the URL) and verify that the data is correctly loaded into the editor and value can be edited: `http://localhost:5601/<REPLACE-THIS>/app/dev_tools#/console?load_from=data:text/plain,AoeQygKgBA9A+gRwK4FMBOBPGBDAzhgOwGMB+AEzQHsAHOApAGwbiMoaQFsDcAoAbx5QoAImToMwgFwiAZgCVKAWShoUHSgBcUAWgBUkgJYEyKAB4pcwgDSCRDSkWwMUUkSgLXbwmQYZa0rgJCQsIARpRsgbbBIhxIuBquANoAujYxIT5+6Mlp0cHCuAAWlIxkuekZwnEJdJq5+QC+ts2NQA` `http://localhost:5601/<REPLACE-THIS>/app/dev_tools#/console?load_from=https://www.elastic.co/guide/en/elasticsearch/reference/current/snippets/86.console` Co-authored-by: Matthew Kime <[email protected]>
- Loading branch information
1 parent
c25a97b
commit e6e4e34
Showing
2 changed files
with
189 additions
and
6 deletions.
There are no files selected for viewing
178 changes: 178 additions & 0 deletions
178
src/plugins/console/public/application/containers/editor/hooks/use_set_initial_value.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the "Elastic License | ||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
* Public License v 1"; you may not use this file except in compliance with, at | ||
* your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
* License v3.0 only", or the "Server Side Public License, v 1". | ||
*/ | ||
|
||
import { renderHook, act } from '@testing-library/react-hooks'; | ||
import { useSetInitialValue } from './use_set_initial_value'; | ||
import { IToasts } from '@kbn/core-notifications-browser'; | ||
import { decompressFromEncodedURIComponent } from 'lz-string'; | ||
import { DEFAULT_INPUT_VALUE } from '../../../../../common/constants'; | ||
|
||
jest.mock('lz-string', () => ({ | ||
decompressFromEncodedURIComponent: jest.fn(), | ||
})); | ||
|
||
jest.mock('./use_set_initial_value', () => ({ | ||
...jest.requireActual('./use_set_initial_value'), | ||
})); | ||
|
||
describe('useSetInitialValue', () => { | ||
const setValueMock = jest.fn(); | ||
const addWarningMock = jest.fn(); | ||
const toastsMock: IToasts = { addWarning: addWarningMock } as any; | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
it('should set the initial value only once', async () => { | ||
const { rerender } = renderHook(() => | ||
useSetInitialValue({ | ||
localStorageValue: 'initial value', | ||
setValue: setValueMock, | ||
toasts: toastsMock, | ||
}) | ||
); | ||
|
||
// Verify initial value is set on first render | ||
expect(setValueMock).toHaveBeenCalledTimes(1); | ||
expect(setValueMock).toHaveBeenCalledWith('initial value'); | ||
|
||
// Re-render the hook to simulate a component update | ||
rerender(); | ||
|
||
// Verify that setValue is not called again after rerender | ||
expect(setValueMock).toHaveBeenCalledTimes(1); // Still 1, no additional calls | ||
}); | ||
|
||
it('should set value from localStorage if no load_from param is present', () => { | ||
renderHook(() => | ||
useSetInitialValue({ | ||
localStorageValue: 'saved value', | ||
setValue: setValueMock, | ||
toasts: toastsMock, | ||
}) | ||
); | ||
|
||
expect(setValueMock).toHaveBeenCalledWith('saved value'); | ||
}); | ||
|
||
it('should set default value if localStorage is undefined and no load_from param is present', () => { | ||
renderHook(() => | ||
useSetInitialValue({ | ||
localStorageValue: undefined, | ||
setValue: setValueMock, | ||
toasts: toastsMock, | ||
}) | ||
); | ||
|
||
expect(setValueMock).toHaveBeenCalledWith(DEFAULT_INPUT_VALUE); | ||
}); | ||
|
||
it('should load data from load_from param if it is a valid Elastic URL', async () => { | ||
Object.defineProperty(window, 'location', { | ||
writable: true, | ||
value: { | ||
hash: '?load_from=https://www.elastic.co/some-data', | ||
}, | ||
}); | ||
|
||
// Mock fetch to return "remote data" | ||
global.fetch = jest.fn(() => | ||
Promise.resolve({ | ||
text: () => Promise.resolve('remote data'), | ||
}) | ||
) as jest.Mock; | ||
|
||
await act(async () => { | ||
renderHook(() => | ||
useSetInitialValue({ | ||
localStorageValue: 'initial value', | ||
setValue: setValueMock, | ||
toasts: toastsMock, | ||
}) | ||
); | ||
}); | ||
|
||
expect(fetch).toHaveBeenCalledWith(new URL('https://www.elastic.co/some-data')); | ||
// The remote data should be appended to the initial value in the editor | ||
expect(setValueMock).toHaveBeenCalledWith('initial value\n\nremote data'); | ||
}); | ||
|
||
it('should show a warning if the load_from param is not an Elastic domain', async () => { | ||
Object.defineProperty(window, 'location', { | ||
writable: true, | ||
value: { | ||
hash: '?load_from=https://not.elastic.com/some-data', | ||
}, | ||
}); | ||
|
||
await act(async () => { | ||
renderHook(() => | ||
useSetInitialValue({ | ||
localStorageValue: 'initial value', | ||
setValue: setValueMock, | ||
toasts: toastsMock, | ||
}) | ||
); | ||
}); | ||
|
||
expect(fetch).not.toHaveBeenCalled(); | ||
expect(addWarningMock).toHaveBeenCalledWith( | ||
'Only URLs with the Elastic domain (www.elastic.co) can be loaded in Console.' | ||
); | ||
}); | ||
|
||
it('should load and decompress data from a data URI', async () => { | ||
Object.defineProperty(window, 'location', { | ||
writable: true, | ||
value: { | ||
hash: '?load_from=data:text/plain,compressed-data', | ||
}, | ||
}); | ||
(decompressFromEncodedURIComponent as jest.Mock).mockReturnValue('decompressed data'); | ||
|
||
await act(async () => { | ||
renderHook(() => | ||
useSetInitialValue({ | ||
localStorageValue: 'initial value', | ||
setValue: setValueMock, | ||
toasts: toastsMock, | ||
}) | ||
); | ||
}); | ||
|
||
expect(decompressFromEncodedURIComponent).toHaveBeenCalledWith('compressed-data'); | ||
// The initial value in the editor should be replaces with the decompressed data | ||
expect(setValueMock).toHaveBeenCalledWith('decompressed data'); | ||
}); | ||
|
||
it('should show a warning if decompressing a data URI fails', async () => { | ||
Object.defineProperty(window, 'location', { | ||
writable: true, | ||
value: { | ||
hash: '?load_from=data:text/plain,invalid-data', | ||
}, | ||
}); | ||
(decompressFromEncodedURIComponent as jest.Mock).mockReturnValue(null); | ||
|
||
await act(async () => { | ||
renderHook(() => | ||
useSetInitialValue({ | ||
localStorageValue: 'initial value', | ||
setValue: setValueMock, | ||
toasts: toastsMock, | ||
}) | ||
); | ||
}); | ||
|
||
expect(addWarningMock).toHaveBeenCalledWith( | ||
'Unable to load data from the load_from query parameter in the URL' | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters