Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [ML] Persisted URL state for the Data frame analytics jobs and models pages (#83439) #83574

Merged
merged 1 commit into from
Nov 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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))"`
);
});
1 change: 1 addition & 0 deletions x-pack/plugins/ml/common/constants/data_frame_analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/ml/common/constants/ml_url_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
/**
Expand Down Expand Up @@ -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];
14 changes: 14 additions & 0 deletions x-pack/plugins/ml/common/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TValue> {
[id: string]: TValue;
}
Expand Down Expand Up @@ -31,3 +33,15 @@ export type DeepReadonly<T> = T extends Array<infer R>
type DeepReadonlyObject<T> = {
readonly [P in keyof T]: DeepReadonly<T[P]>;
};

export interface ListingPageUrlState {
pageSize: number;
pageIndex: number;
sortField: string;
sortDirection: string;
queryText?: string;
}

export type AppPageState<T> = {
[key in MlPages]?: Partial<T>;
};
2 changes: 1 addition & 1 deletion x-pack/plugins/ml/common/util/string_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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'] = [
{
Expand Down Expand Up @@ -84,27 +83,35 @@ interface Props {
isManagementTable?: boolean;
isMlEnabledInSpace?: boolean;
blockRefresh?: boolean;
pageState: ListingPageUrlState;
updatePageState: (update: Partial<ListingPageUrlState>) => void;
}
export const DataFrameAnalyticsList: FC<Props> = ({
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<DataFrameAnalyticsListRow[]>([]);
const [searchQueryText, setSearchQueryText] = useState('');
const [searchError, setSearchError] = useState<string | undefined>();
const [analytics, setAnalytics] = useState<DataFrameAnalyticsListRow[]>([]);
const [analyticsStats, setAnalyticsStats] = useState<AnalyticStatsBarStats | undefined>(
undefined
);
const [expandedRowItemIds, setExpandedRowItemIds] = useState<DataFrameAnalyticsId[]>([]);
const [errorMessage, setErrorMessage] = useState<any>(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') ||
Expand All @@ -119,17 +126,20 @@ export const DataFrameAnalyticsList: FC<Props> = ({
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 !== '' && 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 = [];
Expand All @@ -142,27 +152,9 @@ export const DataFrameAnalyticsList: FC<Props> = ({
}
};

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), []);

Expand All @@ -183,19 +175,19 @@ export const DataFrameAnalyticsList: FC<Props> = ({
);

const { onTableChange, pagination, sorting } = useTableSettings<DataFrameAnalyticsListRow>(
DataFrameAnalyticsListColumn.id,
filteredAnalytics
filteredAnalytics,
pageState,
updatePageState
);

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.
Expand Down Expand Up @@ -251,6 +243,7 @@ export const DataFrameAnalyticsList: FC<Props> = ({
</EuiFlexGroup>
</EuiFlexItem>
);

const search: EuiSearchBarProps = {
query: searchQueryText,
onChange: handleSearchOnChange,
Expand Down Expand Up @@ -284,15 +277,13 @@ export const DataFrameAnalyticsList: FC<Props> = ({
<div data-test-subj="mlAnalyticsTableContainer">
<EuiInMemoryTable<DataFrameAnalyticsListRow>
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}
Expand All @@ -302,6 +293,7 @@ export const DataFrameAnalyticsList: FC<Props> = ({
rowProps={(item) => ({
'data-test-subj': `mlAnalyticsTableRow row-${item.id}`,
})}
error={searchError}
/>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, JSX.Element>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <EuiLink href={href}>{item.id}</EuiLink>;
return <EuiLink href={href}>{jobId}</EuiLink>;
};

export const useColumns = (
Expand Down Expand Up @@ -199,13 +199,17 @@ 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',
scope: 'row',
render: (item: DataFrameAnalyticsListRow) =>
isManagementTable ? <DFAnalyticsJobIdLink item={item} /> : item.id,
render: (jobId: string) => {
return isManagementTable ? <DFAnalyticsJobIdLink jobId={jobId} /> : jobId;
},
},
{
field: DataFrameAnalyticsListColumn.description,
Expand Down
Loading