[Search Relevance] Reorganize text expansion callout into smaller component files (#163196)

The changes in this PR are related to this issue:
https://github.com/elastic/enterprise-search-team/issues/4440.

In this PR, we moved a number of components of
`text_expansion_callout.tsx` and their tests to separate files.


db8657f4-a9f2-4502-87e8-20325c52748b

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Saikat Sarkar 2023-08-08 10:22:33 -06:00 committed by GitHub
parent 6bec8e41f8
commit 874801bf7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 697 additions and 503 deletions

View file

@ -0,0 +1,81 @@
/*
* 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 { setMockValues } from '../../../../../__mocks__/kea_logic';
import React from 'react';
import { shallow } from 'enzyme';
import { EuiButton } from '@elastic/eui';
import { DeployModel } from './deploy_model';
import { TextExpansionDismissButton } from './text_expansion_callout';
const DEFAULT_VALUES = {
startTextExpansionModelError: undefined,
isCreateButtonDisabled: false,
isModelDownloadInProgress: false,
isModelDownloaded: false,
isModelStarted: false,
isStartButtonDisabled: false,
};
describe('DeployModel', () => {
beforeEach(() => {
jest.clearAllMocks();
setMockValues(DEFAULT_VALUES);
});
it('renders deploy button', () => {
const wrapper = shallow(
<DeployModel
dismiss={() => {}}
ingestionMethod="crawler"
isCreateButtonDisabled={false}
isDismissable={false}
/>
);
expect(wrapper.find(EuiButton).length).toBe(1);
const button = wrapper.find(EuiButton);
expect(button.prop('disabled')).toBe(false);
});
it('renders disabled deploy button if it is set to disabled', () => {
const wrapper = shallow(
<DeployModel
dismiss={() => {}}
ingestionMethod="crawler"
isCreateButtonDisabled
isDismissable={false}
/>
);
expect(wrapper.find(EuiButton).length).toBe(1);
const button = wrapper.find(EuiButton);
expect(button.prop('disabled')).toBe(true);
});
it('renders dismiss button if it is set to dismissable', () => {
const wrapper = shallow(
<DeployModel
dismiss={() => {}}
ingestionMethod="crawler"
isCreateButtonDisabled={false}
isDismissable
/>
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
});
it('does not render dismiss button if it is set to non-dismissable', () => {
const wrapper = shallow(
<DeployModel
dismiss={() => {}}
ingestionMethod="crawler"
isCreateButtonDisabled={false}
isDismissable={false}
/>
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
});
});

View file

@ -0,0 +1,119 @@
/*
* 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 } from 'kea';
import {
EuiBadge,
EuiButton,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiLink,
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, FormattedHTMLMessage } from '@kbn/i18n-react';
import { docLinks } from '../../../../../shared/doc_links';
import { TextExpansionCallOutState, TextExpansionDismissButton } from './text_expansion_callout';
import { TextExpansionCalloutLogic } from './text_expansion_callout_logic';
export const DeployModel = ({
dismiss,
ingestionMethod,
isCreateButtonDisabled,
isDismissable,
}: Pick<
TextExpansionCallOutState,
'dismiss' | 'ingestionMethod' | 'isCreateButtonDisabled' | 'isDismissable'
>) => {
const { createTextExpansionModel } = useActions(TextExpansionCalloutLogic);
return (
<EuiCallOut color="success">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiBadge color="success">
<FormattedMessage
id="xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.titleBadge"
defaultMessage="New"
/>
</EuiBadge>
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText color="success" size="xs">
<h3>
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.title',
{ defaultMessage: 'Improve your results with ELSER' }
)}
</h3>
</EuiText>
</EuiFlexItem>
{isDismissable && (
<EuiFlexItem grow={false}>
<TextExpansionDismissButton dismiss={dismiss} />
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiText size="s">
<FormattedHTMLMessage
id="xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.body"
defaultMessage="ELSER (Elastic Learned Sparse EncodeR) is our <strong>new trained machine learning model</strong> designed to efficiently use context in natural language queries. This model delivers better results than BM25 without further training on your data."
tagName="p"
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup
direction="row"
gutterSize="m"
alignItems="center"
justifyContent="flexStart"
>
<EuiFlexItem grow={false}>
<EuiButton
color="success"
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-textExpansionCallOut-deployModel`}
disabled={isCreateButtonDisabled}
iconType="launch"
onClick={() => createTextExpansionModel(undefined)}
>
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCallOut.deployButton.label',
{
defaultMessage: 'Deploy',
}
)}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink target="_blank" href={docLinks.elser}>
<FormattedMessage
id="xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.learnMoreLink"
defaultMessage="Learn more"
/>
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);
};

View file

@ -0,0 +1,81 @@
/*
* 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 { setMockValues } from '../../../../../__mocks__/kea_logic';
import React from 'react';
import { shallow } from 'enzyme';
import { EuiButton } from '@elastic/eui';
import { ModelDeployed } from './model_deployed';
import { TextExpansionDismissButton } from './text_expansion_callout';
const DEFAULT_VALUES = {
startTextExpansionModelError: undefined,
isCreateButtonDisabled: false,
isModelDownloadInProgress: false,
isModelDownloaded: false,
isModelStarted: false,
isStartButtonDisabled: false,
};
describe('ModelDeployed', () => {
beforeEach(() => {
jest.clearAllMocks();
setMockValues(DEFAULT_VALUES);
});
it('renders start button', () => {
const wrapper = shallow(
<ModelDeployed
dismiss={() => {}}
ingestionMethod="crawler"
isDismissable={false}
isStartButtonDisabled={false}
/>
);
expect(wrapper.find(EuiButton).length).toBe(1);
const button = wrapper.find(EuiButton);
expect(button.prop('disabled')).toBe(false);
});
it('renders disabled start button if it is set to disabled', () => {
const wrapper = shallow(
<ModelDeployed
dismiss={() => {}}
ingestionMethod="crawler"
isDismissable={false}
isStartButtonDisabled
/>
);
expect(wrapper.find(EuiButton).length).toBe(1);
const button = wrapper.find(EuiButton);
expect(button.prop('disabled')).toBe(true);
});
it('renders dismiss button if it is set to dismissable', () => {
const wrapper = shallow(
<ModelDeployed
dismiss={() => {}}
ingestionMethod="crawler"
isDismissable
isStartButtonDisabled={false}
/>
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
});
it('does not render dismiss button if it is set to non-dismissable', () => {
const wrapper = shallow(
<ModelDeployed
dismiss={() => {}}
ingestionMethod="crawler"
isDismissable={false}
isStartButtonDisabled={false}
/>
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
});
});

View file

@ -0,0 +1,113 @@
/*
* 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 } from 'kea';
import {
EuiButton,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiText,
EuiIcon,
EuiSpacer,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import {
TextExpansionCallOutState,
TextExpansionDismissButton,
FineTuneModelsButton,
} from './text_expansion_callout';
import { TextExpansionCalloutLogic } from './text_expansion_callout_logic';
export const ModelDeployed = ({
dismiss,
ingestionMethod,
isDismissable,
isStartButtonDisabled,
}: Pick<
TextExpansionCallOutState,
'dismiss' | 'ingestionMethod' | 'isDismissable' | 'isStartButtonDisabled'
>) => {
const { startTextExpansionModel } = useActions(TextExpansionCalloutLogic);
return (
<EuiCallOut color="success">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem grow>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon color="success" type="checkInCircleFilled" />
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText color="success" size="xs">
<h3>
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.deployedTitle',
{ defaultMessage: 'Your ELSER model has deployed but not started.' }
)}
</h3>
</EuiText>
</EuiFlexItem>
{isDismissable && (
<EuiFlexItem grow={false}>
<TextExpansionDismissButton dismiss={dismiss} />
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText size="s">
<p>
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.deployedBody',
{
defaultMessage:
'You may start the model in a single-threaded configuration for testing, or tune the performance for a production environment.',
}
)}
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiSpacer size="s" />
</EuiFlexItem>
<EuiFlexItem grow>
<EuiFlexGroup
direction="row"
gutterSize="s"
alignItems="center"
justifyContent="flexStart"
>
<EuiFlexItem grow={false}>
<EuiButton
color="success"
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-textExpansionCallOut-startModel`}
disabled={isStartButtonDisabled}
iconType="playFilled"
onClick={() => startTextExpansionModel(undefined)}
>
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCallOut.startModelButton.label',
{
defaultMessage: 'Start single-threaded',
}
)}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<FineTuneModelsButton />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);
};

View file

@ -0,0 +1,39 @@
/*
* 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 { setMockValues } from '../../../../../__mocks__/kea_logic';
import React from 'react';
import { shallow } from 'enzyme';
import { ModelDeploymentInProgress } from './model_deployment_in_progress';
import { TextExpansionDismissButton } from './text_expansion_callout';
const DEFAULT_VALUES = {
startTextExpansionModelError: undefined,
isCreateButtonDisabled: false,
isModelDownloadInProgress: false,
isModelDownloaded: false,
isModelStarted: false,
isStartButtonDisabled: false,
};
describe('ModelDeploymentInProgress', () => {
beforeEach(() => {
jest.clearAllMocks();
setMockValues(DEFAULT_VALUES);
});
it('renders dismiss button if it is set to dismissable', () => {
const wrapper = shallow(<ModelDeploymentInProgress dismiss={() => {}} isDismissable />);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
});
it('does not render dismiss button if it is set to non-dismissable', () => {
const wrapper = shallow(<ModelDeploymentInProgress dismiss={() => {}} isDismissable={false} />);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
});
});

View file

@ -0,0 +1,58 @@
/*
* 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 { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiText, EuiIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { TextExpansionCallOutState, TextExpansionDismissButton } from './text_expansion_callout';
export const ModelDeploymentInProgress = ({
dismiss,
isDismissable,
}: Pick<TextExpansionCallOutState, 'dismiss' | 'isDismissable'>) => (
<EuiCallOut color="success">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem grow>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon color="success" type="clock" />
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText color="success" size="xs">
<h3>
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.deployingTitle',
{ defaultMessage: 'Your ELSER model is deploying.' }
)}
</h3>
</EuiText>
</EuiFlexItem>
{isDismissable && (
<EuiFlexItem grow={false}>
<TextExpansionDismissButton dismiss={dismiss} />
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText size="s">
<p>
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.deployingBody',
{
defaultMessage:
'You can continue creating your pipeline with other uploaded models in the meantime.',
}
)}
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);

View file

@ -0,0 +1,57 @@
/*
* 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 { setMockValues } from '../../../../../__mocks__/kea_logic';
import React from 'react';
import { shallow } from 'enzyme';
import { EuiText } from '@elastic/eui';
import { ModelStarted } from './model_started';
import { TextExpansionDismissButton, FineTuneModelsButton } from './text_expansion_callout';
const DEFAULT_VALUES = {
startTextExpansionModelError: undefined,
isCreateButtonDisabled: false,
isModelDownloadInProgress: false,
isModelDownloaded: false,
isModelStarted: false,
isStartButtonDisabled: false,
};
describe('ModelStarted', () => {
beforeEach(() => {
jest.clearAllMocks();
setMockValues(DEFAULT_VALUES);
});
it('renders dismiss button if it is set to dismissable', () => {
const wrapper = shallow(
<ModelStarted dismiss={() => {}} isCompact={false} isDismissable isSingleThreaded />
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
});
it('does not render dismiss button if it is set to non-dismissable', () => {
const wrapper = shallow(
<ModelStarted dismiss={() => {}} isCompact={false} isDismissable={false} isSingleThreaded />
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
});
it('renders fine-tune button if the model is running single-threaded', () => {
const wrapper = shallow(
<ModelStarted dismiss={() => {}} isCompact={false} isDismissable isSingleThreaded />
);
expect(wrapper.find(FineTuneModelsButton).length).toBe(1);
});
it('does not render description if it is set to compact', () => {
const wrapper = shallow(
<ModelStarted dismiss={() => {}} isCompact isDismissable isSingleThreaded />
);
expect(wrapper.find(EuiText).length).toBe(1); // Title only
});
});

View file

@ -0,0 +1,135 @@
/*
* 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 {
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiText,
EuiButtonEmpty,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { KibanaLogic } from '../../../../../shared/kibana';
import {
TextExpansionCallOutState,
TextExpansionDismissButton,
FineTuneModelsButton,
} from './text_expansion_callout';
import { TRAINED_MODELS_PATH } from './utils';
export const ModelStarted = ({
dismiss,
isCompact,
isDismissable,
isSingleThreaded,
}: Pick<
TextExpansionCallOutState,
'dismiss' | 'isCompact' | 'isDismissable' | 'isSingleThreaded'
>) => (
<EuiCallOut color="success">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem grow>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type="checkInCircleFilled" color="success" />
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText color="success" size="xs">
<h3>
{isSingleThreaded
? isCompact
? i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedSingleThreadedTitleCompact',
{ defaultMessage: 'Your ELSER model is running single-threaded.' }
)
: i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedSingleThreadedTitle',
{ defaultMessage: 'Your ELSER model has started single-threaded.' }
)
: isCompact
? i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedTitleCompact',
{ defaultMessage: 'Your ELSER model is running.' }
)
: i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedTitle',
{ defaultMessage: 'Your ELSER model has started.' }
)}
</h3>
</EuiText>
</EuiFlexItem>
{isDismissable && (
<EuiFlexItem grow={false}>
<TextExpansionDismissButton dismiss={dismiss} />
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
{!isCompact && (
<>
<EuiFlexItem grow>
<EuiText size="s">
<p>
{isSingleThreaded
? i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedSingleThreadedBody',
{
defaultMessage:
'This single-threaded configuration is great for testing your custom inference pipelines, however performance should be fine-tuned for production.',
}
)
: i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedBody',
{
defaultMessage:
'Enjoy the power of ELSER in your custom Inference pipeline.',
}
)}
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup
direction="row"
gutterSize="m"
alignItems="center"
justifyContent="flexStart"
>
<EuiFlexItem grow={false}>
{isSingleThreaded ? (
<FineTuneModelsButton />
) : (
<EuiButtonEmpty
iconSide="left"
iconType="wrench"
onClick={() =>
KibanaLogic.values.navigateToUrl(TRAINED_MODELS_PATH, {
shouldNotCreateHref: true,
})
}
>
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCallOut.viewModelsButton',
{
defaultMessage: 'View details',
}
)}
</EuiButtonEmpty>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</>
)}
</EuiFlexGroup>
</EuiCallOut>
);

View file

@ -11,20 +11,13 @@ import React from 'react';
import { shallow } from 'enzyme';
import { EuiButton, EuiText } from '@elastic/eui';
import { HttpError } from '../../../../../../../common/types/api';
import {
TextExpansionCallOut,
DeployModel,
ModelDeploymentInProgress,
ModelDeployed,
TextExpansionDismissButton,
ModelStarted,
FineTuneModelsButton,
} from './text_expansion_callout';
import { DeployModel } from './deploy_model';
import { ModelDeployed } from './model_deployed';
import { ModelDeploymentInProgress } from './model_deployment_in_progress';
import { ModelStarted } from './model_started';
import { TextExpansionCallOut } from './text_expansion_callout';
import { TextExpansionErrors } from './text_expansion_errors';
jest.mock('./text_expansion_callout_data', () => ({
@ -97,146 +90,4 @@ describe('TextExpansionCallOut', () => {
const wrapper = shallow(<TextExpansionCallOut />);
expect(wrapper.find(ModelStarted).length).toBe(1);
});
describe('DeployModel', () => {
it('renders deploy button', () => {
const wrapper = shallow(
<DeployModel
dismiss={() => {}}
ingestionMethod="crawler"
isCreateButtonDisabled={false}
isDismissable={false}
/>
);
expect(wrapper.find(EuiButton).length).toBe(1);
const button = wrapper.find(EuiButton);
expect(button.prop('disabled')).toBe(false);
});
it('renders disabled deploy button if it is set to disabled', () => {
const wrapper = shallow(
<DeployModel
dismiss={() => {}}
ingestionMethod="crawler"
isCreateButtonDisabled
isDismissable={false}
/>
);
expect(wrapper.find(EuiButton).length).toBe(1);
const button = wrapper.find(EuiButton);
expect(button.prop('disabled')).toBe(true);
});
it('renders dismiss button if it is set to dismissable', () => {
const wrapper = shallow(
<DeployModel
dismiss={() => {}}
ingestionMethod="crawler"
isCreateButtonDisabled={false}
isDismissable
/>
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
});
it('does not render dismiss button if it is set to non-dismissable', () => {
const wrapper = shallow(
<DeployModel
dismiss={() => {}}
ingestionMethod="crawler"
isCreateButtonDisabled={false}
isDismissable={false}
/>
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
});
});
describe('ModelDeploymentInProgress', () => {
it('renders dismiss button if it is set to dismissable', () => {
const wrapper = shallow(<ModelDeploymentInProgress dismiss={() => {}} isDismissable />);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
});
it('does not render dismiss button if it is set to non-dismissable', () => {
const wrapper = shallow(
<ModelDeploymentInProgress dismiss={() => {}} isDismissable={false} />
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
});
});
describe('ModelDeployed', () => {
it('renders start button', () => {
const wrapper = shallow(
<ModelDeployed
dismiss={() => {}}
ingestionMethod="crawler"
isDismissable={false}
isStartButtonDisabled={false}
/>
);
expect(wrapper.find(EuiButton).length).toBe(1);
const button = wrapper.find(EuiButton);
expect(button.prop('disabled')).toBe(false);
});
it('renders disabled start button if it is set to disabled', () => {
const wrapper = shallow(
<ModelDeployed
dismiss={() => {}}
ingestionMethod="crawler"
isDismissable={false}
isStartButtonDisabled
/>
);
expect(wrapper.find(EuiButton).length).toBe(1);
const button = wrapper.find(EuiButton);
expect(button.prop('disabled')).toBe(true);
});
it('renders dismiss button if it is set to dismissable', () => {
const wrapper = shallow(
<ModelDeployed
dismiss={() => {}}
ingestionMethod="crawler"
isDismissable
isStartButtonDisabled={false}
/>
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
});
it('does not render dismiss button if it is set to non-dismissable', () => {
const wrapper = shallow(
<ModelDeployed
dismiss={() => {}}
ingestionMethod="crawler"
isDismissable={false}
isStartButtonDisabled={false}
/>
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
});
});
describe('ModelStarted', () => {
it('renders dismiss button if it is set to dismissable', () => {
const wrapper = shallow(
<ModelStarted dismiss={() => {}} isCompact={false} isDismissable isSingleThreaded />
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(1);
});
it('does not render dismiss button if it is set to non-dismissable', () => {
const wrapper = shallow(
<ModelStarted dismiss={() => {}} isCompact={false} isDismissable={false} isSingleThreaded />
);
expect(wrapper.find(TextExpansionDismissButton).length).toBe(0);
});
it('renders fine-tune button if the model is running single-threaded', () => {
const wrapper = shallow(
<ModelStarted dismiss={() => {}} isCompact={false} isDismissable isSingleThreaded />
);
expect(wrapper.find(FineTuneModelsButton).length).toBe(1);
});
it('does not render description if it is set to compact', () => {
const wrapper = shallow(
<ModelStarted dismiss={() => {}} isCompact isDismissable isSingleThreaded />
);
expect(wrapper.find(EuiText).length).toBe(1); // Title only
});
});
});

View file

@ -7,32 +7,22 @@
import React from 'react';
import { useActions, useValues } from 'kea';
import { useValues } from 'kea';
import {
EuiBadge,
EuiButton,
EuiButtonEmpty,
EuiButtonIcon,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiLink,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import { EuiButtonEmpty, EuiButtonIcon } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage, FormattedHTMLMessage } from '@kbn/i18n-react';
import { docLinks } from '../../../../../shared/doc_links';
import { KibanaLogic } from '../../../../../shared/kibana';
import { IndexViewLogic } from '../../index_view_logic';
import { DeployModel } from './deploy_model';
import { ModelDeployed } from './model_deployed';
import { ModelDeploymentInProgress } from './model_deployment_in_progress';
import { ModelStarted } from './model_started';
import { useTextExpansionCallOutData } from './text_expansion_callout_data';
import { getTextExpansionError, TextExpansionCalloutLogic } from './text_expansion_callout_logic';
import { TextExpansionErrors } from './text_expansion_errors';
import { TRAINED_MODELS_PATH } from './utils';
export interface TextExpansionCallOutState {
dismiss: () => void;
@ -50,8 +40,6 @@ export interface TextExpansionCallOutProps {
isDismissable?: boolean;
}
const TRAINED_MODELS_PATH = '/app/ml/trained_models';
export const TextExpansionDismissButton = ({
dismiss,
}: Pick<TextExpansionCallOutState, 'dismiss'>) => {
@ -86,336 +74,6 @@ export const FineTuneModelsButton: React.FC = () => (
</EuiButtonEmpty>
);
export const DeployModel = ({
dismiss,
ingestionMethod,
isCreateButtonDisabled,
isDismissable,
}: Pick<
TextExpansionCallOutState,
'dismiss' | 'ingestionMethod' | 'isCreateButtonDisabled' | 'isDismissable'
>) => {
const { createTextExpansionModel } = useActions(TextExpansionCalloutLogic);
return (
<EuiCallOut color="success">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiBadge color="success">
<FormattedMessage
id="xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.titleBadge"
defaultMessage="New"
/>
</EuiBadge>
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText color="success" size="xs">
<h3>
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.title',
{ defaultMessage: 'Improve your results with ELSER' }
)}
</h3>
</EuiText>
</EuiFlexItem>
{isDismissable && (
<EuiFlexItem grow={false}>
<TextExpansionDismissButton dismiss={dismiss} />
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiText size="s">
<FormattedHTMLMessage
id="xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.body"
defaultMessage="ELSER (Elastic Learned Sparse EncodeR) is our <strong>new trained machine learning model</strong> designed to efficiently use context in natural language queries. This model delivers better results than BM25 without further training on your data."
tagName="p"
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup
direction="row"
gutterSize="m"
alignItems="center"
justifyContent="flexStart"
>
<EuiFlexItem grow={false}>
<EuiButton
color="success"
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-textExpansionCallOut-deployModel`}
disabled={isCreateButtonDisabled}
iconType="launch"
onClick={() => createTextExpansionModel(undefined)}
>
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCallOut.deployButton.label',
{
defaultMessage: 'Deploy',
}
)}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiLink target="_blank" href={docLinks.elser}>
<FormattedMessage
id="xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.learnMoreLink"
defaultMessage="Learn more"
/>
</EuiLink>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);
};
export const ModelDeploymentInProgress = ({
dismiss,
isDismissable,
}: Pick<TextExpansionCallOutState, 'dismiss' | 'isDismissable'>) => (
<EuiCallOut color="success">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem grow>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon color="success" type="clock" />
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText color="success" size="xs">
<h3>
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.deployingTitle',
{ defaultMessage: 'Your ELSER model is deploying.' }
)}
</h3>
</EuiText>
</EuiFlexItem>
{isDismissable && (
<EuiFlexItem grow={false}>
<TextExpansionDismissButton dismiss={dismiss} />
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText size="s">
<p>
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.deployingBody',
{
defaultMessage:
'You can continue creating your pipeline with other uploaded models in the meantime.',
}
)}
</p>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);
export const ModelDeployed = ({
dismiss,
ingestionMethod,
isDismissable,
isStartButtonDisabled,
}: Pick<
TextExpansionCallOutState,
'dismiss' | 'ingestionMethod' | 'isDismissable' | 'isStartButtonDisabled'
>) => {
const { startTextExpansionModel } = useActions(TextExpansionCalloutLogic);
return (
<EuiCallOut color="success">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem grow>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon color="success" type="checkInCircleFilled" />
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText color="success" size="xs">
<h3>
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.deployedTitle',
{ defaultMessage: 'Your ELSER model has deployed but not started.' }
)}
</h3>
</EuiText>
</EuiFlexItem>
{isDismissable && (
<EuiFlexItem grow={false}>
<TextExpansionDismissButton dismiss={dismiss} />
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText size="s">
<p>
{i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.deployedBody',
{
defaultMessage:
'You may start the model in a single-threaded configuration for testing, or tune the performance for a production environment.',
}
)}
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiSpacer size="s" />
</EuiFlexItem>
<EuiFlexItem grow>
<EuiFlexGroup
direction="row"
gutterSize="s"
alignItems="center"
justifyContent="flexStart"
>
<EuiFlexItem grow={false}>
<EuiButton
color="success"
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-textExpansionCallOut-startModel`}
disabled={isStartButtonDisabled}
iconType="playFilled"
onClick={() => startTextExpansionModel(undefined)}
>
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCallOut.startModelButton.label',
{
defaultMessage: 'Start single-threaded',
}
)}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<FineTuneModelsButton />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiCallOut>
);
};
export const ModelStarted = ({
dismiss,
isCompact,
isDismissable,
isSingleThreaded,
}: Pick<
TextExpansionCallOutState,
'dismiss' | 'isCompact' | 'isDismissable' | 'isSingleThreaded'
>) => (
<EuiCallOut color="success">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem grow>
<EuiFlexGroup direction="row" gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>
<EuiIcon type="checkInCircleFilled" color="success" />
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText color="success" size="xs">
<h3>
{isSingleThreaded
? isCompact
? i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedSingleThreadedTitleCompact',
{ defaultMessage: 'Your ELSER model is running single-threaded.' }
)
: i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedSingleThreadedTitle',
{ defaultMessage: 'Your ELSER model has started single-threaded.' }
)
: isCompact
? i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedTitleCompact',
{ defaultMessage: 'Your ELSER model is running.' }
)
: i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedTitle',
{ defaultMessage: 'Your ELSER model has started.' }
)}
</h3>
</EuiText>
</EuiFlexItem>
{isDismissable && (
<EuiFlexItem grow={false}>
<TextExpansionDismissButton dismiss={dismiss} />
</EuiFlexItem>
)}
</EuiFlexGroup>
</EuiFlexItem>
{!isCompact && (
<>
<EuiFlexItem grow>
<EuiText size="s">
<p>
{isSingleThreaded
? i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedSingleThreadedBody',
{
defaultMessage:
'This single-threaded configuration is great for testing your custom inference pipelines, however performance should be fine-tuned for production.',
}
)
: i18n.translate(
'xpack.enterpriseSearch.content.index.pipelines.textExpansionCallOut.startedBody',
{
defaultMessage:
'Enjoy the power of ELSER in your custom Inference pipeline.',
}
)}
</p>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup
direction="row"
gutterSize="m"
alignItems="center"
justifyContent="flexStart"
>
<EuiFlexItem grow={false}>
{isSingleThreaded ? (
<FineTuneModelsButton />
) : (
<EuiButtonEmpty
iconSide="left"
iconType="wrench"
onClick={() =>
KibanaLogic.values.navigateToUrl(TRAINED_MODELS_PATH, {
shouldNotCreateHref: true,
})
}
>
{i18n.translate(
'xpack.enterpriseSearch.content.indices.pipelines.textExpansionCallOut.viewModelsButton',
{
defaultMessage: 'View details',
}
)}
</EuiButtonEmpty>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</>
)}
</EuiFlexGroup>
</EuiCallOut>
);
export const TextExpansionCallOut: React.FC<TextExpansionCallOutProps> = (props) => {
const { dismiss, isCompact, isDismissable, show } = useTextExpansionCallOutData(props);
const { ingestionMethod } = useValues(IndexViewLogic);

View file

@ -12,6 +12,8 @@ import { FetchPipelineResponse } from '../../../../api/pipelines/fetch_pipeline'
import { AddInferencePipelineFormErrors, InferencePipelineConfiguration } from './types';
const VALID_PIPELINE_NAME_REGEX = /^[\w\-]+$/;
export const TRAINED_MODELS_PATH = '/app/ml/trained_models';
export const isValidPipelineName = (input: string): boolean => {
return input.length > 0 && VALID_PIPELINE_NAME_REGEX.test(input);
};