mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ML] Move local storage utilities to package. (#148049)
Moves multiple copies of `useStorage()` and related code to a package as a single source. The different copies with hard coded types have been adapted so `useStorage()` is now based on generics. Also moves duplicates of `isDefined()` to its own package.
This commit is contained in:
parent
9a0e692eb8
commit
dc1ae9e06c
77 changed files with 596 additions and 376 deletions
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"],
|
||||
|
|
3
x-pack/packages/ml/is_defined/README.md
Normal file
3
x-pack/packages/ml/is_defined/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/ml-is-defined
|
||||
|
||||
Utility function to determine if a value is not `undefined` and not `null`.
|
|
@ -5,4 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { MlStorageContextProvider, useStorage } from './storage_context';
|
||||
export { isDefined } from './src/is_defined';
|
|
@ -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
x-pack/packages/ml/is_defined/kibana.jsonc
Normal file
5
x-pack/packages/ml/is_defined/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/ml-is-defined",
|
||||
"owner": "@elastic/ml-ui"
|
||||
}
|
6
x-pack/packages/ml/is_defined/package.json
Normal file
6
x-pack/packages/ml/is_defined/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/ml-is-defined",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -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
x-pack/packages/ml/is_defined/tsconfig.json
Normal file
19
x-pack/packages/ml/is_defined/tsconfig.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node",
|
||||
"react"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": []
|
||||
}
|
3
x-pack/packages/ml/local_storage/README.md
Normal file
3
x-pack/packages/ml/local_storage/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/ml-local-storage
|
||||
|
||||
Utilities to combine url state management with local storage.
|
8
x-pack/packages/ml/local_storage/index.ts
Normal file
8
x-pack/packages/ml/local_storage/index.ts
Normal file
|
@ -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
x-pack/packages/ml/local_storage/jest.config.js
Normal file
12
x-pack/packages/ml/local_storage/jest.config.js
Normal file
|
@ -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
x-pack/packages/ml/local_storage/kibana.jsonc
Normal file
5
x-pack/packages/ml/local_storage/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/ml-local-storage",
|
||||
"owner": "@elastic/ml-ui"
|
||||
}
|
9
x-pack/packages/ml/local_storage/package.json
Normal file
9
x-pack/packages/ml/local_storage/package.json
Normal file
|
@ -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
x-pack/packages/ml/local_storage/src/storage_context.tsx
Normal file
193
x-pack/packages/ml/local_storage/src/storage_context.tsx
Normal file
|
@ -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 {
|
||||
return storageKeys.includes(key as T);
|
||||
}
|
||||
|
||||
/**
|
||||
* React context to hold storage API.
|
||||
*/
|
||||
export const MlStorageContext = React.createContext<StorageAPI>({
|
||||
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>({
|
||||
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
x-pack/packages/ml/local_storage/tsconfig.json
Normal file
22
x-pack/packages/ml/local_storage/tsconfig.json
Normal file
|
@ -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",
|
||||
]
|
||||
}
|
|
@ -16,6 +16,6 @@
|
|||
"licensing"
|
||||
],
|
||||
"optionalPlugins": [],
|
||||
"requiredBundles": ["fieldFormats", "kibanaReact"],
|
||||
"requiredBundles": ["fieldFormats", "kibanaReact", "kibanaUtils"],
|
||||
"extraPublicDirs": ["common"]
|
||||
}
|
||||
|
|
|
@ -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<ChangePointDetectionAppStateProps>
|
|||
<AiopsAppContext.Provider value={appDependencies}>
|
||||
<UrlStateProvider>
|
||||
<DataSourceContext.Provider value={{ dataView, savedSearch }}>
|
||||
<PageHeader />
|
||||
<ChangePointDetectionContextProvider>
|
||||
<ChangePointDetectionPage />
|
||||
</ChangePointDetectionContextProvider>
|
||||
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
|
||||
<PageHeader />
|
||||
<ChangePointDetectionContextProvider>
|
||||
<ChangePointDetectionPage />
|
||||
</ChangePointDetectionContextProvider>
|
||||
</StorageContextProvider>
|
||||
</DataSourceContext.Provider>
|
||||
</UrlStateProvider>
|
||||
</AiopsAppContext.Provider>
|
||||
|
|
|
@ -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<ExplainLogRateSpikesAppStateProps>
|
|||
<AiopsAppContext.Provider value={appDependencies}>
|
||||
<UrlStateProvider>
|
||||
<SpikeAnalysisTableRowStateProvider>
|
||||
<ExplainLogRateSpikesPage dataView={dataView} savedSearch={savedSearch} />
|
||||
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
|
||||
<ExplainLogRateSpikesPage dataView={dataView} savedSearch={savedSearch} />
|
||||
</StorageContextProvider>
|
||||
</SpikeAnalysisTableRowStateProvider>
|
||||
</UrlStateProvider>
|
||||
</AiopsAppContext.Provider>
|
||||
|
|
|
@ -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<FullTimeRangeSelectorProps> = ({
|
||||
timefilter,
|
||||
dataView,
|
||||
|
@ -90,7 +90,10 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = ({
|
|||
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
||||
const [frozenDataPreference, setFrozenDataPreference] = useStorage<FrozenTierPreference>(
|
||||
const [frozenDataPreference, setFrozenDataPreference] = useStorage<
|
||||
AiOpsKey,
|
||||
AiOpsStorageMapped<typeof AIOPS_FROZEN_TIER_PREFERENCE>
|
||||
>(
|
||||
AIOPS_FROZEN_TIER_PREFERENCE,
|
||||
// By default we will exclude frozen data tier
|
||||
FROZEN_TIER_PREFERENCE.EXCLUDE
|
||||
|
|
|
@ -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<LogCategorizationAppStateProps> = ({
|
|||
return (
|
||||
<AiopsAppContext.Provider value={appDependencies}>
|
||||
<UrlStateProvider>
|
||||
<LogCategorizationPage dataView={dataView} savedSearch={savedSearch} />
|
||||
<StorageContextProvider storage={localStorage} storageKeys={AIOPS_STORAGE_KEYS}>
|
||||
<LogCategorizationPage dataView={dataView} savedSearch={savedSearch} />
|
||||
</StorageContextProvider>
|
||||
</UrlStateProvider>
|
||||
</AiopsAppContext.Provider>
|
||||
);
|
||||
|
|
|
@ -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<AiOps, null>;
|
||||
|
||||
/**
|
||||
* Hook for accessing and changing a value in the storage.
|
||||
* @param key - Storage key
|
||||
* @param initValue
|
||||
*/
|
||||
export function useStorage<T>(key: AiOpsKey, initValue?: T): [T, (value: T) => void] {
|
||||
const { storage } = useAiopsAppContext();
|
||||
|
||||
const [val, setVal] = useState<T>(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];
|
||||
}
|
28
x-pack/plugins/aiops/public/types/storage.ts
Normal file
28
x-pack/plugins/aiops/public/types/storage.ts
Normal file
|
@ -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<AiOps, null>;
|
||||
|
||||
export type AiOpsStorageMapped<T extends AiOpsKey> = T extends typeof AIOPS_FROZEN_TIER_PREFERENCE
|
||||
? FrozenTierPreference | undefined
|
||||
: null;
|
||||
|
||||
export const AIOPS_STORAGE_KEYS = [AIOPS_FROZEN_TIER_PREFERENCE] as const;
|
|
@ -43,6 +43,7 @@
|
|||
"@kbn/core-elasticsearch-server",
|
||||
"@kbn/es-types",
|
||||
"@kbn/ml-url-state",
|
||||
"@kbn/ml-local-storage",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact",
|
||||
"kibanaUtils",
|
||||
"maps",
|
||||
"esUiShared",
|
||||
"fieldFormats",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<Props> = ({
|
||||
|
@ -83,7 +83,10 @@ export const FullTimeRangeSelector: FC<Props> = ({
|
|||
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
||||
const [frozenDataPreference, setFrozenDataPreference] = useStorage<FrozenTierPreference>(
|
||||
const [frozenDataPreference, setFrozenDataPreference] = useStorage<
|
||||
DVKey,
|
||||
DVStorageMapped<typeof DV_FROZEN_TIER_PREFERENCE>
|
||||
>(
|
||||
DV_FROZEN_TIER_PREFERENCE,
|
||||
// By default we will exclude frozen data tier
|
||||
FROZEN_TIER_PREFERENCE.EXCLUDE
|
||||
|
|
|
@ -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<IndexDataVisualizerViewProps> = (dataVisualizerProps) => {
|
||||
const euiTheme = useCurrentEuiTheme();
|
||||
|
||||
const [savedRandomSamplerPreference, saveRandomSamplerPreference] =
|
||||
useStorage<RandomSamplerOption>(
|
||||
DV_RANDOM_SAMPLER_PREFERENCE,
|
||||
RANDOM_SAMPLER_OPTION.ON_AUTOMATIC
|
||||
);
|
||||
const [savedRandomSamplerPreference, saveRandomSamplerPreference] = useStorage<
|
||||
DVKey,
|
||||
DVStorageMapped<typeof DV_RANDOM_SAMPLER_PREFERENCE>
|
||||
>(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
|
||||
[]
|
||||
);
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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<DV, null>;
|
||||
|
||||
/**
|
||||
* Hook for accessing and changing a value in the storage.
|
||||
* @param key - Storage key
|
||||
* @param initValue
|
||||
*/
|
||||
export function useStorage<T>(key: DVKey, initValue?: T): [T, (value: T) => void] {
|
||||
const {
|
||||
services: { storage },
|
||||
} = useDataVisualizerKibana();
|
||||
|
||||
const [val, setVal] = useState<T>(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];
|
||||
}
|
|
@ -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<IndexDataVisualizerViewProps>;
|
||||
|
@ -316,10 +321,12 @@ export const IndexDataVisualizer: FC<{
|
|||
return (
|
||||
<KibanaThemeProvider theme$={coreStart.theme.theme$}>
|
||||
<KibanaContextProvider services={{ ...services }}>
|
||||
<DataVisualizerStateContextProvider
|
||||
IndexDataVisualizerComponent={IndexDataVisualizerView}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
/>
|
||||
<StorageContextProvider storage={localStorage} storageKeys={DV_STORAGE_KEYS}>
|
||||
<DataVisualizerStateContextProvider
|
||||
IndexDataVisualizerComponent={IndexDataVisualizerView}
|
||||
getAdditionalLinks={getAdditionalLinks}
|
||||
/>
|
||||
</StorageContextProvider>
|
||||
</KibanaContextProvider>
|
||||
</KibanaThemeProvider>
|
||||
);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<DV, null>;
|
||||
|
||||
export type DVStorageMapped<T extends DVKey> = 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;
|
|
@ -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/**/*",
|
||||
|
|
|
@ -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();
|
||||
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(),
|
||||
};
|
||||
|
||||
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 Provider: FC = ({ children }) => {
|
||||
return (
|
||||
<StorageContextProvider storage={mockStorage} storageKeys={ML_STORAGE_KEYS}>
|
||||
{children}
|
||||
</StorageContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
}
|
||||
);
|
||||
|
|
@ -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<MlStorageRecord> | null;
|
||||
|
||||
export type MlStorageKey = keyof Exclude<MlStorage, null>;
|
||||
|
||||
|
@ -69,7 +80,7 @@ export type TMlStorageMapped<T extends MlStorageKey> = 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;
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<MlAnomalyDetectionJobsHealthRuleParams>;
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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<AppProps> = ({ coreStart, deps, appMountParams }) => {
|
|||
mlServices: getMlGlobalServices(coreStart.http, deps.usageCollection),
|
||||
}}
|
||||
>
|
||||
<MlStorageContextProvider>
|
||||
<StorageContextProvider storage={localStorage} storageKeys={ML_STORAGE_KEYS}>
|
||||
<MlRouter pageDeps={pageDeps} />
|
||||
</MlStorageContextProvider>
|
||||
</StorageContextProvider>
|
||||
</KibanaContextProvider>
|
||||
</KibanaThemeProvider>
|
||||
</I18nContext>
|
||||
|
|
|
@ -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'),
|
||||
};
|
||||
|
|
|
@ -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<Props> = ({ dataView, query, disabled, callback }) => {
|
||||
|
@ -53,10 +52,10 @@ export const FullTimeRangeSelector: FC<Props> = ({ 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<typeof ML_FROZEN_TIER_PREFERENCE>
|
||||
>(ML_FROZEN_TIER_PREFERENCE, FROZEN_TIER_PREFERENCE.EXCLUDE);
|
||||
|
||||
const onButtonClick = () => {
|
||||
setPopover(!isPopoverOpen);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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];
|
||||
}),
|
||||
|
|
|
@ -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<typeof ML_NOTIFICATIONS_LAST_CHECKED_AT>
|
||||
>(ML_NOTIFICATIONS_LAST_CHECKED_AT);
|
||||
const lastCheckedAt$ = useAsObservable(lastCheckedAt);
|
||||
|
||||
/** Holds the value used for the actual request */
|
||||
|
|
|
@ -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: <K extends MlStorageKey, T extends TMlStorageMapped<K>>(key: K, value: T) => void;
|
||||
removeValue: <K extends MlStorageKey>(key: K) => void;
|
||||
}
|
||||
|
||||
export const MlStorageContext = React.createContext<StorageAPI>({
|
||||
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<MlStorage, null>);
|
||||
}, [storage]);
|
||||
|
||||
const [state, setState] = useState<MlStorage>(initialValue);
|
||||
|
||||
const setStorageValue = useCallback(
|
||||
<K extends MlStorageKey, T extends TMlStorageMapped<K>>(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 <MlStorageContext.Provider value={value}>{children}</MlStorageContext.Provider>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for consuming a storage value
|
||||
* @param key
|
||||
* @param initValue
|
||||
*/
|
||||
export function useStorage<K extends MlStorageKey, T extends TMlStorageMapped<K>>(
|
||||
key: K,
|
||||
initValue?: T
|
||||
): [
|
||||
typeof initValue extends undefined
|
||||
? TMlStorageMapped<K> | undefined
|
||||
: Exclude<TMlStorageMapped<K>, undefined>,
|
||||
(value: TMlStorageMapped<K>) => void
|
||||
] {
|
||||
const { value, setValue, removeValue } = useContext(MlStorageContext);
|
||||
|
||||
const resultValue = useMemo(() => {
|
||||
return (value?.[key] ?? initValue) as typeof initValue extends undefined
|
||||
? TMlStorageMapped<K> | undefined
|
||||
: Exclude<TMlStorageMapped<K>, undefined>;
|
||||
}, [value, key, initValue]);
|
||||
|
||||
const setVal = useCallback(
|
||||
(v: TMlStorageMapped<K>) => {
|
||||
if (isDefined(v)) {
|
||||
setValue(key, v);
|
||||
} else {
|
||||
removeValue(key);
|
||||
}
|
||||
},
|
||||
[setValue, removeValue, key]
|
||||
);
|
||||
|
||||
return [resultValue, setVal];
|
||||
}
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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/';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<SeriesControlsProps> = ({
|
|||
return getControlsForDetector(selectedDetectorIndex, selectedEntities, selectedJobId);
|
||||
}, [selectedDetectorIndex, selectedEntities, selectedJobId]);
|
||||
|
||||
const [storageFieldsConfig, setStorageFieldsConfig] = useStorage(ML_ENTITY_FIELDS_CONFIG);
|
||||
const [storageFieldsConfig, setStorageFieldsConfig] = useStorage<
|
||||
MlStorageKey,
|
||||
TMlStorageMapped<typeof ML_ENTITY_FIELDS_CONFIG>
|
||||
>(ML_ENTITY_FIELDS_CONFIG);
|
||||
|
||||
// Merge the default config with the one from the local storage
|
||||
const resultFieldsConfig = useMemo(() => {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<typeof modelsProvider>;
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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/**/*",
|
||||
|
|
|
@ -20,7 +20,3 @@ export function dictionaryToArray<TValue>(dict: Dictionary<TValue>): TValue[] {
|
|||
export type DeepPartial<T> = {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
};
|
||||
|
||||
export function isDefined<T>(argument: T | undefined | null): argument is T {
|
||||
return argument !== undefined && argument !== null;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
"@kbn/ui-theme",
|
||||
"@kbn/field-types",
|
||||
"@kbn/ml-nested-property",
|
||||
"@kbn/ml-is-defined",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -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 ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue