[Search] Reorganize ML pipeline card for indices pipeline view (#172209)

Reorganizes the ML Pipeline card on the Indices Pipelines view. 

Note: Displaying source fields within this card is out of scope of this
PR.

Before: 
<img width="506" alt="pipeline-card-before"
src="9b41e4c0-cb19-4bc8-b33a-31c85797b6e2">

After: 
<img width="517" alt="image"
src="2a15df6c-3564-4957-a847-cf1859c8bd65">

Example of multiple pipeline cards: 
<img width="520" alt="image"
src="ff6dfc1a-c45c-4bd3-b456-c633b3794175">

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Kathleen DeRusso 2023-11-30 13:15:33 -05:00 committed by GitHub
parent af28af8635
commit 1453b4d7ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 114 additions and 137 deletions

View file

@ -16,7 +16,6 @@ import { EuiButtonIcon, EuiPanel, EuiTextColor, EuiTitle } from '@elastic/eui';
import { InferencePipeline, TrainedModelState } from '../../../../../../common/types/pipelines';
import { InferencePipelineCard } from './inference_pipeline_card';
import { TrainedModelHealth } from './ml_model_health';
import { MLModelTypeBadge } from './ml_model_type_badge';
export const DEFAULT_VALUES: InferencePipeline = {
@ -37,13 +36,12 @@ describe('InferencePipelineCard', () => {
it('renders the item', () => {
const wrapper = shallow(<InferencePipelineCard {...mockValues} />);
expect(wrapper.find(EuiPanel)).toHaveLength(1);
expect(wrapper.find(TrainedModelHealth)).toHaveLength(1);
});
it('renders model type as title', () => {
it('renders pipeline as title', () => {
const wrapper = shallow(<InferencePipelineCard {...mockValues} />);
expect(wrapper.find(EuiTitle)).toHaveLength(1);
const title = wrapper.find(EuiTitle).dive();
expect(title.text()).toBe('Named Entity Recognition');
expect(title.text()).toBe(DEFAULT_VALUES.pipelineName);
});
it('renders pipeline as title with unknown model type', () => {
const values = {
@ -57,11 +55,11 @@ describe('InferencePipelineCard', () => {
const title = wrapper.find(EuiTitle).dive();
expect(title.text()).toBe(DEFAULT_VALUES.pipelineName);
});
it('renders pipeline as subtitle', () => {
it('renders model ID as subtitle', () => {
const wrapper = shallow(<InferencePipelineCard {...mockValues} />);
expect(wrapper.find(EuiTextColor)).toHaveLength(1);
const subtitle = wrapper.find(EuiTextColor).dive();
expect(subtitle.text()).toBe(DEFAULT_VALUES.pipelineName);
expect(subtitle.text()).toBe(DEFAULT_VALUES.modelId);
});
it('renders model type as badge', () => {
const wrapper = shallow(<InferencePipelineCard {...mockValues} />);

View file

@ -17,7 +17,6 @@ import {
EuiFlexItem,
EuiPanel,
EuiPopover,
EuiPopoverTitle,
EuiText,
EuiTextColor,
EuiTitle,
@ -51,151 +50,137 @@ export const InferencePipelineCard: React.FC<InferencePipeline> = (pipeline) =>
setShowConfirmDelete(true);
setIsPopOverOpen(false);
};
const { pipelineName, types: modelTypes } = pipeline;
const { modelId, pipelineName, types: modelTypes } = pipeline;
const modelType = getMLType(modelTypes);
const modelTitle = getModelDisplayTitle(modelType);
const actionButton = (
<EuiButtonEmpty
iconSide="right"
flush="both"
iconType="boxesVertical"
iconType="boxesHorizontal"
onClick={() => setIsPopOverOpen(!isPopOverOpen)}
>
{i18n.translate('xpack.enterpriseSearch.inferencePipelineCard.actionButton', {
defaultMessage: 'Actions',
})}
<TrainedModelHealth
modelState={pipeline.modelState}
modelStateReason={pipeline.modelStateReason}
/>
</EuiButtonEmpty>
);
return (
<EuiPanel color="subdued">
<EuiFlexGroup direction="column" gutterSize="xs">
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiFlexGroup alignItems="center">
<EuiFlexGroup direction="column" gutterSize="xs">
<EuiFlexItem>
<EuiTitle size="xs">
<h4>{modelTitle ?? pipelineName}</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
button={actionButton}
isOpen={isPopOverOpen}
closePopover={() => setIsPopOverOpen(false)}
>
<EuiPopoverTitle paddingSize="m">
{i18n.translate('xpack.enterpriseSearch.inferencePipelineCard.action.title', {
defaultMessage: 'Actions',
})}
</EuiPopoverTitle>
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem>
<div>
<EuiButtonEmpty
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-inferencePipeline-stackManagement`}
size="s"
flush="both"
iconType="eye"
color="text"
href={http.basePath.prepend(
`/app/management/ingest/ingest_pipelines/?pipeline=${pipelineName}`
)}
>
{i18n.translate(
'xpack.enterpriseSearch.inferencePipelineCard.action.view',
{ defaultMessage: 'View in Stack Management' }
)}
</EuiButtonEmpty>
</div>
</EuiFlexItem>
<EuiFlexItem>
<div>
<EuiButtonEmpty
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-inferencePipeline-detachPipeline`}
size="s"
flush="both"
iconType="unlink"
color="text"
onClick={() => {
detachMlPipeline({ indexName, pipelineName });
setIsPopOverOpen(false);
}}
>
{i18n.translate(
'xpack.enterpriseSearch.inferencePipelineCard.action.detach',
{ defaultMessage: 'Detach pipeline' }
)}
</EuiButtonEmpty>
</div>
</EuiFlexItem>
<EuiFlexItem>
<div>
<DeleteInferencePipelineButton
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-inferencePipeline-deletePipeline`}
onClick={showConfirmDeleteModal}
pipeline={pipeline}
/>
</div>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="flexEnd">
{modelTitle && (
<EuiFlexItem>
<EuiTextColor color="subdued">{pipelineName}</EuiTextColor>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<TrainedModelHealth
modelState={pipeline.modelState}
modelStateReason={pipeline.modelStateReason}
/>
</EuiFlexItem>
{pipeline.modelState === TrainedModelState.NotDeployed && (
<EuiFlexItem grow={false} style={{ paddingRight: '1rem' }}>
<EuiToolTip
position="top"
content={i18n.translate(
'xpack.enterpriseSearch.inferencePipelineCard.modelState.notDeployed.fixLink',
{ defaultMessage: 'Fix issue in Trained Models' }
)}
>
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.enterpriseSearch.inferencePipelineCard.modelState.notDeployed.fixLink',
{
defaultMessage: 'Fix issue in Trained Models',
}
)}
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-inferencePipeline-fixIssueInTrainedModels`}
href={http.basePath.prepend(ML_MANAGE_TRAINED_MODELS_PATH)}
display="base"
size="xs"
iconType="wrench"
/>
</EuiToolTip>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs">
<EuiFlexItem>
<span>
<MLModelTypeBadge type={modelType} />
</span>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiTitle size="xs">
<h4>{pipelineName ?? modelTitle}</h4>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem grow={false} />
</EuiFlexGroup>
</EuiFlexItem>
{modelTitle && (
<EuiFlexItem>
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<EuiTextColor color="subdued">{modelId}</EuiTextColor>
</EuiFlexItem>
<EuiFlexItem>
<span>
<MLModelTypeBadge type={modelType} />
</span>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
button={actionButton}
isOpen={isPopOverOpen}
closePopover={() => setIsPopOverOpen(false)}
>
{pipeline.modelState === TrainedModelState.NotDeployed && (
<EuiFlexItem grow={false} style={{ paddingRight: '1rem' }}>
<EuiToolTip
position="top"
content={i18n.translate(
'xpack.enterpriseSearch.inferencePipelineCard.modelState.notDeployed.fixLink',
{ defaultMessage: 'Fix issue in Trained Models' }
)}
>
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.enterpriseSearch.inferencePipelineCard.modelState.notDeployed.fixLink',
{
defaultMessage: 'Fix issue in Trained Models',
}
)}
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-inferencePipeline-fixIssueInTrainedModels`}
href={http.basePath.prepend(ML_MANAGE_TRAINED_MODELS_PATH)}
display="base"
size="xs"
iconType="wrench"
/>
</EuiToolTip>
</EuiFlexItem>
)}
<EuiFlexGroup direction="column" gutterSize="none">
<EuiFlexItem>
<div>
<EuiButtonEmpty
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-inferencePipeline-stackManagement`}
size="s"
flush="both"
iconType="eye"
color="text"
href={http.basePath.prepend(
`/app/management/ingest/ingest_pipelines/?pipeline=${pipelineName}`
)}
>
{i18n.translate('xpack.enterpriseSearch.inferencePipelineCard.action.view', {
defaultMessage: 'View in Stack Management',
})}
</EuiButtonEmpty>
</div>
</EuiFlexItem>
<EuiFlexItem>
<div>
<EuiButtonEmpty
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-inferencePipeline-detachPipeline`}
size="s"
flush="both"
iconType="unlink"
color="text"
onClick={() => {
detachMlPipeline({ indexName, pipelineName });
setIsPopOverOpen(false);
}}
>
{i18n.translate('xpack.enterpriseSearch.inferencePipelineCard.action.detach', {
defaultMessage: 'Detach pipeline',
})}
</EuiButtonEmpty>
</div>
</EuiFlexItem>
<EuiFlexItem>
<div>
<DeleteInferencePipelineButton
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-inferencePipeline-deletePipeline`}
onClick={showConfirmDeleteModal}
pipeline={pipeline}
/>
</div>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopover>
</EuiFlexItem>
</EuiFlexGroup>
{showConfirmDelete && (
<EuiConfirmModal
onCancel={() => setShowConfirmDelete(false)}

View file

@ -15249,9 +15249,7 @@
"xpack.enterpriseSearch.index.header.more.incrementalSync": "Contenu progressif",
"xpack.enterpriseSearch.inferencePipelineCard.action.delete": "Supprimer un pipeline",
"xpack.enterpriseSearch.inferencePipelineCard.action.detach": "Détacher le pipeline",
"xpack.enterpriseSearch.inferencePipelineCard.action.title": "Actions",
"xpack.enterpriseSearch.inferencePipelineCard.action.view": "Afficher dans Gestion de la Suite",
"xpack.enterpriseSearch.inferencePipelineCard.actionButton": "Actions",
"xpack.enterpriseSearch.inferencePipelineCard.deleteConfirm.title": "Supprimer le pipeline",
"xpack.enterpriseSearch.inferencePipelineCard.modelState.deploymentFailed": "Échec du déploiement",
"xpack.enterpriseSearch.inferencePipelineCard.modelState.notDeployed": "Non démarré",

View file

@ -15262,9 +15262,7 @@
"xpack.enterpriseSearch.index.header.more.incrementalSync": "増分コンテンツ",
"xpack.enterpriseSearch.inferencePipelineCard.action.delete": "パイプラインを削除",
"xpack.enterpriseSearch.inferencePipelineCard.action.detach": "パイプラインのデタッチ",
"xpack.enterpriseSearch.inferencePipelineCard.action.title": "アクション",
"xpack.enterpriseSearch.inferencePipelineCard.action.view": "スタック管理で表示",
"xpack.enterpriseSearch.inferencePipelineCard.actionButton": "アクション",
"xpack.enterpriseSearch.inferencePipelineCard.deleteConfirm.title": "パイプラインを削除",
"xpack.enterpriseSearch.inferencePipelineCard.modelState.deploymentFailed": "デプロイが失敗しました",
"xpack.enterpriseSearch.inferencePipelineCard.modelState.notDeployed": "未開始",

View file

@ -15262,9 +15262,7 @@
"xpack.enterpriseSearch.index.header.more.incrementalSync": "增量内容",
"xpack.enterpriseSearch.inferencePipelineCard.action.delete": "删除管道",
"xpack.enterpriseSearch.inferencePipelineCard.action.detach": "分离管道",
"xpack.enterpriseSearch.inferencePipelineCard.action.title": "操作",
"xpack.enterpriseSearch.inferencePipelineCard.action.view": "在 Stack Management 中查看",
"xpack.enterpriseSearch.inferencePipelineCard.actionButton": "操作",
"xpack.enterpriseSearch.inferencePipelineCard.deleteConfirm.title": "删除管道",
"xpack.enterpriseSearch.inferencePipelineCard.modelState.deploymentFailed": "部署失败",
"xpack.enterpriseSearch.inferencePipelineCard.modelState.notDeployed": "未开始",