diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 02c94120cec22..828a9e80f7c01 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 diff --git a/package.json b/package.json index d5e1dbeadb1bf..8d735d59a50ab 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/tsconfig.base.json b/tsconfig.base.json index a6e06ff470905..00ac0a8205740 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -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"], diff --git a/x-pack/packages/ml/is_defined/README.md b/x-pack/packages/ml/is_defined/README.md new file mode 100644 index 0000000000000..c87e487b565a6 --- /dev/null +++ b/x-pack/packages/ml/is_defined/README.md @@ -0,0 +1,3 @@ +# @kbn/ml-is-defined + +Utility function to determine if a value is not `undefined` and not `null`. diff --git a/x-pack/plugins/ml/public/application/contexts/storage/index.ts b/x-pack/packages/ml/is_defined/index.ts similarity index 77% rename from x-pack/plugins/ml/public/application/contexts/storage/index.ts rename to x-pack/packages/ml/is_defined/index.ts index 8b17073d3b099..8b04a61a8bdae 100644 --- a/x-pack/plugins/ml/public/application/contexts/storage/index.ts +++ b/x-pack/packages/ml/is_defined/index.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { MlStorageContextProvider, useStorage } from './storage_context'; +export { isDefined } from './src/is_defined'; diff --git a/x-pack/plugins/ml/common/types/guards.ts b/x-pack/packages/ml/is_defined/jest.config.js similarity index 65% rename from x-pack/plugins/ml/common/types/guards.ts rename to x-pack/packages/ml/is_defined/jest.config.js index ead91eafc2d4e..a4deb4d18ecf8 100644 --- a/x-pack/plugins/ml/common/types/guards.ts +++ b/x-pack/packages/ml/is_defined/jest.config.js @@ -5,6 +5,8 @@ * 2.0. */ -export function isDefined(argument: T | undefined | null): argument is T { - return argument !== undefined && argument !== null; -} +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/x-pack/packages/ml/is_defined'], +}; diff --git a/x-pack/packages/ml/is_defined/kibana.jsonc b/x-pack/packages/ml/is_defined/kibana.jsonc new file mode 100644 index 0000000000000..b25718598901b --- /dev/null +++ b/x-pack/packages/ml/is_defined/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/ml-is-defined", + "owner": "@elastic/ml-ui" +} diff --git a/x-pack/packages/ml/is_defined/package.json b/x-pack/packages/ml/is_defined/package.json new file mode 100644 index 0000000000000..2ed8896c22552 --- /dev/null +++ b/x-pack/packages/ml/is_defined/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/ml-is-defined", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/x-pack/plugins/data_visualizer/public/application/common/util/is_defined.ts b/x-pack/packages/ml/is_defined/src/is_defined.ts similarity index 75% rename from x-pack/plugins/data_visualizer/public/application/common/util/is_defined.ts rename to x-pack/packages/ml/is_defined/src/is_defined.ts index ead91eafc2d4e..1cf980293277c 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/util/is_defined.ts +++ b/x-pack/packages/ml/is_defined/src/is_defined.ts @@ -5,6 +5,12 @@ * 2.0. */ +/** + * Checks whether the supplied argument is not `undefined` and not `null`. + * + * @param argument + * @returns boolean + */ export function isDefined(argument: T | undefined | null): argument is T { return argument !== undefined && argument !== null; } diff --git a/x-pack/packages/ml/is_defined/tsconfig.json b/x-pack/packages/ml/is_defined/tsconfig.json new file mode 100644 index 0000000000000..5c989599ec9ad --- /dev/null +++ b/x-pack/packages/ml/is_defined/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] +} diff --git a/x-pack/packages/ml/local_storage/README.md b/x-pack/packages/ml/local_storage/README.md new file mode 100644 index 0000000000000..d1e7c46957397 --- /dev/null +++ b/x-pack/packages/ml/local_storage/README.md @@ -0,0 +1,3 @@ +# @kbn/ml-local-storage + +Utilities to combine url state management with local storage. diff --git a/x-pack/packages/ml/local_storage/index.ts b/x-pack/packages/ml/local_storage/index.ts new file mode 100644 index 0000000000000..f950f8791a341 --- /dev/null +++ b/x-pack/packages/ml/local_storage/index.ts @@ -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'; diff --git a/x-pack/packages/ml/local_storage/jest.config.js b/x-pack/packages/ml/local_storage/jest.config.js new file mode 100644 index 0000000000000..7de0a696c57ef --- /dev/null +++ b/x-pack/packages/ml/local_storage/jest.config.js @@ -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: ['/x-pack/packages/ml/local_storage'], +}; diff --git a/x-pack/packages/ml/local_storage/kibana.jsonc b/x-pack/packages/ml/local_storage/kibana.jsonc new file mode 100644 index 0000000000000..8afac70248f4f --- /dev/null +++ b/x-pack/packages/ml/local_storage/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/ml-local-storage", + "owner": "@elastic/ml-ui" +} diff --git a/x-pack/packages/ml/local_storage/package.json b/x-pack/packages/ml/local_storage/package.json new file mode 100644 index 0000000000000..af2452a92220b --- /dev/null +++ b/x-pack/packages/ml/local_storage/package.json @@ -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" +} diff --git a/x-pack/packages/ml/local_storage/src/storage_context.tsx b/x-pack/packages/ml/local_storage/src/storage_context.tsx new file mode 100644 index 0000000000000..e8cb64d81025e --- /dev/null +++ b/x-pack/packages/ml/local_storage/src/storage_context.tsx @@ -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 | null; +/** + * TStorageKey, keys of StorageDefintion. + */ +type TStorageKey = keyof Exclude; +/** + * TStorageMapped, mapping of TStorage with TStorageKey. + */ +type TStorageMapped = T extends string ? unknown : null; + +/** + * StorageAPI definition of store TStorage with accessors. + */ +interface StorageAPI { + value: TStorage; + setValue: >(key: K, value: T) => void; + removeValue: (key: K) => void; +} + +/** + * Type guard to check if a supplied `key` is in `storageKey`. + * + * @param key + * @param storageKeys + * @returns boolean + */ +export function isStorageKey(key: unknown, storageKeys: readonly T[]): key is T { + return storageKeys.includes(key as T); +} + +/** + * React context to hold storage API. + */ +export const MlStorageContext = React.createContext({ + 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 { + storage: Storage; + storageKeys: readonly K[]; +} + +/** + * Provider to manage context for the `useStorage` hook. + */ +export function StorageContextProvider({ + children, + storage, + storageKeys, +}: PropsWithChildren>) { + const initialValue = useMemo(() => { + return storageKeys.reduce((acc, curr) => { + acc[curr as K] = storage.get(curr as string); + return acc; + }, {} as Exclude); + }, [storage, storageKeys]); + + const [state, setState] = useState(initialValue); + + const setStorageValue = useCallback( + >(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 {children}; +} + +/** + * Hook for consuming a storage value + * @param key + * @param initValue + */ +export function useStorage>( + key: K, + initValue?: T +): [ + typeof initValue extends undefined ? T | undefined : Exclude, + (value: T) => void +] { + const { value, setValue, removeValue } = useContext(MlStorageContext); + + const resultValue = useMemo(() => { + return (value?.[key] ?? initValue) as typeof initValue extends undefined + ? T | undefined + : Exclude; + }, [value, key, initValue]); + + const setVal = useCallback( + (v: T) => { + if (isDefined(v)) { + setValue(key, v); + } else { + removeValue(key); + } + }, + [setValue, removeValue, key] + ); + + return [resultValue, setVal]; +} diff --git a/x-pack/packages/ml/local_storage/tsconfig.json b/x-pack/packages/ml/local_storage/tsconfig.json new file mode 100644 index 0000000000000..1f42768195c2b --- /dev/null +++ b/x-pack/packages/ml/local_storage/tsconfig.json @@ -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", + ] +} diff --git a/x-pack/plugins/aiops/kibana.json b/x-pack/plugins/aiops/kibana.json index dba431234ec0a..08cbaf4613464 100755 --- a/x-pack/plugins/aiops/kibana.json +++ b/x-pack/plugins/aiops/kibana.json @@ -16,6 +16,6 @@ "licensing" ], "optionalPlugins": [], - "requiredBundles": ["fieldFormats", "kibanaReact"], + "requiredBundles": ["fieldFormats", "kibanaReact", "kibanaUtils"], "extraPublicDirs": ["common"] } diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detetion_root.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detetion_root.tsx index 44c68c6017588..f7a039c082dee 100644 --- a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detetion_root.tsx +++ b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detetion_root.tsx @@ -5,16 +5,25 @@ * 2.0. */ +import React, { FC } from 'react'; + import { DataView } from '@kbn/data-views-plugin/common'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; -import React, { FC } from 'react'; +import { StorageContextProvider } from '@kbn/ml-local-storage'; import { UrlStateProvider } from '@kbn/ml-url-state'; -import { PageHeader } from '../page_header'; -import { ChangePointDetectionContextProvider } from './change_point_detection_context'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; + import { DataSourceContext } from '../../hooks/use_data_source'; import { SavedSearchSavedObject } from '../../application/utils/search_utils'; import { AiopsAppContext, AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; +import { AIOPS_STORAGE_KEYS } from '../../types/storage'; + +import { PageHeader } from '../page_header'; + import { ChangePointDetectionPage } from './change_point_detection_page'; +import { ChangePointDetectionContextProvider } from './change_point_detection_context'; + +const localStorage = new Storage(window.localStorage); export interface ChangePointDetectionAppStateProps { dataView: DataView; @@ -31,10 +40,12 @@ export const ChangePointDetectionAppState: FC - - - - + + + + + + diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx index 2e745894cac0a..6b6b477807d41 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_app_state.tsx @@ -11,11 +11,12 @@ import { EuiCallOut } from '@elastic/eui'; import type { Filter, Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; - import type { SavedSearch } from '@kbn/discover-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; - +import { StorageContextProvider } from '@kbn/ml-local-storage'; import { UrlStateProvider } from '@kbn/ml-url-state'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; + import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage, @@ -23,11 +24,14 @@ import { } from '../../application/utils/search_utils'; import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { AIOPS_STORAGE_KEYS } from '../../types/storage'; import { SpikeAnalysisTableRowStateProvider } from '../spike_analysis_table/spike_analysis_table_row_provider'; import { ExplainLogRateSpikesPage } from './explain_log_rate_spikes_page'; +const localStorage = new Storage(window.localStorage); + export interface ExplainLogRateSpikesAppStateProps { /** The data view to analyze. */ dataView: DataView; @@ -95,7 +99,9 @@ export const ExplainLogRateSpikesAppState: FC - + + + diff --git a/x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector.tsx b/x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector.tsx index 592240675197c..78cf53295cc26 100644 --- a/x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector.tsx +++ b/x-pack/plugins/aiops/public/components/full_time_range_selector/full_time_range_selector.tsx @@ -25,12 +25,19 @@ import { EuiToolTip, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useStorage } from '@kbn/ml-local-storage'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { type GetTimeFieldRangeResponse, setFullTimeRange, } from './full_time_range_selector_service'; -import { AIOPS_FROZEN_TIER_PREFERENCE, useStorage } from '../../hooks/use_storage'; +import { + AIOPS_FROZEN_TIER_PREFERENCE, + FROZEN_TIER_PREFERENCE, + type AiOpsKey, + type AiOpsStorageMapped, + type FrozenTierPreference, +} from '../../types/storage'; export interface FullTimeRangeSelectorProps { timefilter: TimefilterContract; @@ -40,13 +47,6 @@ export interface FullTimeRangeSelectorProps { callback?: (a: GetTimeFieldRangeResponse) => void; } -const FROZEN_TIER_PREFERENCE = { - EXCLUDE: 'exclude-frozen', - INCLUDE: 'include-frozen', -} as const; - -type FrozenTierPreference = typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE]; - export const FullTimeRangeSelector: FC = ({ timefilter, dataView, @@ -90,7 +90,10 @@ export const FullTimeRangeSelector: FC = ({ const [isPopoverOpen, setPopover] = useState(false); - const [frozenDataPreference, setFrozenDataPreference] = useStorage( + const [frozenDataPreference, setFrozenDataPreference] = useStorage< + AiOpsKey, + AiOpsStorageMapped + >( AIOPS_FROZEN_TIER_PREFERENCE, // By default we will exclude frozen data tier FROZEN_TIER_PREFERENCE.EXCLUDE diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx index 4c0da804f5eb6..ee571d990c832 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_app_state.tsx @@ -7,12 +7,19 @@ import React, { FC } from 'react'; import type { SavedSearch } from '@kbn/discover-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; +import { StorageContextProvider } from '@kbn/ml-local-storage'; import { UrlStateProvider } from '@kbn/ml-url-state'; -import { LogCategorizationPage } from './log_categorization_page'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; + import { SavedSearchSavedObject } from '../../application/utils/search_utils'; import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; +import { AIOPS_STORAGE_KEYS } from '../../types/storage'; import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; +import { LogCategorizationPage } from './log_categorization_page'; + +const localStorage = new Storage(window.localStorage); + export interface LogCategorizationAppStateProps { dataView: DataView; savedSearch: SavedSearch | SavedSearchSavedObject | null; @@ -27,7 +34,9 @@ export const LogCategorizationAppState: FC = ({ return ( - + + + ); diff --git a/x-pack/plugins/aiops/public/hooks/use_storage.ts b/x-pack/plugins/aiops/public/hooks/use_storage.ts deleted file mode 100644 index c377a85b3e84c..0000000000000 --- a/x-pack/plugins/aiops/public/hooks/use_storage.ts +++ /dev/null @@ -1,42 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useCallback, useState } from 'react'; -import { useAiopsAppContext } from './use_aiops_app_context'; - -export const AIOPS_FROZEN_TIER_PREFERENCE = 'aiops.frozenDataTierPreference'; - -export type AiOps = Partial<{ - [AIOPS_FROZEN_TIER_PREFERENCE]: 'exclude_frozen' | 'include_frozen'; -}> | null; - -export type AiOpsKey = keyof Exclude; - -/** - * Hook for accessing and changing a value in the storage. - * @param key - Storage key - * @param initValue - */ -export function useStorage(key: AiOpsKey, initValue?: T): [T, (value: T) => void] { - const { storage } = useAiopsAppContext(); - - const [val, setVal] = useState(storage.get(key) ?? initValue); - - const setStorage = useCallback( - (value: T): void => { - try { - storage.set(key, value); - setVal(value); - } catch (e) { - throw new Error('Unable to update storage with provided value'); - } - }, - [key, storage] - ); - - return [val, setStorage]; -} diff --git a/x-pack/plugins/aiops/public/types/storage.ts b/x-pack/plugins/aiops/public/types/storage.ts new file mode 100644 index 0000000000000..96eb612d9ca7a --- /dev/null +++ b/x-pack/plugins/aiops/public/types/storage.ts @@ -0,0 +1,28 @@ +/* + * 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 const AIOPS_FROZEN_TIER_PREFERENCE = 'aiops.frozenDataTierPreference'; + +export const FROZEN_TIER_PREFERENCE = { + EXCLUDE: 'exclude-frozen', + INCLUDE: 'include-frozen', +} as const; + +export type FrozenTierPreference = + typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE]; + +export type AiOps = Partial<{ + [AIOPS_FROZEN_TIER_PREFERENCE]: FrozenTierPreference; +}> | null; + +export type AiOpsKey = keyof Exclude; + +export type AiOpsStorageMapped = T extends typeof AIOPS_FROZEN_TIER_PREFERENCE + ? FrozenTierPreference | undefined + : null; + +export const AIOPS_STORAGE_KEYS = [AIOPS_FROZEN_TIER_PREFERENCE] as const; diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index 9f2e110d0af25..9eef408f5a513 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -43,6 +43,7 @@ "@kbn/core-elasticsearch-server", "@kbn/es-types", "@kbn/ml-url-state", + "@kbn/ml-local-storage", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/data_visualizer/kibana.json b/x-pack/plugins/data_visualizer/kibana.json index afc9fe34be640..98d27ba9f0481 100644 --- a/x-pack/plugins/data_visualizer/kibana.json +++ b/x-pack/plugins/data_visualizer/kibana.json @@ -26,6 +26,7 @@ ], "requiredBundles": [ "kibanaReact", + "kibanaUtils", "maps", "esUiShared", "fieldFormats", diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_content.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_content.tsx index 09b59cf10abb5..4e3ed74c4470e 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_content.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/document_count_content/document_count_content.tsx @@ -22,7 +22,7 @@ import { import { i18n } from '@kbn/i18n'; import { debounce, sortedIndex } from 'lodash'; import { FormattedMessage } from '@kbn/i18n-react'; -import { isDefined } from '../../util/is_defined'; +import { isDefined } from '@kbn/ml-is-defined'; import type { DocumentCountChartPoint } from './document_count_chart'; import { RANDOM_SAMPLER_STEP, diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx index 387c41f2b44f9..9f84bb6d1debd 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/results_links/results_links.tsx @@ -14,9 +14,9 @@ import { RefreshInterval } from '@kbn/data-plugin/public'; import { FindFileStructureResponse } from '@kbn/file-upload-plugin/common'; import type { FileUploadPluginStart } from '@kbn/file-upload-plugin/public'; import { flatten } from 'lodash'; +import { isDefined } from '@kbn/ml-is-defined'; import { LinkCardProps } from '../link_card/link_card'; import { useDataVisualizerKibana } from '../../../kibana_context'; -import { isDefined } from '../../util/is_defined'; type LinkType = 'file' | 'index'; diff --git a/x-pack/plugins/data_visualizer/public/application/common/util/example_utils.ts b/x-pack/plugins/data_visualizer/public/application/common/util/example_utils.ts index cc4a9a3ca9bfa..e3e97666844c4 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/util/example_utils.ts +++ b/x-pack/plugins/data_visualizer/public/application/common/util/example_utils.ts @@ -6,7 +6,7 @@ */ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { isDefined } from './is_defined'; +import { isDefined } from '@kbn/ml-is-defined'; import { GeoPointExample, LatLongExample } from '../../../../common/types/field_request_config'; export function isGeoPointExample(arg: unknown): arg is GeoPointExample { diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/actions_panel/actions_panel.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/actions_panel/actions_panel.tsx index fc2efab6610bc..a8f454816e2b6 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/actions_panel/actions_panel.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/actions_panel/actions_panel.tsx @@ -14,12 +14,12 @@ import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { DataView } from '@kbn/data-views-plugin/public'; import { useUrlState } from '@kbn/ml-url-state'; +import { isDefined } from '@kbn/ml-is-defined'; import { LinkCardProps } from '../../../common/components/link_card/link_card'; import { useDataVisualizerKibana } from '../../../kibana_context'; import { LinkCard } from '../../../common/components/link_card'; import { GetAdditionalLinks } from '../../../common/components/results_links'; -import { isDefined } from '../../../common/util/is_defined'; interface Props { dataView: DataView; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector.tsx index 1cade1deaa59a..57c9f4fa18a40 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/full_time_range_selector/full_time_range_selector.tsx @@ -23,9 +23,16 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { useStorage } from '@kbn/ml-local-storage'; import { setFullTimeRange } from './full_time_range_selector_service'; import { useDataVisualizerKibana } from '../../../kibana_context'; -import { DV_FROZEN_TIER_PREFERENCE, useStorage } from '../../hooks/use_storage'; +import { + DV_FROZEN_TIER_PREFERENCE, + FROZEN_TIER_PREFERENCE, + type DVKey, + type DVStorageMapped, + type FrozenTierPreference, +} from '../../types/storage'; export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference'; @@ -37,13 +44,6 @@ interface Props { callback?: (a: any) => void; } -const FROZEN_TIER_PREFERENCE = { - EXCLUDE: 'exclude-frozen', - INCLUDE: 'include-frozen', -} as const; - -type FrozenTierPreference = typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE]; - // Component for rendering a button which automatically sets the range of the time filter // to the time range of data in the index(es) mapped to the supplied Kibana data view or query. export const FullTimeRangeSelector: FC = ({ @@ -83,7 +83,10 @@ export const FullTimeRangeSelector: FC = ({ const [isPopoverOpen, setPopover] = useState(false); - const [frozenDataPreference, setFrozenDataPreference] = useStorage( + const [frozenDataPreference, setFrozenDataPreference] = useStorage< + DVKey, + DVStorageMapped + >( DV_FROZEN_TIER_PREFERENCE, // By default we will exclude frozen data tier FROZEN_TIER_PREFERENCE.EXCLUDE diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx index 8e13ce92a5c1e..9ec8b90d84f02 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/index_data_visualizer_view/index_data_visualizer_view.tsx @@ -27,8 +27,13 @@ import { generateFilters } from '@kbn/data-plugin/public'; import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; import { usePageUrlState, useUrlState } from '@kbn/ml-url-state'; +import { useStorage } from '@kbn/ml-local-storage'; import { useCurrentEuiTheme } from '../../../common/hooks/use_current_eui_theme'; -import { DV_RANDOM_SAMPLER_PREFERENCE, useStorage } from '../../hooks/use_storage'; +import { + DV_RANDOM_SAMPLER_PREFERENCE, + type DVKey, + type DVStorageMapped, +} from '../../types/storage'; import { FullTimeRangeSelector } from '../full_time_range_selector'; import { DataVisualizerTable, @@ -58,7 +63,7 @@ import { DataVisualizerDataViewManagement } from '../data_view_management'; import { GetAdditionalLinks } from '../../../common/components/results_links'; import { useDataVisualizerGridData } from '../../hooks/use_data_visualizer_grid_data'; import { DataVisualizerGridInput } from '../../embeddables/grid_embeddable/grid_embeddable'; -import { RANDOM_SAMPLER_OPTION, RandomSamplerOption } from '../../constants/random_sampler'; +import { RANDOM_SAMPLER_OPTION } from '../../constants/random_sampler'; interface DataVisualizerPageState { overallStats: OverallStats; @@ -126,18 +131,17 @@ export interface IndexDataVisualizerViewProps { export const IndexDataVisualizerView: FC = (dataVisualizerProps) => { const euiTheme = useCurrentEuiTheme(); - const [savedRandomSamplerPreference, saveRandomSamplerPreference] = - useStorage( - DV_RANDOM_SAMPLER_PREFERENCE, - RANDOM_SAMPLER_OPTION.ON_AUTOMATIC - ); + const [savedRandomSamplerPreference, saveRandomSamplerPreference] = useStorage< + DVKey, + DVStorageMapped + >(DV_RANDOM_SAMPLER_PREFERENCE, RANDOM_SAMPLER_OPTION.ON_AUTOMATIC); const restorableDefaults = useMemo( () => getDefaultDataVisualizerListState({ rndSamplerPref: savedRandomSamplerPreference, }), - // We just need to load the saved preference when the page is first loaded + // We just need to load the saved preference when the page is first loaded // eslint-disable-next-line react-hooks/exhaustive-deps [] ); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx index b6981bf996da2..ee4a002071b3f 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/components/search_panel/search_panel.tsx @@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n'; import { Query, Filter } from '@kbn/es-query'; import type { TimeRange } from '@kbn/es-query'; import { DataView, DataViewField } from '@kbn/data-views-plugin/public'; -import { isDefined } from '../../../common/util/is_defined'; +import { isDefined } from '@kbn/ml-is-defined'; import { DataVisualizerFieldNamesFilter } from './field_name_filter'; import { DataVisualizerFieldTypeFilter } from './field_type_filter'; import { SupportedFieldType } from '../../../../../common/types'; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_storage.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_storage.ts deleted file mode 100644 index cd09967a81fa0..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_storage.ts +++ /dev/null @@ -1,48 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { useCallback, useState } from 'react'; -import { useDataVisualizerKibana } from '../../kibana_context'; - -export const DV_FROZEN_TIER_PREFERENCE = 'dataVisualizer.frozenDataTierPreference'; -export const DV_RANDOM_SAMPLER_PREFERENCE = 'dataVisualizer.randomSamplerPreference'; -export const DV_RANDOM_SAMPLER_P_VALUE = 'dataVisualizer.randomSamplerPValue'; - -export type DV = Partial<{ - [DV_FROZEN_TIER_PREFERENCE]: 'exclude_frozen' | 'include_frozen'; - [DV_RANDOM_SAMPLER_PREFERENCE]: 'true' | 'false'; - [DV_RANDOM_SAMPLER_P_VALUE]: number; -}> | null; - -export type DVKey = keyof Exclude; - -/** - * Hook for accessing and changing a value in the storage. - * @param key - Storage key - * @param initValue - */ -export function useStorage(key: DVKey, initValue?: T): [T, (value: T) => void] { - const { - services: { storage }, - } = useDataVisualizerKibana(); - - const [val, setVal] = useState(storage.get(key) ?? initValue); - - const setStorage = useCallback( - (value: T): void => { - try { - storage.set(key, value); - setVal(value); - } catch (e) { - throw new Error('Unable to update storage with provided value'); - } - }, - [key, storage] - ); - - return [val, setStorage]; -} diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx index 671d410523fdd..156898449ee79 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/index_data_visualizer.tsx @@ -13,7 +13,9 @@ import { EuiResizeObserver } from '@elastic/eui'; import { encode } from '@kbn/rison'; import { SimpleSavedObject } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; +import { Storage } from '@kbn/kibana-utils-plugin/public'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; +import { StorageContextProvider } from '@kbn/ml-local-storage'; import { DataView } from '@kbn/data-views-plugin/public'; import { getNestedProperty } from '@kbn/ml-nested-property'; import { @@ -34,6 +36,9 @@ import { GetAdditionalLinks } from '../common/components/results_links'; import { DATA_VISUALIZER_APP_LOCATOR, IndexDataVisualizerLocatorParams } from './locator'; import { DATA_VISUALIZER_INDEX_VIEWER } from './constants/index_data_visualizer_viewer'; import { INDEX_DATA_VISUALIZER_NAME } from '../common/constants'; +import { DV_STORAGE_KEYS } from './types/storage'; + +const localStorage = new Storage(window.localStorage); export interface DataVisualizerStateContextProviderProps { IndexDataVisualizerComponent: FC; @@ -316,10 +321,12 @@ export const IndexDataVisualizer: FC<{ return ( - + + + ); diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts index f74a8800d2bd0..e6fc220ea7f3c 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_document_stats.ts @@ -10,7 +10,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { DataPublicPluginStart, ISearchOptions } from '@kbn/data-plugin/public'; import seedrandom from 'seedrandom'; -import { isDefined } from '../../../common/util/is_defined'; +import { isDefined } from '@kbn/ml-is-defined'; import { RANDOM_SAMPLER_PROBABILITIES } from '../../constants/random_sampler'; import { buildBaseFilterCriteria } from '../../../../../common/utils/query_utils'; import type { diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_numeric_field_stats.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_numeric_field_stats.ts index 66a7134919760..20143f5fc1581 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_numeric_field_stats.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/search_strategy/requests/get_numeric_field_stats.ts @@ -17,8 +17,8 @@ import type { } from '@kbn/data-plugin/common'; import type { ISearchStart } from '@kbn/data-plugin/public'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { isDefined } from '@kbn/ml-is-defined'; import { processTopValues } from './utils'; -import { isDefined } from '../../../common/util/is_defined'; import { buildAggregationWithSamplingOption } from './build_random_sampler_agg'; import { MAX_PERCENT, PERCENTILE_SPACING, SAMPLER_TOP_TERMS_THRESHOLD } from './constants'; import type { diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/storage.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/storage.ts new file mode 100644 index 0000000000000..f2718bc8e803f --- /dev/null +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/storage.ts @@ -0,0 +1,42 @@ +/* + * 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 { RandomSamplerOption } from '../constants/random_sampler'; + +export const DV_FROZEN_TIER_PREFERENCE = 'dataVisualizer.frozenDataTierPreference'; +export const DV_RANDOM_SAMPLER_PREFERENCE = 'dataVisualizer.randomSamplerPreference'; +export const DV_RANDOM_SAMPLER_P_VALUE = 'dataVisualizer.randomSamplerPValue'; + +export const FROZEN_TIER_PREFERENCE = { + EXCLUDE: 'exclude-frozen', + INCLUDE: 'include-frozen', +} as const; + +export type FrozenTierPreference = + typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE]; + +export type DV = Partial<{ + [DV_FROZEN_TIER_PREFERENCE]: FrozenTierPreference; + [DV_RANDOM_SAMPLER_PREFERENCE]: RandomSamplerOption; + [DV_RANDOM_SAMPLER_P_VALUE]: number; +}> | null; + +export type DVKey = keyof Exclude; + +export type DVStorageMapped = T extends typeof DV_FROZEN_TIER_PREFERENCE + ? FrozenTierPreference | undefined + : T extends typeof DV_RANDOM_SAMPLER_PREFERENCE + ? RandomSamplerOption | undefined + : T extends typeof DV_RANDOM_SAMPLER_P_VALUE + ? number | undefined + : null; + +export const DV_STORAGE_KEYS = [ + DV_FROZEN_TIER_PREFERENCE, + DV_RANDOM_SAMPLER_PREFERENCE, + DV_RANDOM_SAMPLER_P_VALUE, +] as const; diff --git a/x-pack/plugins/data_visualizer/tsconfig.json b/x-pack/plugins/data_visualizer/tsconfig.json index b6c5477266e4c..3098da0f61bc2 100644 --- a/x-pack/plugins/data_visualizer/tsconfig.json +++ b/x-pack/plugins/data_visualizer/tsconfig.json @@ -52,6 +52,8 @@ "@kbn/field-types", "@kbn/ml-nested-property", "@kbn/ml-url-state", + "@kbn/ml-local-storage", + "@kbn/ml-is-defined", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/ml/public/application/contexts/storage/storage_context.test.tsx b/x-pack/plugins/ml/common/types/storage.test.tsx similarity index 77% rename from x-pack/plugins/ml/public/application/contexts/storage/storage_context.test.tsx rename to x-pack/plugins/ml/common/types/storage.test.tsx index 6aeeb396f38c3..c27c6e74d9edb 100644 --- a/x-pack/plugins/ml/public/application/contexts/storage/storage_context.test.tsx +++ b/x-pack/plugins/ml/common/types/storage.test.tsx @@ -5,33 +5,38 @@ * 2.0. */ +import React, { FC } from 'react'; import { renderHook, act } from '@testing-library/react-hooks'; -import { MlStorageContextProvider, useStorage } from './storage_context'; -import { MlStorageKey } from '../../../../common/types/storage'; + +import type { Storage } from '@kbn/kibana-utils-plugin/public'; +import { StorageContextProvider, useStorage } from '@kbn/ml-local-storage'; + +import { ML_STORAGE_KEYS } from './storage'; const mockSet = jest.fn(); const mockRemove = jest.fn(); - -jest.mock('../kibana', () => ({ - useMlKibana: () => { - return { - services: { - storage: { - set: mockSet, - get: jest.fn((key: MlStorageKey) => { - switch (key) { - case 'ml.gettingStarted.isDismissed': - return true; - default: - return; - } - }), - remove: mockRemove, - }, - }, - }; - }, -})); +const mockStorage: Storage = { + set: mockSet, + get: jest.fn((key: string) => { + switch (key) { + case 'ml.gettingStarted.isDismissed': + return true; + default: + return; + } + }), + remove: mockRemove, + store: jest.fn() as any, + clear: jest.fn(), +}; + +const Provider: FC = ({ children }) => { + return ( + + {children} + + ); +}; describe('useStorage', () => { afterEach(() => { @@ -40,7 +45,7 @@ describe('useStorage', () => { test('returns the default value', () => { const { result } = renderHook(() => useStorage('ml.jobSelectorFlyout.applyTimeRange', true), { - wrapper: MlStorageContextProvider, + wrapper: Provider, }); expect(result.current[0]).toBe(true); @@ -48,7 +53,7 @@ describe('useStorage', () => { test('returns the value from storage', () => { const { result } = renderHook(() => useStorage('ml.gettingStarted.isDismissed', false), { - wrapper: MlStorageContextProvider, + wrapper: Provider, }); expect(result.current[0]).toBe(true); @@ -58,7 +63,7 @@ describe('useStorage', () => { const { result, waitForNextUpdate } = renderHook( () => useStorage('ml.gettingStarted.isDismissed'), { - wrapper: MlStorageContextProvider, + wrapper: Provider, } ); @@ -79,7 +84,7 @@ describe('useStorage', () => { const { result, waitForNextUpdate } = renderHook( () => useStorage('ml.gettingStarted.isDismissed'), { - wrapper: MlStorageContextProvider, + wrapper: Provider, } ); @@ -100,7 +105,7 @@ describe('useStorage', () => { const { result, waitForNextUpdate } = renderHook( () => useStorage('ml.gettingStarted.isDismissed'), { - wrapper: MlStorageContextProvider, + wrapper: Provider, } ); diff --git a/x-pack/plugins/ml/common/types/storage.ts b/x-pack/plugins/ml/common/types/storage.ts index 34673b7c51059..7e2e1ba799961 100644 --- a/x-pack/plugins/ml/common/types/storage.ts +++ b/x-pack/plugins/ml/common/types/storage.ts @@ -14,6 +14,14 @@ export const ML_FROZEN_TIER_PREFERENCE = 'ml.frozenDataTierPreference'; export const ML_ANOMALY_EXPLORER_PANELS = 'ml.anomalyExplorerPanels'; export const ML_NOTIFICATIONS_LAST_CHECKED_AT = 'ml.notificationsLastCheckedAt'; +export const FROZEN_TIER_PREFERENCE = { + EXCLUDE: 'exclude-frozen', + INCLUDE: 'include-frozen', +} as const; + +export type FrozenTierPreference = + typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE]; + export type PartitionFieldConfig = | { /** @@ -51,14 +59,17 @@ export interface AnomalyExplorerPanelsState { mainPage: { size: number }; } -export type MlStorage = Partial<{ +export interface MlStorageRecord { + [key: string]: unknown; [ML_ENTITY_FIELDS_CONFIG]: PartitionFieldsConfig; [ML_APPLY_TIME_RANGE_CONFIG]: ApplyTimeRangeConfig; [ML_GETTING_STARTED_CALLOUT_DISMISSED]: boolean | undefined; - [ML_FROZEN_TIER_PREFERENCE]: 'exclude-frozen' | 'include-frozen'; + [ML_FROZEN_TIER_PREFERENCE]: FrozenTierPreference; [ML_ANOMALY_EXPLORER_PANELS]: AnomalyExplorerPanelsState | undefined; [ML_NOTIFICATIONS_LAST_CHECKED_AT]: number | undefined; -}> | null; +} + +export type MlStorage = Partial | null; export type MlStorageKey = keyof Exclude; @@ -69,7 +80,7 @@ export type TMlStorageMapped = T extends typeof ML_ENTIT : T extends typeof ML_GETTING_STARTED_CALLOUT_DISMISSED ? boolean | undefined : T extends typeof ML_FROZEN_TIER_PREFERENCE - ? 'exclude-frozen' | 'include-frozen' | undefined + ? FrozenTierPreference | undefined : T extends typeof ML_ANOMALY_EXPLORER_PANELS ? AnomalyExplorerPanelsState | undefined : T extends typeof ML_NOTIFICATIONS_LAST_CHECKED_AT @@ -83,8 +94,4 @@ export const ML_STORAGE_KEYS = [ ML_FROZEN_TIER_PREFERENCE, ML_ANOMALY_EXPLORER_PANELS, ML_NOTIFICATIONS_LAST_CHECKED_AT, -]; - -export function isMlStorageKey(key: unknown): key is MlStorageKey { - return typeof key === 'string' && ML_STORAGE_KEYS.includes(key); -} +] as const; diff --git a/x-pack/plugins/ml/common/util/alerts.ts b/x-pack/plugins/ml/common/util/alerts.ts index 6abc5333a1f73..6c997dfb71566 100644 --- a/x-pack/plugins/ml/common/util/alerts.ts +++ b/x-pack/plugins/ml/common/util/alerts.ts @@ -6,9 +6,9 @@ */ import { pick } from 'lodash'; +import { isDefined } from '@kbn/ml-is-defined'; import { CombinedJobWithStats, Datafeed, Job } from '../types/anomaly_detection_jobs'; import { resolveMaxTimeInterval } from './job_utils'; -import { isDefined } from '../types/guards'; import { parseInterval } from './parse_interval'; import { JobsHealthRuleTestsConfig, JobsHealthTests } from '../types/alerts'; diff --git a/x-pack/plugins/ml/common/util/job_utils.ts b/x-pack/plugins/ml/common/util/job_utils.ts index 4d50af19a8b96..a0cbb5d5762de 100644 --- a/x-pack/plugins/ml/common/util/job_utils.ts +++ b/x-pack/plugins/ml/common/util/job_utils.ts @@ -16,6 +16,7 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import type { SerializableRecord } from '@kbn/utility-types'; import { FilterStateStore } from '@kbn/es-query'; import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { isDefined } from '@kbn/ml-is-defined'; import { ALLOWED_DATA_UNITS, JOB_ID_MAX_LENGTH } from '../constants/validation'; import { parseInterval } from './parse_interval'; import { maxLengthValidator } from './validators'; @@ -35,7 +36,6 @@ import { MLCATEGORY } from '../constants/field_types'; import { getAggregations, getDatafeedAggregations } from './datafeed_utils'; import { findAggField } from './validation_utils'; import { getFirstKeyInObject } from './object_utils'; -import { isDefined } from '../types/guards'; export interface ValidationResults { valid: boolean; diff --git a/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx b/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx index 9fff85c82888f..daab200ff0709 100644 --- a/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx +++ b/x-pack/plugins/ml/public/alerting/jobs_health_rule/anomaly_detection_jobs_health_rule_trigger.tsx @@ -12,6 +12,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import useDebounce from 'react-use/lib/useDebounce'; import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { isDefined } from '@kbn/ml-is-defined'; import { MlAnomalyDetectionJobsHealthRuleParams } from '../../../common/types/alerts'; import { JobSelectorControl } from '../job_selector'; import { jobsApiProvider } from '../../application/services/ml_api_service/jobs'; @@ -20,7 +21,6 @@ import { useMlKibana } from '../../application/contexts/kibana'; import { TestsSelectionControl } from './tests_selection_control'; import { ALL_JOBS_SELECTION } from '../../../common/constants/alerts'; import { BetaBadge } from '../beta_badge'; -import { isDefined } from '../../../common/types/guards'; export type MlAnomalyAlertTriggerProps = RuleTypeParamsExpressionProps; diff --git a/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx b/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx index a5ef1e7943438..980abb23e659d 100644 --- a/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx +++ b/x-pack/plugins/ml/public/alerting/ml_anomaly_alert_trigger.tsx @@ -10,6 +10,7 @@ import { EuiSpacer, EuiForm } from '@elastic/eui'; import useMount from 'react-use/lib/useMount'; import { i18n } from '@kbn/i18n'; import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public'; +import { isDefined } from '@kbn/ml-is-defined'; import { JobSelectorControl } from './job_selector'; import { useMlKibana } from '../application/contexts/kibana'; import { jobsApiProvider } from '../application/services/ml_api_service/jobs'; @@ -29,7 +30,6 @@ import { ConfigValidator } from './config_validator'; import { CombinedJobWithStats } from '../../common/types/anomaly_detection_jobs'; import { AdvancedSettings } from './advanced_settings'; import { getLookbackInterval, getTopNBuckets } from '../../common/util/alerts'; -import { isDefined } from '../../common/types/guards'; import { parseInterval } from '../../common/util/parse_interval'; import { BetaBadge } from './beta_badge'; diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index 98fe9674bb567..463cb34691144 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -15,7 +15,8 @@ import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; -import { MlStorageContextProvider } from './contexts/storage'; +import { StorageContextProvider } from '@kbn/ml-local-storage'; +import { ML_STORAGE_KEYS } from '../../common/types/storage'; import { setDependencyCache, clearCache } from './util/dependency_cache'; import { setLicenseCache } from './license'; import type { MlSetupDependencies, MlStartDependencies } from '../plugin'; @@ -111,9 +112,9 @@ const App: FC = ({ coreStart, deps, appMountParams }) => { mlServices: getMlGlobalServices(coreStart.http, deps.usageCollection), }} > - + - + diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx index 7566d3664af61..442b1407aaf6d 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx @@ -20,7 +20,7 @@ jest.mock('./full_time_range_selector_service', () => ({ mockSetFullTimeRange(indexPattern, query), })); -jest.mock('../../contexts/storage', () => { +jest.mock('@kbn/ml-local-storage', () => { return { useStorage: jest.fn(() => 'exclude-frozen'), }; diff --git a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx index 9b9154eb33660..3f4c42bc30ca5 100644 --- a/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx @@ -22,9 +22,15 @@ import { import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { i18n } from '@kbn/i18n'; import type { DataView } from '@kbn/data-views-plugin/public'; +import { useStorage } from '@kbn/ml-local-storage'; import { setFullTimeRange } from './full_time_range_selector_service'; -import { useStorage } from '../../contexts/storage'; -import { ML_FROZEN_TIER_PREFERENCE } from '../../../../common/types/storage'; +import { + ML_FROZEN_TIER_PREFERENCE, + FROZEN_TIER_PREFERENCE, + type MlStorageKey, + type TMlStorageMapped, + type FrozenTierPreference, +} from '../../../../common/types/storage'; import { GetTimeFieldRangeResponse } from '../../services/ml_api_service'; interface Props { @@ -34,13 +40,6 @@ interface Props { callback?: (a: GetTimeFieldRangeResponse) => void; } -const FROZEN_TIER_PREFERENCE = { - EXCLUDE: 'exclude-frozen', - INCLUDE: 'include-frozen', -} as const; - -type FrozenTierPreference = typeof FROZEN_TIER_PREFERENCE[keyof typeof FROZEN_TIER_PREFERENCE]; - // Component for rendering a button which automatically sets the range of the time filter // to the time range of data in the index(es) mapped to the supplied Kibana index pattern or query. export const FullTimeRangeSelector: FC = ({ dataView, query, disabled, callback }) => { @@ -53,10 +52,10 @@ export const FullTimeRangeSelector: FC = ({ dataView, query, disabled, ca } const [isPopoverOpen, setPopover] = useState(false); - const [frozenDataPreference, setFrozenDataPreference] = useStorage( - ML_FROZEN_TIER_PREFERENCE, - FROZEN_TIER_PREFERENCE.EXCLUDE - ); + const [frozenDataPreference, setFrozenDataPreference] = useStorage< + MlStorageKey, + TMlStorageMapped + >(ML_FROZEN_TIER_PREFERENCE, FROZEN_TIER_PREFERENCE.EXCLUDE); const onButtonClick = () => { setPopover(!isPopoverOpen); diff --git a/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx b/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx index 9c49099c45946..fa2808638cbf5 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/job_selector/job_selector.tsx @@ -19,6 +19,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useUrlState } from '@kbn/ml-url-state'; import './_index.scss'; +import { useStorage } from '@kbn/ml-local-storage'; import { Dictionary } from '../../../../common/types/common'; import { IdBadges } from './id_badges'; import { @@ -27,7 +28,6 @@ import { JobSelectorFlyoutProps, } from './job_selector_flyout'; import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; -import { useStorage } from '../../contexts/storage'; import { ML_APPLY_TIME_RANGE_CONFIG } from '../../../../common/types/storage'; interface GroupObj { diff --git a/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.test.tsx b/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.test.tsx index dd24789462cf7..ad6c05d07d4e1 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.test.tsx +++ b/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.test.tsx @@ -8,7 +8,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { of, throwError } from 'rxjs'; import { useMlNotifications, MlNotificationsContextProvider } from './ml_notifications_context'; -import { useStorage } from '../storage'; +import { useStorage } from '@kbn/ml-local-storage'; import { useMlKibana } from '../kibana'; const mockCountMessages = jest.fn(() => { @@ -43,7 +43,7 @@ jest.mock('../kibana', () => ({ })); const mockSetStorageValue = jest.fn(); -jest.mock('../storage', () => ({ +jest.mock('@kbn/ml-local-storage', () => ({ useStorage: jest.fn(() => { return [undefined, mockSetStorageValue]; }), diff --git a/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.tsx b/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.tsx index 74b0a1ad54cb8..67e479f1291f5 100644 --- a/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.tsx +++ b/x-pack/plugins/ml/public/application/contexts/ml/ml_notifications_context.tsx @@ -10,9 +10,13 @@ import { combineLatest, timer } from 'rxjs'; import { switchMap, map, tap, retry } from 'rxjs/operators'; import moment from 'moment'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { useStorage } from '@kbn/ml-local-storage'; import { useMlKibana } from '../kibana'; -import { useStorage } from '../storage'; -import { ML_NOTIFICATIONS_LAST_CHECKED_AT } from '../../../../common/types/storage'; +import { + ML_NOTIFICATIONS_LAST_CHECKED_AT, + type MlStorageKey, + type TMlStorageMapped, +} from '../../../../common/types/storage'; import { useAsObservable } from '../../hooks'; import type { NotificationsCountResponse } from '../../../../common/types/notifications'; @@ -47,7 +51,10 @@ export const MlNotificationsContextProvider: FC = ({ children }) => { const canGetNotifications = canGetJobs && canGetDataFrameAnalytics && canGetTrainedModels; - const [lastCheckedAt, setLastCheckedAt] = useStorage(ML_NOTIFICATIONS_LAST_CHECKED_AT); + const [lastCheckedAt, setLastCheckedAt] = useStorage< + MlStorageKey, + TMlStorageMapped + >(ML_NOTIFICATIONS_LAST_CHECKED_AT); const lastCheckedAt$ = useAsObservable(lastCheckedAt); /** Holds the value used for the actual request */ diff --git a/x-pack/plugins/ml/public/application/contexts/storage/storage_context.tsx b/x-pack/plugins/ml/public/application/contexts/storage/storage_context.tsx deleted file mode 100644 index 65e2a64ee3181..0000000000000 --- a/x-pack/plugins/ml/public/application/contexts/storage/storage_context.tsx +++ /dev/null @@ -1,140 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React, { FC, useEffect, useMemo, useCallback, useState, useContext } from 'react'; -import { omit } from 'lodash'; -import { isDefined } from '../../../../common/types/guards'; -import { useMlKibana } from '../kibana'; -import { MlStorage, ML_STORAGE_KEYS, isMlStorageKey } from '../../../../common/types/storage'; -import { MlStorageKey, TMlStorageMapped } from '../../../../common/types/storage'; - -interface StorageAPI { - value: MlStorage; - setValue: >(key: K, value: T) => void; - removeValue: (key: K) => void; -} - -export const MlStorageContext = React.createContext({ - value: null, - setValue() { - throw new Error('MlStorageContext set method is not implemented'); - }, - removeValue() { - throw new Error('MlStorageContext remove method is not implemented'); - }, -}); - -export const MlStorageContextProvider: FC = ({ children }) => { - const { - services: { storage }, - } = useMlKibana(); - - const initialValue = useMemo(() => { - return ML_STORAGE_KEYS.reduce((acc, curr) => { - acc[curr as MlStorageKey] = storage.get(curr); - return acc; - }, {} as Exclude); - }, [storage]); - - const [state, setState] = useState(initialValue); - - const setStorageValue = useCallback( - >(key: K, value: T) => { - storage.set(key, value); - - setState((prevState) => ({ - ...prevState, - [key]: value, - })); - }, - [storage] - ); - - const removeStorageValue = useCallback( - (key: MlStorageKey) => { - storage.remove(key); - setState((prevState) => omit(prevState, key)); - }, - [storage] - ); - - useEffect(function updateStorageOnExternalChange() { - const eventListener = (event: StorageEvent) => { - if (!isMlStorageKey(event.key)) return; - - if (isDefined(event.newValue)) { - setState((prev) => { - return { - ...prev, - [event.key as MlStorageKey]: - typeof event.newValue === 'string' ? JSON.parse(event.newValue) : event.newValue, - }; - }); - } else { - setState((prev) => { - return omit(prev, event.key as MlStorageKey); - }); - } - }; - - /** - * This event listener is only invoked when - * the change happens in another browser's tab. - */ - window.addEventListener('storage', eventListener); - - return () => { - window.removeEventListener('storage', eventListener); - }; - }, []); - - const value: StorageAPI = useMemo(() => { - return { - value: state, - setValue: setStorageValue, - removeValue: removeStorageValue, - }; - }, [state, setStorageValue, removeStorageValue]); - - return {children}; -}; - -/** - * Hook for consuming a storage value - * @param key - * @param initValue - */ -export function useStorage>( - key: K, - initValue?: T -): [ - typeof initValue extends undefined - ? TMlStorageMapped | undefined - : Exclude, undefined>, - (value: TMlStorageMapped) => void -] { - const { value, setValue, removeValue } = useContext(MlStorageContext); - - const resultValue = useMemo(() => { - return (value?.[key] ?? initValue) as typeof initValue extends undefined - ? TMlStorageMapped | undefined - : Exclude, undefined>; - }, [value, key, initValue]); - - const setVal = useCallback( - (v: TMlStorageMapped) => { - if (isDefined(v)) { - setValue(key, v); - } else { - removeValue(key); - } - }, - [setValue, removeValue, key] - ); - - return [resultValue, setVal]; -} diff --git a/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx b/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx index b9a1ef555dbc1..3edfcd9842a18 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomalies_map.tsx @@ -25,8 +25,8 @@ import { VectorLayerDescriptor, } from '@kbn/maps-plugin/common'; import { EMSTermJoinConfig } from '@kbn/maps-plugin/public'; +import { isDefined } from '@kbn/ml-is-defined'; import { useMlKibana } from '../contexts/kibana'; -import { isDefined } from '../../../common/types/guards'; import { MlEmbeddedMapComponent } from '../components/ml_embedded_map'; import { AnomaliesTableRecord } from '../../../common/types/anomalies'; diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_context_menu.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_context_menu.tsx index 2bfe0c3b2844e..55feffe8ff5c4 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_context_menu.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_context_menu.tsx @@ -19,7 +19,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import useObservable from 'react-use/lib/useObservable'; import type { Query, TimeRange } from '@kbn/es-query'; -import { isDefined } from '../../../common/types/guards'; +import { isDefined } from '@kbn/ml-is-defined'; import { useAnomalyExplorerContext } from './anomaly_explorer_context'; import { escapeKueryForFieldValuePair } from '../util/string_utils'; import { SEARCH_QUERY_LANGUAGE } from '../../../common/constants/search'; diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx index 6b33ba8187d9d..a0ff293a2b203 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_timeline.tsx @@ -27,6 +27,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import useDebounce from 'react-use/lib/useDebounce'; import useObservable from 'react-use/lib/useObservable'; import type { Query } from '@kbn/es-query'; +import { isDefined } from '@kbn/ml-is-defined'; import { SEARCH_QUERY_LANGUAGE } from '../../../common/constants/search'; import { useCasesModal } from '../contexts/kibana/use_cases_modal'; import { useTimeRangeUpdates } from '../contexts/kibana/use_timefilter'; @@ -46,7 +47,6 @@ import { AppStateSelectedCells, OverallSwimlaneData, ViewBySwimLaneData } from ' import { NoOverallData } from './components/no_overall_data'; import { SeverityControl } from '../components/severity_control'; import { AnomalyTimelineHelpPopover } from './anomaly_timeline_help_popover'; -import { isDefined } from '../../../common/types/guards'; import { MlTooltipComponent } from '../components/chart_tooltip'; import { SwimlaneAnnotationContainer, Y_AXIS_LABEL_WIDTH } from './swimlane_annotation_container'; import { AnomalyTimelineService } from '../services/anomaly_timeline_service'; diff --git a/x-pack/plugins/ml/public/application/explorer/dashboard_controls/add_anomaly_charts_to_dashboard_controls.tsx b/x-pack/plugins/ml/public/application/explorer/dashboard_controls/add_anomaly_charts_to_dashboard_controls.tsx index 0e928166127fd..cb5d6d2b0201b 100644 --- a/x-pack/plugins/ml/public/application/explorer/dashboard_controls/add_anomaly_charts_to_dashboard_controls.tsx +++ b/x-pack/plugins/ml/public/application/explorer/dashboard_controls/add_anomaly_charts_to_dashboard_controls.tsx @@ -9,8 +9,8 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFieldNumber, EuiFormRow, htmlIdGenerator } from '@elastic/eui'; import type { Query } from '@kbn/es-query'; import useObservable from 'react-use/lib/useObservable'; +import { isDefined } from '@kbn/ml-is-defined'; import { getSelectionInfluencers } from '../explorer_utils'; -import { isDefined } from '../../../../common/types/guards'; import { useAnomalyExplorerContext } from '../anomaly_explorer_context'; import { escapeKueryForFieldValuePair } from '../../util/string_utils'; import { SEARCH_QUERY_LANGUAGE } from '../../../../common/constants/search'; diff --git a/x-pack/plugins/ml/public/application/explorer/explorer.tsx b/x-pack/plugins/ml/public/application/explorer/explorer.tsx index 6e224b25f506f..f764ede7750c9 100644 --- a/x-pack/plugins/ml/public/application/explorer/explorer.tsx +++ b/x-pack/plugins/ml/public/application/explorer/explorer.tsx @@ -31,6 +31,8 @@ import { css } from '@emotion/react'; import useObservable from 'react-use/lib/useObservable'; import type { DataView } from '@kbn/data-views-plugin/common'; import type { TimefilterContract } from '@kbn/data-plugin/public'; +import { useStorage } from '@kbn/ml-local-storage'; +import { isDefined } from '@kbn/ml-is-defined'; import { HelpPopover } from '../components/help_popover'; import { AnnotationFlyout } from '../components/annotations/annotation_flyout'; // @ts-ignore @@ -69,7 +71,6 @@ import { AnomaliesTable } from '../components/anomalies_table/anomalies_table'; import { AnomaliesMap } from './anomalies_map'; import { ANOMALY_DETECTION_DEFAULT_TIME_RANGE } from '../../../common/constants/settings'; import { AnomalyContextMenu } from './anomaly_context_menu'; -import { isDefined } from '../../../common/types/guards'; import type { JobSelectorProps } from '../components/job_selector/job_selector'; import type { ExplorerState } from './reducers'; import type { TimeBuckets } from '../util/time_buckets'; @@ -78,7 +79,6 @@ import { useMlKibana, useMlLocator } from '../contexts/kibana'; import { useMlContext } from '../contexts/ml'; import { useAnomalyExplorerContext } from './anomaly_explorer_context'; import { ML_ANOMALY_EXPLORER_PANELS } from '../../../common/types/storage'; -import { useStorage } from '../contexts/storage'; interface ExplorerPageProps { jobSelectorProps: JobSelectorProps; diff --git a/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx b/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx index 7b1f380c90658..d7a0370b4aeac 100644 --- a/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/getting_started_callout.tsx @@ -8,8 +8,8 @@ import React, { FC } from 'react'; import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useStorage } from '@kbn/ml-local-storage'; import { useMlKibana } from '../../contexts/kibana'; -import { useStorage } from '../../contexts/storage'; import { ML_GETTING_STARTED_CALLOUT_DISMISSED } from '../../../../common/types/storage'; const feedbackLink = 'https://www.elastic.co/community/'; diff --git a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts index 7aa0bb03611e1..6a0f3c8c692b0 100644 --- a/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts +++ b/x-pack/plugins/ml/public/application/services/anomaly_explorer_charts_service.ts @@ -10,6 +10,7 @@ import { map as mapObservable } from 'rxjs/operators'; import type { TimeRange } from '@kbn/es-query'; import type { TimefilterContract } from '@kbn/data-plugin/public'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { isDefined } from '@kbn/ml-is-defined'; import type { RecordForInfluencer } from './results_service/results_service'; import type { EntityField } from '../../../common/util/anomaly_utils'; import type { CombinedJob } from '../../../common/types/anomaly_detection_jobs'; @@ -17,7 +18,6 @@ import type { MlApiServices } from './ml_api_service'; import type { MlResultsService } from './results_service'; import { ExplorerChartsData } from '../explorer/explorer_charts/explorer_charts_container_service'; import type { TimeRangeBounds } from '../util/time_buckets'; -import { isDefined } from '../../../common/types/guards'; import type { AppStateSelectedCells } from '../explorer/explorer_utils'; import type { InfluencersFilterQuery } from '../../../common/types/es_client'; import type { SeriesConfigWithMetadata } from '../../../common/types/results'; diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/notifications.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/notifications.ts index 653ceebc52cda..fd6f7bd708cd4 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/notifications.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/notifications.ts @@ -6,7 +6,7 @@ */ import { omitBy } from 'lodash'; -import { isDefined } from '../../../../common/types/guards'; +import { isDefined } from '@kbn/ml-is-defined'; import type { NotificationsQueryParams, NotificationsSearchResponse, diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx index 23084c8d8886d..8d79a41b0ab36 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/components/series_controls/series_controls.tsx @@ -10,6 +10,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect, EuiSelectProps } from '@elastic/eui'; import { debounce } from 'lodash'; import { lastValueFrom } from 'rxjs'; +import { useStorage } from '@kbn/ml-local-storage'; import { EntityControl } from '../entity_control'; import { mlJobService } from '../../../services/job_service'; import { Detector, JobId } from '../../../../../common/types/anomaly_detection_jobs'; @@ -23,10 +24,11 @@ import { import { getControlsForDetector } from '../../get_controls_for_detector'; import { ML_ENTITY_FIELDS_CONFIG, - PartitionFieldConfig, - PartitionFieldsConfig, + type PartitionFieldConfig, + type PartitionFieldsConfig, + type MlStorageKey, + type TMlStorageMapped, } from '../../../../../common/types/storage'; -import { useStorage } from '../../../contexts/storage'; import { EntityFieldType } from '../../../../../common/types/anomalies'; import { FieldDefinition } from '../../../services/results_service/result_service_rx'; import { getViewableDetectors } from '../../timeseriesexplorer_utils/get_viewable_detectors'; @@ -113,7 +115,10 @@ export const SeriesControls: FC = ({ return getControlsForDetector(selectedDetectorIndex, selectedEntities, selectedJobId); }, [selectedDetectorIndex, selectedEntities, selectedJobId]); - const [storageFieldsConfig, setStorageFieldsConfig] = useStorage(ML_ENTITY_FIELDS_CONFIG); + const [storageFieldsConfig, setStorageFieldsConfig] = useStorage< + MlStorageKey, + TMlStorageMapped + >(ML_ENTITY_FIELDS_CONFIG); // Merge the default config with the one from the local storage const resultFieldsConfig = useMemo(() => { diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/expanded_row.tsx index 879bed447c864..daa813fe4e1e6 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/expanded_row.tsx @@ -24,8 +24,8 @@ import { import { FormattedMessage } from '@kbn/i18n-react'; import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { isDefined } from '@kbn/ml-is-defined'; import type { ModelItemFull } from './models_list'; -import { isDefined } from '../../../../common/types/guards'; import { ModelPipelines } from './pipelines'; import { AllocatedModels } from '../nodes_overview/allocated_models'; import type { AllocatedModel } from '../../../../common/types/trained_models'; diff --git a/x-pack/plugins/ml/public/application/util/string_utils.ts b/x-pack/plugins/ml/public/application/util/string_utils.ts index b870cdff345b5..e1fa570efc20c 100644 --- a/x-pack/plugins/ml/public/application/util/string_utils.ts +++ b/x-pack/plugins/ml/public/application/util/string_utils.ts @@ -12,7 +12,7 @@ import d3 from 'd3'; import he from 'he'; import { escapeKuery } from '@kbn/es-query'; -import { isDefined } from '../../../common/types/guards'; +import { isDefined } from '@kbn/ml-is-defined'; import { CustomUrlAnomalyRecordDoc } from '../../../common/types/custom_urls'; import { Detector } from '../../../common/types/anomaly_detection_jobs'; diff --git a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts index 45eef7f63226a..36182956b7388 100644 --- a/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/alerting_service.ts @@ -15,6 +15,7 @@ import { IFieldFormat, SerializedFieldFormat, } from '@kbn/field-formats-plugin/common'; +import { isDefined } from '@kbn/ml-is-defined'; import { MlClient } from '../ml_client'; import { MlAnomalyDetectionAlertParams, @@ -32,7 +33,6 @@ import { } from '../../../common/types/alerts'; import { AnomalyDetectionAlertContext } from './register_anomaly_detection_alert_type'; import { resolveMaxTimeInterval } from '../../../common/util/job_utils'; -import { isDefined } from '../../../common/types/guards'; import { getTopNBuckets, resolveLookbackInterval } from '../../../common/util/alerts'; import type { DatafeedsService } from '../../models/job_service/datafeeds'; import { getEntityFieldName, getEntityFieldValue } from '../../../common/util/anomaly_utils'; diff --git a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts index 4c19846794de1..a1ec1931485ac 100644 --- a/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts +++ b/x-pack/plugins/ml/server/lib/alerts/jobs_health_service.ts @@ -9,6 +9,7 @@ import { groupBy, keyBy, memoize, partition } from 'lodash'; import { KibanaRequest, Logger, SavedObjectsClientContract } from '@kbn/core/server'; import { i18n } from '@kbn/i18n'; import { MlJob } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { isDefined } from '@kbn/ml-is-defined'; import { MlClient } from '../ml_client'; import { JobSelection } from '../../routes/schemas/alerting_schema'; import { datafeedsProvider, DatafeedsService } from '../../models/job_service/datafeeds'; @@ -30,7 +31,6 @@ import { import { AnnotationService } from '../../models/annotation_service/annotation'; import { annotationServiceProvider } from '../../models/annotation_service'; import { parseInterval } from '../../../common/util/parse_interval'; -import { isDefined } from '../../../common/types/guards'; import { jobAuditMessagesProvider, JobAuditMessagesService, diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts index 1f6bb64fd76f4..ccb60e6379e48 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/models_provider.ts @@ -11,6 +11,7 @@ import { MlTrainedModelStats, NodesInfoNodeInfo, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { isDefined } from '@kbn/ml-is-defined'; import type { NodeDeploymentStatsResponse, PipelineDefinition, @@ -21,7 +22,6 @@ import { TrainedModelDeploymentStatsResponse, TrainedModelModelSizeStats, } from '../../../common/types/trained_models'; -import { isDefined } from '../../../common/types/guards'; export type ModelService = ReturnType; diff --git a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts index 136722f4ab5b1..e9b92c2f01e6c 100644 --- a/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts +++ b/x-pack/plugins/ml/server/models/data_recognizer/data_recognizer.ts @@ -18,6 +18,7 @@ import moment from 'moment'; import { merge } from 'lodash'; import type { DataViewsService } from '@kbn/data-views-plugin/common'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { isDefined } from '@kbn/ml-is-defined'; import type { AnalysisLimits } from '../../../common/types/anomaly_detection_jobs'; import { getAuthorizationHeader } from '../../lib/request_authorization'; import type { MlClient } from '../../lib/ml_client'; @@ -54,7 +55,6 @@ import { resultsServiceProvider } from '../results_service'; import type { JobExistResult, JobStat } from '../../../common/types/data_recognizer'; import type { Datafeed } from '../../../common/types/anomaly_detection_jobs'; import type { MLSavedObjectService } from '../../saved_objects'; -import { isDefined } from '../../../common/types/guards'; const ML_DIR = 'ml'; const KIBANA_DIR = 'kibana'; diff --git a/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts b/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts index dbbf95d4806c2..bcce2455539f7 100644 --- a/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts +++ b/x-pack/plugins/ml/server/models/results_service/anomaly_charts.ts @@ -11,6 +11,7 @@ import { each, find, get, keyBy, map, reduce, sortBy } from 'lodash'; import type * as estypes from '@elastic/elasticsearch/lib/api/types'; import { extent, max, min } from 'd3'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { isDefined } from '@kbn/ml-is-defined'; import type { MlClient } from '../../lib/ml_client'; import { isRuntimeMappings } from '../../../common'; import type { @@ -40,7 +41,6 @@ import { isMultiBucketAnomaly, } from '../../../common/util/anomaly_utils'; import { InfluencersFilterQuery } from '../../../common/types/es_client'; -import { isDefined } from '../../../common/types/guards'; import { AnomalyRecordDoc, CombinedJob, Datafeed, RecordForInfluencer } from '../../shared'; import { ES_AGGREGATION, ML_JOB_AGGREGATION } from '../../../common/constants/aggregation_types'; import { parseInterval } from '../../../common/util/parse_interval'; diff --git a/x-pack/plugins/ml/tsconfig.json b/x-pack/plugins/ml/tsconfig.json index d5d42f865e2c0..2b99e5d22616a 100644 --- a/x-pack/plugins/ml/tsconfig.json +++ b/x-pack/plugins/ml/tsconfig.json @@ -68,6 +68,8 @@ "@kbn/repo-info", "@kbn/ml-url-state", "@kbn/ml-nested-property", + "@kbn/ml-local-storage", + "@kbn/ml-is-defined", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/transform/common/types/common.ts b/x-pack/plugins/transform/common/types/common.ts index 94cb935f52634..1cbb370b0a3ab 100644 --- a/x-pack/plugins/transform/common/types/common.ts +++ b/x-pack/plugins/transform/common/types/common.ts @@ -20,7 +20,3 @@ export function dictionaryToArray(dict: Dictionary): TValue[] { export type DeepPartial = { [P in keyof T]?: DeepPartial; }; - -export function isDefined(argument: T | undefined | null): argument is T { - return argument !== undefined && argument !== null; -} diff --git a/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_selector_control.tsx b/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_selector_control.tsx index 4300b75cb3fa4..2a44d8fd973f7 100644 --- a/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_selector_control.tsx +++ b/x-pack/plugins/transform/public/alerting/transform_health_rule_type/transform_selector_control.tsx @@ -7,8 +7,8 @@ import { EuiComboBox, EuiComboBoxProps, EuiFormRow } from '@elastic/eui'; import React, { FC, useMemo } from 'react'; +import { isDefined } from '@kbn/ml-is-defined'; import { ALL_TRANSFORMS_SELECTION } from '../../../common/constants'; -import { isDefined } from '../../../common/types/common'; export interface TransformSelectorControlProps { label?: string | JSX.Element; diff --git a/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts b/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts index c92a7a41da03f..55e3bc40360cf 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts +++ b/x-pack/plugins/transform/public/app/hooks/use_get_transforms.ts @@ -6,6 +6,7 @@ */ import type { IHttpFetchError } from '@kbn/core-http-browser'; +import { isDefined } from '@kbn/ml-is-defined'; import { isGetTransformNodesResponseSchema, isGetTransformsResponseSchema, @@ -22,7 +23,6 @@ import { import { useApi } from './use_api'; import { TRANSFORM_ERROR_TYPE } from '../common/transform'; -import { isDefined } from '../../../common/types/common'; export type GetTransforms = (forceRefresh?: boolean) => void; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx index a07978e12ec0f..db617690efc5f 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/advanced_runtime_mappings_settings/advanced_runtime_mappings_settings.tsx @@ -18,7 +18,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; -import { isDefined } from '../../../../../../common/types/common'; +import { isDefined } from '@kbn/ml-is-defined'; import { StepDefineFormHook } from '../step_define'; import { AdvancedRuntimeMappingsEditor } from '../advanced_runtime_mappings_editor/advanced_runtime_mappings_editor'; import { AdvancedRuntimeMappingsEditorSwitch } from '../advanced_runtime_mappings_editor_switch'; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts index 962bc45f3d212..17240497d2057 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/hooks/use_pivot_config.ts @@ -9,8 +9,9 @@ import { useCallback, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { KBN_FIELD_TYPES } from '@kbn/field-types'; +import { isDefined } from '@kbn/ml-is-defined'; import { AggName } from '../../../../../../../common/types/aggregations'; -import { dictionaryToArray, isDefined } from '../../../../../../../common/types/common'; +import { dictionaryToArray } from '../../../../../../../common/types/common'; import { useToastNotifications } from '../../../../../app_dependencies'; import { diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx index d96911cfdaa71..6f1d219ce7928 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row.tsx @@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n'; import { stringHash } from '@kbn/ml-string-hash'; import moment from 'moment-timezone'; -import { isDefined } from '../../../../../../common/types/common'; +import { isDefined } from '@kbn/ml-is-defined'; import { TransformListRow } from '../../../../common'; import { useAppDependencies } from '../../../../app_dependencies'; import { ExpandedRowDetailsPane, SectionConfig, SectionItem } from './expanded_row_details_pane'; diff --git a/x-pack/plugins/transform/tsconfig.json b/x-pack/plugins/transform/tsconfig.json index 2b4b5554ef9d0..8cb77da845d58 100644 --- a/x-pack/plugins/transform/tsconfig.json +++ b/x-pack/plugins/transform/tsconfig.json @@ -45,6 +45,7 @@ "@kbn/ui-theme", "@kbn/field-types", "@kbn/ml-nested-property", + "@kbn/ml-is-defined", ], "exclude": [ "target/**/*", diff --git a/yarn.lock b/yarn.lock index 40ebc66b80d16..423d287bb063e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3773,10 +3773,18 @@ version "0.0.0" uid "" +"@kbn/ml-is-defined@link:x-pack/packages/ml/is_defined": + version "0.0.0" + uid "" + "@kbn/ml-is-populated-object@link:x-pack/packages/ml/is_populated_object": version "0.0.0" uid "" +"@kbn/ml-local-storage@link:x-pack/packages/ml/local_storage": + version "0.0.0" + uid "" + "@kbn/ml-nested-property@link:x-pack/packages/ml/nested_property": version "0.0.0" uid ""