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

Change pipeline selector dropdown to selection list #172330

Merged
merged 22 commits into from
Dec 4, 2023
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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 @@ -29,11 +29,10 @@ import { IndexViewLogic } from '../../index_view_logic';

import { EMPTY_PIPELINE_CONFIGURATION, MLInferenceLogic } from './ml_inference_logic';
import { MlModelSelectOption } from './model_select_option';
import { PipelineSelectOption } from './pipeline_select_option';
import { PipelineSelect } from './pipeline_select';
import { MODEL_REDACTED_VALUE, MODEL_SELECT_PLACEHOLDER, normalizeModelName } from './utils';

const MODEL_SELECT_PLACEHOLDER_VALUE = 'model_placeholder$$';
const PIPELINE_SELECT_PLACEHOLDER_VALUE = 'pipeline_placeholder$$';

const CREATE_NEW_TAB_NAME = i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.tabs.createNew.name',
Expand All @@ -54,11 +53,9 @@ export const ConfigurePipeline: React.FC = () => {
const {
addInferencePipelineModal: { configuration },
formErrors,
existingInferencePipelines,
supportedMLModels,
} = useValues(MLInferenceLogic);
const { selectExistingPipeline, setInferencePipelineConfiguration } =
useActions(MLInferenceLogic);
const { setInferencePipelineConfiguration } = useActions(MLInferenceLogic);
const { ingestionMethod } = useValues(IndexViewLogic);
const { indexName } = useValues(IndexNameLogic);

Expand All @@ -81,22 +78,6 @@ export const ConfigurePipeline: React.FC = () => {
value: model.model_id,
})),
];
const pipelineOptions: Array<EuiSuperSelectOption<string>> = [
{
disabled: true,
inputDisplay: i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.placeholder',
{ defaultMessage: 'Select one' }
),
value: PIPELINE_SELECT_PLACEHOLDER_VALUE,
},
...(existingInferencePipelines?.map((pipeline) => ({
disabled: pipeline.disabled,
dropdownDisplay: <PipelineSelectOption pipeline={pipeline} />,
inputDisplay: pipeline.pipelineName,
value: pipeline.pipelineName,
})) ?? []),
];

const inputsDisabled = configuration.existingPipeline !== false;

Expand Down Expand Up @@ -202,15 +183,8 @@ export const ConfigurePipeline: React.FC = () => {
}
)}
>
<EuiSuperSelect
fullWidth
hasDividers
<PipelineSelect
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-configureInferencePipeline-selectExistingPipeline`}
valueOfSelected={
pipelineName.length > 0 ? pipelineName : PIPELINE_SELECT_PLACEHOLDER_VALUE
}
options={pipelineOptions}
onChange={(value) => selectExistingPipeline(value)}
/>
</EuiFormRow>
</EuiForm>
Expand Down Expand Up @@ -247,6 +221,7 @@ export const ConfigurePipeline: React.FC = () => {
<EuiTabbedContent
tabs={tabs}
autoFocus="selected"
initialSelectedTab={tabs[existingPipeline ? 1 : 0]}
onTabClick={(tab) => {
const isExistingPipeline = tab.id === ConfigurePipelineTabId.USE_EXISTING;
if (isExistingPipeline !== configuration.existingPipeline) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';

import { useActions, useValues } from 'kea';

import { EuiSelectable, useIsWithinMaxBreakpoint } from '@elastic/eui';

import { MLInferenceLogic, MLInferencePipelineOption } from './ml_inference_logic';
import { PipelineSelectOption, PipelineSelectOptionProps } from './pipeline_select_option';

export const PipelineSelect: React.FC = () => {
const {
addInferencePipelineModal: { configuration },
existingInferencePipelines,
} = useValues(MLInferenceLogic);
const { selectExistingPipeline } = useActions(MLInferenceLogic);

const { existingPipeline, pipelineName } = configuration;

const getPipelineOptions = (
pipelineOptions: MLInferencePipelineOption[]
): PipelineSelectOptionProps[] => {
return pipelineOptions.map((pipelineOption) => ({
checked: existingPipeline && pipelineOption.pipelineName === pipelineName ? 'on' : undefined,
Mikep86 marked this conversation as resolved.
Show resolved Hide resolved
disabled: pipelineOption.disabled,
label: pipelineOption.pipelineName,
pipeline: pipelineOption,
}));
};

const renderPipelineOption = (option: PipelineSelectOptionProps) => {
return <PipelineSelectOption {...option} />;
};

const onChange = (options: PipelineSelectOptionProps[]) => {
const selectedOption = options.find((option) => option.checked === 'on');
if (selectedOption) {
selectExistingPipeline(selectedOption.pipeline.pipelineName);
}
};

const getActiveOptionIndex = (): number | undefined => {
if (!existingPipeline) {
return undefined;
}

const index = existingInferencePipelines.findIndex(
(pipelineOption) => pipelineOption.pipelineName === pipelineName
);

return index >= 0 ? index : undefined;
};

return (
<EuiSelectable
options={getPipelineOptions(existingInferencePipelines)}
listProps={{
activeOptionIndex: getActiveOptionIndex(),
bordered: true,
rowHeight: useIsWithinMaxBreakpoint('s') ? 120 : 90,
showIcons: true,
onFocusBadge: false,
}}
searchable
singleSelection="always"
onChange={onChange}
renderOption={renderPipelineOption}
>
{(list, search) => (
<>
{search}
{list}
</>
)}
</EuiSelectable>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,20 @@ describe('PipelineSelectOption', () => {
sourceFields: ['my-source-field1', 'my-source-field2'],
indexFields: [],
};
const label = pipeline.pipelineName;

beforeEach(() => {
jest.clearAllMocks();
});
it('renders pipeline selection option', () => {
const wrapper = shallow(<PipelineSelectOption pipeline={pipeline} />);
const wrapper = shallow(<PipelineSelectOption label={label} pipeline={pipeline} />);
expect(wrapper.find(EuiTitle)).toHaveLength(1);
expect(wrapper.find(MLModelTypeBadge)).toHaveLength(1);
});
it('does not render model type badge if model type is unknown', () => {
const wrapper = shallow(
<PipelineSelectOption
label={label}
pipeline={{
...pipeline,
modelType: '',
Expand All @@ -51,18 +53,20 @@ describe('PipelineSelectOption', () => {
it("redacts model ID if it's unavailable", () => {
const wrapper = shallow(
<PipelineSelectOption
label={label}
pipeline={{
...pipeline,
modelId: '',
}}
/>
);
expect(wrapper.find(EuiText)).toHaveLength(4);
expect(wrapper.find(EuiText).at(1).children().text()).toEqual(MODEL_REDACTED_VALUE);
expect(wrapper.find(EuiText)).toHaveLength(2);
expect(wrapper.find(EuiText).at(0).children().text()).toEqual(MODEL_REDACTED_VALUE);
});
it('renders disable warning text if the pipeline is disabled', () => {
const wrapper = shallow(
<PipelineSelectOption
label={label}
pipeline={{
...pipeline,
disabled: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,107 +7,70 @@

import React from 'react';

import {
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiSpacer,
EuiText,
EuiTextColor,
EuiTitle,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText, EuiTextColor, EuiTitle } from '@elastic/eui';

import { MLModelTypeBadge } from '../ml_model_type_badge';

import { MLInferencePipelineOption } from './ml_inference_logic';
import { EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELDS, MODEL_REDACTED_VALUE } from './utils';

export interface PipelineSelectOptionProps {
checked?: 'on';
disabled?: boolean;
label: string;
pipeline: MLInferencePipelineOption;
}

// TODO: Make disabledReason required and remove EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELDS call without args
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is here to serve as a reminder to address this in a separate tech debt PR

export const PipelineSelectOptionDisabled: React.FC<{ disabledReason?: string }> = ({
disabledReason,
}) => {
return (
<>
<EuiSpacer size="xs" />
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false}>
<EuiFlexItem grow={false}>
<EuiIcon type="warning" color="warning" />
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup alignItems="center" gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon type="warning" color="warning" />
</EuiFlexItem>
<EuiFlexItem>
<EuiTextColor color="warning">
{disabledReason ?? EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELDS}
</EuiTextColor>
</EuiFlexItem>
</EuiFlexGroup>
<EuiTextColor color="warning">
{disabledReason ?? EXISTING_PIPELINE_DISABLED_MISSING_SOURCE_FIELDS}
</EuiTextColor>
</EuiFlexItem>
<EuiSpacer size="xs" />
</>
</EuiFlexGroup>
);
};

export const PipelineSelectOption: React.FC<PipelineSelectOptionProps> = ({ pipeline }) => {
const modelIdDisplay = pipeline.modelId.length > 0 ? pipeline.modelId : MODEL_REDACTED_VALUE;
return (
<EuiFlexGroup direction="column" gutterSize="none">
// TODO: Add model state & pipeline info link. Make sure to check mobile rendering when doing this!
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Model state & pipeline info link was originally in scope, but was removed in the interest of meeting 8.12 FF. It will be added in a follow-up PR.

<EuiFlexGroup direction="column" gutterSize="xs">
<EuiFlexItem>
<EuiTitle size="xxs">
<h4>{pipeline.pipelineName}</h4>
Mikep86 marked this conversation as resolved.
Show resolved Hide resolved
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="flexEnd">
<EuiFlexItem>
<EuiTitle size="xs">
<h4>{pipeline.pipelineName}</h4>
</EuiTitle>
<EuiFlexGroup gutterSize="s" justifyContent="flexStart">
<EuiFlexItem grow={pipeline.modelType.length === 0}>
<EuiText size="s">{modelIdDisplay}</EuiText>
</EuiFlexItem>
{pipeline.modelType.length > 0 && (
<EuiFlexItem grow={false}>
<MLModelTypeBadge type={pipeline.modelType} />
<EuiFlexItem>
{/* Wrap in a div to prevent the badge from growing to a whole row on mobile*/}
<div>
<MLModelTypeBadge type={pipeline.modelType} />
</div>
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiSpacer size="m" />
<EuiFlexItem>
<EuiFlexGroup gutterSize="s" alignItems="center" justifyContent="flexEnd">
<EuiFlexItem>
<EuiText size="s" color="subdued">
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.model',
{ defaultMessage: 'Model' }
)}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color={pipeline.disabled ? 'subdued' : 'normal'}>
{modelIdDisplay}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiSpacer size="xs" />
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem style={{ minWidth: 100 }}>
<EuiText size="s" color="subdued">
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.sourceFields',
{ defaultMessage: 'Source fields' }
)}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color={pipeline.disabled ? 'subdued' : 'normal'} textAlign="right">
{pipeline.sourceFields.join(', ')}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
{pipeline.disabled ? (
<PipelineSelectOptionDisabled disabledReason={pipeline.disabledReason} />
) : (
<EuiText size="s">{pipeline.sourceFields.join(', ')}</EuiText>
)}
</EuiFlexItem>
<EuiSpacer size="s" />
{pipeline.disabled && (
<PipelineSelectOptionDisabled disabledReason={pipeline.disabledReason} />
)}
</EuiFlexGroup>
);
};
3 changes: 0 additions & 3 deletions x-pack/plugins/translations/translations/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -14569,9 +14569,6 @@
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.noModels.imageAlt": "Illustration d'absence de modèles de Machine Learning",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.description": "Créez ou réutilisez un pipeline enfant qui servira de processeur dans votre pipeline principal.",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.emptyValueError": "Champ obligatoire.",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.model": "Modèle",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.placeholder": "Effectuez une sélection",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.sourceFields": "Champs sources",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipelineLabel": "Sélectionner un pipeline d'inférence existant",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.inference.title": "Configuration de l'inférence",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.inference.zeroShot.labels.label": "Étiquettes de classe",
Expand Down
3 changes: 0 additions & 3 deletions x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -14582,9 +14582,6 @@
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.noModels.imageAlt": "機械学習モデル例がありません",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.description": "メインパイプラインでプロセッサーとして使用される子パイプラインを作成または再利用します。",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.emptyValueError": "フィールドが必要です。",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.model": "モデル",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.placeholder": "1 つ選択してください",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.sourceFields": "ソースフィールド",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipelineLabel": "既存の推論パイプラインを選択",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.inference.title": "推論構成",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.inference.zeroShot.labels.label": "クラスラベル",
Expand Down
3 changes: 0 additions & 3 deletions x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -14582,9 +14582,6 @@
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.noModels.imageAlt": "无 Machine Learning 模型图示",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.description": "构建或重复使用将在您的主管道中用作处理器的子管道。",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.emptyValueError": "“字段”必填。",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.model": "模型",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.placeholder": "选择一个",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipeline.sourceFields": "源字段",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.existingPipelineLabel": "选择现有推理管道",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.inference.title": "推理配置",
"xpack.enterpriseSearch.content.indices.pipelines.addInferencePipelineModal.steps.configure.inference.zeroShot.labels.label": "类标签",
Expand Down