Skip to content

Commit

Permalink
[ML] Data frame analytics: Adds map view (#81666) (#83319)
Browse files Browse the repository at this point in the history
* add analytics map endpoint and server model

* add map action to job and models list

* wip:fetch models for jobs. Use url generator

* get models when extending node. deduplicate elements

* add job type icons. disable map action if job not finished.

* move shared const to common dir

* persist map tab. handle indexPattern from visualizer

* use url generator in models list

* temporarily disable delete action in flyout

* update legend style. make map horizontal

* update dfa model to use spaces changes

* format creation time

* update from indexPattern to index.remove refresh button

* handle index patterns with wildcard
  • Loading branch information
alvarezmelissa87 authored Nov 12, 2020
1 parent 2d89716 commit 77510cd
Show file tree
Hide file tree
Showing 37 changed files with 1,657 additions and 27 deletions.
9 changes: 9 additions & 0 deletions x-pack/plugins/ml/common/constants/data_frame_analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ export const ANALYSIS_CONFIG_TYPE = {
CLASSIFICATION: 'classification',
} as const;
export const DEFAULT_RESULTS_FIELD = 'ml';

export const JOB_MAP_NODE_TYPES = {
ANALYTICS: 'analytics',
TRANSFORM: 'transform',
INDEX: 'index',
INFERENCE_MODEL: 'inferenceModel',
} as const;

export type JobMapNodeTypes = typeof JOB_MAP_NODE_TYPES[keyof typeof JOB_MAP_NODE_TYPES];
1 change: 1 addition & 0 deletions x-pack/plugins/ml/common/constants/ml_url_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const ML_PAGES = {
SINGLE_METRIC_VIEWER: 'timeseriesexplorer',
DATA_FRAME_ANALYTICS_JOBS_MANAGE: 'data_frame_analytics',
DATA_FRAME_ANALYTICS_EXPLORATION: 'data_frame_analytics/exploration',
DATA_FRAME_ANALYTICS_MAP: 'data_frame_analytics/map',
/**
* Page: Data Visualizer
*/
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/ml/common/types/ml_url_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export interface DataFrameAnalyticsQueryState {
}

export type DataFrameAnalyticsUrlState = MLPageState<
typeof ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE,
typeof ML_PAGES.DATA_FRAME_ANALYTICS_JOBS_MANAGE | typeof ML_PAGES.DATA_FRAME_ANALYTICS_MAP,
DataFrameAnalyticsQueryState | undefined
>;

Expand Down
15 changes: 14 additions & 1 deletion x-pack/plugins/ml/common/util/analytics_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
OutlierAnalysis,
RegressionAnalysis,
} from '../types/data_frame_analytics';
import { ANALYSIS_CONFIG_TYPE } from '../../common/constants/data_frame_analytics';
import { ANALYSIS_CONFIG_TYPE } from '../constants/data_frame_analytics';
import { DataFrameAnalysisConfigType } from '../types/data_frame_analytics';

export const isOutlierAnalysis = (arg: any): arg is OutlierAnalysis => {
if (typeof arg !== 'object' || arg === null) return false;
Expand Down Expand Up @@ -80,3 +81,15 @@ export const getPredictedFieldName = (
}`;
return predictedField;
};

export const getAnalysisType = (
analysis: AnalysisConfig
): DataFrameAnalysisConfigType | 'unknown' => {
const keys = Object.keys(analysis || {});

if (keys.length === 1) {
return keys[0] as DataFrameAnalysisConfigType;
}

return 'unknown';
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import 'pages/analytics_exploration/components/regression_exploration/index';
@import 'pages/job_map/components/index';
@import 'pages/analytics_management/components/analytics_list/index';
@import 'pages/analytics_management/components/create_analytics_button/index';
@import 'pages/analytics_creation/components/index';
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
getPredictedFieldName,
} from '../../../../common/util/analytics_utils';
import { ANALYSIS_CONFIG_TYPE } from '../../../../common/constants/data_frame_analytics';

export { getAnalysisType } from '../../../../common/util/analytics_utils';
export type IndexPattern = string;

export enum ANALYSIS_ADVANCED_FIELDS {
Expand Down Expand Up @@ -159,18 +161,6 @@ interface LoadEvaluateResult {
error: string | null;
}

export const getAnalysisType = (
analysis: AnalysisConfig
): DataFrameAnalysisConfigType | 'unknown' => {
const keys = Object.keys(analysis);

if (keys.length === 1) {
return keys[0] as DataFrameAnalysisConfigType;
}

return 'unknown';
};

export const getTrainingPercent = (
analysis: AnalysisConfig
):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

export { MapButton } from './map_button';
export { useMapAction } from './use_map_action';
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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 React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiToolTip } from '@elastic/eui';

import {
isRegressionAnalysis,
isOutlierAnalysis,
isClassificationAnalysis,
} from '../../../../common/analytics';

import { DataFrameAnalyticsListRow } from '../analytics_list/common';

export const mapActionButtonText = i18n.translate(
'xpack.ml.dataframe.analyticsList.mapActionName',
{
defaultMessage: 'Map',
}
);
interface MapButtonProps {
item: DataFrameAnalyticsListRow;
}

export const MapButton: FC<MapButtonProps> = ({ item }) => {
const disabled =
!isRegressionAnalysis(item.config.analysis) &&
!isOutlierAnalysis(item.config.analysis) &&
!isClassificationAnalysis(item.config.analysis);

if (disabled) {
const toolTipContent = i18n.translate(
'xpack.ml.dataframe.analyticsList.mapActionDisabledTooltipContent',
{
defaultMessage: 'Unknown analysis type.',
}
);

return (
<EuiToolTip position="top" content={toolTipContent}>
<>{mapActionButtonText}</>
</EuiToolTip>
);
}

return <>{mapActionButtonText}</>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 React, { useCallback, useMemo } from 'react';
import { useMlUrlGenerator, useNavigateToPath } from '../../../../../contexts/kibana';
import { DataFrameAnalyticsListAction, DataFrameAnalyticsListRow } from '../analytics_list/common';
import { ML_PAGES } from '../../../../../../../common/constants/ml_url_generator';
import { getViewLinkStatus } from '../action_view/get_view_link_status';

import { mapActionButtonText, MapButton } from './map_button';

export type MapAction = ReturnType<typeof useMapAction>;
export const useMapAction = () => {
const mlUrlGenerator = useMlUrlGenerator();
const navigateToPath = useNavigateToPath();

const clickHandler = useCallback(async (item: DataFrameAnalyticsListRow) => {
const path = await mlUrlGenerator.createUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_MAP,
pageState: { jobId: item.id },
});

await navigateToPath(path, false);
}, []);

const action: DataFrameAnalyticsListAction = useMemo(
() => ({
isPrimary: true,
name: (item: DataFrameAnalyticsListRow) => <MapButton item={item} />,
enabled: (item: DataFrameAnalyticsListRow) => !getViewLinkStatus(item).disabled,
description: mapActionButtonText,
icon: 'graphApp',
type: 'icon',
onClick: clickHandler,
'data-test-subj': 'mlAnalyticsJobMapButton',
}),
[clickHandler]
);

return { action };
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { isEditActionFlyoutVisible, useEditAction, EditActionFlyout } from '../a
import { useStartAction, StartActionModal } from '../action_start';
import { useStopAction, StopActionModal } from '../action_stop';
import { useViewAction } from '../action_view';
import { useMapAction } from '../action_map';

import { DataFrameAnalyticsListRow } from './common';

Expand All @@ -30,6 +31,7 @@ export const useActions = (
const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics');

const viewAction = useViewAction();
const mapAction = useMapAction();
const cloneAction = useCloneAction(canCreateDataFrameAnalytics);
const deleteAction = useDeleteAction(canDeleteDataFrameAnalytics);
const editAction = useEditAction(canStartStopDataFrameAnalytics);
Expand All @@ -40,6 +42,7 @@ export const useActions = (

const actions: EuiTableActionsColumnType<DataFrameAnalyticsListRow>['actions'] = [
viewAction.action,
mapAction.action,
];

// isManagementTable will be the same for the lifecycle of the component
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ interface Tab {
path: string;
}

export const AnalyticsNavigationBar: FC<{ selectedTabId?: string }> = ({ selectedTabId }) => {
export const AnalyticsNavigationBar: FC<{ selectedTabId?: string; jobId?: string }> = ({
jobId,
selectedTabId,
}) => {
const navigateToPath = useNavigateToPath();

const tabs = useMemo(
() => [
const tabs = useMemo(() => {
const navTabs = [
{
id: 'data_frame_analytics',
name: i18n.translate('xpack.ml.dataframe.jobsTabLabel', {
Expand All @@ -34,12 +37,21 @@ export const AnalyticsNavigationBar: FC<{ selectedTabId?: string }> = ({ selecte
}),
path: '/data_frame_analytics/models',
},
],
[]
);
];
if (jobId !== undefined) {
navTabs.push({
id: 'map',
name: i18n.translate('xpack.ml.dataframe.mapTabLabel', {
defaultMessage: 'Map',
}),
path: '/data_frame_analytics/map',
});
}
return navTabs;
}, [jobId !== undefined]);

const onTabClick = useCallback(async (tab: Tab) => {
await navigateToPath(tab.path);
await navigateToPath(tab.path, true);
}, []);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@ import { StatsBar, ModelsBarStats } from '../../../../../components/stats_bar';
import { useTrainedModelsApiService } from '../../../../../services/ml_api_service/trained_models';
import { ModelsTableToConfigMapping } from './index';
import { DeleteModelsModal } from './delete_models_modal';
import { useMlKibana, useMlUrlGenerator, useNotifications } from '../../../../../contexts/kibana';
import {
useMlKibana,
useMlUrlGenerator,
useNavigateToPath,
useNotifications,
} from '../../../../../contexts/kibana';
import { ExpandedRow } from './expanded_row';

import {
TrainedModelConfigResponse,
ModelPipelines,
Expand Down Expand Up @@ -80,6 +86,9 @@ export const ModelsList: FC = () => {
{}
);

const mlUrlGenerator = useMlUrlGenerator();
const navigateToPath = useNavigateToPath();

const updateFilteredItems = (queryClauses: any) => {
if (queryClauses.length) {
const filtered = filterAnalyticsModels(items, queryClauses);
Expand Down Expand Up @@ -298,6 +307,26 @@ export const ModelsList: FC = () => {
},
isPrimary: true,
},
{
name: i18n.translate('xpack.ml.inference.modelsList.analyticsMapActionLabel', {
defaultMessage: 'Analytics map',
}),
description: i18n.translate('xpack.ml.inference.modelsList.analyticsMapActionLabel', {
defaultMessage: 'Analytics map',
}),
icon: 'graphApp',
type: 'icon',
isPrimary: true,
available: (item) => item.metadata?.analytics_config?.id,
onClick: async (item) => {
const path = await mlUrlGenerator.createUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_MAP,
pageState: { jobId: item.metadata?.analytics_config.id },
});

await navigateToPath(path, false);
},
},
{
name: i18n.translate('xpack.ml.trainedModels.modelsList.deleteModelActionLabel', {
defaultMessage: 'Delete model',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from '@elastic/eui';

import { useLocation } from 'react-router-dom';
import { useUrlState } from '../../../util/url_state';
import { NavigationMenu } from '../../../components/navigation_menu';
import { DatePickerWrapper } from '../../../components/navigation_menu/date_picker_wrapper';
import { DataFrameAnalyticsList } from './components/analytics_list';
Expand All @@ -31,14 +32,17 @@ 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 { JobMap } from '../job_map';

export const Page: FC = () => {
const [blockRefresh, setBlockRefresh] = useState(false);
const [globalState] = useUrlState('_g');

useRefreshInterval(setBlockRefresh);

const location = useLocation();
const selectedTabId = useMemo(() => location.pathname.split('/').pop(), [location]);
const mapJobId = globalState?.ml?.jobId;

return (
<Fragment>
Expand Down Expand Up @@ -73,9 +77,11 @@ export const Page: FC = () => {
</EuiPageHeaderSection>
<EuiPageHeaderSection>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<RefreshAnalyticsListButton />
</EuiFlexItem>
{selectedTabId !== 'map' && (
<EuiFlexItem grow={false}>
<RefreshAnalyticsListButton />
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<DatePickerWrapper />
</EuiFlexItem>
Expand All @@ -87,8 +93,8 @@ export const Page: FC = () => {
<UpgradeWarning />

<EuiPageContent>
<AnalyticsNavigationBar selectedTabId={selectedTabId} />

<AnalyticsNavigationBar selectedTabId={selectedTabId} jobId={mapJobId} />
{selectedTabId === 'map' && mapJobId && <JobMap analyticsId={mapJobId} />}
{selectedTabId === 'data_frame_analytics' && (
<DataFrameAnalyticsList blockRefresh={blockRefresh} />
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import 'legend';
Loading

0 comments on commit 77510cd

Please sign in to comment.