From f4ea0201103cedff5bf2c5c9e884754c718dd031 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 16 Nov 2020 10:17:53 +0100 Subject: [PATCH 01/12] [ML] support table settings from the URL state --- x-pack/plugins/ml/common/types/common.ts | 8 ++++ .../analytics_list/analytics_list.tsx | 46 ++++++++----------- .../analytics_list/use_table_settings.ts | 46 +++++-------------- .../pages/analytics_management/page.tsx | 18 +++++++- .../ml/public/application/util/url_state.tsx | 33 ++++++++++++- 5 files changed, 86 insertions(+), 65 deletions(-) diff --git a/x-pack/plugins/ml/common/types/common.ts b/x-pack/plugins/ml/common/types/common.ts index f04ff2539e4e9..5ea5b1c376f31 100644 --- a/x-pack/plugins/ml/common/types/common.ts +++ b/x-pack/plugins/ml/common/types/common.ts @@ -31,3 +31,11 @@ export type DeepReadonly = T extends Array type DeepReadonlyObject = { readonly [P in keyof T]: DeepReadonly; }; + +export interface ListingPageUrlState { + pageSize: number; + pageIndex: number; + sortField: string; + sortDirection: string; + queryText?: string; +} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 17ef84179ce63..15edb7f51449c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -30,13 +30,12 @@ import { getTaskStateBadge, getJobTypeBadge, useColumns } from './use_columns'; import { ExpandedRow } from './expanded_row'; import { AnalyticStatsBarStats, StatsBar } from '../../../../../components/stats_bar'; import { CreateAnalyticsButton } from '../create_analytics_button'; -import { getSelectedIdFromUrl } from '../../../../../jobs/jobs_list/components/utils'; import { SourceSelection } from '../source_selection'; import { filterAnalytics } from '../../../../common/search_bar_filters'; import { AnalyticsEmptyPrompt } from './empty_prompt'; import { useTableSettings } from './use_table_settings'; import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button'; -import { getGroupQueryText } from '../../../../../../../common/util/string_utils'; +import { ListingPageUrlState } from '../../../../../../../common/types/common'; const filters: EuiSearchBarProps['filters'] = [ { @@ -84,17 +83,28 @@ interface Props { isManagementTable?: boolean; isMlEnabledInSpace?: boolean; blockRefresh?: boolean; + pageState: ListingPageUrlState; + updatePageState: (update: Partial) => void; } export const DataFrameAnalyticsList: FC = ({ isManagementTable = false, isMlEnabledInSpace = true, blockRefresh = false, + pageState, + updatePageState, }) => { + const searchQueryText = pageState.queryText ?? ''; + const setSearchQueryText = useCallback( + (value) => { + updatePageState({ queryText: value }); + }, + [updatePageState] + ); + const [isInitialized, setIsInitialized] = useState(false); const [isSourceIndexModalVisible, setIsSourceIndexModalVisible] = useState(false); const [isLoading, setIsLoading] = useState(false); const [filteredAnalytics, setFilteredAnalytics] = useState([]); - const [searchQueryText, setSearchQueryText] = useState(''); const [searchError, setSearchError] = useState(); const [analytics, setAnalytics] = useState([]); const [analyticsStats, setAnalyticsStats] = useState( @@ -102,9 +112,6 @@ export const DataFrameAnalyticsList: FC = ({ ); const [expandedRowItemIds, setExpandedRowItemIds] = useState([]); const [errorMessage, setErrorMessage] = useState(undefined); - // Query text/job_id based on url but only after getAnalytics is done first - // selectedJobIdFromUrlInitialized makes sure the query is only run once since analytics is being refreshed constantly - const [selectedIdFromUrlInitialized, setSelectedIdFromUrlInitialized] = useState(false); const disabled = !checkPermission('canCreateDataFrameAnalytics') || @@ -129,7 +136,7 @@ export const DataFrameAnalyticsList: FC = ({ }; const filterList = () => { - if (searchQueryText !== '' && selectedIdFromUrlInitialized === true) { + if (searchQueryText !== '') { // trigger table filtering with query for job id to trigger table filter const query = EuiSearchBar.Query.parse(searchQueryText); let clauses: any = []; @@ -142,27 +149,9 @@ export const DataFrameAnalyticsList: FC = ({ } }; - useEffect(() => { - if (selectedIdFromUrlInitialized === false && analytics.length > 0) { - const { jobId, groupIds } = getSelectedIdFromUrl(window.location.href); - let queryText = ''; - - if (groupIds !== undefined) { - queryText = getGroupQueryText(groupIds); - } else if (jobId !== undefined) { - queryText = jobId; - } - - setSelectedIdFromUrlInitialized(true); - setSearchQueryText(queryText); - } else { - filterList(); - } - }, [selectedIdFromUrlInitialized, analytics]); - useEffect(() => { filterList(); - }, [selectedIdFromUrlInitialized, searchQueryText]); + }, [searchQueryText]); const getAnalyticsCallback = useCallback(() => getAnalytics(true), []); @@ -183,8 +172,9 @@ export const DataFrameAnalyticsList: FC = ({ ); const { onTableChange, pagination, sorting } = useTableSettings( - DataFrameAnalyticsListColumn.id, - filteredAnalytics + filteredAnalytics, + pageState, + updatePageState ); const handleSearchOnChange: EuiSearchBarProps['onChange'] = (search) => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts index 5b7d71dacccf8..bba79fbd24ee3 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useState } from 'react'; import { Direction, EuiBasicTableProps, Pagination, PropertySort } from '@elastic/eui'; +import { ListingPageUrlState } from '../../../../../../../common/types/common'; -const PAGE_SIZE = 10; const PAGE_SIZE_OPTIONS = [10, 25, 50]; // Copying from EUI EuiBasicTable types as type is not correctly picked up for table's onChange @@ -29,15 +28,6 @@ export interface CriteriaWithPagination extends Criteria { }; } -interface AnalyticsBasicTableSettings { - pageIndex: number; - pageSize: number; - totalItemCount: number; - hidePerPageOptions: boolean; - sortField: keyof T; - sortDirection: Direction; -} - interface UseTableSettingsReturnValue { onTableChange: EuiBasicTableProps['onChange']; pagination: Pagination; @@ -45,35 +35,21 @@ interface UseTableSettingsReturnValue { } export function useTableSettings( - sortByField: keyof TypeOfItem, - items: TypeOfItem[] + items: TypeOfItem[], + pageState: ListingPageUrlState, + updatePageState: (update: Partial) => void ): UseTableSettingsReturnValue { - const [tableSettings, setTableSettings] = useState>({ - pageIndex: 0, - pageSize: PAGE_SIZE, - totalItemCount: 0, - hidePerPageOptions: false, - sortField: sortByField, - sortDirection: 'asc', - }); - const onTableChange: EuiBasicTableProps['onChange'] = ({ - page = { index: 0, size: PAGE_SIZE }, - sort = { field: sortByField, direction: 'asc' }, + page, + sort, }: CriteriaWithPagination) => { - const { index, size } = page; - const { field, direction } = sort; - - setTableSettings({ - ...tableSettings, - pageIndex: index, - pageSize: size, - sortField: field, - sortDirection: direction, + updatePageState({ + ...(page ? { pageIndex: page.index, pageSize: page.size } : {}), + ...(sort ? { sortField: sort.field as string, sortDirection: sort.direction } : {}), }); }; - const { pageIndex, pageSize, sortField, sortDirection } = tableSettings; + const { pageIndex, pageSize, sortField, sortDirection } = pageState; const pagination = { pageIndex, @@ -85,7 +61,7 @@ export function useTableSettings( const sorting = { sort: { field: sortField as string, - direction: sortDirection, + direction: sortDirection as Direction, }, }; 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 7ffd477039e78..bbcff67323d2d 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 @@ -31,10 +31,22 @@ import { NodeAvailableWarning } from '../../../components/node_available_warning import { UpgradeWarning } from '../../../components/upgrade'; import { AnalyticsNavigationBar } from './components/analytics_navigation_bar'; import { ModelsList } from './components/models_management'; +import { useListingPageUrlState } from '../../../util/url_state'; +import { ListingPageUrlState } from '../../../../../common/types/common'; +import { DataFrameAnalyticsListColumn } from './components/analytics_list/common'; + +export const getDefaultDFAListState = (): ListingPageUrlState => ({ + pageIndex: 0, + pageSize: 10, + sortField: DataFrameAnalyticsListColumn.id as string, + sortDirection: 'asc', +}); export const Page: FC = () => { const [blockRefresh, setBlockRefresh] = useState(false); + const [dfaPageState, setDfaPageState] = useListingPageUrlState(getDefaultDFAListState()); + useRefreshInterval(setBlockRefresh); const location = useLocation(); @@ -90,7 +102,11 @@ export const Page: FC = () => { {selectedTabId === 'data_frame_analytics' && ( - + )} {selectedTabId === 'models' && } diff --git a/x-pack/plugins/ml/public/application/util/url_state.tsx b/x-pack/plugins/ml/public/application/util/url_state.tsx index a3c70e1130904..959d000213a12 100644 --- a/x-pack/plugins/ml/public/application/util/url_state.tsx +++ b/x-pack/plugins/ml/public/application/util/url_state.tsx @@ -10,7 +10,7 @@ import { isEqual } from 'lodash'; import { decode, encode } from 'rison-node'; import { useHistory, useLocation } from 'react-router-dom'; -import { Dictionary } from '../../../common/types/common'; +import { Dictionary, ListingPageUrlState } from '../../../common/types/common'; import { getNestedProperty } from './object_utils'; @@ -150,3 +150,34 @@ export const useUrlState = (accessor: Accessor) => { ); return [urlState, setUrlState]; }; + +/** + * Hooks for managing URL state on the listing pages, e.g. + * Anomaly Detection jobs, DFA job, Trained models + */ +export const useListingPageUrlState = ( + defaultState: PageUrlState +): [PageUrlState, (update: Partial) => void] => { + const [appState, setAppState] = useUrlState('_a'); + + const listingPageState: PageUrlState = useMemo(() => { + return { + ...defaultState, + ...(appState ?? {}), + }; + }, [appState]); + + const onStateUpdate = useCallback( + (update: Partial) => { + setAppState({ + ...listingPageState, + ...update, + }); + }, + [appState, setAppState] + ); + + return useMemo(() => { + return [listingPageState, onStateUpdate]; + }, [listingPageState, onStateUpdate]); +}; From ee1e32b3c6dcd5a03a73ad5cde1d17d16719556c Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 16 Nov 2020 10:55:39 +0100 Subject: [PATCH 02/12] [ML] fix management page --- .../pages/analytics_management/page.tsx | 4 +- .../jobs_list_page/jobs_list_page.tsx | 38 +++++++++++-------- .../ml/public/application/util/url_state.tsx | 11 +++--- 3 files changed, 30 insertions(+), 23 deletions(-) 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 bbcff67323d2d..c98d14bbfac4a 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 @@ -31,7 +31,7 @@ import { NodeAvailableWarning } from '../../../components/node_available_warning import { UpgradeWarning } from '../../../components/upgrade'; import { AnalyticsNavigationBar } from './components/analytics_navigation_bar'; import { ModelsList } from './components/models_management'; -import { useListingPageUrlState } from '../../../util/url_state'; +import { usePageUrlState } from '../../../util/url_state'; import { ListingPageUrlState } from '../../../../../common/types/common'; import { DataFrameAnalyticsListColumn } from './components/analytics_list/common'; @@ -45,7 +45,7 @@ export const getDefaultDFAListState = (): ListingPageUrlState => ({ export const Page: FC = () => { const [blockRefresh, setBlockRefresh] = useState(false); - const [dfaPageState, setDfaPageState] = useListingPageUrlState(getDefaultDFAListState()); + const [dfaPageState, setDfaPageState] = usePageUrlState(getDefaultDFAListState()); useRefreshInterval(setBlockRefresh); diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index ad4b9ad78902b..4b0a341200f49 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -35,11 +35,10 @@ import { JobsListView } from '../../../../jobs/jobs_list/components/jobs_list_vi import { DataFrameAnalyticsList } from '../../../../data_frame_analytics/pages/analytics_management/components/analytics_list'; import { AccessDeniedPage } from '../access_denied_page'; import { SharePluginStart } from '../../../../../../../../../src/plugins/share/public'; -import { - AnomalyDetectionJobsListState, - getDefaultAnomalyDetectionJobsListState, -} from '../../../../jobs/jobs_list/jobs'; +import { getDefaultAnomalyDetectionJobsListState } from '../../../../jobs/jobs_list/jobs'; import { getMlGlobalServices } from '../../../../app'; +import { ListingPageUrlState } from '../../../../../../common/types/common'; +import { getDefaultDFAListState } from '../../../../data_frame_analytics/pages/analytics_management/page'; interface Tab { 'data-test-subj': string; @@ -48,21 +47,28 @@ interface Tab { content: any; } -function useTabs(isMlEnabledInSpace: boolean): Tab[] { - const [jobsViewState, setJobsViewState] = useState( - getDefaultAnomalyDetectionJobsListState() - ); +function usePageState( + defaultState: T +): [T, (update: Partial) => void] { + const [pageState, setPageState] = useState(defaultState); const updateState = useCallback( - (update: Partial) => { - setJobsViewState({ - ...jobsViewState, + (update: Partial) => { + setPageState({ + ...pageState, ...update, }); }, - [jobsViewState] + [pageState] ); + return [pageState, updateState]; +} + +function useTabs(isMlEnabledInSpace: boolean): Tab[] { + const [adPageState, updateAdPageState] = usePageState(getDefaultAnomalyDetectionJobsListState()); + const [dfaPageState, updateDfaPageState] = usePageState(getDefaultDFAListState()); + return useMemo( () => [ { @@ -75,8 +81,8 @@ function useTabs(isMlEnabledInSpace: boolean): Tab[] { @@ -95,12 +101,14 @@ function useTabs(isMlEnabledInSpace: boolean): Tab[] { ), }, ], - [isMlEnabledInSpace, jobsViewState, updateState] + [isMlEnabledInSpace, adPageState, updateAdPageState, dfaPageState, updateAdPageState] ); } diff --git a/x-pack/plugins/ml/public/application/util/url_state.tsx b/x-pack/plugins/ml/public/application/util/url_state.tsx index 959d000213a12..bdb924335b28a 100644 --- a/x-pack/plugins/ml/public/application/util/url_state.tsx +++ b/x-pack/plugins/ml/public/application/util/url_state.tsx @@ -10,7 +10,7 @@ import { isEqual } from 'lodash'; import { decode, encode } from 'rison-node'; import { useHistory, useLocation } from 'react-router-dom'; -import { Dictionary, ListingPageUrlState } from '../../../common/types/common'; +import { Dictionary } from '../../../common/types/common'; import { getNestedProperty } from './object_utils'; @@ -152,10 +152,9 @@ export const useUrlState = (accessor: Accessor) => { }; /** - * Hooks for managing URL state on the listing pages, e.g. - * Anomaly Detection jobs, DFA job, Trained models + * Hook for managing the URL state of the page. */ -export const useListingPageUrlState = ( +export const usePageUrlState = ( defaultState: PageUrlState ): [PageUrlState, (update: Partial) => void] => { const [appState, setAppState] = useUrlState('_a'); @@ -168,9 +167,9 @@ export const useListingPageUrlState = }, [appState]); const onStateUpdate = useCallback( - (update: Partial) => { + (update: Partial, replace?: boolean) => { setAppState({ - ...listingPageState, + ...(replace ? {} : listingPageState), ...update, }); }, From 3ffe3f40859a8929f03a704b0a6fce7d4185ee7c Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 16 Nov 2020 15:14:10 +0100 Subject: [PATCH 03/12] [ML] models page support --- .../common/constants/data_frame_analytics.ts | 1 + .../analytics_list/analytics_list.tsx | 30 ++++++----- .../components/analytics_list/common.ts | 16 +++--- .../analytics_list/use_table_settings.ts | 54 +++++++++++-------- .../analytics_navigation_bar.tsx | 9 ++-- .../components/models_management/index.ts | 10 ++-- .../models_management/models_list.tsx | 28 ++++++++-- .../pages/analytics_management/page.tsx | 4 +- .../jobs_list_page/jobs_list_page.tsx | 2 +- .../ml/public/application/util/url_state.tsx | 18 ++++--- 10 files changed, 106 insertions(+), 66 deletions(-) diff --git a/x-pack/plugins/ml/common/constants/data_frame_analytics.ts b/x-pack/plugins/ml/common/constants/data_frame_analytics.ts index 5c8000566bb38..958d5ae250185 100644 --- a/x-pack/plugins/ml/common/constants/data_frame_analytics.ts +++ b/x-pack/plugins/ml/common/constants/data_frame_analytics.ts @@ -9,6 +9,7 @@ export const ANALYSIS_CONFIG_TYPE = { REGRESSION: 'regression', CLASSIFICATION: 'classification', } as const; + export const DEFAULT_RESULTS_FIELD = 'ml'; export const JOB_MAP_NODE_TYPES = { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 15edb7f51449c..63b7074ec3aaa 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -7,10 +7,10 @@ import React, { FC, useCallback, useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { + EuiInMemoryTable, EuiCallOut, EuiFlexGroup, EuiFlexItem, - EuiInMemoryTable, EuiSearchBar, EuiSearchBarProps, EuiSpacer, @@ -126,14 +126,17 @@ export const DataFrameAnalyticsList: FC = ({ isManagementTable ); - const updateFilteredItems = (queryClauses: any) => { - if (queryClauses.length) { - const filtered = filterAnalytics(analytics, queryClauses); - setFilteredAnalytics(filtered); - } else { - setFilteredAnalytics(analytics); - } - }; + const updateFilteredItems = useCallback( + (queryClauses: any[]) => { + if (queryClauses.length) { + const filtered = filterAnalytics(analytics, queryClauses); + setFilteredAnalytics(filtered); + } else { + setFilteredAnalytics(analytics); + } + }, + [analytics] + ); const filterList = () => { if (searchQueryText !== '') { @@ -180,12 +183,11 @@ export const DataFrameAnalyticsList: FC = ({ const handleSearchOnChange: EuiSearchBarProps['onChange'] = (search) => { if (search.error !== null) { setSearchError(search.error.message); - return false; + return; } setSearchError(undefined); setSearchQueryText(search.queryText); - return true; }; // Before the analytics have been loaded for the first time, display the loading indicator only. @@ -241,6 +243,7 @@ export const DataFrameAnalyticsList: FC = ({ ); + const search: EuiSearchBarProps = { query: searchQueryText, onChange: handleSearchOnChange, @@ -274,15 +277,13 @@ export const DataFrameAnalyticsList: FC = ({
allowNeutralSort={false} - className="mlAnalyticsInMemoryTable" columns={columns} - error={searchError} hasActions={false} isExpandable={true} + itemIdToExpandedRowMap={itemIdToExpandedRowMap} isSelectable={false} items={analytics} itemId={DataFrameAnalyticsListColumn.id} - itemIdToExpandedRowMap={itemIdToExpandedRowMap} loading={isLoading} onTableChange={onTableChange} pagination={pagination} @@ -292,6 +293,7 @@ export const DataFrameAnalyticsList: FC = ({ rowProps={(item) => ({ 'data-test-subj': `mlAnalyticsTableRow row-${item.id}`, })} + error={searchError} />
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts index 8c7c8b9db8b64..84c37ac8b816b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts @@ -116,14 +116,14 @@ export interface DataFrameAnalyticsListRow { } // Used to pass on attribute names to table columns -export enum DataFrameAnalyticsListColumn { - configDestIndex = 'config.dest.index', - configSourceIndex = 'config.source.index', - configCreateTime = 'config.create_time', - description = 'config.description', - id = 'id', - memoryStatus = 'stats.memory_usage.status', -} +export const DataFrameAnalyticsListColumn = { + configDestIndex: 'config.dest.index', + configSourceIndex: 'config.source.index', + configCreateTime: 'config.create_time', + description: 'config.description', + id: 'id', + memoryStatus: 'stats.memory_usage.status', +} as const; export type ItemIdToExpandedRowMap = Record; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts index bba79fbd24ee3..68774fb86fe96 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings.ts @@ -5,6 +5,7 @@ */ import { Direction, EuiBasicTableProps, Pagination, PropertySort } from '@elastic/eui'; +import { useCallback, useMemo } from 'react'; import { ListingPageUrlState } from '../../../../../../../common/types/common'; const PAGE_SIZE_OPTIONS = [10, 25, 50]; @@ -39,31 +40,40 @@ export function useTableSettings( pageState: ListingPageUrlState, updatePageState: (update: Partial) => void ): UseTableSettingsReturnValue { - const onTableChange: EuiBasicTableProps['onChange'] = ({ - page, - sort, - }: CriteriaWithPagination) => { - updatePageState({ - ...(page ? { pageIndex: page.index, pageSize: page.size } : {}), - ...(sort ? { sortField: sort.field as string, sortDirection: sort.direction } : {}), - }); - }; - const { pageIndex, pageSize, sortField, sortDirection } = pageState; - const pagination = { - pageIndex, - pageSize, - totalItemCount: items.length, - pageSizeOptions: PAGE_SIZE_OPTIONS, - }; - - const sorting = { - sort: { - field: sortField as string, - direction: sortDirection as Direction, + const onTableChange: EuiBasicTableProps['onChange'] = useCallback( + ({ page, sort }: CriteriaWithPagination) => { + const result = { + pageIndex: page?.index ?? pageState.pageIndex, + pageSize: page?.size ?? pageState.pageSize, + sortField: (sort?.field as string) ?? pageState.sortField, + sortDirection: sort?.direction ?? pageState.sortDirection, + }; + updatePageState(result); }, - }; + [pageState, updatePageState] + ); + + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + totalItemCount: items.length, + pageSizeOptions: PAGE_SIZE_OPTIONS, + }), + [items, pageIndex, pageSize] + ); + + const sorting = useMemo( + () => ({ + sort: { + field: sortField as string, + direction: sortDirection as Direction, + }, + }), + [sortField, sortDirection] + ); return { onTableChange, pagination, sorting }; } diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx index eaeae6cc64520..a5d3555fcc278 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_navigation_bar/analytics_navigation_bar.tsx @@ -50,9 +50,12 @@ export const AnalyticsNavigationBar: FC<{ selectedTabId?: string; jobId?: string return navTabs; }, [jobId !== undefined]); - const onTabClick = useCallback(async (tab: Tab) => { - await navigateToPath(tab.path, true); - }, []); + const onTabClick = useCallback( + async (tab: Tab) => { + await navigateToPath(tab.path, true); + }, + [navigateToPath] + ); return ( diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts index 7c70a25071640..77c794dce10ce 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/index.ts @@ -6,8 +6,8 @@ export * from './models_list'; -export enum ModelsTableToConfigMapping { - id = 'model_id', - createdAt = 'create_time', - type = 'type', -} +export const ModelsTableToConfigMapping = { + id: 'model_id', + createdAt: 'create_time', + type: 'type', +} as const; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index a87f11df937d3..1099f59c1f4f2 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -52,6 +52,8 @@ import { filterAnalyticsModels } from '../../../../common/search_bar_filters'; import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator'; import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics'; import { timeFormatter } from '../../../../../../../common/util/date_utils'; +import { ListingPageUrlState } from '../../../../../../../common/types/common'; +import { usePageUrlState } from '../../../../../util/url_state'; type Stats = Omit; @@ -63,6 +65,13 @@ export type ModelItem = TrainedModelConfigResponse & { export type ModelItemFull = Required; +export const getDefaultModelsListState = (): ListingPageUrlState => ({ + pageIndex: 0, + pageSize: 10, + sortField: ModelsTableToConfigMapping.id, + sortDirection: 'asc', +}); + export const ModelsList: FC = () => { const { services: { @@ -71,12 +80,24 @@ export const ModelsList: FC = () => { } = useMlKibana(); const urlGenerator = useMlUrlGenerator(); + const [pageState, updatePageState] = usePageUrlState( + 'trained_models', + getDefaultModelsListState() + ); + + const searchQueryText = pageState.queryText ?? ''; + const setSearchQueryText = useCallback( + (value) => { + updatePageState({ queryText: value }); + }, + [updatePageState] + ); + const canDeleteDataFrameAnalytics = capabilities.ml.canDeleteDataFrameAnalytics as boolean; const trainedModelsApiService = useTrainedModelsApiService(); const { toasts } = useNotifications(); - const [searchQueryText, setSearchQueryText] = useState(''); const [filteredModels, setFilteredModels] = useState([]); const [isLoading, setIsLoading] = useState(false); const [items, setItems] = useState([]); @@ -432,8 +453,9 @@ export const ModelsList: FC = () => { : []; const { onTableChange, pagination, sorting } = useTableSettings( - ModelsTableToConfigMapping.id, - filteredModels + filteredModels, + pageState, + updatePageState ); const toolsLeft = ( 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 c31a2d170f258..5be222d9a8507 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 @@ -40,7 +40,7 @@ import { DataFrameAnalyticsListColumn } from './components/analytics_list/common export const getDefaultDFAListState = (): ListingPageUrlState => ({ pageIndex: 0, pageSize: 10, - sortField: DataFrameAnalyticsListColumn.id as string, + sortField: DataFrameAnalyticsListColumn.id, sortDirection: 'asc', }); @@ -48,7 +48,7 @@ export const Page: FC = () => { const [blockRefresh, setBlockRefresh] = useState(false); const [globalState] = useUrlState('_g'); - const [dfaPageState, setDfaPageState] = usePageUrlState(getDefaultDFAListState()); + const [dfaPageState, setDfaPageState] = usePageUrlState('dfa_jobs', getDefaultDFAListState()); useRefreshInterval(setBlockRefresh); diff --git a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index 4b0a341200f49..1089484449bab 100644 --- a/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -108,7 +108,7 @@ function useTabs(isMlEnabledInSpace: boolean): Tab[] { ), }, ], - [isMlEnabledInSpace, adPageState, updateAdPageState, dfaPageState, updateAdPageState] + [isMlEnabledInSpace, adPageState, updateAdPageState, dfaPageState, updateDfaPageState] ); } diff --git a/x-pack/plugins/ml/public/application/util/url_state.tsx b/x-pack/plugins/ml/public/application/util/url_state.tsx index bdb924335b28a..dc9a39fea6846 100644 --- a/x-pack/plugins/ml/public/application/util/url_state.tsx +++ b/x-pack/plugins/ml/public/application/util/url_state.tsx @@ -155,28 +155,30 @@ export const useUrlState = (accessor: Accessor) => { * Hook for managing the URL state of the page. */ export const usePageUrlState = ( + pageKey: string, defaultState: PageUrlState ): [PageUrlState, (update: Partial) => void] => { const [appState, setAppState] = useUrlState('_a'); + const pageState = appState?.[pageKey]; - const listingPageState: PageUrlState = useMemo(() => { + const resultPageState: PageUrlState = useMemo(() => { return { ...defaultState, - ...(appState ?? {}), + ...(pageState ?? {}), }; - }, [appState]); + }, [pageState]); const onStateUpdate = useCallback( (update: Partial, replace?: boolean) => { - setAppState({ - ...(replace ? {} : listingPageState), + setAppState(pageKey, { + ...(replace ? {} : resultPageState), ...update, }); }, - [appState, setAppState] + [pageKey, resultPageState, setAppState] ); return useMemo(() => { - return [listingPageState, onStateUpdate]; - }, [listingPageState, onStateUpdate]); + return [resultPageState, onStateUpdate]; + }, [resultPageState, onStateUpdate]); }; From a058c0baf8e1baa5f48100f1ac04311fa6285334 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 16 Nov 2020 15:59:17 +0100 Subject: [PATCH 04/12] [ML] update URL generator --- .../data_frame_analytics_urls_generator.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts index 6c58a9d28bcc2..a1689febab612 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts @@ -16,6 +16,8 @@ import { } from '../../common/types/ml_url_generator'; import { ML_PAGES } from '../../common/constants/ml_url_generator'; import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public'; +import { getGroupQueryText, getJobQueryText } from '../../common/util/string_utils'; +import { ListingPageUrlState } from '../../common/types/common'; export function createDataFrameAnalyticsJobManagementUrl( appBasePath: string, @@ -26,13 +28,23 @@ export function createDataFrameAnalyticsJobManagementUrl( if (mlUrlGeneratorState) { const { jobId, groupIds, globalState } = mlUrlGeneratorState; if (jobId || groupIds) { - const queryState: Partial = { - jobId, - groupIds, + const queryTextArr = []; + if (jobId) { + queryTextArr.push(getJobQueryText(jobId)); + } + if (groupIds) { + queryTextArr.push(getGroupQueryText(groupIds)); + } + const jobsListState: Partial = { + ...(queryTextArr.length > 0 ? { queryText: queryTextArr.join(' ') } : {}), + }; + + const queryState: Record> = { + dfa_jobs: jobsListState, }; url = setStateToKbnUrl>( - 'mlManagement', + '_a', queryState, { useHash: false, storeInHashQuery: false }, url From 2c5e3364bd7155ea3d922d2802e87b2833aeb5e5 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 16 Nov 2020 16:03:48 +0100 Subject: [PATCH 05/12] [ML] rename id column --- .../components/analytics_list/use_columns.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx index 2b63b9e780819..66dd28c23dae4 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx @@ -199,7 +199,7 @@ export const useColumns = ( 'data-test-subj': 'mlAnalyticsTableRowDetailsToggle', }, { - name: 'ID', + name: 'id', sortable: (item: DataFrameAnalyticsListRow) => item.id, truncateText: true, 'data-test-subj': 'mlAnalyticsTableColumnId', From 045fa80397149159369d452722508242e0ca2db9 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 16 Nov 2020 16:49:34 +0100 Subject: [PATCH 06/12] [ML] update types and tests --- x-pack/plugins/ml/common/constants/ml_url_generator.ts | 3 +++ x-pack/plugins/ml/common/types/common.ts | 6 ++++++ .../components/models_management/models_list.tsx | 2 +- .../pages/analytics_management/page.tsx | 6 +++++- x-pack/plugins/ml/public/application/util/url_state.tsx | 3 ++- .../data_frame_analytics_urls_generator.ts | 9 ++++----- .../ml/public/ml_url_generator/ml_url_generator.test.ts | 8 ++++++-- 7 files changed, 27 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/ml/common/constants/ml_url_generator.ts b/x-pack/plugins/ml/common/constants/ml_url_generator.ts index a79e72a84c08e..0c931d281d2d5 100644 --- a/x-pack/plugins/ml/common/constants/ml_url_generator.ts +++ b/x-pack/plugins/ml/common/constants/ml_url_generator.ts @@ -11,6 +11,7 @@ export const ML_PAGES = { ANOMALY_EXPLORER: 'explorer', SINGLE_METRIC_VIEWER: 'timeseriesexplorer', DATA_FRAME_ANALYTICS_JOBS_MANAGE: 'data_frame_analytics', + DATA_FRAME_ANALYTICS_MODELS_MANAGE: 'data_frame_analytics/models', DATA_FRAME_ANALYTICS_EXPLORATION: 'data_frame_analytics/exploration', DATA_FRAME_ANALYTICS_MAP: 'data_frame_analytics/map', /** @@ -45,3 +46,5 @@ export const ML_PAGES = { ACCESS_DENIED: 'access-denied', OVERVIEW: 'overview', } as const; + +export type MlPages = typeof ML_PAGES[keyof typeof ML_PAGES]; diff --git a/x-pack/plugins/ml/common/types/common.ts b/x-pack/plugins/ml/common/types/common.ts index 5ea5b1c376f31..4ae542c510a26 100644 --- a/x-pack/plugins/ml/common/types/common.ts +++ b/x-pack/plugins/ml/common/types/common.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { MlPages } from '../constants/ml_url_generator'; + export interface Dictionary { [id: string]: TValue; } @@ -39,3 +41,7 @@ export interface ListingPageUrlState { sortDirection: string; queryText?: string; } + +export type AppPageState = { + [key in MlPages]?: Partial; +}; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx index 1099f59c1f4f2..2d74d08c4550c 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/models_management/models_list.tsx @@ -81,7 +81,7 @@ export const ModelsList: FC = () => { const urlGenerator = useMlUrlGenerator(); const [pageState, updatePageState] = usePageUrlState( - 'trained_models', + ML_PAGES.DATA_FRAME_ANALYTICS_MODELS_MANAGE, getDefaultModelsListState() ); 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 5be222d9a8507..2c97b0549350e 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 @@ -36,6 +36,7 @@ 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/ml_url_generator'; export const getDefaultDFAListState = (): ListingPageUrlState => ({ pageIndex: 0, @@ -48,7 +49,10 @@ export const Page: FC = () => { const [blockRefresh, setBlockRefresh] = useState(false); const [globalState] = useUrlState('_g'); - const [dfaPageState, setDfaPageState] = usePageUrlState('dfa_jobs', getDefaultDFAListState()); + const [dfaPageState, setDfaPageState] = usePageUrlState( + ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE, + getDefaultDFAListState() + ); useRefreshInterval(setBlockRefresh); diff --git a/x-pack/plugins/ml/public/application/util/url_state.tsx b/x-pack/plugins/ml/public/application/util/url_state.tsx index dc9a39fea6846..448a888ab32c2 100644 --- a/x-pack/plugins/ml/public/application/util/url_state.tsx +++ b/x-pack/plugins/ml/public/application/util/url_state.tsx @@ -13,6 +13,7 @@ import { useHistory, useLocation } from 'react-router-dom'; import { Dictionary } from '../../../common/types/common'; import { getNestedProperty } from './object_utils'; +import { MlPages } from '../../../common/constants/ml_url_generator'; type Accessor = '_a' | '_g'; export type SetUrlState = ( @@ -155,7 +156,7 @@ export const useUrlState = (accessor: Accessor) => { * Hook for managing the URL state of the page. */ export const usePageUrlState = ( - pageKey: string, + pageKey: MlPages, defaultState: PageUrlState ): [PageUrlState, (update: Partial) => void] => { const [appState, setAppState] = useUrlState('_a'); diff --git a/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts index a1689febab612..dc9c3bd86cc63 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/data_frame_analytics_urls_generator.ts @@ -10,14 +10,13 @@ import { DataFrameAnalyticsExplorationQueryState, DataFrameAnalyticsExplorationUrlState, - DataFrameAnalyticsQueryState, DataFrameAnalyticsUrlState, MlCommonGlobalState, } from '../../common/types/ml_url_generator'; import { ML_PAGES } from '../../common/constants/ml_url_generator'; import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public'; import { getGroupQueryText, getJobQueryText } from '../../common/util/string_utils'; -import { ListingPageUrlState } from '../../common/types/common'; +import { AppPageState, ListingPageUrlState } from '../../common/types/common'; export function createDataFrameAnalyticsJobManagementUrl( appBasePath: string, @@ -39,11 +38,11 @@ export function createDataFrameAnalyticsJobManagementUrl( ...(queryTextArr.length > 0 ? { queryText: queryTextArr.join(' ') } : {}), }; - const queryState: Record> = { - dfa_jobs: jobsListState, + const queryState: AppPageState = { + [ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE]: jobsListState, }; - url = setStateToKbnUrl>( + url = setStateToKbnUrl>( '_a', queryState, { useHash: false, storeInHashQuery: false }, diff --git a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts index e7f12ead3ffe9..38fd9de9fecd3 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts @@ -180,7 +180,9 @@ describe('MlUrlGenerator', () => { jobId: 'grid_regression_1', }, }); - expect(url).toBe('/app/ml/data_frame_analytics?mlManagement=(jobId:grid_regression_1)'); + expect(url).toBe( + '/app/ml/data_frame_analytics?_a=(data_frame_analytics:(queryText:grid_regression_1))' + ); }); it('should generate valid URL for the Data Frame Analytics job management page with groupIds', async () => { @@ -190,7 +192,9 @@ describe('MlUrlGenerator', () => { groupIds: ['group_1', 'group_2'], }, }); - expect(url).toBe('/app/ml/data_frame_analytics?mlManagement=(groupIds:!(group_1,group_2))'); + expect(url).toBe( + "/app/ml/data_frame_analytics?_a=(data_frame_analytics:(queryText:'groups:(group_1%20or%20group_2)'))" + ); }); }); From 54efa0d781a36cdda54d518f50ce60ef91bcbfca Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 17 Nov 2020 10:40:12 +0100 Subject: [PATCH 07/12] [ML] fix id column name and field --- .../components/analytics_list/use_columns.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx index 66dd28c23dae4..5688de47f5142 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx @@ -199,7 +199,10 @@ export const useColumns = ( 'data-test-subj': 'mlAnalyticsTableRowDetailsToggle', }, { - name: 'id', + field: DataFrameAnalyticsListColumn.id, + name: i18n.translate('xpack.ml.dataframe.analyticsList.id', { + defaultMessage: 'ID', + }), sortable: (item: DataFrameAnalyticsListRow) => item.id, truncateText: true, 'data-test-subj': 'mlAnalyticsTableColumnId', From fe2cda6e45779455a3d0658fd79effbc1ffd1781 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 17 Nov 2020 10:42:31 +0100 Subject: [PATCH 08/12] [ML] remove legacy functions --- .../jobs/jobs_list/components/utils.d.ts | 8 ----- .../jobs/jobs_list/components/utils.js | 29 --------------- .../jobs/jobs_list/components/utils.test.ts | 35 ------------------- 3 files changed, 72 deletions(-) delete mode 100644 x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.d.ts delete mode 100644 x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.test.ts diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.d.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.d.ts deleted file mode 100644 index b781199c85237..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.d.ts +++ /dev/null @@ -1,8 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -export function getSelectedIdFromUrl(str: string): { groupIds?: string[]; jobId?: string }; -export function clearSelectedJobIdFromUrl(str: string): void; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js index 397062248689d..338222e3ac4a2 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.js @@ -6,7 +6,6 @@ import { each } from 'lodash'; import { i18n } from '@kbn/i18n'; -import rison from 'rison-node'; import { mlJobService } from '../../../services/job_service'; import { @@ -367,31 +366,3 @@ function jobProperty(job, prop) { }; return job[propMap[prop]]; } - -function getUrlVars(url) { - const vars = {}; - url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (_, key, value) { - vars[key] = value; - }); - return vars; -} - -export function getSelectedIdFromUrl(url) { - const result = {}; - if (typeof url === 'string') { - const isGroup = url.includes('groupIds'); - url = decodeURIComponent(url); - - if (url.includes('mlManagement')) { - const urlParams = getUrlVars(url); - const decodedJson = rison.decode(urlParams.mlManagement); - - if (isGroup) { - result.groupIds = decodedJson.groupIds; - } else { - result.jobId = decodedJson.jobId; - } - } - } - return result; -} diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.test.ts b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.test.ts deleted file mode 100644 index 4414be0b4fdcb..0000000000000 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/utils.test.ts +++ /dev/null @@ -1,35 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getSelectedIdFromUrl } from './utils'; - -describe('ML - Jobs List utils', () => { - const jobId = 'test_job_id_1'; - const jobIdUrl = `http://localhost:5601/aql/app/ml#/jobs?mlManagement=(jobId:${jobId})`; - const groupIdOne = 'test_group_id_1'; - const groupIdTwo = 'test_group_id_2'; - const groupIdsUrl = `http://localhost:5601/aql/app/ml#/jobs?mlManagement=(groupIds:!(${groupIdOne},${groupIdTwo}))`; - const groupIdUrl = `http://localhost:5601/aql/app/ml#/jobs?mlManagement=(groupIds:!(${groupIdOne}))`; - - describe('getSelectedIdFromUrl', () => { - it('should get selected job id from the url', () => { - const actual = getSelectedIdFromUrl(jobIdUrl); - expect(actual).toStrictEqual({ jobId }); - }); - - it('should get selected group ids from the url', () => { - const expected = { groupIds: [groupIdOne, groupIdTwo] }; - const actual = getSelectedIdFromUrl(groupIdsUrl); - expect(actual).toStrictEqual(expected); - }); - - it('should get selected group id from the url', () => { - const expected = { groupIds: [groupIdOne] }; - const actual = getSelectedIdFromUrl(groupIdUrl); - expect(actual).toStrictEqual(expected); - }); - }); -}); From aa1f66bd1186587a398490a0ac4f1cced372f019 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 17 Nov 2020 11:04:11 +0100 Subject: [PATCH 09/12] [ML] set id key for the job query text --- x-pack/plugins/ml/common/util/string_utils.ts | 2 +- .../ml/public/ml_url_generator/ml_url_generator.test.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/common/util/string_utils.ts b/x-pack/plugins/ml/common/util/string_utils.ts index 4691bac0a065a..ffb8b19dc9aa1 100644 --- a/x-pack/plugins/ml/common/util/string_utils.ts +++ b/x-pack/plugins/ml/common/util/string_utils.ts @@ -45,5 +45,5 @@ export function getGroupQueryText(groupIds: string[]): string { } export function getJobQueryText(jobIds: string | string[]): string { - return Array.isArray(jobIds) ? `id:(${jobIds.join(' OR ')})` : jobIds; + return Array.isArray(jobIds) ? `id:(${jobIds.join(' OR ')})` : `id:${jobIds}`; } diff --git a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts index 38fd9de9fecd3..c80bae6f5c42a 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts @@ -30,7 +30,7 @@ describe('MlUrlGenerator', () => { jobId: 'fq_single_1', }, }); - expect(url).toBe('/app/ml/jobs?_a=(queryText:fq_single_1)'); + expect(url).toBe("/app/ml/jobs?_a=(queryText:'id:fq_single_1')"); }); it('should generate valid URL for the Anomaly Detection job management page for groupIds', async () => { @@ -181,7 +181,7 @@ describe('MlUrlGenerator', () => { }, }); expect(url).toBe( - '/app/ml/data_frame_analytics?_a=(data_frame_analytics:(queryText:grid_regression_1))' + "/app/ml/data_frame_analytics?_a=(data_frame_analytics:(queryText:'id:grid_regression_1'))" ); }); From 7c4a4c816b8f3d7a3c9998ff210d013184f5e174 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 17 Nov 2020 11:09:20 +0100 Subject: [PATCH 10/12] [ML] fix id column rendering --- .../components/analytics_list/use_columns.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx index 5688de47f5142..93868ce0c17e6 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/use_columns.tsx @@ -135,13 +135,13 @@ export const progressColumn = { 'data-test-subj': 'mlAnalyticsTableColumnProgress', }; -export const DFAnalyticsJobIdLink = ({ item }: { item: DataFrameAnalyticsListRow }) => { +export const DFAnalyticsJobIdLink = ({ jobId }: { jobId: string }) => { const href = useMlLink({ page: ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE, - pageState: { jobId: item.id }, + pageState: { jobId }, }); - return {item.id}; + return {jobId}; }; export const useColumns = ( @@ -207,8 +207,9 @@ export const useColumns = ( truncateText: true, 'data-test-subj': 'mlAnalyticsTableColumnId', scope: 'row', - render: (item: DataFrameAnalyticsListRow) => - isManagementTable ? : item.id, + render: (jobId: string) => { + return isManagementTable ? : jobId; + }, }, { field: DataFrameAnalyticsListColumn.description, From 15724fc96a298df4f55ee4e5054f4a61bb5fc774 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 17 Nov 2020 11:48:03 +0100 Subject: [PATCH 11/12] [ML] ad jobs with usePageUrlState --- .../job_filter_bar/job_filter_bar.tsx | 4 +-- .../application/jobs/jobs_list/jobs.tsx | 31 +++++-------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx index f0fa62b7a3d8a..1b1bea889925f 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/job_filter_bar/job_filter_bar.tsx @@ -22,8 +22,6 @@ import { JobGroup } from '../job_group'; import { useMlKibana } from '../../../../contexts/kibana'; interface JobFilterBarProps { - jobId: string; - groupIds: string[]; setFilters: (query: Query | null) => void; queryText?: string; } @@ -75,7 +73,7 @@ export const JobFilterBar: FC = ({ queryText, setFilters }) = useEffect(() => { setFilters(queryInstance); - }, []); + }, [queryText]); const filters: SearchFilterConfig[] = useMemo( () => [ 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 4c6469f6800a7..67265611c1473 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 @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useCallback, useMemo } from 'react'; +import React, { FC } from 'react'; import { NavigationMenu } from '../../components/navigation_menu'; // @ts-ignore import { JobsListView } from './components/jobs_list_view/index'; -import { useUrlState } from '../../util/url_state'; +import { usePageUrlState } from '../../util/url_state'; +import { ML_PAGES } from '../../../../common/constants/ml_url_generator'; interface JobsPageProps { blockRefresh?: boolean; @@ -33,33 +34,15 @@ export const getDefaultAnomalyDetectionJobsListState = (): AnomalyDetectionJobsL }); export const JobsPage: FC = (props) => { - const [appState, setAppState] = useUrlState('_a'); - - const jobListState: AnomalyDetectionJobsListState = useMemo(() => { - return { - ...getDefaultAnomalyDetectionJobsListState(), - ...(appState ?? {}), - }; - }, [appState]); - - const onJobsViewStateUpdate = useCallback( - (update: Partial) => { - setAppState({ - ...jobListState, - ...update, - }); - }, - [appState, setAppState] + const [pageState, setPageState] = usePageUrlState( + ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE, + getDefaultAnomalyDetectionJobsListState() ); return (
- +
); }; From 4c6b00b1e5ef8c9420ee89d5c232eb26c8ebdb12 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 17 Nov 2020 12:10:41 +0100 Subject: [PATCH 12/12] [ML] update unit tests for solutions --- .../shared/Links/MachineLearningLinks/MLLink.test.tsx | 2 +- .../ml/public/application/jobs/jobs_list/jobs.tsx | 11 ++--------- .../anomaly_detection_urls_generator.ts | 10 +++++++--- .../public/ml_url_generator/ml_url_generator.test.ts | 6 ++++-- .../ml_popover/jobs_table/jobs_table.test.tsx | 6 ++++-- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx index 30d4bb34ea345..c453de709a5d2 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.test.tsx @@ -19,6 +19,6 @@ test('MLLink produces the correct URL', async () => { ); expect(href).toMatchInlineSnapshot( - `"/app/ml/jobs?_a=(queryText:'id:(something)%20groups:(apm)')&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-5h,to:now-2h))"` + `"/app/ml/jobs?_a=(jobs:(queryText:'id:(something)%20groups:(apm)'))&_g=(refreshInterval:(pause:!t,value:0),time:(from:now-5h,to:now-2h))"` ); }); 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 67265611c1473..df50f53b811fa 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 @@ -10,6 +10,7 @@ import { NavigationMenu } from '../../components/navigation_menu'; import { JobsListView } from './components/jobs_list_view/index'; import { usePageUrlState } from '../../util/url_state'; import { ML_PAGES } from '../../../../common/constants/ml_url_generator'; +import { ListingPageUrlState } from '../../../../common/types/common'; interface JobsPageProps { blockRefresh?: boolean; @@ -18,15 +19,7 @@ interface JobsPageProps { lastRefresh?: number; } -export interface AnomalyDetectionJobsListState { - pageSize: number; - pageIndex: number; - sortField: string; - sortDirection: string; - queryText?: string; -} - -export const getDefaultAnomalyDetectionJobsListState = (): AnomalyDetectionJobsListState => ({ +export const getDefaultAnomalyDetectionJobsListState = (): ListingPageUrlState => ({ pageIndex: 0, pageSize: 10, sortField: 'id', diff --git a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts index 717d293ccd7fa..6d7e286a29476 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/anomaly_detection_urls_generator.ts @@ -19,8 +19,8 @@ import type { import { ML_PAGES } from '../../common/constants/ml_url_generator'; import { createGenericMlUrl } from './common'; import { setStateToKbnUrl } from '../../../../../src/plugins/kibana_utils/public'; -import type { AnomalyDetectionJobsListState } from '../application/jobs/jobs_list/jobs'; import { getGroupQueryText, getJobQueryText } from '../../common/util/string_utils'; +import { AppPageState, ListingPageUrlState } from '../../common/types/common'; /** * Creates URL to the Anomaly Detection Job management page */ @@ -41,11 +41,15 @@ export function createAnomalyDetectionJobManagementUrl( if (groupIds) { queryTextArr.push(getGroupQueryText(groupIds)); } - const queryState: Partial = { + const jobsListState: Partial = { ...(queryTextArr.length > 0 ? { queryText: queryTextArr.join(' ') } : {}), }; - url = setStateToKbnUrl>( + const queryState: AppPageState = { + [ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE]: jobsListState, + }; + + url = setStateToKbnUrl>( '_a', queryState, { useHash: false, storeInHashQuery: false }, diff --git a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts index c80bae6f5c42a..3f3d88f1a31d9 100644 --- a/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts +++ b/x-pack/plugins/ml/public/ml_url_generator/ml_url_generator.test.ts @@ -30,7 +30,7 @@ describe('MlUrlGenerator', () => { jobId: 'fq_single_1', }, }); - expect(url).toBe("/app/ml/jobs?_a=(queryText:'id:fq_single_1')"); + expect(url).toBe("/app/ml/jobs?_a=(jobs:(queryText:'id:fq_single_1'))"); }); it('should generate valid URL for the Anomaly Detection job management page for groupIds', async () => { @@ -40,7 +40,9 @@ describe('MlUrlGenerator', () => { groupIds: ['farequote', 'categorization'], }, }); - expect(url).toBe("/app/ml/jobs?_a=(queryText:'groups:(farequote%20or%20categorization)')"); + expect(url).toBe( + "/app/ml/jobs?_a=(jobs:(queryText:'groups:(farequote%20or%20categorization)'))" + ); }); it('should generate valid URL for the page for selecting the type of anomaly detection job to create', async () => { diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/jobs_table.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/jobs_table.test.tsx index b0965f8708558..90ab5c2f888fe 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/jobs_table.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/jobs_table.test.tsx @@ -55,7 +55,9 @@ describe('JobsTableComponent', () => { '[data-test-subj="jobs-table-link"]' ); await waitFor(() => - expect(href).toEqual('/app/ml/jobs?_a=(queryText:linux_anomalous_network_activity_ecs)') + expect(href).toEqual( + "/app/ml/jobs?_a=(jobs:(queryText:'id:linux_anomalous_network_activity_ecs'))" + ) ); }); @@ -72,7 +74,7 @@ describe('JobsTableComponent', () => { '[data-test-subj="jobs-table-link"]' ); await waitFor(() => - expect(href).toEqual("/app/ml/jobs?_a=(queryText:'job%20id%20with%20spaces')") + expect(href).toEqual("/app/ml/jobs?_a=(jobs:(queryText:'id:job%20id%20with%20spaces'))") ); });