mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Console] Fix load_from param (#196836)](https://github.com/elastic/kibana/pull/196836) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Elena Stoeva","email":"59341489+ElenaStoeva@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-10-21T19:20:30Z","message":"[Console] Fix load_from param (#196836)\n\nFixes https://github.com/elastic/kibana/issues/195877\r\nAddresses https://github.com/elastic/kibana/issues/179658\r\n\r\n## Summary\r\n\r\nThis PR fixes the bug in Console where using the `load_from` param in\r\nthe URL made Console uneditable because every re-render reset the\r\ninitial value in the editor. This is fixed by restricting the hook to\r\nonly set the initial value once. This PR also adds some unit tests for\r\nthe hook, as I realized that this was a long-standing improvement.\r\n\r\n### How to test:\r\n\r\nTry loading the following URL (making the necessary replacement in the\r\nURL) and verify that the data is correctly loaded into the editor and\r\nvalue can be edited:\r\n\r\n\r\n`http://localhost:5601/<REPLACE-THIS>/app/dev_tools#/console?load_from=data:text/plain,AoeQygKgBA9A+gRwK4FMBOBPGBDAzhgOwGMB+AEzQHsAHOApAGwbiMoaQFsDcAoAbx5QoAImToMwgFwiAZgCVKAWShoUHSgBcUAWgBUkgJYEyKAB4pcwgDSCRDSkWwMUUkSgLXbwmQYZa0rgJCQsIARpRsgbbBIhxIuBquANoAujYxIT5+6Mlp0cHCuAAWlIxkuekZwnEJdJq5+QC+ts2NQA`\r\n\r\n\r\n\r\n`http://localhost:5601/<REPLACE-THIS>/app/dev_tools#/console?load_from=https://www.elastic.co/guide/en/elasticsearch/reference/current/snippets/86.console`\r\n\r\nCo-authored-by: Matthew Kime <matt@mattki.me>","sha":"e6e4e343aa700f1b489915f8df233a0abf2ea058","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Console","Team:Kibana Management","release_note:skip","v9.0.0","backport:prev-minor"],"title":"[Console] Fix load_from param","number":196836,"url":"https://github.com/elastic/kibana/pull/196836","mergeCommit":{"message":"[Console] Fix load_from param (#196836)\n\nFixes https://github.com/elastic/kibana/issues/195877\r\nAddresses https://github.com/elastic/kibana/issues/179658\r\n\r\n## Summary\r\n\r\nThis PR fixes the bug in Console where using the `load_from` param in\r\nthe URL made Console uneditable because every re-render reset the\r\ninitial value in the editor. This is fixed by restricting the hook to\r\nonly set the initial value once. This PR also adds some unit tests for\r\nthe hook, as I realized that this was a long-standing improvement.\r\n\r\n### How to test:\r\n\r\nTry loading the following URL (making the necessary replacement in the\r\nURL) and verify that the data is correctly loaded into the editor and\r\nvalue can be edited:\r\n\r\n\r\n`http://localhost:5601/<REPLACE-THIS>/app/dev_tools#/console?load_from=data:text/plain,AoeQygKgBA9A+gRwK4FMBOBPGBDAzhgOwGMB+AEzQHsAHOApAGwbiMoaQFsDcAoAbx5QoAImToMwgFwiAZgCVKAWShoUHSgBcUAWgBUkgJYEyKAB4pcwgDSCRDSkWwMUUkSgLXbwmQYZa0rgJCQsIARpRsgbbBIhxIuBquANoAujYxIT5+6Mlp0cHCuAAWlIxkuekZwnEJdJq5+QC+ts2NQA`\r\n\r\n\r\n\r\n`http://localhost:5601/<REPLACE-THIS>/app/dev_tools#/console?load_from=https://www.elastic.co/guide/en/elasticsearch/reference/current/snippets/86.console`\r\n\r\nCo-authored-by: Matthew Kime <matt@mattki.me>","sha":"e6e4e343aa700f1b489915f8df233a0abf2ea058"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/196836","number":196836,"mergeCommit":{"message":"[Console] Fix load_from param (#196836)\n\nFixes https://github.com/elastic/kibana/issues/195877\r\nAddresses https://github.com/elastic/kibana/issues/179658\r\n\r\n## Summary\r\n\r\nThis PR fixes the bug in Console where using the `load_from` param in\r\nthe URL made Console uneditable because every re-render reset the\r\ninitial value in the editor. This is fixed by restricting the hook to\r\nonly set the initial value once. This PR also adds some unit tests for\r\nthe hook, as I realized that this was a long-standing improvement.\r\n\r\n### How to test:\r\n\r\nTry loading the following URL (making the necessary replacement in the\r\nURL) and verify that the data is correctly loaded into the editor and\r\nvalue can be edited:\r\n\r\n\r\n`http://localhost:5601/<REPLACE-THIS>/app/dev_tools#/console?load_from=data:text/plain,AoeQygKgBA9A+gRwK4FMBOBPGBDAzhgOwGMB+AEzQHsAHOApAGwbiMoaQFsDcAoAbx5QoAImToMwgFwiAZgCVKAWShoUHSgBcUAWgBUkgJYEyKAB4pcwgDSCRDSkWwMUUkSgLXbwmQYZa0rgJCQsIARpRsgbbBIhxIuBquANoAujYxIT5+6Mlp0cHCuAAWlIxkuekZwnEJdJq5+QC+ts2NQA`\r\n\r\n\r\n\r\n`http://localhost:5601/<REPLACE-THIS>/app/dev_tools#/console?load_from=https://www.elastic.co/guide/en/elasticsearch/reference/current/snippets/86.console`\r\n\r\nCo-authored-by: Matthew Kime <matt@mattki.me>","sha":"e6e4e343aa700f1b489915f8df233a0abf2ea058"}}]}] BACKPORT--> Co-authored-by: Elena Stoeva <59341489+ElenaStoeva@users.noreply.github.com>
This commit is contained in:
parent
3b401f3c16
commit
a5ad3e5d93
2 changed files with 189 additions and 6 deletions
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -12,7 +12,7 @@ import { parse } from 'query-string';
|
|||
import { IToasts } from '@kbn/core-notifications-browser';
|
||||
import { decompressFromEncodedURIComponent } from 'lz-string';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { DEFAULT_INPUT_VALUE } from '../../../../../common/constants';
|
||||
|
||||
interface QueryParams {
|
||||
|
@ -46,6 +46,7 @@ export const readLoadFromParam = () => {
|
|||
*/
|
||||
export const useSetInitialValue = (params: SetInitialValueParams) => {
|
||||
const { localStorageValue, setValue, toasts } = params;
|
||||
const isInitialValueSet = useRef<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const loadBufferFromRemote = async (url: string) => {
|
||||
|
@ -104,11 +105,15 @@ export const useSetInitialValue = (params: SetInitialValueParams) => {
|
|||
|
||||
const loadFromParam = readLoadFromParam();
|
||||
|
||||
if (loadFromParam) {
|
||||
loadBufferFromRemote(loadFromParam);
|
||||
} else {
|
||||
// Only set to default input value if the localstorage value is undefined
|
||||
setValue(localStorageValue ?? DEFAULT_INPUT_VALUE);
|
||||
// Only set the value in the editor if an initial value hasn't been set yet
|
||||
if (!isInitialValueSet.current) {
|
||||
if (loadFromParam) {
|
||||
loadBufferFromRemote(loadFromParam);
|
||||
} else {
|
||||
// Only set to default input value if the localstorage value is undefined
|
||||
setValue(localStorageValue ?? DEFAULT_INPUT_VALUE);
|
||||
}
|
||||
isInitialValueSet.current = true;
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue