[Security Solution] Remove kbn-url-state package (#176122)

## Summary

This PR removes unused kbn url state package. It was only used in the
flyout and is now replaced with a shared utility, used by several other
modules.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Luke G 2024-02-02 22:44:32 +01:00 committed by GitHub
parent d3a2221ae4
commit 81ccdc2ac5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 0 additions and 300 deletions

1
.github/CODEOWNERS vendored
View file

@ -844,7 +844,6 @@ x-pack/plugins/upgrade_assistant @elastic/platform-deployment-management
x-pack/plugins/uptime @elastic/obs-ux-infra_services-team
x-pack/plugins/drilldowns/url_drilldown @elastic/appex-sharedux
src/plugins/url_forwarding @elastic/kibana-visualizations
packages/kbn-url-state @elastic/security-threat-hunting-investigations
src/plugins/usage_collection @elastic/kibana-core
test/plugin_functional/plugins/usage_collection @elastic/kibana-core
packages/kbn-use-tracked-promise @elastic/obs-ux-logs-team

View file

@ -832,7 +832,6 @@
"@kbn/uptime-plugin": "link:x-pack/plugins/uptime",
"@kbn/url-drilldown-plugin": "link:x-pack/plugins/drilldowns/url_drilldown",
"@kbn/url-forwarding-plugin": "link:src/plugins/url_forwarding",
"@kbn/url-state": "link:packages/kbn-url-state",
"@kbn/usage-collection-plugin": "link:src/plugins/usage_collection",
"@kbn/usage-collection-test-plugin": "link:test/plugin_functional/plugins/usage_collection",
"@kbn/use-tracked-promise": "link:packages/kbn-use-tracked-promise",

View file

@ -1,33 +0,0 @@
# @kbn/url-state - utils for syncing state to URL
This package provides a React hook called `useUrlState` that can be used to synchronize state to the URL. This can be useful when you want to make a portion of state shareable.
The state is grouped under a namespace, to avoid collisions. See the example url below for how it would look like.
### Example usage:
```
import React, { useState } from 'react';
import { useUrlState } from '@kbn/url-state';
function MyComponent() {
const [name, setName] = useUrlState<number>('namespace','name');
const handleClick = () => {
setName('John Doe')
};
return (
<div>
<p>Name: {name}</p>
<button onClick={handleClick}>Set name</button>
</div>
);
}
```
The resulting URL will look like this:
```
http://localhost:5601/?namespace=(name:John%20Doe)
```

View file

@ -1,113 +0,0 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { renderHook, act } from '@testing-library/react-hooks';
import { useUrlState } from '.';
describe('useSyncToUrl', () => {
let originalLocation: Location;
let originalHistory: History;
beforeEach(() => {
originalLocation = window.location;
originalHistory = window.history;
delete (window as any).location;
delete (window as any).history;
window.location = {
...originalLocation,
search: '',
hash: '',
};
window.history = {
...originalHistory,
replaceState: jest.fn(),
pushState: jest.fn(),
};
jest.useFakeTimers();
});
afterEach(() => {
window.location = originalLocation;
window.history = originalHistory;
jest.useRealTimers();
});
it('should update the URL when the state changes', () => {
window.location.hash = '#should_be_there';
const { result } = renderHook(() => useUrlState('namespace', 'test'));
act(() => {
result.current[1]('foo');
jest.runAllTimers();
});
expect(window.history.pushState).toHaveBeenCalledWith(
{},
'',
'#should_be_there?namespace=(test%3Afoo)'
);
});
it('should escape values correctly', () => {
window.location.hash = '#should_be_there';
const { result } = renderHook(() => useUrlState('namespace', 'test'));
act(() => {
result.current[1]('foo#bar');
jest.runAllTimers();
});
expect(window.history.pushState).toHaveBeenCalledWith(
{},
'',
'#should_be_there?namespace=(test%3Afoo%23bar)'
);
});
it('should remove the key from the namespace after undefined is passed (state clear mechanism)', () => {
window.location.hash = '#should_be_there';
const { result } = renderHook(() => useUrlState('namespace', 'test'));
act(() => {
result.current[1](undefined);
jest.runAllTimers();
});
expect(window.history.pushState).toHaveBeenCalledWith({}, '', '#should_be_there?namespace=()');
});
it('should restore the value from the query string on mount', () => {
window.location.search = `?namespace=(test:foo)`;
const {
result: { current: state },
} = renderHook(() => useUrlState('namespace', 'test'));
expect(state[0]).toEqual('foo');
});
it('should return updated state on browser navigation', () => {
window.location.search = '?namespace=(test:foo)';
const { result } = renderHook(() => useUrlState('namespace', 'test'));
expect(result.current[0]).toEqual('foo');
act(() => {
window.location.search = '?namespace=(test:bar)';
window.dispatchEvent(new CustomEvent('popstate'));
});
expect(result.current[0]).toEqual('bar');
});
});

View file

@ -1,101 +0,0 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
import { useCallback, useEffect, useState } from 'react';
import { encode, decode, RisonValue } from '@kbn/rison';
import { stringify, parse } from 'query-string';
import { merge } from 'lodash';
const CUSTOM_URL_EVENT = 'url:update' as const;
// This is a list of events that can trigger a render.
const URL_CHANGE_EVENTS: string[] = ['popstate', CUSTOM_URL_EVENT];
/**
* This hook stores state in the URL, but with a namespace to avoid collisions with other values in the URL.
* It also batches updates to the URL to avoid excessive history entries.
* With it, you can store state in the URL and have it persist across page refreshes.
* The state is stored in the URL as a Rison encoded object.
*
* Example: when called like this `const [value, setValue] = useUrlState<boolean>('myNamespace', 'myKey');`
* the state will be stored in the URL like this: `?myNamespace=(myKey:!n)`
*
* State is not cleared from the URL when the hook is unmounted and this is by design.
* If you want it to be cleared, you can do it manually by calling `setValue(undefined)`.
*
* @param urlNamespace actual top level query param key
* @param key sub key of the query param
*/
export const useUrlState = <T = unknown>(urlNamespace: string, key: string) => {
const [internalValue, setInternalValue] = useState<T | undefined>(undefined);
useEffect(() => {
// This listener is called on browser navigation or on custom event.
// It updates the LOCAL state, allowing dependent components to re-render.
const listener = () => {
const searchParams = new URLSearchParams(window.location.search);
const param = searchParams.get(urlNamespace);
const decodedState = param ? decode(param) : ({} as Record<string, RisonValue>);
const decodedValue = (decodedState as Record<string, RisonValue> | undefined)?.[key];
setInternalValue(decodedValue as unknown as T);
};
listener();
URL_CHANGE_EVENTS.forEach((event) => window.addEventListener(event, listener));
return () => URL_CHANGE_EVENTS.forEach((event) => window.removeEventListener(event, listener));
}, [key, urlNamespace]);
const setValue = useCallback(
(newValue: T | undefined) => {
const queryParams = parse(location.search) as any;
const currentNsValue = (
queryParams?.[urlNamespace] ? decode(queryParams?.[urlNamespace]) : {}
) as any;
const currentValue = currentNsValue?.[key];
const canSpread =
typeof newValue === 'object' &&
typeof currentValue === 'object' &&
!Array.isArray(newValue) &&
!Array.isArray(currentValue);
const upatedValueToStoreAtKey = canSpread
? (merge(currentValue, newValue) as unknown as T)
: (newValue as unknown as T);
if (upatedValueToStoreAtKey) {
currentNsValue[key] = upatedValueToStoreAtKey;
} else {
delete currentNsValue[key];
}
queryParams[urlNamespace] = encodeURIComponent(encode(currentNsValue));
// NOTE: don't re-encode the entire url params string
const newSearch = stringify(queryParams, { encode: false });
if (window.location.search === newSearch) {
return;
}
const newUrl = `${window.location.hash}?${newSearch}`;
window.history.pushState({}, '', newUrl);
// This custom event is used to notify other instances
// of this hook that the URL has changed.
window.dispatchEvent(new Event(CUSTOM_URL_EVENT));
},
[key, urlNamespace]
);
return [internalValue, setValue] as const;
};

View file

@ -1,13 +0,0 @@
/*
* 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 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 or the Server
* Side Public License, v 1.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../..',
roots: ['<rootDir>/packages/kbn-url-state'],
};

View file

@ -1,5 +0,0 @@
{
"type": "shared-common",
"id": "@kbn/url-state",
"owner": "@elastic/security-threat-hunting-investigations"
}

View file

@ -1,6 +0,0 @@
{
"name": "@kbn/url-state",
"private": true,
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0"
}

View file

@ -1,21 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/rison",
]
}

View file

@ -1682,8 +1682,6 @@
"@kbn/url-drilldown-plugin/*": ["x-pack/plugins/drilldowns/url_drilldown/*"],
"@kbn/url-forwarding-plugin": ["src/plugins/url_forwarding"],
"@kbn/url-forwarding-plugin/*": ["src/plugins/url_forwarding/*"],
"@kbn/url-state": ["packages/kbn-url-state"],
"@kbn/url-state/*": ["packages/kbn-url-state/*"],
"@kbn/usage-collection-plugin": ["src/plugins/usage_collection"],
"@kbn/usage-collection-plugin/*": ["src/plugins/usage_collection/*"],
"@kbn/usage-collection-test-plugin": ["test/plugin_functional/plugins/usage_collection"],

View file

@ -6428,10 +6428,6 @@
version "0.0.0"
uid ""
"@kbn/url-state@link:packages/kbn-url-state":
version "0.0.0"
uid ""
"@kbn/usage-collection-plugin@link:src/plugins/usage_collection":
version "0.0.0"
uid ""