[8.x] [Console] Fix load_from param (#196836) (#197131)

# 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:
Kibana Machine 2024-10-22 08:09:39 +11:00 committed by GitHub
parent 3b401f3c16
commit a5ad3e5d93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 189 additions and 6 deletions

View file

@ -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'
);
});
});

View file

@ -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 () => {