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] Data frame analytics: Adds functionality to map view (#83710) #83926

Merged
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
2 changes: 1 addition & 1 deletion x-pack/plugins/ml/common/constants/data_frame_analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const JOB_MAP_NODE_TYPES = {
ANALYTICS: 'analytics',
TRANSFORM: 'transform',
INDEX: 'index',
INFERENCE_MODEL: 'inferenceModel',
TRAINED_MODEL: 'trainedModel',
} as const;

export type JobMapNodeTypes = typeof JOB_MAP_NODE_TYPES[keyof typeof JOB_MAP_NODE_TYPES];
3 changes: 3 additions & 0 deletions x-pack/plugins/ml/common/types/ml_url_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export type TimeSeriesExplorerUrlState = MLPageState<

export interface DataFrameAnalyticsQueryState {
jobId?: JobId | JobId[];
modelId?: string;
groupIds?: string[];
globalState?: MlCommonGlobalState;
}
Expand All @@ -170,6 +171,7 @@ export interface DataFrameAnalyticsExplorationQueryState {
jobId: JobId;
analysisType: DataFrameAnalysisConfigType;
defaultIsTraining?: boolean;
modelId?: string;
};
}

Expand All @@ -180,6 +182,7 @@ export type DataFrameAnalyticsExplorationUrlState = MLPageState<
analysisType: DataFrameAnalysisConfigType;
globalState?: MlCommonGlobalState;
defaultIsTraining?: boolean;
modelId?: string;
}
>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ interface Tab {
path: string;
}

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

const tabs = useMemo(() => {
Expand All @@ -38,7 +39,7 @@ export const AnalyticsNavigationBar: FC<{ selectedTabId?: string; jobId?: string
path: '/data_frame_analytics/models',
},
];
if (jobId !== undefined) {
if (jobId !== undefined || modelId !== undefined) {
navTabs.push({
id: 'map',
name: i18n.translate('xpack.ml.dataframe.mapTabLabel', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ export const ModelsList: FC = () => {
onClick: async (item) => {
const path = await mlUrlGenerator.createUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_MAP,
pageState: { jobId: item.metadata?.analytics_config.id },
pageState: { modelId: item.model_id },
});

await navigateToPath(path, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export const Page: FC = () => {
const location = useLocation();
const selectedTabId = useMemo(() => location.pathname.split('/').pop(), [location]);
const mapJobId = globalState?.ml?.jobId;
const mapModelId = globalState?.ml?.modelId;

return (
<Fragment>
Expand Down Expand Up @@ -106,8 +107,14 @@ export const Page: FC = () => {
<UpgradeWarning />

<EuiPageContent>
<AnalyticsNavigationBar selectedTabId={selectedTabId} jobId={mapJobId} />
{selectedTabId === 'map' && mapJobId && <JobMap analyticsId={mapJobId} />}
<AnalyticsNavigationBar
selectedTabId={selectedTabId}
jobId={mapJobId}
modelId={mapModelId}
/>
{selectedTabId === 'map' && (mapJobId || mapModelId) && (
<JobMap analyticsId={mapJobId} modelId={mapModelId} />
)}
{selectedTabId === 'data_frame_analytics' && (
<DataFrameAnalyticsList
blockRefresh={blockRefresh}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
.mlJobMapLegend__indexPattern {
height: $euiSizeM;
width: $euiSizeM;
background-color: '#FFFFFF';
background-color: $euiColorGhost;
border: 1px solid $euiColorVis2;
transform: rotate(45deg);
display: 'inline-block';
Expand All @@ -14,25 +14,34 @@
.mlJobMapLegend__transform {
height: $euiSizeM;
width: $euiSizeM;
background-color: '#FFFFFF';
background-color: $euiColorGhost;
border: 1px solid $euiColorVis1;
display: 'inline-block';
}

.mlJobMapLegend__analytics {
height: $euiSizeM;
width: $euiSizeM;
background-color: '#FFFFFF';
background-color: $euiColorGhost;
border: 1px solid $euiColorVis0;
border-radius: 50%;
border-radius: $euiBorderRadius;
display: 'inline-block';
}

.mlJobMapLegend__inferenceModel {
.mlJobMapLegend__trainedModel {
height: $euiSizeM;
width: $euiSizeM;
background-color: '#FFFFFF';
border: 1px solid $euiColorMediumShade;
border-radius: 50%;
background-color: $euiColorGhost;
border: $euiBorderThin;
border-radius: $euiBorderRadius;
display: 'inline-block';
}

.mlJobMapLegend__sourceNode {
height: $euiSizeM;
width: $euiSizeM;
background-color: $euiColorLightShade;
border: $euiBorderThin;
border-radius: $euiBorderRadius;
display: 'inline-block';
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import { EuiDescriptionListProps } from '@elastic/eui/src/components/description
import { CytoscapeContext } from './cytoscape';
import { formatHumanReadableDateTimeSeconds } from '../../../../../../common/util/date_utils';
import { JOB_MAP_NODE_TYPES } from '../../../../../../common/constants/data_frame_analytics';
// import { DeleteButton } from './delete_button';
// import { DeleteButton } from './delete_button'; // TODO: add delete functionality in followup

interface Props {
analyticsId: string;
analyticsId?: string;
modelId?: string;
details: any;
getNodeData: any;
}
Expand Down Expand Up @@ -56,7 +57,7 @@ function getListItems(details: object): EuiDescriptionListProps['listItems'] {
});
}

export const Controls: FC<Props> = ({ analyticsId, details, getNodeData }) => {
export const Controls: FC<Props> = ({ analyticsId, modelId, details, getNodeData }) => {
const [showFlyout, setShowFlyout] = useState<boolean>(false);
const [selectedNode, setSelectedNode] = useState<cytoscape.NodeSingular | undefined>();

Expand Down Expand Up @@ -98,10 +99,12 @@ export const Controls: FC<Props> = ({ analyticsId, details, getNodeData }) => {
}

const nodeDataButton =
analyticsId !== nodeLabel && nodeType === JOB_MAP_NODE_TYPES.ANALYTICS ? (
analyticsId !== nodeLabel &&
modelId !== nodeLabel &&
(nodeType === JOB_MAP_NODE_TYPES.ANALYTICS || nodeType === JOB_MAP_NODE_TYPES.INDEX) ? (
<EuiButtonEmpty
onClick={() => {
getNodeData(nodeLabel);
getNodeData({ id: nodeLabel, type: nodeType });
setShowFlyout(false);
}}
iconType="branch"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export const cytoscapeOptions: cytoscape.CytoscapeOptions = {
{
selector: 'node',
style: {
'background-color': theme.euiColorGhost,
'background-color': (el: cytoscape.NodeSingular) =>
el.data('isRoot') ? theme.euiColorLightShade : theme.euiColorGhost,
'background-height': '60%',
'background-width': '60%',
'border-color': (el: cytoscape.NodeSingular) => borderColorForNode(el),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import React, { FC } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { JOB_MAP_NODE_TYPES } from '../../../../../../common/constants/data_frame_analytics';

export const JobMapLegend: FC = () => (
Expand All @@ -17,7 +18,10 @@ export const JobMapLegend: FC = () => (
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
{JOB_MAP_NODE_TYPES.INDEX}
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.indexLabel"
defaultMessage="index"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
Expand All @@ -41,19 +45,40 @@ export const JobMapLegend: FC = () => (
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
{JOB_MAP_NODE_TYPES.ANALYTICS}
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.analyticsJobLabel"
defaultMessage="analytics job"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__inferenceModel" />
<span className="mlJobMapLegend__trainedModel" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
{'inference model'}
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.trainedModelLabel"
defaultMessage="trained model"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<span className="mlJobMapLegend__sourceNode" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
<FormattedMessage
id="xpack.ml.dataframe.analyticsMap.legend.rootNodeLabel"
defaultMessage="source node"
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Cytoscape, Controls, JobMapLegend } from './components';
import { ml } from '../../../services/ml_api_service';
import { useMlKibana } from '../../../contexts/kibana';
import { useRefDimensions } from './components/use_ref_dimensions';
import { JOB_MAP_NODE_TYPES } from '../../../../../common/constants/data_frame_analytics';

const cytoscapeDivStyle = {
background: `linear-gradient(
Expand All @@ -36,22 +37,36 @@ ${theme.euiColorLightShade}`,
marginTop: 0,
};

export const JobMapTitle: React.FC<{ analyticsId: string }> = ({ analyticsId }) => (
export const JobMapTitle: React.FC<{ analyticsId?: string; modelId?: string }> = ({
analyticsId,
modelId,
}) => (
<EuiTitle size="xs">
<span>
{i18n.translate('xpack.ml.dataframe.analyticsMap.analyticsIdTitle', {
defaultMessage: 'Map for analytics ID {analyticsId}',
values: { analyticsId },
})}
{analyticsId
? i18n.translate('xpack.ml.dataframe.analyticsMap.analyticsIdTitle', {
defaultMessage: 'Map for analytics ID {analyticsId}',
values: { analyticsId },
})
: i18n.translate('xpack.ml.dataframe.analyticsMap.modelIdTitle', {
defaultMessage: 'Map for trained model ID {modelId}',
values: { modelId },
})}
</span>
</EuiTitle>
);

interface GetDataObjectParameter {
id: string;
type: string;
}

interface Props {
analyticsId: string;
analyticsId?: string;
modelId?: string;
}

export const JobMap: FC<Props> = ({ analyticsId }) => {
export const JobMap: FC<Props> = ({ analyticsId, modelId }) => {
const [elements, setElements] = useState<cytoscape.ElementDefinition[]>([]);
const [nodeDetails, setNodeDetails] = useState({});
const [error, setError] = useState(undefined);
Expand All @@ -60,14 +75,33 @@ export const JobMap: FC<Props> = ({ analyticsId }) => {
services: { notifications },
} = useMlKibana();

const getData = async (id?: string) => {
const getDataWrapper = async (params?: GetDataObjectParameter) => {
const { id, type } = params ?? {};
const treatAsRoot = id !== undefined;
const idToUse = treatAsRoot ? id : analyticsId;
// Pass in treatAsRoot flag - endpoint will take job destIndex to grab jobs created from it
let idToUse: string;

if (id !== undefined) {
idToUse = id;
} else if (modelId !== undefined) {
idToUse = modelId;
} else {
idToUse = analyticsId as string;
}

await getData(
idToUse,
treatAsRoot,
modelId !== undefined && treatAsRoot === false ? JOB_MAP_NODE_TYPES.TRAINED_MODEL : type
);
};

const getData = async (idToUse: string, treatAsRoot: boolean, type?: string) => {
// Pass in treatAsRoot flag - endpoint will take job or index to grab jobs created from it
// TODO: update analyticsMap return type here
const analyticsMap: any = await ml.dataFrameAnalytics.getDataFrameAnalyticsMap(
idToUse,
treatAsRoot
treatAsRoot,
type
);

const { elements: nodeElements, details, error: fetchError } = analyticsMap;
Expand All @@ -86,7 +120,7 @@ export const JobMap: FC<Props> = ({ analyticsId }) => {
}

if (nodeElements && nodeElements.length > 0) {
if (id === undefined) {
if (treatAsRoot === false) {
setElements(nodeElements);
setNodeDetails(details);
} else {
Expand All @@ -98,8 +132,8 @@ export const JobMap: FC<Props> = ({ analyticsId }) => {
};

useEffect(() => {
getData();
}, [analyticsId]);
getDataWrapper();
}, [analyticsId, modelId]);

if (error !== undefined) {
notifications.toasts.addDanger(
Expand All @@ -119,14 +153,19 @@ export const JobMap: FC<Props> = ({ analyticsId }) => {
<div style={{ height: height - parseInt(theme.gutterTypes.gutterLarge, 10) }} ref={ref}>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<JobMapTitle analyticsId={analyticsId} />
<JobMapTitle analyticsId={analyticsId} modelId={modelId} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<JobMapLegend />
</EuiFlexItem>
</EuiFlexGroup>
<Cytoscape height={height} elements={elements} width={width} style={cytoscapeDivStyle}>
<Controls details={nodeDetails} getNodeData={getData} analyticsId={analyticsId} />
<Controls
details={nodeDetails}
getNodeData={getDataWrapper}
analyticsId={analyticsId}
modelId={modelId}
/>
</Cytoscape>
</div>
</>
Expand Down
Loading