mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Enterprise Search] promote copy ingest pipeline (#143396)
Promited the copy and customize action from within the modal to the ingest pipeline card. This is to make it more discoverable for users and give better UX.
This commit is contained in:
parent
61f0889d66
commit
608a05d2d9
5 changed files with 257 additions and 84 deletions
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 { crawlerIndex } from '../../../../__mocks__/view_index.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { EuiText, EuiButtonEmpty } from '@elastic/eui';
|
||||
|
||||
import { CustomizeIngestPipelineItem } from './customize_pipeline_item';
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
// LicensingLogic
|
||||
hasPlatinumLicense: true,
|
||||
// IndexViewLogic
|
||||
indexName: crawlerIndex.name,
|
||||
ingestionMethod: 'crawler',
|
||||
// KibanaLogic
|
||||
isCloud: false,
|
||||
};
|
||||
|
||||
describe('CustomizeIngestPipelineItem', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues({ ...DEFAULT_VALUES });
|
||||
});
|
||||
it('renders cta with license', () => {
|
||||
const wrapper = shallow(<CustomizeIngestPipelineItem />);
|
||||
expect(wrapper.find(EuiButtonEmpty)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiText)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiText).children().text()).toContain('create an index-specific version');
|
||||
expect(wrapper.find(EuiText).children().text()).not.toContain('With a platinum license');
|
||||
});
|
||||
it('renders cta on cloud', () => {
|
||||
setMockValues({
|
||||
...DEFAULT_VALUES,
|
||||
hasPlatinumLicense: false,
|
||||
isCloud: true,
|
||||
});
|
||||
const wrapper = shallow(<CustomizeIngestPipelineItem />);
|
||||
expect(wrapper.find(EuiText)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiText).children().text()).toContain('create an index-specific version');
|
||||
expect(wrapper.find(EuiText).children().text()).not.toContain('With a platinum license');
|
||||
});
|
||||
it('gates cta without license', () => {
|
||||
setMockValues({
|
||||
...DEFAULT_VALUES,
|
||||
hasPlatinumLicense: false,
|
||||
isCloud: false,
|
||||
});
|
||||
const wrapper = shallow(<CustomizeIngestPipelineItem />);
|
||||
expect(wrapper.find(EuiButtonEmpty)).toHaveLength(1);
|
||||
expect(wrapper.find(EuiText)).toHaveLength(1);
|
||||
|
||||
const ctaButton = wrapper.find(EuiButtonEmpty);
|
||||
expect(ctaButton.prop('disabled')).toBe(true);
|
||||
expect(ctaButton.prop('iconType')).toBe('lock');
|
||||
|
||||
expect(wrapper.find(EuiText).children().text()).toContain('With a platinum license');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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 { EuiButtonEmpty, EuiFlexGroup, EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { KibanaLogic } from '../../../../../shared/kibana';
|
||||
import { LicensingLogic } from '../../../../../shared/licensing';
|
||||
import { CreateCustomPipelineApiLogic } from '../../../../api/index/create_custom_pipeline_api_logic';
|
||||
|
||||
import { IndexViewLogic } from '../../index_view_logic';
|
||||
|
||||
export const CustomizeIngestPipelineItem: React.FC = () => {
|
||||
const { indexName, ingestionMethod } = useValues(IndexViewLogic);
|
||||
const { isCloud } = useValues(KibanaLogic);
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
const { makeRequest: createCustomPipeline } = useActions(CreateCustomPipelineApiLogic);
|
||||
const isGated = !isCloud && !hasPlatinumLicense;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isGated ? (
|
||||
<EuiText color="subdued" size="s" grow={false}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.copyAndCustomize.platinumText',
|
||||
{
|
||||
defaultMessage:
|
||||
'With a platinum license, you can create an index-specific version of this configuration and modify it for your use case.',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
) : (
|
||||
<EuiText color="subdued" size="s" grow={false}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.copyAndCustomize.description',
|
||||
{
|
||||
defaultMessage:
|
||||
'You can create an index-specific version of this configuration and modify it for your use case.',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
)}
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiButtonEmpty
|
||||
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-ingestPipelines-copyAndCustomize`}
|
||||
disabled={isGated}
|
||||
iconType={isGated ? 'lock' : undefined}
|
||||
onClick={() => createCustomPipeline({ indexName })}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.copyButtonLabel',
|
||||
{ defaultMessage: 'Copy and customize' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -27,8 +27,6 @@ import {
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { DEFAULT_PIPELINE_NAME } from '../../../../../../../common/constants';
|
||||
|
||||
import { IngestPipelineParams } from '../../../../../../../common/types/connectors';
|
||||
|
||||
import { CurlRequest } from '../../components/curl_request/curl_request';
|
||||
|
@ -37,11 +35,9 @@ import { PipelineSettingsForm } from '../pipeline_settings_form';
|
|||
|
||||
interface IngestPipelineModalProps {
|
||||
closeModal: () => void;
|
||||
createCustomPipelines: () => void;
|
||||
displayOnly: boolean;
|
||||
indexName: string;
|
||||
ingestionMethod: string;
|
||||
isGated: boolean;
|
||||
isLoading: boolean;
|
||||
pipeline: IngestPipelineParams;
|
||||
savePipeline: () => void;
|
||||
|
@ -50,11 +46,9 @@ interface IngestPipelineModalProps {
|
|||
|
||||
export const IngestPipelineModal: React.FC<IngestPipelineModalProps> = ({
|
||||
closeModal,
|
||||
createCustomPipelines,
|
||||
displayOnly,
|
||||
indexName,
|
||||
ingestionMethod,
|
||||
isGated,
|
||||
isLoading,
|
||||
pipeline,
|
||||
savePipeline,
|
||||
|
@ -62,9 +56,6 @@ export const IngestPipelineModal: React.FC<IngestPipelineModalProps> = ({
|
|||
}) => {
|
||||
const { name } = pipeline;
|
||||
|
||||
// can't customize if you already have a custom pipeline!
|
||||
const canCustomize = name === DEFAULT_PIPELINE_NAME;
|
||||
|
||||
return (
|
||||
<EuiModal onClose={closeModal} maxWidth={'40rem'}>
|
||||
<EuiModalHeader>
|
||||
|
@ -192,38 +183,6 @@ export const IngestPipelineModal: React.FC<IngestPipelineModalProps> = ({
|
|||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
{canCustomize && (
|
||||
<>
|
||||
<EuiSpacer />
|
||||
<EuiFlexItem>
|
||||
<EuiText color="subdued" size="s" grow={false}>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.platinumText',
|
||||
{
|
||||
defaultMessage:
|
||||
'With a platinum license, you can create an index-specific version of this configuration and modify it for your use case.',
|
||||
}
|
||||
)}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
<EuiSpacer />
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup justifyContent="flexStart">
|
||||
<EuiButtonEmpty
|
||||
data-telemetry-id={`entSearchContent-${ingestionMethod}-pipelines-ingestPipelines-copyAndCustomize`}
|
||||
disabled={isGated}
|
||||
iconType={isGated ? 'lock' : undefined}
|
||||
onClick={createCustomPipelines}
|
||||
>
|
||||
{i18n.translate(
|
||||
'xpack.enterpriseSearch.content.index.pipelines.ingestModal.copyButtonLabel',
|
||||
{ defaultMessage: 'Copy and customize' }
|
||||
)}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 { crawlerIndex } from '../../../../__mocks__/view_index.mock';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { IngestPipeline } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
import { DEFAULT_PIPELINE_NAME } from '../../../../../../../common/constants';
|
||||
|
||||
import { CustomPipelineItem } from './custom_pipeline_item';
|
||||
import { CustomizeIngestPipelineItem } from './customize_pipeline_item';
|
||||
import { DefaultPipelineItem } from './default_pipeline_item';
|
||||
import { IngestPipelinesCard } from './ingest_pipelines_card';
|
||||
|
||||
const DEFAULT_VALUES = {
|
||||
// IndexViewLogic
|
||||
indexName: crawlerIndex.name,
|
||||
ingestionMethod: 'crawler',
|
||||
// FetchCustomPipelineApiLogic
|
||||
data: undefined,
|
||||
// PipelinesLogic
|
||||
canSetPipeline: true,
|
||||
hasIndexIngestionPipeline: false,
|
||||
index: crawlerIndex,
|
||||
pipelineName: DEFAULT_PIPELINE_NAME,
|
||||
pipelineState: {
|
||||
extract_binary_content: true,
|
||||
name: DEFAULT_PIPELINE_NAME,
|
||||
reduce_whitespace: true,
|
||||
run_ml_inference: false,
|
||||
},
|
||||
showModal: false,
|
||||
};
|
||||
|
||||
describe('IngestPipelinesCard', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
setMockValues({ ...DEFAULT_VALUES });
|
||||
});
|
||||
it('renders with default ingest pipeline', () => {
|
||||
const wrapper = shallow(<IngestPipelinesCard />);
|
||||
expect(wrapper.find(DefaultPipelineItem)).toHaveLength(1);
|
||||
expect(wrapper.find(CustomizeIngestPipelineItem)).toHaveLength(1);
|
||||
expect(wrapper.find(CustomPipelineItem)).toHaveLength(0);
|
||||
});
|
||||
it('does not render customize cta with index ingest pipeline', () => {
|
||||
const pipelineName = crawlerIndex.name;
|
||||
const pipelines: Record<string, IngestPipeline | undefined> = {
|
||||
[pipelineName]: {},
|
||||
[`${pipelineName}@custom`]: {
|
||||
processors: [],
|
||||
},
|
||||
};
|
||||
setMockValues({
|
||||
...DEFAULT_VALUES,
|
||||
data: pipelines,
|
||||
hasIndexIngestionPipeline: true,
|
||||
pipelineName,
|
||||
pipelineState: {
|
||||
...DEFAULT_VALUES.pipelineState,
|
||||
name: pipelineName,
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = shallow(<IngestPipelinesCard />);
|
||||
expect(wrapper.find(CustomizeIngestPipelineItem)).toHaveLength(0);
|
||||
expect(wrapper.find(DefaultPipelineItem)).toHaveLength(1);
|
||||
expect(wrapper.find(CustomPipelineItem)).toHaveLength(1);
|
||||
});
|
||||
});
|
|
@ -11,32 +11,31 @@ import { useActions, useValues } from 'kea';
|
|||
|
||||
import { EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import { KibanaLogic } from '../../../../../shared/kibana';
|
||||
|
||||
import { LicensingLogic } from '../../../../../shared/licensing';
|
||||
import { CreateCustomPipelineApiLogic } from '../../../../api/index/create_custom_pipeline_api_logic';
|
||||
import { FetchCustomPipelineApiLogic } from '../../../../api/index/fetch_custom_pipeline_api_logic';
|
||||
import { IndexViewLogic } from '../../index_view_logic';
|
||||
|
||||
import { PipelinesLogic } from '../pipelines_logic';
|
||||
|
||||
import { CustomPipelineItem } from './custom_pipeline_item';
|
||||
import { CustomizeIngestPipelineItem } from './customize_pipeline_item';
|
||||
import { DefaultPipelineItem } from './default_pipeline_item';
|
||||
import { IngestPipelineModal } from './ingest_pipeline_modal';
|
||||
|
||||
export const IngestPipelinesCard: React.FC = () => {
|
||||
const { indexName, ingestionMethod } = useValues(IndexViewLogic);
|
||||
|
||||
const { canSetPipeline, index, pipelineName, pipelineState, showModal } =
|
||||
useValues(PipelinesLogic);
|
||||
const {
|
||||
canSetPipeline,
|
||||
hasIndexIngestionPipeline,
|
||||
index,
|
||||
pipelineName,
|
||||
pipelineState,
|
||||
showModal,
|
||||
} = useValues(PipelinesLogic);
|
||||
const { closeModal, openModal, setPipelineState, savePipeline } = useActions(PipelinesLogic);
|
||||
const { makeRequest: fetchCustomPipeline } = useActions(FetchCustomPipelineApiLogic);
|
||||
const { makeRequest: createCustomPipeline } = useActions(CreateCustomPipelineApiLogic);
|
||||
const { data: customPipelines } = useValues(FetchCustomPipelineApiLogic);
|
||||
const { isCloud } = useValues(KibanaLogic);
|
||||
const { hasPlatinumLicense } = useValues(LicensingLogic);
|
||||
|
||||
const isGated = !isCloud && !hasPlatinumLicense;
|
||||
const customPipeline = customPipelines ? customPipelines[`${indexName}@custom`] : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -44,45 +43,46 @@ export const IngestPipelinesCard: React.FC = () => {
|
|||
}, [indexName]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{showModal && (
|
||||
<IngestPipelineModal
|
||||
closeModal={closeModal}
|
||||
createCustomPipelines={() => createCustomPipeline({ indexName })}
|
||||
displayOnly={!canSetPipeline}
|
||||
indexName={indexName}
|
||||
ingestionMethod={ingestionMethod}
|
||||
isGated={isGated}
|
||||
isLoading={false}
|
||||
pipeline={{ ...pipelineState, name: pipelineName }}
|
||||
savePipeline={savePipeline}
|
||||
setPipeline={setPipelineState}
|
||||
/>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiPanel color="subdued">
|
||||
<DefaultPipelineItem
|
||||
index={index}
|
||||
openModal={openModal}
|
||||
pipelineName={pipelineName}
|
||||
ingestionMethod={ingestionMethod}
|
||||
<>
|
||||
{!hasIndexIngestionPipeline && <CustomizeIngestPipelineItem />}
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{showModal && (
|
||||
<IngestPipelineModal
|
||||
closeModal={closeModal}
|
||||
displayOnly={!canSetPipeline}
|
||||
indexName={indexName}
|
||||
pipelineState={pipelineState}
|
||||
ingestionMethod={ingestionMethod}
|
||||
isLoading={false}
|
||||
pipeline={{ ...pipelineState, name: pipelineName }}
|
||||
savePipeline={savePipeline}
|
||||
setPipeline={setPipelineState}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
{customPipeline && (
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<EuiPanel color="primary">
|
||||
<CustomPipelineItem
|
||||
indexName={indexName}
|
||||
<EuiPanel color="subdued">
|
||||
<DefaultPipelineItem
|
||||
index={index}
|
||||
openModal={openModal}
|
||||
pipelineName={pipelineName}
|
||||
ingestionMethod={ingestionMethod}
|
||||
pipelineSuffix="custom"
|
||||
processorsCount={customPipeline.processors?.length ?? 0}
|
||||
indexName={indexName}
|
||||
pipelineState={pipelineState}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
{customPipeline && (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel color="primary">
|
||||
<CustomPipelineItem
|
||||
indexName={indexName}
|
||||
ingestionMethod={ingestionMethod}
|
||||
pipelineSuffix="custom"
|
||||
processorsCount={customPipeline.processors?.length ?? 0}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue