diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b4e1691627c23..99ef3ca541135 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1071,4 +1071,6 @@ 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_populated_object @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 93aded0bec2b0..b618e20ffb178 100644 --- a/package.json +++ b/package.json @@ -350,7 +350,9 @@ "@kbn/mapbox-gl": "link:packages/kbn-mapbox-gl", "@kbn/ml-agg-utils": "link:x-pack/packages/ml/agg_utils", "@kbn/ml-is-populated-object": "link:x-pack/packages/ml/is_populated_object", + "@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", "@kbn/monaco": "link:packages/kbn-monaco", "@kbn/osquery-io-ts-types": "link:packages/kbn-osquery-io-ts-types", "@kbn/plugin-discovery": "link:packages/kbn-plugin-discovery", diff --git a/tsconfig.base.json b/tsconfig.base.json index a27504bd796d8..15291b2f482e0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -816,10 +816,14 @@ "@kbn/ml-agg-utils/*": ["x-pack/packages/ml/agg_utils/*"], "@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-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"], "@kbn/ml-plugin/*": ["x-pack/plugins/ml/*"], "@kbn/ml-string-hash": ["x-pack/packages/ml/string_hash"], "@kbn/ml-string-hash/*": ["x-pack/packages/ml/string_hash/*"], + "@kbn/ml-url-state": ["x-pack/packages/ml/url_state"], + "@kbn/ml-url-state/*": ["x-pack/packages/ml/url_state/*"], "@kbn/monaco": ["packages/kbn-monaco"], "@kbn/monaco/*": ["packages/kbn-monaco/*"], "@kbn/monitoring-collection-plugin": ["x-pack/plugins/monitoring_collection"], diff --git a/x-pack/packages/ml/nested_property/README.md b/x-pack/packages/ml/nested_property/README.md new file mode 100644 index 0000000000000..498b8c6a4add4 --- /dev/null +++ b/x-pack/packages/ml/nested_property/README.md @@ -0,0 +1,3 @@ +# @kbn/ml-nested-property + +Provides functionality similar to lodash's get() except that it's TypeScript aware and able to infer return types. diff --git a/x-pack/packages/ml/nested_property/index.ts b/x-pack/packages/ml/nested_property/index.ts new file mode 100644 index 0000000000000..d5cea3ee17973 --- /dev/null +++ b/x-pack/packages/ml/nested_property/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { getNestedProperty } from './src/get_nested_property'; +export { setNestedProperty } from './src/set_nested_property'; diff --git a/x-pack/packages/ml/nested_property/jest.config.js b/x-pack/packages/ml/nested_property/jest.config.js new file mode 100644 index 0000000000000..1f242155d4093 --- /dev/null +++ b/x-pack/packages/ml/nested_property/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/nested_property'], +}; diff --git a/x-pack/packages/ml/nested_property/kibana.jsonc b/x-pack/packages/ml/nested_property/kibana.jsonc new file mode 100644 index 0000000000000..8256bcfc7d725 --- /dev/null +++ b/x-pack/packages/ml/nested_property/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/ml-nested-property", + "owner": "@elastic/ml-ui" +} diff --git a/x-pack/packages/ml/nested_property/package.json b/x-pack/packages/ml/nested_property/package.json new file mode 100644 index 0000000000000..6498da088ece7 --- /dev/null +++ b/x-pack/packages/ml/nested_property/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/ml-nested-property", + "description": "TypeScript-aware utility functions to get/set attributes from objects.", + "author": "Machine Learning UI", + "homepage": "https://docs.elastic.dev/kibana-dev-docs/api/kbn-ml-nested-property", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/x-pack/plugins/transform/common/utils/object_utils.test.ts b/x-pack/packages/ml/nested_property/src/get_nested_property.test.ts similarity index 97% rename from x-pack/plugins/transform/common/utils/object_utils.test.ts rename to x-pack/packages/ml/nested_property/src/get_nested_property.test.ts index c99adf6b6d189..510ce8ce342f6 100644 --- a/x-pack/plugins/transform/common/utils/object_utils.test.ts +++ b/x-pack/packages/ml/nested_property/src/get_nested_property.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { getNestedProperty } from './object_utils'; +import { getNestedProperty } from './get_nested_property'; describe('object_utils', () => { test('getNestedProperty()', () => { diff --git a/x-pack/plugins/transform/common/utils/object_utils.ts b/x-pack/packages/ml/nested_property/src/get_nested_property.ts similarity index 72% rename from x-pack/plugins/transform/common/utils/object_utils.ts rename to x-pack/packages/ml/nested_property/src/get_nested_property.ts index 605af48914360..fbc26ad79d7bb 100644 --- a/x-pack/plugins/transform/common/utils/object_utils.ts +++ b/x-pack/packages/ml/nested_property/src/get_nested_property.ts @@ -33,21 +33,3 @@ export function getNestedProperty( return o; } - -export const setNestedProperty = (obj: Record, accessor: string, value: any) => { - let ref = obj; - const accessors = accessor.split('.'); - const len = accessors.length; - for (let i = 0; i < len - 1; i++) { - const attribute = accessors[i]; - if (ref[attribute] === undefined) { - ref[attribute] = {}; - } - - ref = ref[attribute]; - } - - ref[accessors[len - 1]] = value; - - return obj; -}; diff --git a/x-pack/packages/ml/nested_property/src/set_nested_property.test.ts b/x-pack/packages/ml/nested_property/src/set_nested_property.test.ts new file mode 100644 index 0000000000000..43996f603ffa0 --- /dev/null +++ b/x-pack/packages/ml/nested_property/src/set_nested_property.test.ts @@ -0,0 +1,72 @@ +/* + * 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 { setNestedProperty } from './set_nested_property'; + +describe('object_utils', () => { + test('setNestedProperty()', () => { + function getTestObj() { + return { + the: { + nested: { + value: 'the-nested-value', + }, + }, + }; + } + + function getFalseyObject() { + return { + the: { + nested: { + value: false, + }, + other_nested: { + value: 0, + }, + }, + }; + } + + const test1 = setNestedProperty(getTestObj(), 'the', 'update'); + expect(test1.the).toBe('update'); + + const test2 = setNestedProperty(getTestObj(), 'the$', 'update'); + expect(test2.the$).toBe('update'); + + const test3 = setNestedProperty(getTestObj(), 'the$', 'the-default-value'); + expect(test3.the$).toBe('the-default-value'); + + const test4 = setNestedProperty(getTestObj(), 'the.neSted', 'update'); + expect(test4.the.neSted).toBe('update'); + + const test5 = setNestedProperty(getTestObj(), 'the.nested', 'update'); + expect(test5.the.nested).toStrictEqual('update'); + + const test6 = setNestedProperty(getTestObj(), 'the.nested.vaLue', 'update'); + expect(test6.the.nested.vaLue).toBe('update'); + + const test7 = setNestedProperty(getTestObj(), 'the.nested.value', 'update'); + expect(test7.the.nested.value).toBe('update'); + + const test8 = setNestedProperty(getTestObj(), 'the.nested.value.didntExist', 'update'); + expect(test8.the.nested.value.didntExist).toBe('update'); + + const test9 = setNestedProperty( + getTestObj(), + 'the.nested.value.didntExist', + 'the-default-value' + ); + expect(test9.the.nested.value.didntExist).toBe('the-default-value'); + + const test10 = setNestedProperty(getFalseyObject(), 'the.nested.value', 'update'); + expect(test10.the.nested.value).toBe('update'); + + const test11 = setNestedProperty(getFalseyObject(), 'the.other_nested.value', 'update'); + expect(test11.the.other_nested.value).toBe('update'); + }); +}); diff --git a/x-pack/packages/ml/nested_property/src/set_nested_property.ts b/x-pack/packages/ml/nested_property/src/set_nested_property.ts new file mode 100644 index 0000000000000..01e2200d4369a --- /dev/null +++ b/x-pack/packages/ml/nested_property/src/set_nested_property.ts @@ -0,0 +1,24 @@ +/* + * 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 setNestedProperty = (obj: Record, accessor: string, value: any) => { + let ref = obj; + const accessors = accessor.split('.'); + const len = accessors.length; + for (let i = 0; i < len - 1; i++) { + const attribute = accessors[i]; + if (typeof ref[attribute] !== 'object') { + ref[attribute] = {}; + } + + ref = ref[attribute]; + } + + ref[accessors[len - 1]] = value; + + return obj; +}; diff --git a/x-pack/packages/ml/nested_property/tsconfig.json b/x-pack/packages/ml/nested_property/tsconfig.json new file mode 100644 index 0000000000000..e71c7853b63f8 --- /dev/null +++ b/x-pack/packages/ml/nested_property/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*", + ] +} diff --git a/x-pack/packages/ml/url_state/README.md b/x-pack/packages/ml/url_state/README.md new file mode 100644 index 0000000000000..c444e28569e5a --- /dev/null +++ b/x-pack/packages/ml/url_state/README.md @@ -0,0 +1,3 @@ +# @kbn/ml-url-state + +URL state management. diff --git a/x-pack/packages/ml/url_state/index.ts b/x-pack/packages/ml/url_state/index.ts new file mode 100644 index 0000000000000..c2b3f80c62a15 --- /dev/null +++ b/x-pack/packages/ml/url_state/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { + isRisonSerializationRequired, + parseUrlState, + usePageUrlState, + useUrlState, + PageUrlStateService, + Provider, + UrlStateProvider, + type Accessor, + type Dictionary, + type SetUrlState, +} from './src/url_state'; diff --git a/x-pack/packages/ml/url_state/jest.config.js b/x-pack/packages/ml/url_state/jest.config.js new file mode 100644 index 0000000000000..34c3a85409e99 --- /dev/null +++ b/x-pack/packages/ml/url_state/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/url_state'], +}; diff --git a/x-pack/packages/ml/url_state/kibana.jsonc b/x-pack/packages/ml/url_state/kibana.jsonc new file mode 100644 index 0000000000000..8850913bfa8c2 --- /dev/null +++ b/x-pack/packages/ml/url_state/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/ml-url-state", + "owner": "@elastic/ml-ui" +} diff --git a/x-pack/packages/ml/url_state/package.json b/x-pack/packages/ml/url_state/package.json new file mode 100644 index 0000000000000..f53d53db4b504 --- /dev/null +++ b/x-pack/packages/ml/url_state/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/ml-url-state", + "description": "Url state management utilities.", + "author": "Machine Learning UI", + "homepage": "https://docs.elastic.dev/kibana-dev-docs/api/kbn-ml-url-state", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} diff --git a/x-pack/plugins/ml/public/application/util/url_state.test.tsx b/x-pack/packages/ml/url_state/src/url_state.test.tsx similarity index 100% rename from x-pack/plugins/ml/public/application/util/url_state.test.tsx rename to x-pack/packages/ml/url_state/src/url_state.test.tsx diff --git a/x-pack/plugins/ml/public/application/util/url_state.tsx b/x-pack/packages/ml/url_state/src/url_state.tsx similarity index 90% rename from x-pack/plugins/ml/public/application/util/url_state.tsx rename to x-pack/packages/ml/url_state/src/url_state.tsx index 8aca531286a42..d643a22bde6e4 100644 --- a/x-pack/plugins/ml/public/application/util/url_state.tsx +++ b/x-pack/packages/ml/url_state/src/url_state.tsx @@ -11,23 +11,25 @@ import React, { useCallback, useContext, useMemo, - FC, useRef, useEffect, + type FC, } from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; import { isEqual } from 'lodash'; + +import { getNestedProperty } from '@kbn/ml-nested-property'; import { decode, encode } from '@kbn/rison'; -import { useHistory, useLocation } from 'react-router-dom'; import { BehaviorSubject, Observable } from 'rxjs'; import { distinctUntilChanged } from 'rxjs/operators'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { Dictionary } from '../../../common/types/common'; -import { getNestedProperty } from './object_utils'; -import { MlPages } from '../../../common/constants/locator'; +export interface Dictionary { + [id: string]: TValue; +} -type Accessor = '_a' | '_g'; +export type Accessor = '_a' | '_g'; export type SetUrlState = ( accessor: Accessor, attribute: string | Dictionary, @@ -48,7 +50,7 @@ const risonSerializedParams = new Set(['_a', '_g']); * Checks if the URL query parameter requires rison serialization. * @param queryParam */ -function isRisonSerializationRequired(queryParam: string): boolean { +export function isRisonSerializationRequired(queryParam: string): boolean { return risonSerializedParams.has(queryParam); } @@ -86,7 +88,7 @@ export const urlStateStore = createContext({ setUrlState: () => {}, }); -const { Provider } = urlStateStore; +export const { Provider } = urlStateStore; export const UrlStateProvider: FC = ({ children }) => { const history = useHistory(); @@ -183,15 +185,6 @@ export const useUrlState = ( return [urlState, setUrlState]; }; -type LegacyUrlKeys = 'mlExplorerSwimlane'; - -export type AppStateKey = - | 'mlSelectSeverity' - | 'mlSelectInterval' - | 'mlAnomaliesTable' - | MlPages - | LegacyUrlKeys; - /** * Service for managing URL state of particular page. */ @@ -235,16 +228,21 @@ export class PageUrlStateService { } } +interface PageUrlState { + pageKey: string; + pageUrlState: object; +} + /** * Hook for managing the URL state of the page. */ -export const usePageUrlState = ( - pageKey: AppStateKey, - defaultState?: PageUrlState +export const usePageUrlState = ( + pageKey: T['pageKey'], + defaultState?: T['pageUrlState'] ): [ - PageUrlState, - (update: Partial, replaceState?: boolean) => void, - PageUrlStateService + T['pageUrlState'], + (update: Partial, replaceState?: boolean) => void, + PageUrlStateService ] => { const [appState, setAppState] = useUrlState('_a'); const pageState = appState?.[pageKey]; @@ -255,9 +253,9 @@ export const usePageUrlState = ( setCallback.current = setAppState; }, [setAppState]); - const prevPageState = useRef(); + const prevPageState = useRef(); - const resultPageState: PageUrlState = useMemo(() => { + const resultPageState: T['pageUrlState'] = useMemo(() => { const result = { ...(defaultState ?? {}), ...(pageState ?? {}), @@ -283,7 +281,7 @@ export const usePageUrlState = ( }, [pageState]); const onStateUpdate = useCallback( - (update: Partial, replaceState?: boolean) => { + (update: Partial, replaceState?: boolean) => { if (!setCallback?.current) { throw new Error('Callback for URL state update has not been initialized.'); } @@ -300,7 +298,7 @@ export const usePageUrlState = ( [pageKey, resultPageState] ); - const pageUrlStateService = useMemo(() => new PageUrlStateService(), []); + const pageUrlStateService = useMemo(() => new PageUrlStateService(), []); useEffect( function updatePageUrlService() { diff --git a/x-pack/packages/ml/url_state/tsconfig.json b/x-pack/packages/ml/url_state/tsconfig.json new file mode 100644 index 0000000000000..715cdade684a1 --- /dev/null +++ b/x-pack/packages/ml/url_state/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*", + ], + "kbn_references": [ + "@kbn/ml-nested-property", + "@kbn/rison", + "@kbn/ml-is-populated-object", + ] +} diff --git a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx index 7fd586953fe96..600a68ff1a498 100644 --- a/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx +++ b/x-pack/plugins/aiops/public/components/change_point_detection/change_point_detection_context.tsx @@ -18,6 +18,7 @@ import { type DataViewField } from '@kbn/data-views-plugin/public'; import { startWith } from 'rxjs'; import useMount from 'react-use/lib/useMount'; import type { Query, Filter } from '@kbn/es-query'; +import { usePageUrlState } from '@kbn/ml-url-state'; import { createMergedEsQuery, getEsQueryFromSavedSearch, @@ -27,9 +28,13 @@ import { useTimefilter, useTimeRangeUpdates } from '../../hooks/use_time_filter' import { useChangePointResults } from './use_change_point_agg_request'; import { type TimeBuckets, TimeBucketsInterval } from '../../../common/time_buckets'; import { useDataSource } from '../../hooks/use_data_source'; -import { usePageUrlState } from '../../hooks/use_url_state'; import { useTimeBuckets } from '../../hooks/use_time_buckets'; +export interface ChangePointDetectionPageUrlState { + pageKey: 'changePoint'; + pageUrlState: ChangePointDetectionRequestParams; +} + export interface ChangePointDetectionRequestParams { fn: string; splitField: string; @@ -157,7 +162,7 @@ export const ChangePointDetectionContextProvider: FC = ({ children }) => { }, [dataView]); const [requestParamsFromUrl, updateRequestParams] = - usePageUrlState('changePoint'); + usePageUrlState('changePoint'); const resultQuery = useMemo(() => { return ( 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 0b4a14928a19c..44c68c6017588 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 @@ -8,10 +8,10 @@ import { DataView } from '@kbn/data-views-plugin/common'; import { SavedSearch } from '@kbn/saved-search-plugin/public'; import React, { FC } from 'react'; +import { UrlStateProvider } from '@kbn/ml-url-state'; import { PageHeader } from '../page_header'; import { ChangePointDetectionContextProvider } from './change_point_detection_context'; import { DataSourceContext } from '../../hooks/use_data_source'; -import { UrlStateProvider } from '../../hooks/use_url_state'; import { SavedSearchSavedObject } from '../../application/utils/search_utils'; import { AiopsAppContext, AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; import { ChangePointDetectionPage } from './change_point_detection_page'; diff --git a/x-pack/plugins/aiops/public/components/date_picker_wrapper/date_picker_wrapper.tsx b/x-pack/plugins/aiops/public/components/date_picker_wrapper/date_picker_wrapper.tsx index 566d3b6ae7b5b..74ca9148309bc 100644 --- a/x-pack/plugins/aiops/public/components/date_picker_wrapper/date_picker_wrapper.tsx +++ b/x-pack/plugins/aiops/public/components/date_picker_wrapper/date_picker_wrapper.tsx @@ -20,14 +20,15 @@ import { OnRefreshProps, OnTimeChangeProps, } from '@elastic/eui'; + import type { TimeRange } from '@kbn/es-query'; import { TimeHistoryContract, UI_SETTINGS } from '@kbn/data-plugin/public'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; +import { useUrlState } from '@kbn/ml-url-state'; import { useRefreshIntervalUpdates, useTimeRangeUpdates } from '../../hooks/use_time_filter'; -import { useUrlState } from '../../hooks/use_url_state'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { aiopsRefresh$ } from '../../application/services/timefilter_refresh_service'; 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 ba69fdfa8a7b6..2e745894cac0a 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 @@ -15,12 +15,12 @@ import { i18n } from '@kbn/i18n'; import type { SavedSearch } from '@kbn/discover-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; +import { UrlStateProvider } from '@kbn/ml-url-state'; import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage, SavedSearchSavedObject, } from '../../application/utils/search_utils'; -import { UrlStateProvider } from '../../hooks/use_url_state'; import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; @@ -41,6 +41,11 @@ const defaultSearchQuery = { match_all: {}, }; +export interface AiOpsPageUrlState { + pageKey: 'AIOPS_INDEX_VIEWER'; + pageUrlState: AiOpsIndexBasedAppState; +} + export interface AiOpsIndexBasedAppState { searchString?: Query['query']; searchQuery?: Query['query']; diff --git a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx index a6327331a5105..98727faeea027 100644 --- a/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx +++ b/x-pack/plugins/aiops/public/components/explain_log_rate_spikes/explain_log_rate_spikes_page.tsx @@ -27,16 +27,16 @@ import { Filter, FilterStateStore, Query } from '@kbn/es-query'; import { FormattedMessage } from '@kbn/i18n-react'; import { SavedSearch } from '@kbn/discover-plugin/public'; +import { useUrlState, usePageUrlState } from '@kbn/ml-url-state'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { SearchQueryLanguage, SavedSearchSavedObject } from '../../application/utils/search_utils'; -import { useUrlState, usePageUrlState, AppStateKey } from '../../hooks/use_url_state'; import { useData } from '../../hooks/use_data'; import { FullTimeRangeSelector } from '../full_time_range_selector'; import { DocumentCountContent } from '../document_count_content/document_count_content'; import { DatePickerWrapper } from '../date_picker_wrapper'; import { SearchPanel } from '../search_panel'; -import { restorableDefaults } from './explain_log_rate_spikes_app_state'; +import { restorableDefaults, type AiOpsPageUrlState } from './explain_log_rate_spikes_app_state'; import { ExplainLogRateSpikesAnalysis } from './explain_log_rate_spikes_analysis'; import type { GroupTableItem } from '../spike_analysis_table/types'; import { useSpikeAnalysisTableRowContext } from '../spike_analysis_table/spike_analysis_table_row_provider'; @@ -79,7 +79,10 @@ export const ExplainLogRateSpikesPage: FC = ({ setSelectedGroup, } = useSpikeAnalysisTableRowContext(); - const [aiopsListState, setAiopsListState] = usePageUrlState(AppStateKey, restorableDefaults); + const [aiopsListState, setAiopsListState] = usePageUrlState( + 'AIOPS_INDEX_VIEWER', + restorableDefaults + ); const [globalState, setGlobalState] = useUrlState('_g'); const [currentSavedSearch, setCurrentSavedSearch] = useState(savedSearch); 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 bc67b0f32eda2..4c0da804f5eb6 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,11 +7,11 @@ import React, { FC } from 'react'; import type { SavedSearch } from '@kbn/discover-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; +import { UrlStateProvider } from '@kbn/ml-url-state'; import { LogCategorizationPage } from './log_categorization_page'; import { SavedSearchSavedObject } from '../../application/utils/search_utils'; import type { AiopsAppDependencies } from '../../hooks/use_aiops_app_context'; import { AiopsAppContext } from '../../hooks/use_aiops_app_context'; -import { UrlStateProvider } from '../../hooks/use_url_state'; export interface LogCategorizationAppStateProps { dataView: DataView; diff --git a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx index 4eb6da0e58b9f..f0eccec160375 100644 --- a/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx +++ b/x-pack/plugins/aiops/public/components/log_categorization/log_categorization_page.tsx @@ -25,6 +25,7 @@ import { EuiLoadingContent, } from '@elastic/eui'; +import { useUrlState } from '@kbn/ml-url-state'; import { FullTimeRangeSelector } from '../full_time_range_selector'; import { DatePickerWrapper } from '../date_picker_wrapper'; import { useData } from '../../hooks/use_data'; @@ -33,7 +34,6 @@ import type { SearchQueryLanguage, SavedSearchSavedObject, } from '../../application/utils/search_utils'; -import { useUrlState } from '../../hooks/use_url_state'; import { useAiopsAppContext } from '../../hooks/use_aiops_app_context'; import { restorableDefaults } from '../explain_log_rate_spikes/explain_log_rate_spikes_app_state'; import { useCategorizeRequest } from './use_categorize_request'; diff --git a/x-pack/plugins/aiops/public/components/page_header/page_header.tsx b/x-pack/plugins/aiops/public/components/page_header/page_header.tsx index fb45adcc3cde0..2105eaa0cb938 100644 --- a/x-pack/plugins/aiops/public/components/page_header/page_header.tsx +++ b/x-pack/plugins/aiops/public/components/page_header/page_header.tsx @@ -14,8 +14,8 @@ import { EuiPageContentHeader_Deprecated as EuiPageContentHeader, EuiPageContentHeaderSection_Deprecated as EuiPageContentHeaderSection, } from '@elastic/eui'; +import { useUrlState } from '@kbn/ml-url-state'; import { FullTimeRangeSelectorProps } from '../full_time_range_selector/full_time_range_selector'; -import { useUrlState } from '../../hooks/use_url_state'; import { useDataSource } from '../../hooks/use_data_source'; import { useTimefilter } from '../../hooks/use_time_filter'; import { FullTimeRangeSelector } from '../full_time_range_selector'; diff --git a/x-pack/plugins/aiops/public/hooks/use_data.ts b/x-pack/plugins/aiops/public/hooks/use_data.ts index 73b5f79be3b4f..37e2454f9fb70 100644 --- a/x-pack/plugins/aiops/public/hooks/use_data.ts +++ b/x-pack/plugins/aiops/public/hooks/use_data.ts @@ -13,6 +13,7 @@ import type { ChangePoint } from '@kbn/ml-agg-utils'; import type { SavedSearch } from '@kbn/discover-plugin/public'; +import type { Dictionary } from '@kbn/ml-url-state'; import { useTimeBuckets } from './use_time_buckets'; import { useAiopsAppContext } from './use_aiops_app_context'; @@ -26,7 +27,6 @@ import { import { useTimefilter } from './use_time_filter'; import { useDocumentCountStats } from './use_document_count_stats'; -import type { Dictionary } from './use_url_state'; import type { GroupTableItem } from '../components/spike_analysis_table/types'; const DEFAULT_BAR_TARGET = 75; diff --git a/x-pack/plugins/aiops/public/hooks/use_url_state.tsx b/x-pack/plugins/aiops/public/hooks/use_url_state.tsx deleted file mode 100644 index 94273a204f5cc..0000000000000 --- a/x-pack/plugins/aiops/public/hooks/use_url_state.tsx +++ /dev/null @@ -1,223 +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 } from 'react'; -import { parse, stringify } from 'query-string'; -import { createContext, useCallback, useContext, useMemo } from 'react'; -import { decode, encode } from '@kbn/rison'; -import { useHistory, useLocation } from 'react-router-dom'; -import { isEqual } from 'lodash'; - -export interface Dictionary { - [id: string]: TValue; -} - -// TODO duplicate of ml/object_utils -export const getNestedProperty = ( - obj: Record, - accessor: string, - defaultValue?: any -) => { - const value = accessor.split('.').reduce((o, i) => o?.[i], obj); - - if (value === undefined) return defaultValue; - - return value; -}; - -export type Accessor = '_a' | '_g'; -export type SetUrlState = ( - accessor: Accessor, - attribute: string | Dictionary, - value?: any, - replaceState?: boolean -) => void; -export interface UrlState { - searchString: string; - setUrlState: SetUrlState; -} - -/** - * Set of URL query parameters that require the rison serialization. - */ -const risonSerializedParams = new Set(['_a', '_g']); - -/** - * Checks if the URL query parameter requires rison serialization. - * @param queryParam - */ -export function isRisonSerializationRequired(queryParam: string): boolean { - return risonSerializedParams.has(queryParam); -} - -export function parseUrlState(search: string): Dictionary { - const urlState: Dictionary = {}; - const parsedQueryString = parse(search, { sort: false }); - - try { - Object.keys(parsedQueryString).forEach((a) => { - if (isRisonSerializationRequired(a)) { - urlState[a] = decode(parsedQueryString[a] as string); - } else { - urlState[a] = parsedQueryString[a]; - } - }); - } catch (error) { - // eslint-disable-next-line no-console - console.error('Could not read url state', error); - } - - return urlState; -} - -// Compared to the original appState/globalState, -// this no longer makes use of fetch/save methods. -// - Reading from `location.search` is the successor of `fetch`. -// - `history.push()` is the successor of `save`. -// - The exposed state and set call make use of the above and make sure that -// different urlStates(e.g. `_a` / `_g`) don't overwrite each other. -// This uses a context to be able to maintain only one instance -// of the url state. It gets passed down with `UrlStateProvider` -// and can be used via `useUrlState`. -export const aiopsUrlStateStore = createContext({ - searchString: '', - setUrlState: () => {}, -}); - -export const { Provider } = aiopsUrlStateStore; - -export const UrlStateProvider: FC = ({ children }) => { - const { Provider: StateProvider } = aiopsUrlStateStore; - - const history = useHistory(); - const { search: urlSearchString } = useLocation(); - - const setUrlState: SetUrlState = useCallback( - ( - accessor: Accessor, - attribute: string | Dictionary, - value?: any, - replaceState?: boolean - ) => { - const prevSearchString = urlSearchString; - const urlState = parseUrlState(prevSearchString); - const parsedQueryString = parse(prevSearchString, { sort: false }); - - if (!Object.prototype.hasOwnProperty.call(urlState, accessor)) { - urlState[accessor] = {}; - } - - if (typeof attribute === 'string') { - if (isEqual(getNestedProperty(urlState, `${accessor}.${attribute}`), value)) { - return prevSearchString; - } - - urlState[accessor][attribute] = value; - } else { - const attributes = attribute; - Object.keys(attributes).forEach((a) => { - urlState[accessor][a] = attributes[a]; - }); - } - - try { - const oldLocationSearchString = stringify(parsedQueryString, { - sort: false, - encode: false, - }); - - Object.keys(urlState).forEach((a) => { - if (isRisonSerializationRequired(a)) { - parsedQueryString[a] = encode(urlState[a]); - } else { - parsedQueryString[a] = urlState[a]; - } - }); - const newLocationSearchString = stringify(parsedQueryString, { - sort: false, - encode: false, - }); - - if (oldLocationSearchString !== newLocationSearchString) { - const newSearchString = stringify(parsedQueryString, { sort: false }); - if (replaceState) { - history.replace({ search: newSearchString }); - } else { - history.push({ search: newSearchString }); - } - } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Could not save url state', error); - } - }, - [history, urlSearchString] - ); - - return ( - {children} - ); -}; - -export const useUrlState = (accessor: Accessor) => { - const { searchString, setUrlState: setUrlStateContext } = useContext(aiopsUrlStateStore); - - const urlState = useMemo(() => { - const fullUrlState = parseUrlState(searchString); - if (typeof fullUrlState === 'object') { - return fullUrlState[accessor]; - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searchString]); - - const setUrlState = useCallback( - (attribute: string | Dictionary, value?: any, replaceState?: boolean) => { - setUrlStateContext(accessor, attribute, value, replaceState); - }, - [accessor, setUrlStateContext] - ); - return [urlState, setUrlState]; -}; - -export const AppStateKey = 'AIOPS_INDEX_VIEWER'; -export const ChangePointStateKey = 'changePoint' as const; -/** - * Hook for managing the URL state of the page. - */ -export const usePageUrlState = ( - pageKey: typeof AppStateKey | typeof ChangePointStateKey, - defaultState?: PageUrlState -): [PageUrlState, (update: Partial, replaceState?: boolean) => void] => { - const [appState, setAppState] = useUrlState('_a'); - const pageState = appState?.[pageKey]; - - const resultPageState: PageUrlState = useMemo(() => { - return { - ...(defaultState ?? {}), - ...(pageState ?? {}), - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pageState]); - - const onStateUpdate = useCallback( - (update: Partial, replaceState?: boolean) => { - setAppState( - pageKey, - { - ...resultPageState, - ...update, - }, - replaceState - ); - }, - [pageKey, resultPageState, setAppState] - ); - - return useMemo(() => { - return [resultPageState, onStateUpdate]; - }, [resultPageState, onStateUpdate]); -}; diff --git a/x-pack/plugins/aiops/tsconfig.json b/x-pack/plugins/aiops/tsconfig.json index 30fc6a4d3b187..9f2e110d0af25 100644 --- a/x-pack/plugins/aiops/tsconfig.json +++ b/x-pack/plugins/aiops/tsconfig.json @@ -42,6 +42,7 @@ "@kbn/logging", "@kbn/core-elasticsearch-server", "@kbn/es-types", + "@kbn/ml-url-state", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/data_visualizer/public/application/common/components/date_picker_wrapper/date_picker_wrapper.tsx b/x-pack/plugins/data_visualizer/public/application/common/components/date_picker_wrapper/date_picker_wrapper.tsx index e0dba00ec95ee..dee9fa17e0bd9 100644 --- a/x-pack/plugins/data_visualizer/public/application/common/components/date_picker_wrapper/date_picker_wrapper.tsx +++ b/x-pack/plugins/data_visualizer/public/application/common/components/date_picker_wrapper/date_picker_wrapper.tsx @@ -20,18 +20,20 @@ import { OnRefreshProps, OnTimeChangeProps, } from '@elastic/eui'; + import type { TimeRange } from '@kbn/es-query'; import { TimeHistoryContract, UI_SETTINGS } from '@kbn/data-plugin/public'; import { i18n } from '@kbn/i18n'; +import { useUrlState } from '@kbn/ml-url-state'; import { wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import { FormattedMessage } from '@kbn/i18n-react'; + import { useRefreshIntervalUpdates, useTimeRangeUpdates, } from '../../../index_data_visualizer/hooks/use_time_filter'; import { useDataVisualizerKibana } from '../../../kibana_context'; import { dataVisualizerRefresh$ } from '../../../index_data_visualizer/services/timefilter_refresh_service'; -import { useUrlState } from '../../util/url_state'; const DEFAULT_REFRESH_INTERVAL_MS = 5000; const DATE_PICKER_MAX_WIDTH = 540; diff --git a/x-pack/plugins/data_visualizer/public/application/common/util/url_state.tsx b/x-pack/plugins/data_visualizer/public/application/common/util/url_state.tsx deleted file mode 100644 index 24f9be6639900..0000000000000 --- a/x-pack/plugins/data_visualizer/public/application/common/util/url_state.tsx +++ /dev/null @@ -1,147 +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 { parse } from 'query-string'; -import { createContext, useCallback, useContext, useMemo } from 'react'; -import { decode } from '@kbn/rison'; - -export interface Dictionary { - [id: string]: TValue; -} - -// duplicate of ml/object_utils -export const getNestedProperty = ( - obj: Record, - accessor: string, - defaultValue?: any -) => { - const value = accessor.split('.').reduce((o, i) => o?.[i], obj); - - if (value === undefined) return defaultValue; - - return value; -}; - -export type Accessor = '_a' | '_g'; -export type SetUrlState = ( - accessor: Accessor, - attribute: string | Dictionary, - value?: any, - replaceState?: boolean -) => void; -export interface UrlState { - searchString: string; - setUrlState: SetUrlState; -} - -/** - * Set of URL query parameters that require the rison serialization. - */ -const risonSerializedParams = new Set(['_a', '_g']); - -/** - * Checks if the URL query parameter requires rison serialization. - * @param queryParam - */ -export function isRisonSerializationRequired(queryParam: string): boolean { - return risonSerializedParams.has(queryParam); -} - -export function parseUrlState(search: string): Dictionary { - const urlState: Dictionary = {}; - const parsedQueryString = parse(search, { sort: false }); - - try { - Object.keys(parsedQueryString).forEach((a) => { - if (isRisonSerializationRequired(a)) { - urlState[a] = decode(parsedQueryString[a] as string); - } else { - urlState[a] = parsedQueryString[a]; - } - }); - } catch (error) { - // eslint-disable-next-line no-console - console.error('Could not read url state', error); - } - - return urlState; -} - -// Compared to the original appState/globalState, -// this no longer makes use of fetch/save methods. -// - Reading from `location.search` is the successor of `fetch`. -// - `history.push()` is the successor of `save`. -// - The exposed state and set call make use of the above and make sure that -// different urlStates(e.g. `_a` / `_g`) don't overwrite each other. -// This uses a context to be able to maintain only one instance -// of the url state. It gets passed down with `UrlStateProvider` -// and can be used via `useUrlState`. -export const dataVisualizerUrlStateStore = createContext({ - searchString: '', - setUrlState: () => {}, -}); - -export const { Provider } = dataVisualizerUrlStateStore; - -export const useUrlState = (accessor: Accessor) => { - const { searchString, setUrlState: setUrlStateContext } = useContext(dataVisualizerUrlStateStore); - - const urlState = useMemo(() => { - const fullUrlState = parseUrlState(searchString); - if (typeof fullUrlState === 'object') { - return fullUrlState[accessor]; - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [searchString]); - - const setUrlState = useCallback( - (attribute: string | Dictionary, value?: any, replaceState?: boolean) => { - setUrlStateContext(accessor, attribute, value, replaceState); - }, - [accessor, setUrlStateContext] - ); - return [urlState, setUrlState]; -}; - -export type AppStateKey = 'DATA_VISUALIZER_INDEX_VIEWER'; - -/** - * Hook for managing the URL state of the page. - */ -export const usePageUrlState = ( - pageKey: AppStateKey, - defaultState?: PageUrlState -): [PageUrlState, (update: Partial, replaceState?: boolean) => void] => { - const [appState, setAppState] = useUrlState('_a'); - const pageState = appState?.[pageKey]; - - const resultPageState: PageUrlState = useMemo(() => { - return { - ...(defaultState ?? {}), - ...(pageState ?? {}), - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [pageState]); - - const onStateUpdate = useCallback( - (update: Partial, replaceState?: boolean) => { - setAppState( - pageKey, - { - ...resultPageState, - ...update, - }, - replaceState - ); - }, - [pageKey, resultPageState, setAppState] - ); - - return useMemo(() => { - return [resultPageState, onStateUpdate]; - }, [resultPageState, onStateUpdate]); -}; 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 b7eb35e24ba52..fc2efab6610bc 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 @@ -5,17 +5,18 @@ * 2.0. */ +import { css } from '@emotion/react'; +import { flatten } from 'lodash'; import React, { FC, useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiTitle } from '@elastic/eui'; import { DataView } from '@kbn/data-views-plugin/public'; -import { css } from '@emotion/react'; -import { flatten } from 'lodash'; +import { useUrlState } from '@kbn/ml-url-state'; + import { LinkCardProps } from '../../../common/components/link_card/link_card'; import { useDataVisualizerKibana } from '../../../kibana_context'; -import { useUrlState } from '../../../common/util/url_state'; import { LinkCard } from '../../../common/components/link_card'; import { GetAdditionalLinks } from '../../../common/components/results_links'; import { isDefined } from '../../../common/util/is_defined'; 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 238ceb960de36..8e13ce92a5c1e 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 @@ -6,6 +6,8 @@ */ import React, { FC, useEffect, useMemo, useState, useCallback, useRef } from 'react'; +import type { Required } from 'utility-types'; + import { EuiFlexGroup, EuiFlexItem, @@ -18,15 +20,16 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { Required } from 'utility-types'; + import { i18n } from '@kbn/i18n'; import { Filter, FilterStateStore, Query } from '@kbn/es-query'; 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 { useCurrentEuiTheme } from '../../../common/hooks/use_current_eui_theme'; import { DV_RANDOM_SAMPLER_PREFERENCE, useStorage } from '../../hooks/use_storage'; import { FullTimeRangeSelector } from '../full_time_range_selector'; -import { usePageUrlState, useUrlState } from '../../../common/util/url_state'; import { DataVisualizerTable, ItemIdToExpandedRowMap, @@ -36,7 +39,10 @@ import type { TotalFieldsStats } from '../../../common/components/stats_table/co import { OverallStats } from '../../types/overall_stats'; import { IndexBasedDataVisualizerExpandedRow } from '../../../common/components/expanded_row/index_based_expanded_row'; import { DATA_VISUALIZER_INDEX_VIEWER } from '../../constants/index_data_visualizer_viewer'; -import { DataVisualizerIndexBasedAppState } from '../../types/index_data_visualizer_state'; +import { + DataVisualizerIndexBasedAppState, + DataVisualizerIndexBasedPageUrlState, +} from '../../types/index_data_visualizer_state'; import { SEARCH_QUERY_LANGUAGE, SearchQueryLanguage } from '../../types/combined_query'; import { SupportedFieldType, SavedSearchSavedObject } from '../../../../../common/types'; import { useDataVisualizerKibana } from '../../../kibana_context'; @@ -140,10 +146,11 @@ export const IndexDataVisualizerView: FC = (dataVi const { notifications, uiSettings, data } = services; const { toasts } = notifications; - const [dataVisualizerListState, setDataVisualizerListState] = usePageUrlState( - DATA_VISUALIZER_INDEX_VIEWER, - restorableDefaults - ); + const [dataVisualizerListState, setDataVisualizerListState] = + usePageUrlState( + DATA_VISUALIZER_INDEX_VIEWER, + restorableDefaults + ); const [globalState, setGlobalState] = useUrlState('_g'); const [currentSavedSearch, setCurrentSavedSearch] = useState( diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts index b09e9fec4de64..90ef0a616d7ff 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/hooks/use_data_visualizer_grid_data.ts @@ -14,6 +14,7 @@ import { type DataViewField, UI_SETTINGS } from '@kbn/data-plugin/common'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; import seedrandom from 'seedrandom'; import type { SamplingOption } from '@kbn/discover-plugin/public/application/main/components/field_stats_table/field_stats_table'; +import type { Dictionary } from '@kbn/ml-url-state'; import type { RandomSamplerOption } from '../constants/random_sampler'; import type { DataVisualizerIndexBasedAppState } from '../types/index_data_visualizer_state'; import { useDataVisualizerKibana } from '../../kibana_context'; @@ -36,7 +37,6 @@ import { getDefaultPageState } from '../components/index_data_visualizer_view/in import { useFieldStatsSearchStrategy } from './use_field_stats'; import { useOverallStats } from './use_overall_stats'; import type { OverallStatsSearchStrategyParams } from '../../../../common/types/field_stats'; -import type { Dictionary } from '../../common/util/url_state'; import type { AggregatableField, NonAggregatableField } from '../types/overall_stats'; const defaults = getDefaultPageState(); 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 355ed26d3edc9..671d410523fdd 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 @@ -9,26 +9,26 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { parse, stringify } from 'query-string'; import { isEqual, throttle } from 'lodash'; +import { EuiResizeObserver } from '@elastic/eui'; import { encode } from '@kbn/rison'; import { SimpleSavedObject } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { DataView } from '@kbn/data-views-plugin/public'; -import { EuiResizeObserver } from '@elastic/eui'; +import { getNestedProperty } from '@kbn/ml-nested-property'; +import { + Provider as UrlStateContextProvider, + parseUrlState, + isRisonSerializationRequired, + type Accessor, + type Dictionary, + type SetUrlState, +} from '@kbn/ml-url-state'; import { getCoreStart, getPluginsStart } from '../../kibana_services'; import { IndexDataVisualizerViewProps, IndexDataVisualizerView, } from './components/index_data_visualizer_view'; -import { - Accessor, - Provider as UrlStateContextProvider, - Dictionary, - parseUrlState, - SetUrlState, - getNestedProperty, - isRisonSerializationRequired, -} from '../common/util/url_state'; import { useDataVisualizerKibana } from '../kibana_context'; import { GetAdditionalLinks } from '../common/components/results_links'; import { DATA_VISUALIZER_APP_LOCATOR, IndexDataVisualizerLocatorParams } from './locator'; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/locator/locator.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/locator/locator.ts index ef93159b795c7..4b53f815a986c 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/locator/locator.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/locator/locator.ts @@ -11,7 +11,7 @@ import { Filter, TimeRange } from '@kbn/es-query'; import type { RefreshInterval } from '@kbn/data-plugin/common'; import { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/common'; import { GlobalQueryStateFromUrl } from '@kbn/data-plugin/public'; -import { Dictionary, isRisonSerializationRequired } from '../../common/util/url_state'; +import { type Dictionary, isRisonSerializationRequired } from '@kbn/ml-url-state'; import { SearchQueryLanguage } from '../types/combined_query'; export const DATA_VISUALIZER_APP_LOCATOR = 'DATA_VISUALIZER_APP_LOCATOR'; diff --git a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts index 2baba961691fb..5b0bb046f2596 100644 --- a/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts +++ b/x-pack/plugins/data_visualizer/public/application/index_data_visualizer/types/index_data_visualizer_state.ts @@ -9,6 +9,14 @@ import type { Filter } from '@kbn/es-query'; import type { Query } from '@kbn/data-plugin/common/query'; import type { RandomSamplerOption } from '../constants/random_sampler'; import type { SearchQueryLanguage } from './combined_query'; + +import type { DATA_VISUALIZER_INDEX_VIEWER } from '../constants/index_data_visualizer_viewer'; + +export interface DataVisualizerIndexBasedPageUrlState { + pageKey: typeof DATA_VISUALIZER_INDEX_VIEWER; + pageUrlState: Required; +} + export interface ListingPageUrlState { pageSize: number; pageIndex: number; @@ -16,6 +24,7 @@ export interface ListingPageUrlState { sortDirection: string; queryText?: string; } + export interface DataVisualizerIndexBasedAppState extends Omit { searchString?: Query['query']; searchQuery?: Query['query']; diff --git a/x-pack/plugins/data_visualizer/tsconfig.json b/x-pack/plugins/data_visualizer/tsconfig.json index 9aba91e813c84..b6c5477266e4c 100644 --- a/x-pack/plugins/data_visualizer/tsconfig.json +++ b/x-pack/plugins/data_visualizer/tsconfig.json @@ -50,6 +50,8 @@ "@kbn/ml-agg-utils", "@kbn/test-jest-helpers", "@kbn/field-types", + "@kbn/ml-nested-property", + "@kbn/ml-url-state", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js index 78c2c705c29e9..38eccf76022ca 100644 --- a/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js +++ b/x-pack/plugins/ml/public/application/components/anomalies_table/anomalies_table.js @@ -17,6 +17,7 @@ import React, { Component } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiInMemoryTable, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { usePageUrlState } from '@kbn/ml-url-state'; import { getColumns } from './anomalies_table_columns'; @@ -26,7 +27,6 @@ import { mlTableService } from '../../services/table_service'; import { RuleEditorFlyout } from '../rule_editor'; import { ml } from '../../services/ml_api_service'; import { INFLUENCERS_LIMIT, ANOMALIES_TABLE_TABS, MAX_CHARS } from './anomalies_table_constants'; -import { usePageUrlState } from '../../util/url_state'; export class AnomaliesTableInternal extends Component { constructor(props) { diff --git a/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx b/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx index d0111c5aed438..e43f1a0c708e7 100644 --- a/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx +++ b/x-pack/plugins/ml/public/application/components/anomaly_results_view_selector/anomaly_results_view_selector.tsx @@ -10,9 +10,9 @@ import React, { FC, useMemo } from 'react'; import { EuiButtonGroup, EuiButtonGroupProps } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useUrlState } from '@kbn/ml-url-state'; import type { ExplorerJob } from '../../explorer/explorer_utils'; -import { useUrlState } from '../../util/url_state'; import { useMlLocator, useNavigateToPath } from '../../contexts/kibana'; import { ML_PAGES } from '../../../../common/constants/locator'; diff --git a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx index cfa9a30f4602e..288437fd367c7 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx @@ -12,7 +12,7 @@ import { mount } from 'enzyme'; import { EuiSelect } from '@elastic/eui'; -import { UrlStateProvider } from '../../../util/url_state'; +import { UrlStateProvider } from '@kbn/ml-url-state'; import { SelectInterval } from './select_interval'; diff --git a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx index 75a51d439a73c..fa06273e3dd90 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx @@ -8,7 +8,12 @@ import React, { FC } from 'react'; import { EuiIcon, EuiSelect, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { usePageUrlState } from '../../../util/url_state'; +import { usePageUrlState } from '@kbn/ml-url-state'; + +interface TableIntervalPageUrlState { + pageKey: 'mlSelectInterval'; + pageUrlState: TableInterval; +} export interface TableInterval { display: string; @@ -55,7 +60,7 @@ function optionValueToInterval(value: string) { export const TABLE_INTERVAL_DEFAULT = optionValueToInterval('auto'); export const useTableInterval = (): [TableInterval, (v: TableInterval) => void] => { - const [interval, updateCallback] = usePageUrlState( + const [interval, updateCallback] = usePageUrlState( 'mlSelectInterval', TABLE_INTERVAL_DEFAULT ); diff --git a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx index 59afbe1cb9f66..a2777867e3ab2 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx @@ -12,7 +12,7 @@ import { mount } from 'enzyme'; import { EuiSuperSelect } from '@elastic/eui'; -import { UrlStateProvider } from '../../../util/url_state'; +import { UrlStateProvider } from '@kbn/ml-url-state'; import { SelectSeverity } from './select_severity'; diff --git a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx index 444ff0f0ab9ec..a9375fec0a69e 100644 --- a/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx +++ b/x-pack/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx @@ -9,25 +9,26 @@ * React component for rendering a select element with threshold levels. */ import React, { Fragment, FC, useMemo } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n-react'; import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText, EuiSuperSelectProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { usePageUrlState } from '@kbn/ml-url-state'; + import { getSeverityColor } from '../../../../../common/util/anomaly_utils'; -import { usePageUrlState } from '../../../util/url_state'; import { ANOMALY_THRESHOLD } from '../../../../../common'; -const warningLabel = i18n.translate('xpack.ml.controls.selectSeverity.warningLabel', { +const warningLabel: string = i18n.translate('xpack.ml.controls.selectSeverity.warningLabel', { defaultMessage: 'warning', }); -const minorLabel = i18n.translate('xpack.ml.controls.selectSeverity.minorLabel', { +const minorLabel: string = i18n.translate('xpack.ml.controls.selectSeverity.minorLabel', { defaultMessage: 'minor', }); -const majorLabel = i18n.translate('xpack.ml.controls.selectSeverity.majorLabel', { +const majorLabel: string = i18n.translate('xpack.ml.controls.selectSeverity.majorLabel', { defaultMessage: 'major', }); -const criticalLabel = i18n.translate('xpack.ml.controls.selectSeverity.criticalLabel', { +const criticalLabel: string = i18n.translate('xpack.ml.controls.selectSeverity.criticalLabel', { defaultMessage: 'critical', }); @@ -38,6 +39,11 @@ const optionsMap = { [criticalLabel]: ANOMALY_THRESHOLD.CRITICAL, }; +export interface TableSeverityPageUrlState { + pageKey: 'mlSelectSeverity'; + pageUrlState: TableSeverity; +} + export interface TableSeverity { val: number; display: string; @@ -82,7 +88,7 @@ export function optionValueToThreshold(value: number) { const TABLE_SEVERITY_DEFAULT = SEVERITY_OPTIONS[0]; export const useTableSeverity = () => { - return usePageUrlState('mlSelectSeverity', TABLE_SEVERITY_DEFAULT); + return usePageUrlState('mlSelectSeverity', TABLE_SEVERITY_DEFAULT); }; export const getSeverityOptions = () => diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts index 904cd3e83249b..feb5ff73dcfb8 100644 --- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts +++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts @@ -15,9 +15,9 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup } from '@kbn/core/public'; -import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; - import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; +import { getNestedProperty } from '@kbn/ml-nested-property'; import { DEFAULT_RESULTS_FIELD } from '../../../../common/constants/data_frame_analytics'; import { extractErrorMessage } from '../../../../common/util/errors'; @@ -39,7 +39,6 @@ import { TOP_CLASSES, } from '../../data_frame_analytics/common/constants'; import { formatHumanReadableDateTimeSeconds } from '../../../../common/util/date_utils'; -import { getNestedProperty } from '../../util/object_utils'; import { mlFieldFormatService } from '../../services/field_format_service'; import { DataGridItem, IndexPagination, RenderCellValue } from './types'; 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 2ca5320572da2..9c49099c45946 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 @@ -16,10 +16,10 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useUrlState } from '@kbn/ml-url-state'; import './_index.scss'; import { Dictionary } from '../../../../common/types/common'; -import { useUrlState } from '../../util/url_state'; import { IdBadges } from './id_badges'; import { BADGE_LIMIT, diff --git a/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts b/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts index 2af031f117e81..d5a5928845c6a 100644 --- a/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts +++ b/x-pack/plugins/ml/public/application/components/job_selector/use_job_selection.ts @@ -9,11 +9,10 @@ import { difference } from 'lodash'; import { useEffect, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; +import { useUrlState } from '@kbn/ml-url-state'; import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; -import { useUrlState } from '../../util/url_state'; - import { useNotifications } from '../../contexts/kibana'; import { useJobSelectionFlyout } from '../../contexts/ml/use_job_selection_flyout'; diff --git a/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx b/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx index 2d755d3cb1d54..6a928b2a365a8 100644 --- a/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx +++ b/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx @@ -9,9 +9,9 @@ import { i18n } from '@kbn/i18n'; import type { EuiSideNavItemType } from '@elastic/eui'; import React, { ReactNode, useCallback, useMemo } from 'react'; import { AIOPS_ENABLED, CHANGE_POINT_DETECTION_ENABLED } from '@kbn/aiops-plugin/common'; +import { useUrlState } from '@kbn/ml-url-state'; import { NotificationsIndicator } from './notifications_indicator'; import type { MlLocatorParams } from '../../../../common/types/locator'; -import { useUrlState } from '../../util/url_state'; import { useMlLocator, useNavigateToPath } from '../../contexts/kibana'; import { isFullLicense } from '../../license'; import type { MlRoute } from '../../routing'; diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx index d1e125c412cb5..30304d78c234e 100644 --- a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx +++ b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.test.tsx @@ -11,7 +11,8 @@ import React from 'react'; import { EuiSuperDatePicker } from '@elastic/eui'; -import { useUrlState } from '../../../util/url_state'; +import { useUrlState } from '@kbn/ml-url-state'; + import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service'; import { useToastNotificationService } from '../../../services/toast_notification_service'; @@ -34,7 +35,7 @@ jest.mock('@elastic/eui', () => { }; }); -jest.mock('../../../util/url_state', () => { +jest.mock('@kbn/ml-url-state', () => { return { useUrlState: jest.fn(() => { return [{ refreshInterval: { value: 0, pause: true } }, jest.fn()]; diff --git a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx index 75c503a139499..e8e42822ca885 100644 --- a/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx +++ b/x-pack/plugins/ml/public/application/components/navigation_menu/date_picker_wrapper/date_picker_wrapper.tsx @@ -24,8 +24,8 @@ import { TimeHistoryContract } from '@kbn/data-plugin/public'; import { UI_SETTINGS } from '@kbn/data-plugin/common'; import { wrapWithTheme, toMountPoint } from '@kbn/kibana-react-plugin/public'; +import { useUrlState } from '@kbn/ml-url-state'; import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service'; -import { useUrlState } from '../../../util/url_state'; import { useMlKibana } from '../../../contexts/kibana'; import { useRefreshIntervalUpdates, diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts b/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts index cbd35193c8d94..572a179d2a7ea 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/use_create_url.ts @@ -7,10 +7,10 @@ import { useCallback, useEffect, useState } from 'react'; import { LocatorGetUrlParams } from '@kbn/share-plugin/common/url_service'; +import { useUrlState } from '@kbn/ml-url-state'; import { useMlKibana } from './kibana_context'; import { ML_APP_LOCATOR } from '../../../../common/constants/locator'; import { MlLocatorParams } from '../../../../common/types/locator'; -import { useUrlState } from '../../util/url_state'; export const useMlLocator = () => { const { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_exploration_url_state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_exploration_url_state.ts index 1a26cce465d85..443b4c2298928 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_exploration_url_state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/hooks/use_exploration_url_state.ts @@ -6,7 +6,7 @@ */ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; -import { usePageUrlState } from '../../../../util/url_state'; +import { usePageUrlState } from '@kbn/ml-url-state'; import { ML_PAGES } from '../../../../../../common/constants/locator'; import { ExplorationPageUrlState } from '../../../../../../common/types/locator'; import { SEARCH_QUERY_LANGUAGE } from '../../../../../../common/constants/search'; @@ -28,8 +28,13 @@ export function getDefaultExplorationPageUrlState( }; } +interface UsePageUrlState { + pageKey: typeof ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION; + pageUrlState: ExplorationPageUrlState; +} + export function useExplorationUrlState(overrides?: Partial) { - return usePageUrlState( + return usePageUrlState( ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION, getDefaultExplorationPageUrlState(overrides) ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx index 0550226599eb1..0d15450ff0163 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/page.tsx @@ -9,6 +9,7 @@ import React, { FC, useState, useEffect } from 'react'; import { EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; +import { useUrlState } from '@kbn/ml-url-state'; import { OutlierExploration } from './components/outlier_exploration'; import { RegressionExploration } from './components/regression_exploration'; import { ClassificationExploration } from './components/classification_exploration'; @@ -24,7 +25,6 @@ import { AnalyticsIdSelectorControls, } from '../components/analytics_selector'; import { AnalyticsEmptyPrompt } from '../analytics_management/components/empty_prompt'; -import { useUrlState } from '../../../util/url_state'; import { SavedObjectsWarning } from '../../../components/saved_objects_warning'; export const Page: FC<{ diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_map/use_map_action.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_map/use_map_action.tsx index 54161fa8c801a..4b4ac38a304ef 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_map/use_map_action.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/action_map/use_map_action.tsx @@ -7,11 +7,11 @@ import React, { useCallback, useMemo } from 'react'; import { cloneDeep } from 'lodash'; +import { useUrlState } from '@kbn/ml-url-state'; import { useMlLocator, useNavigateToPath } from '../../../../../contexts/kibana'; import { DataFrameAnalyticsListAction, DataFrameAnalyticsListRow } from '../analytics_list/common'; import { ML_PAGES } from '../../../../../../../common/constants/locator'; import { getViewLinkStatus } from '../action_view/get_view_link_status'; -import { useUrlState } from '../../../../../util/url_state'; import { mapActionButtonText, MapButton } from './map_button'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts index b95358eb4c477..3e9e404146fd8 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_refresh_interval.ts @@ -7,8 +7,9 @@ import React, { useEffect } from 'react'; +import { useUrlState } from '@kbn/ml-url-state'; + import { useMlKibana } from '../../../../../contexts/kibana'; -import { useUrlState } from '../../../../../util/url_state'; import { DEFAULT_REFRESH_INTERVAL_MS, diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx index 8172a44e60142..870715401d709 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/source_selection/source_selection.tsx @@ -6,7 +6,6 @@ */ import React, { useState, FC } from 'react'; -import { i18n } from '@kbn/i18n'; import { EuiCallOut, @@ -16,14 +15,12 @@ import { } from '@elastic/eui'; import type { SimpleSavedObject } from '@kbn/core/public'; - +import { i18n } from '@kbn/i18n'; +import { getNestedProperty } from '@kbn/ml-nested-property'; import { SavedObjectFinderUi } from '@kbn/saved-objects-plugin/public'; -import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana'; +import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana'; import { useToastNotificationService } from '../../../../../services/toast_notification_service'; - -import { getNestedProperty } from '../../../../../util/object_utils'; - import { getDataViewAndSavedSearch, isCcsIndexPattern } from '../../../../../util/index_utils'; const fixedPageSize: number = 20; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx index 26401c21af524..c53112c6ddf35 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/page.tsx @@ -9,14 +9,13 @@ import React, { FC, useMemo, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useUrlState } from '../../../util/url_state'; +import { useUrlState, usePageUrlState } from '@kbn/ml-url-state'; import { DataFrameAnalyticsList } from './components/analytics_list'; import { useRefreshInterval } from './components/analytics_list/use_refresh_interval'; import { NodeAvailableWarning } from '../../../components/node_available_warning'; import { SavedObjectsWarning } from '../../../components/saved_objects_warning'; import { UpgradeWarning } from '../../../components/upgrade'; import { JobMap } from '../job_map'; -import { usePageUrlState } from '../../../util/url_state'; import { ListingPageUrlState } from '../../../../../common/types/common'; import { DataFrameAnalyticsListColumn } from './components/analytics_list/common'; import { ML_PAGES } from '../../../../../common/constants/locator'; @@ -25,6 +24,11 @@ import { useMlKibana } from '../../../contexts/kibana'; import { useRefreshAnalyticsList } from '../../common'; import { MlPageHeader } from '../../../components/page_header'; +interface PageUrlState { + pageKey: typeof ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE; + pageUrlState: ListingPageUrlState; +} + export const getDefaultDFAListState = (): ListingPageUrlState => ({ pageIndex: 0, pageSize: 10, @@ -36,7 +40,7 @@ export const Page: FC = () => { const [blockRefresh, setBlockRefresh] = useState(false); const [globalState] = useUrlState('_g'); - const [dfaPageState, setDfaPageState] = usePageUrlState( + const [dfaPageState, setDfaPageState] = usePageUrlState( ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE, getDefaultDFAListState() ); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/page.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/page.tsx index 084897cf61659..99f873d2d1125 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/page.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/page.tsx @@ -9,7 +9,7 @@ import React, { FC, useState, useEffect, useCallback } from 'react'; import { EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useUrlState } from '../../../util/url_state'; +import { useUrlState } from '@kbn/ml-url-state'; import { NodeAvailableWarning } from '../../../components/node_available_warning'; import { SavedObjectsWarning } from '../../../components/saved_objects_warning'; import { UpgradeWarning } from '../../../components/upgrade'; diff --git a/x-pack/plugins/ml/public/application/explorer/anomaly_charts_state_service.ts b/x-pack/plugins/ml/public/application/explorer/anomaly_charts_state_service.ts index 1ffa93344ab03..752fa204efa4b 100644 --- a/x-pack/plugins/ml/public/application/explorer/anomaly_charts_state_service.ts +++ b/x-pack/plugins/ml/public/application/explorer/anomaly_charts_state_service.ts @@ -7,6 +7,7 @@ import { BehaviorSubject, combineLatest, Observable, of, Subscription } from 'rxjs'; import { distinctUntilChanged, map, skipWhile, switchMap } from 'rxjs/operators'; +import type { PageUrlStateService } from '@kbn/ml-url-state'; import { StateService } from '../services/state_service'; import type { AnomalyExplorerCommonStateService } from './anomaly_explorer_common_state'; import type { AnomalyTimelineStateService } from './anomaly_timeline_state_service'; @@ -16,7 +17,6 @@ import { } from './explorer_charts/explorer_charts_container_service'; import { AnomalyExplorerChartsService } from '../services/anomaly_explorer_charts_service'; import { getSelectionInfluencers, getSelectionJobIds } from './explorer_utils'; -import type { PageUrlStateService } from '../util/url_state'; import type { TableSeverity } from '../components/controls/select_severity/select_severity'; import { AnomalyExplorerUrlStateService } from './hooks/use_explorer_url_state'; diff --git a/x-pack/plugins/ml/public/application/explorer/hooks/use_explorer_url_state.ts b/x-pack/plugins/ml/public/application/explorer/hooks/use_explorer_url_state.ts index 5af9684c3a09b..221ac4e624c5b 100644 --- a/x-pack/plugins/ml/public/application/explorer/hooks/use_explorer_url_state.ts +++ b/x-pack/plugins/ml/public/application/explorer/hooks/use_explorer_url_state.ts @@ -5,21 +5,30 @@ * 2.0. */ -import { PageUrlStateService, usePageUrlState } from '../../util/url_state'; +import { PageUrlStateService, usePageUrlState } from '@kbn/ml-url-state'; import { ExplorerAppState } from '../../../../common/types/locator'; import { ML_PAGES } from '../../../../common/constants/locator'; export type AnomalyExplorerUrlStateService = PageUrlStateService; +interface LegacyExplorerPageUrlState { + pageKey: 'mlExplorerSwimlane'; + pageUrlState: ExplorerAppState['mlExplorerSwimlane']; +} + +interface ExplorerPageUrlState { + pageKey: typeof ML_PAGES.ANOMALY_EXPLORER; + pageUrlState: ExplorerAppState; +} + export function useExplorerUrlState() { /** * Originally `mlExplorerSwimlane` resided directly in the app URL state (`_a` URL state key). * With current URL structure it has been moved under the `explorer` key of the app state (_a). */ - const [legacyExplorerState] = - usePageUrlState('mlExplorerSwimlane'); + const [legacyExplorerState] = usePageUrlState('mlExplorerSwimlane'); - return usePageUrlState(ML_PAGES.ANOMALY_EXPLORER, { + return usePageUrlState(ML_PAGES.ANOMALY_EXPLORER, { mlExplorerSwimlane: legacyExplorerState, mlExplorerFilter: {}, }); diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx index f6909b3d98f52..f93f1642b10f0 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/jobs.tsx @@ -7,9 +7,8 @@ import React, { FC } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; -// @ts-ignore +import { usePageUrlState } from '@kbn/ml-url-state'; import { JobsListView } from './components/jobs_list_view'; -import { usePageUrlState } from '../../util/url_state'; import { ML_PAGES } from '../../../../common/constants/locator'; import { ListingPageUrlState } from '../../../../common/types/common'; import { HelpMenu } from '../../components/help_menu'; @@ -18,6 +17,11 @@ import { MlPageHeader } from '../../components/page_header'; import { HeaderMenuPortal } from '../../components/header_menu_portal'; import { JobsActionMenu } from '../components/jobs_action_menu'; +interface PageUrlState { + pageKey: typeof ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE; + pageUrlState: ListingPageUrlState; +} + interface JobsPageProps { isMlEnabledInSpace?: boolean; lastRefresh?: number; @@ -31,7 +35,7 @@ export const getDefaultAnomalyDetectionJobsListState = (): ListingPageUrlState = }); export const JobsPage: FC = ({ isMlEnabledInSpace, lastRefresh }) => { - const [pageState, setPageState] = usePageUrlState( + const [pageState, setPageState] = usePageUrlState( ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, getDefaultAnomalyDetectionJobsListState() ); diff --git a/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx b/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx index ffe66b8e5b9b6..83c5f895c9b96 100644 --- a/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx +++ b/x-pack/plugins/ml/public/application/notifications/components/notifications_list.tsx @@ -23,6 +23,7 @@ import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/bas import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; import useDebounce from 'react-use/lib/useDebounce'; import useMount from 'react-use/lib/useMount'; +import { usePageUrlState } from '@kbn/ml-url-state'; import { EntityFilter } from './entity_filter'; import { useMlNotifications } from '../../contexts/ml/ml_notifications_context'; import { ML_NOTIFICATIONS_MESSAGE_LEVEL } from '../../../../common/constants/notifications'; @@ -33,7 +34,6 @@ import { useFieldFormatter } from '../../contexts/kibana/use_field_formatter'; import { useRefresh } from '../../routing/use_refresh'; import { useTableSettings } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings'; import { ListingPageUrlState } from '../../../../common/types/common'; -import { usePageUrlState } from '../../util/url_state'; import { ML_PAGES } from '../../../../common/constants/locator'; import type { MlNotificationMessageLevel, @@ -47,6 +47,11 @@ const levelBadgeMap: Record = { [ML_NOTIFICATIONS_MESSAGE_LEVEL.INFO]: 'default', }; +interface PageUrlState { + pageKey: typeof ML_PAGES.NOTIFICATIONS; + pageUrlState: ListingPageUrlState; +} + export const getDefaultNotificationsListState = (): ListingPageUrlState => ({ pageIndex: 0, pageSize: 25, @@ -81,7 +86,7 @@ export const NotificationsList: FC = () => { const dateFormatter = useFieldFormatter(FIELD_FORMAT_IDS.DATE); - const [pageState, updatePageState] = usePageUrlState( + const [pageState, updatePageState] = usePageUrlState( ML_PAGES.NOTIFICATIONS, getDefaultNotificationsListState() ); diff --git a/x-pack/plugins/ml/public/application/routing/router.tsx b/x-pack/plugins/ml/public/application/routing/router.tsx index abaac9bd05f9f..7a2d631e7a8ad 100644 --- a/x-pack/plugins/ml/public/application/routing/router.tsx +++ b/x-pack/plugins/ml/public/application/routing/router.tsx @@ -18,9 +18,9 @@ import type { import type { DataViewsContract } from '@kbn/data-views-plugin/public'; import { EuiLoadingContent } from '@elastic/eui'; +import { UrlStateProvider } from '@kbn/ml-url-state'; import { MlNotificationsContextProvider } from '../contexts/ml/ml_notifications_context'; import { MlContext, MlContextValue } from '../contexts/ml'; -import { UrlStateProvider } from '../util/url_state'; import { MlPage } from '../components/ml_page'; diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx index a226ec72cbf0c..eb05087a28770 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx @@ -9,6 +9,7 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; +import { useUrlState } from '@kbn/ml-url-state'; import { NavigateToPath } from '../../../contexts/kibana'; import { MlRoute, PageLoader, PageProps } from '../../router'; @@ -17,7 +18,6 @@ import { basicResolvers } from '../../resolvers'; import { Page } from '../../../data_frame_analytics/pages/analytics_exploration'; import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs'; import { DataFrameAnalysisConfigType } from '../../../../../common/types/data_frame_analytics'; -import { useUrlState } from '../../../util/url_state'; export const analyticsJobExplorationRouteFactory = ( navigateToPath: NavigateToPath, diff --git a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx index 5f4c71a909720..55225e08db991 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/explorer.tsx @@ -13,6 +13,7 @@ import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiThemeProvider as StyledComponentsThemeProvider } from '@kbn/kibana-react-plugin/common'; +import { useUrlState } from '@kbn/ml-url-state'; import { NavigateToPath, useMlKibana, useTimefilter } from '../../contexts/kibana'; import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs'; @@ -30,7 +31,6 @@ import { getDateFormatTz } from '../../explorer/explorer_utils'; import { useJobSelection } from '../../components/job_selector/use_job_selection'; import { useTableInterval } from '../../components/controls/select_interval'; import { useTableSeverity } from '../../components/controls/select_severity'; -import { useUrlState } from '../../util/url_state'; import { getBreadcrumbWithUrlForApp } from '../breadcrumbs'; import { JOB_ID } from '../../../../common/constants/anomalies'; import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context'; diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx index 893aa6afda81e..1995a4b0b301a 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.test.tsx @@ -43,7 +43,16 @@ const MockedTimeseriesexplorerNoJobsFound = TimeseriesexplorerNoJobsFound as jes typeof TimeseriesexplorerNoJobsFound >; -jest.mock('../../util/url_state'); +jest.mock('@kbn/ml-url-state', () => { + return { + usePageUrlState: jest.fn(() => { + return [{}, jest.fn(), {}]; + }), + useUrlState: jest.fn(() => { + return [{ refreshInterval: { value: 0, pause: true } }, jest.fn()]; + }), + }; +}); jest.mock('../../timeseriesexplorer/hooks/use_timeseriesexplorer_url_state'); diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index b25808d7b637b..68bc9eef1f70c 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -12,6 +12,7 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { useUrlState } from '@kbn/ml-url-state'; import { getViewableDetectors } from '../../timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors'; import { NavigateToPath, useNotifications } from '../../contexts/kibana'; import { useMlContext } from '../../contexts/ml'; @@ -31,7 +32,6 @@ import { } from '../../timeseriesexplorer/timeseriesexplorer_utils'; import { TimeSeriesExplorerPage } from '../../timeseriesexplorer/timeseriesexplorer_page'; import { TimeseriesexplorerNoJobsFound } from '../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found'; -import { useUrlState } from '../../util/url_state'; import { useTableInterval } from '../../components/controls/select_interval'; import { useTableSeverity } from '../../components/controls/select_severity'; diff --git a/x-pack/plugins/ml/public/application/timeseriesexplorer/hooks/use_timeseriesexplorer_url_state.ts b/x-pack/plugins/ml/public/application/timeseriesexplorer/hooks/use_timeseriesexplorer_url_state.ts index 614888fc005ad..0ec0851f7be04 100644 --- a/x-pack/plugins/ml/public/application/timeseriesexplorer/hooks/use_timeseriesexplorer_url_state.ts +++ b/x-pack/plugins/ml/public/application/timeseriesexplorer/hooks/use_timeseriesexplorer_url_state.ts @@ -5,10 +5,15 @@ * 2.0. */ -import { usePageUrlState } from '../../util/url_state'; +import { usePageUrlState } from '@kbn/ml-url-state'; import { TimeSeriesExplorerAppState } from '../../../../common/types/locator'; import { ML_PAGES } from '../../../../common/constants/locator'; +interface TimeSeriesExplorerPageUrlState { + pageKey: typeof ML_PAGES.SINGLE_METRIC_VIEWER; + pageUrlState: TimeSeriesExplorerAppState; +} + export function useTimeSeriesExplorerUrlState() { - return usePageUrlState(ML_PAGES.SINGLE_METRIC_VIEWER); + return usePageUrlState(ML_PAGES.SINGLE_METRIC_VIEWER); } diff --git a/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx index f37118f877936..6c665654bc92d 100644 --- a/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/models_management/models_list.tsx @@ -25,6 +25,7 @@ import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/bas import { EuiTableSelectionType } from '@elastic/eui/src/components/basic_table/table_types'; import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { usePageUrlState } from '@kbn/ml-url-state'; import { useModelActions } from './model_actions'; import { ModelsTableToConfigMapping } from '.'; import { ModelsBarStats, StatsBar } from '../../components/stats_bar'; @@ -39,7 +40,6 @@ import { BUILT_IN_MODEL_TAG } from '../../../../common/constants/data_frame_anal import { DeleteModelsModal } from './delete_models_modal'; import { ML_PAGES } from '../../../../common/constants/locator'; import { ListingPageUrlState } from '../../../../common/types/common'; -import { usePageUrlState } from '../../util/url_state'; import { ExpandedRow } from './expanded_row'; import { useTableSettings } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings'; import { useToastNotificationService } from '../../services/toast_notification_service'; @@ -59,6 +59,11 @@ export type ModelItem = TrainedModelConfigResponse & { export type ModelItemFull = Required; +interface PageUrlState { + pageKey: typeof ML_PAGES.TRAINED_MODELS_MANAGE; + pageUrlState: ListingPageUrlState; +} + export const getDefaultModelsListState = (): ListingPageUrlState => ({ pageIndex: 0, pageSize: 10, @@ -88,7 +93,7 @@ export const ModelsList: FC = ({ // allow for an internally controlled page state which stores the state in the URL // or an external page state, which is passed in as a prop. // external page state is used on the management page. - const [pageStateInternal, updatePageStateInternal] = usePageUrlState( + const [pageStateInternal, updatePageStateInternal] = usePageUrlState( ML_PAGES.TRAINED_MODELS_MANAGE, getDefaultModelsListState() ); diff --git a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx index 78dd6c8a017b2..9ec06645d8e29 100644 --- a/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx +++ b/x-pack/plugins/ml/public/application/trained_models/nodes_overview/nodes_list.tsx @@ -17,9 +17,9 @@ import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/bas import { i18n } from '@kbn/i18n'; import { cloneDeep } from 'lodash'; import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; +import { usePageUrlState } from '@kbn/ml-url-state'; import { ModelsBarStats, StatsBar } from '../../components/stats_bar'; import { NodeDeploymentStatsResponse } from '../../../../common/types/trained_models'; -import { usePageUrlState } from '../../util/url_state'; import { ML_PAGES } from '../../../../common/constants/locator'; import { useTrainedModelsApiService } from '../../services/ml_api_service/trained_models'; import { useTableSettings } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings'; @@ -32,6 +32,11 @@ import { useRefresh } from '../../routing/use_refresh'; export type NodeItem = NodeDeploymentStatsResponse; +interface PageUrlState { + pageKey: typeof ML_PAGES.TRAINED_MODELS_NODES; + pageUrlState: ListingPageUrlState; +} + export const getDefaultNodesListState = (): ListingPageUrlState => ({ pageIndex: 0, pageSize: 10, @@ -55,7 +60,7 @@ export const NodesList: FC = ({ compactView = false }) => { const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>( {} ); - const [pageState, updatePageState] = usePageUrlState( + const [pageState, updatePageState] = usePageUrlState( ML_PAGES.TRAINED_MODELS_NODES, getDefaultNodesListState() ); diff --git a/x-pack/plugins/ml/public/application/util/__mocks__/url_state.tsx b/x-pack/plugins/ml/public/application/util/__mocks__/url_state.tsx index 5d6eb5c7e55ed..011765b972fe2 100644 --- a/x-pack/plugins/ml/public/application/util/__mocks__/url_state.tsx +++ b/x-pack/plugins/ml/public/application/util/__mocks__/url_state.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { AppStateKey } from '../url_state'; import { TABLE_INTERVAL_DEFAULT } from '../../components/controls/select_interval/select_interval'; export const useUrlState = jest.fn((accessor: '_a' | '_g') => { @@ -14,7 +13,7 @@ export const useUrlState = jest.fn((accessor: '_a' | '_g') => { } }); -export const usePageUrlState = jest.fn((pageKey: AppStateKey) => { +export const usePageUrlState = jest.fn((pageKey: string) => { let state: unknown; switch (pageKey) { case 'timeseriesexplorer': diff --git a/x-pack/plugins/ml/public/application/util/object_utils.test.ts b/x-pack/plugins/ml/public/application/util/object_utils.test.ts deleted file mode 100644 index c99adf6b6d189..0000000000000 --- a/x-pack/plugins/ml/public/application/util/object_utils.test.ts +++ /dev/null @@ -1,71 +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 { getNestedProperty } from './object_utils'; - -describe('object_utils', () => { - test('getNestedProperty()', () => { - const testObj = { - the: { - nested: { - value: 'the-nested-value', - }, - }, - }; - - const falseyObj = { - the: { - nested: { - value: false, - }, - other_nested: { - value: 0, - }, - }, - }; - - const test1 = getNestedProperty(testObj, 'the'); - expect(typeof test1).toBe('object'); - expect(Object.keys(test1)).toStrictEqual(['nested']); - - const test2 = getNestedProperty(testObj, 'the$'); - expect(typeof test2).toBe('undefined'); - - const test3 = getNestedProperty(testObj, 'the$', 'the-default-value'); - expect(typeof test3).toBe('string'); - expect(test3).toBe('the-default-value'); - - const test4 = getNestedProperty(testObj, 'the.neSted'); - expect(typeof test4).toBe('undefined'); - - const test5 = getNestedProperty(testObj, 'the.nested'); - expect(typeof test5).toBe('object'); - expect(Object.keys(test5)).toStrictEqual(['value']); - - const test6 = getNestedProperty(testObj, 'the.nested.vaLue'); - expect(typeof test6).toBe('undefined'); - - const test7 = getNestedProperty(testObj, 'the.nested.value'); - expect(typeof test7).toBe('string'); - expect(test7).toBe('the-nested-value'); - - const test8 = getNestedProperty(testObj, 'the.nested.value.doesntExist'); - expect(typeof test8).toBe('undefined'); - - const test9 = getNestedProperty(testObj, 'the.nested.value.doesntExist', 'the-default-value'); - expect(typeof test9).toBe('string'); - expect(test9).toBe('the-default-value'); - - const test10 = getNestedProperty(falseyObj, 'the.nested.value'); - expect(typeof test10).toBe('boolean'); - expect(test10).toBe(false); - - const test11 = getNestedProperty(falseyObj, 'the.other_nested.value'); - expect(typeof test11).toBe('number'); - expect(test11).toBe(0); - }); -}); diff --git a/x-pack/plugins/ml/public/application/util/object_utils.ts b/x-pack/plugins/ml/public/application/util/object_utils.ts deleted file mode 100644 index d166f0f456875..0000000000000 --- a/x-pack/plugins/ml/public/application/util/object_utils.ts +++ /dev/null @@ -1,20 +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. - */ - -// This is similar to lodash's get() except that it's TypeScript aware and is able to infer return types. -// It splits the attribute key string and uses reduce with an idx check to access nested attributes. -export const getNestedProperty = ( - obj: Record, - accessor: string, - defaultValue?: any -) => { - const value = accessor.split('.').reduce((o, i) => o?.[i], obj); - - if (value === undefined) return defaultValue; - - return value; -}; diff --git a/x-pack/plugins/ml/tsconfig.json b/x-pack/plugins/ml/tsconfig.json index 145399120c08c..d5d42f865e2c0 100644 --- a/x-pack/plugins/ml/tsconfig.json +++ b/x-pack/plugins/ml/tsconfig.json @@ -66,6 +66,8 @@ "@kbn/task-manager-plugin", "@kbn/config-schema", "@kbn/repo-info", + "@kbn/ml-url-state", + "@kbn/ml-nested-property", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts index 41cce79a67143..87d80938eb6e2 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_define/common/get_pivot_dropdown_options.ts @@ -8,8 +8,8 @@ import { EuiComboBoxOptionOption } from '@elastic/eui'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; import { DataView } from '@kbn/data-views-plugin/public'; +import { getNestedProperty } from '@kbn/ml-nested-property'; -import { getNestedProperty } from '../../../../../../../common/utils/object_utils'; import { removeKeywordPostfix } from '../../../../../../../common/utils/field_utils'; import { isRuntimeMappings } from '../../../../../../../common/shared_imports'; diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts index 523c36658919d..c4d43cfd8285a 100644 --- a/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts +++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/edit_transform_flyout/use_edit_transform_flyout.ts @@ -5,13 +5,12 @@ * 2.0. */ -import { isEqual } from 'lodash'; -import { merge } from 'lodash'; -import { numberValidator } from '@kbn/ml-agg-utils'; - +import { isEqual, merge } from 'lodash'; import { useReducer } from 'react'; import { i18n } from '@kbn/i18n'; +import { numberValidator } from '@kbn/ml-agg-utils'; +import { getNestedProperty, setNestedProperty } from '@kbn/ml-nested-property'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { PostTransformsUpdateRequestSchema } from '../../../../../../common/api_schemas/update_transforms'; @@ -20,7 +19,6 @@ import { DEFAULT_TRANSFORM_SETTINGS_MAX_PAGE_SEARCH_SIZE, } from '../../../../../../common/constants'; import { TransformConfigUnion } from '../../../../../../common/types/transform'; -import { getNestedProperty, setNestedProperty } from '../../../../../../common/utils/object_utils'; import { isValidFrequency, diff --git a/x-pack/plugins/transform/tsconfig.json b/x-pack/plugins/transform/tsconfig.json index 312e1243e6e57..2b4b5554ef9d0 100644 --- a/x-pack/plugins/transform/tsconfig.json +++ b/x-pack/plugins/transform/tsconfig.json @@ -44,6 +44,7 @@ "@kbn/ml-string-hash", "@kbn/ui-theme", "@kbn/field-types", + "@kbn/ml-nested-property", ], "exclude": [ "target/**/*", diff --git a/yarn.lock b/yarn.lock index 1c8275f54108b..77941952af994 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3741,7 +3741,7 @@ version "0.0.0" uid "" -"@kbn/ml-is-populated-object@link:x-pack/packages/ml/is_populated_object": +"@kbn/ml-nested-property@link:x-pack/packages/ml/nested_property": version "0.0.0" uid "" @@ -3749,6 +3749,14 @@ version "0.0.0" uid "" +"@kbn/ml-url-state@link:x-pack/packages/ml/url_state": + version "0.0.0" + uid "" + +"@kbn/ml-is-populated-object@link:x-pack/packages/ml/is_populated_object": + version "0.0.0" + uid "" + "@kbn/monaco@link:packages/kbn-monaco": version "0.0.0" uid ""