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

[ML] Move local storage utilities to package. #148049

Merged
merged 32 commits into from
Jan 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6113e98
wip to bring useStorage to package
walterra Dec 23, 2022
ff7b2eb
fix package setup
walterra Dec 23, 2022
ade605d
refactor storage context to be based on generics.
walterra Dec 23, 2022
20a58e3
fix jest tests
walterra Dec 23, 2022
6be2cde
use local storage package for data visualizer
walterra Dec 23, 2022
ef2454e
use local storage package for aiops
walterra Dec 23, 2022
98c2732
types/consts cleanup
walterra Dec 23, 2022
e3f0d73
fix local storage for data visualizer
walterra Dec 27, 2022
ec91b4c
fix local storage for explain log rate spikes
walterra Dec 27, 2022
eea9870
fix local storage for aiops section
walterra Dec 27, 2022
45bf5fa
fix space
walterra Dec 27, 2022
92fb231
move isDefined to package
walterra Dec 27, 2022
995ae43
[CI] Auto-commit changed files from 'node scripts/generate codeowners'
kibanamachine Dec 27, 2022
183e884
fix imports
walterra Dec 27, 2022
915e6b2
Merge branch 'ml-package-storage' of github.com:walterra/kibana into …
walterra Dec 27, 2022
ad5cfc9
[CI] Auto-commit changed files from 'node scripts/ts_project_linter -…
kibanamachine Dec 27, 2022
40e18a1
fix imports
walterra Dec 27, 2022
5fa22db
Merge branch 'ml-package-storage' of github.com:walterra/kibana into …
walterra Dec 27, 2022
ae7842f
Merge branch 'main' into ml-package-storage
walterra Dec 27, 2022
286a971
Merge branch 'main' into ml-package-storage
walterra Dec 28, 2022
6794796
Merge branch 'main' into ml-package-storage
walterra Dec 29, 2022
b4d0bfd
Merge branch 'main' into ml-package-storage
walterra Dec 29, 2022
928a2d6
Merge branch 'main' into ml-package-storage
walterra Dec 30, 2022
c0e5b87
improve READMEs. remove prefix.
walterra Dec 30, 2022
834f71f
Merge branch 'main' into ml-package-storage
walterra Jan 3, 2023
64a4aa0
Merge branch 'main' into ml-package-storage
walterra Jan 3, 2023
2bd1942
Merge branch 'main' into ml-package-storage
walterra Jan 4, 2023
543387f
Adds comments to exported code
walterra Jan 4, 2023
6c50789
Merge branch 'main' into ml-package-storage
walterra Jan 4, 2023
469fa97
Merge branch 'main' into ml-package-storage
walterra Jan 4, 2023
b41dacf
more api docs
walterra Jan 4, 2023
58fbb87
Merge branch 'main' into ml-package-storage
walterra Jan 5, 2023
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
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,9 @@ packages/shared-ux/storybook/mock @elastic/kibana-global-experience
x-pack/packages/ml/agg_utils @elastic/ml-ui
x-pack/packages/ml/aiops_components @elastic/ml-ui
x-pack/packages/ml/aiops_utils @elastic/ml-ui
x-pack/packages/ml/is_defined @elastic/ml-ui
x-pack/packages/ml/is_populated_object @elastic/ml-ui
x-pack/packages/ml/local_storage @elastic/ml-ui
x-pack/packages/ml/nested_property @elastic/ml-ui
x-pack/packages/ml/string_hash @elastic/ml-ui
x-pack/packages/ml/url_state @elastic/ml-ui
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,9 @@
"@kbn/logging-mocks": "link:packages/kbn-logging-mocks",
"@kbn/mapbox-gl": "link:packages/kbn-mapbox-gl",
"@kbn/ml-agg-utils": "link:x-pack/packages/ml/agg_utils",
"@kbn/ml-is-defined": "link:x-pack/packages/ml/is_defined",
"@kbn/ml-is-populated-object": "link:x-pack/packages/ml/is_populated_object",
"@kbn/ml-local-storage": "link:x-pack/packages/ml/local_storage",
"@kbn/ml-nested-property": "link:x-pack/packages/ml/nested_property",
"@kbn/ml-string-hash": "link:x-pack/packages/ml/string_hash",
"@kbn/ml-url-state": "link:x-pack/packages/ml/url_state",
Expand Down
4 changes: 4 additions & 0 deletions tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -816,8 +816,12 @@
"@kbn/maps-plugin/*": ["x-pack/plugins/maps/*"],
"@kbn/ml-agg-utils": ["x-pack/packages/ml/agg_utils"],
"@kbn/ml-agg-utils/*": ["x-pack/packages/ml/agg_utils/*"],
"@kbn/ml-is-defined": ["x-pack/packages/ml/is_defined"],
"@kbn/ml-is-defined/*": ["x-pack/packages/ml/is_defined/*"],
"@kbn/ml-is-populated-object": ["x-pack/packages/ml/is_populated_object"],
"@kbn/ml-is-populated-object/*": ["x-pack/packages/ml/is_populated_object/*"],
"@kbn/ml-local-storage": ["x-pack/packages/ml/local_storage"],
"@kbn/ml-local-storage/*": ["x-pack/packages/ml/local_storage/*"],
"@kbn/ml-nested-property": ["x-pack/packages/ml/nested_property"],
"@kbn/ml-nested-property/*": ["x-pack/packages/ml/nested_property/*"],
"@kbn/ml-plugin": ["x-pack/plugins/ml"],
Expand Down
3 changes: 3 additions & 0 deletions x-pack/packages/ml/is_defined/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @kbn/ml-is-defined

Utility function to determine if a value is not `undefined` and not `null`.
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
* 2.0.
*/

export { MlStorageContextProvider, useStorage } from './storage_context';
export { isDefined } from './src/is_defined';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment to this exported function to keep the 'public APIs without comments' count at 0?

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

export function isDefined<T>(argument: T | undefined | null): argument is T {
return argument !== undefined && argument !== null;
}
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/x-pack/packages/ml/is_defined'],
};
5 changes: 5 additions & 0 deletions x-pack/packages/ml/is_defined/kibana.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/ml-is-defined",
"owner": "@elastic/ml-ui"
}
6 changes: 6 additions & 0 deletions x-pack/packages/ml/is_defined/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "@kbn/ml-is-defined",
"private": true,
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
* 2.0.
*/

/**
* Checks whether the supplied argument is not `undefined` and not `null`.
*
* @param argument
* @returns boolean
*/
export function isDefined<T>(argument: T | undefined | null): argument is T {
return argument !== undefined && argument !== null;
}
19 changes: 19 additions & 0 deletions x-pack/packages/ml/is_defined/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": []
}
3 changes: 3 additions & 0 deletions x-pack/packages/ml/local_storage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# @kbn/ml-local-storage

Utilities to combine url state management with local storage.
8 changes: 8 additions & 0 deletions x-pack/packages/ml/local_storage/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export { StorageContextProvider, useStorage } from './src/storage_context';
12 changes: 12 additions & 0 deletions x-pack/packages/ml/local_storage/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: ['<rootDir>/x-pack/packages/ml/local_storage'],
};
5 changes: 5 additions & 0 deletions x-pack/packages/ml/local_storage/kibana.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/ml-local-storage",
"owner": "@elastic/ml-ui"
}
9 changes: 9 additions & 0 deletions x-pack/packages/ml/local_storage/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "@kbn/ml-local-storage",
"description": "Utilities to combine url state management with local storage.",
"author": "Machine Learning UI",
"homepage": "https://docs.elastic.dev/kibana-dev-docs/api/kbn-ml-local-storage",
"private": true,
"version": "1.0.0",
"license": "SSPL-1.0 OR Elastic License 2.0"
}
193 changes: 193 additions & 0 deletions x-pack/packages/ml/local_storage/src/storage_context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, {
type PropsWithChildren,
useEffect,
useMemo,
useCallback,
useState,
useContext,
} from 'react';
import { omit } from 'lodash';

import type { Storage } from '@kbn/kibana-utils-plugin/public';
import { isDefined } from '@kbn/ml-is-defined';

/**
* StorageDefinition is a dictionary with `string` based keys.
*/
interface StorageDefinition {
[key: string]: unknown;
}

/**
* TStorage, a partial `StorageDefinition` or `null`.
*/
type TStorage = Partial<StorageDefinition> | null;
/**
* TStorageKey, keys of StorageDefintion.
*/
type TStorageKey = keyof Exclude<TStorage, null>;
/**
* TStorageMapped, mapping of TStorage with TStorageKey.
*/
type TStorageMapped<T extends TStorageKey> = T extends string ? unknown : null;

/**
* StorageAPI definition of store TStorage with accessors.
*/
interface StorageAPI {
value: TStorage;
setValue: <K extends TStorageKey, T extends TStorageMapped<K>>(key: K, value: T) => void;
removeValue: <K extends TStorageKey>(key: K) => void;
}

/**
* Type guard to check if a supplied `key` is in `storageKey`.
*
* @param key
* @param storageKeys
* @returns boolean
*/
export function isStorageKey<T>(key: unknown, storageKeys: readonly T[]): key is T {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment to this to keep the 'public APIs without comments' count at 0 for this package.

return storageKeys.includes(key as T);
}

/**
* React context to hold storage API.
*/
export const MlStorageContext = React.createContext<StorageAPI>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment to this to keep the 'public APIs without comments' count at 0 for this package.

value: null,
setValue() {
throw new Error('MlStorageContext set method is not implemented');
},
removeValue() {
throw new Error('MlStorageContext remove method is not implemented');
},
});

/**
* Props for StorageContextProvider
*/
interface StorageContextProviderProps<K extends TStorageKey> {
storage: Storage;
storageKeys: readonly K[];
}

/**
* Provider to manage context for the `useStorage` hook.
*/
export function StorageContextProvider<K extends TStorageKey, T extends TStorage>({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a comment to this to keep the 'public APIs without comments' count at 0 for this package.

children,
storage,
storageKeys,
}: PropsWithChildren<StorageContextProviderProps<K>>) {
const initialValue = useMemo(() => {
return storageKeys.reduce((acc, curr) => {
acc[curr as K] = storage.get(curr as string);
return acc;
}, {} as Exclude<T, null>);
}, [storage, storageKeys]);

const [state, setState] = useState<T>(initialValue);

const setStorageValue = useCallback(
<TM extends TStorageMapped<K>>(key: K, value: TM) => {
storage.set(key as string, value);

setState((prevState) => ({
...prevState,
[key]: value,
}));
},
[storage]
);

const removeStorageValue = useCallback(
(key: K) => {
storage.remove(key as string);
setState((prevState) => omit(prevState, key) as T);
},
[storage]
);

useEffect(
function updateStorageOnExternalChange() {
const eventListener = (event: StorageEvent) => {
if (!isStorageKey(event.key, storageKeys)) return;

if (isDefined(event.newValue)) {
setState((prev) => {
return {
...prev,
[event.key as K]:
typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue,
};
});
} else {
setState((prev) => omit(prev, event.key as K) as T);
}
};

/**
* This event listener is only invoked when
* the change happens in another browser's tab.
*/
window.addEventListener('storage', eventListener);

return () => {
window.removeEventListener('storage', eventListener);
};
},
[storageKeys]
);

const value = useMemo(() => {
return {
value: state,
setValue: setStorageValue,
removeValue: removeStorageValue,
} as StorageAPI;
}, [state, setStorageValue, removeStorageValue]);

return <MlStorageContext.Provider value={value}>{children}</MlStorageContext.Provider>;
}

/**
* Hook for consuming a storage value
* @param key
* @param initValue
*/
export function useStorage<K extends TStorageKey, T extends TStorageMapped<K>>(
key: K,
initValue?: T
): [
typeof initValue extends undefined ? T | undefined : Exclude<T, undefined>,
(value: T) => void
] {
const { value, setValue, removeValue } = useContext(MlStorageContext);

const resultValue = useMemo(() => {
return (value?.[key] ?? initValue) as typeof initValue extends undefined
? T | undefined
: Exclude<T, undefined>;
}, [value, key, initValue]);

const setVal = useCallback(
(v: T) => {
if (isDefined(v)) {
setValue(key, v);
} else {
removeValue(key);
}
},
[setValue, removeValue, key]
);

return [resultValue, setVal];
}
22 changes: 22 additions & 0 deletions x-pack/packages/ml/local_storage/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*",
],
"kbn_references": [
"@kbn/kibana-utils-plugin",
"@kbn/ml-is-defined",
]
}
2 changes: 1 addition & 1 deletion x-pack/plugins/aiops/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@
"licensing"
],
"optionalPlugins": [],
"requiredBundles": ["fieldFormats", "kibanaReact"],
"requiredBundles": ["fieldFormats", "kibanaReact", "kibanaUtils"],
"extraPublicDirs": ["common"]
}
Loading