Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.16] [Console] Fix load_from param (#196836) #197194

Merged
merged 1 commit into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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 () => {
Expand Down