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

[ML] Persisted URL state for the Data frame analytics jobs and models pages #83439

Merged
merged 13 commits into from
Nov 17, 2020
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(
peteharverson marked this conversation as resolved.
Show resolved Hide resolved
(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