[Search] Enable content app without Enterprise Search present (#163738)

## Summary

This enables the content plugin within Search when Enterprise Search is
not up. Crawler indices are made inaccessible as disentangling their
logic is too complicated to make sense.



300236c8-06b6-4052-8ed0-adb6f2a64564



88faba9a-cb49-412c-84e3-394e04bb04c4


62dc5d5d-a6c5-4d18-969a-2da971adb794

feature
This commit is contained in:
Sander Philipse 2023-08-15 14:53:28 +02:00 committed by GitHub
parent 1c0d656ae1
commit 2081139af6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 140 additions and 61 deletions

View file

@ -17,11 +17,13 @@ import { INGESTION_METHOD_IDS } from '../../../../../common/constants';
import { ProductFeatures } from '../../../../../common/types';
import { generateEncodedPath } from '../../../shared/encode_path_params';
import { HttpLogic } from '../../../shared/http';
import { KibanaLogic } from '../../../shared/kibana/kibana_logic';
import { EuiLinkTo } from '../../../shared/react_router_helpers';
import { NEW_INDEX_METHOD_PATH, NEW_INDEX_SELECT_CONNECTOR_PATH } from '../../routes';
import { EnterpriseSearchContentPageTemplate } from '../layout/page_template';
import { CannotConnect } from '../search_index/components/cannot_connect';
import { baseBreadcrumbs } from '../search_indices';
import { NewIndexCard } from './new_index_card';
@ -35,8 +37,9 @@ const getAvailableMethodOptions = (productFeatures: ProductFeatures): INGESTION_
};
export const NewIndex: React.FC = () => {
const { capabilities, productFeatures } = useValues(KibanaLogic);
const { capabilities, config, productFeatures } = useValues(KibanaLogic);
const availableIngestionMethodOptions = getAvailableMethodOptions(productFeatures);
const { errorConnectingMessage } = useValues(HttpLogic);
const [selectedMethod, setSelectedMethod] = useState<string>('');
return (
@ -60,12 +63,17 @@ export const NewIndex: React.FC = () => {
}}
>
<EuiFlexGroup direction="column">
{errorConnectingMessage && productFeatures.hasWebCrawler && <CannotConnect />}
<>
<EuiFlexItem>
<EuiFlexGroup>
{availableIngestionMethodOptions.map((type) => (
<EuiFlexItem key={type}>
<NewIndexCard
disabled={Boolean(
type === INGESTION_METHOD_IDS.CRAWLER &&
(errorConnectingMessage || !config.host)
)}
type={type}
onSelect={() => {
setSelectedMethod(type);

View file

@ -17,6 +17,7 @@ import { INGESTION_METHOD_IDS } from '../../../../../common/constants';
import { getIngestionMethodIconType } from './utils';
export interface NewIndexCardProps {
disabled: boolean;
isSelected?: boolean;
onSelect?: MouseEventHandler<HTMLButtonElement>;
type: INGESTION_METHOD_IDS;
@ -96,7 +97,12 @@ const METHOD_CARD_OPTIONS: Record<INGESTION_METHOD_IDS, MethodCardOptions> = {
}),
},
};
export const NewIndexCard: React.FC<NewIndexCardProps> = ({ onSelect, isSelected, type }) => {
export const NewIndexCard: React.FC<NewIndexCardProps> = ({
disabled,
onSelect,
isSelected,
type,
}) => {
if (!METHOD_CARD_OPTIONS[type]) {
return null;
}
@ -104,6 +110,7 @@ export const NewIndexCard: React.FC<NewIndexCardProps> = ({ onSelect, isSelected
return (
<EuiCard
isDisabled={disabled}
data-test-subj="entSearch-content-newIndexCard-cardBody"
hasBorder
icon={<EuiIcon type={icon} size="xxl" />}
@ -118,6 +125,7 @@ export const NewIndexCard: React.FC<NewIndexCardProps> = ({ onSelect, isSelected
</>
)}
<EuiButton
isDisabled={disabled}
data-test-subj={`entSearchContent-newIndexCard-button-${type}`}
fullWidth
onClick={onSelect}

View file

@ -0,0 +1,47 @@
/*
* 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, EuiSpacer, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiLinkTo } from '../../../../shared/react_router_helpers';
import { ERROR_STATE_PATH } from '../../../routes';
export const CannotConnect: React.FC = () => {
return (
<EuiCallOut
iconType="warning"
color="warning"
title={i18n.translate('xpack.enterpriseSearch.content.cannotConnect.title', {
defaultMessage: 'Cannot connect to Enterprise Search',
})}
>
<EuiSpacer size="s" />
<EuiText size="s">
<FormattedMessage
id="xpack.enterpriseSearch.content.searchIndex.cannotConnect.body"
defaultMessage="The Elastic web crawler requires Enterprise Search. {link}"
values={{
link: (
<EuiLinkTo to={ERROR_STATE_PATH}>
{i18n.translate('xpack.enterpriseSearch.content.cannotConnect.body', {
defaultMessage: 'More information.',
})}
</EuiLinkTo>
),
}}
/>
</EuiText>
</EuiCallOut>
);
};

View file

@ -18,6 +18,8 @@ import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { generateEncodedPath } from '../../../shared/encode_path_params';
import { ErrorStatePrompt } from '../../../shared/error_state';
import { HttpLogic } from '../../../shared/http';
import { KibanaLogic } from '../../../shared/kibana';
import { SEARCH_INDEX_PATH, SEARCH_INDEX_TAB_PATH } from '../../routes';
@ -65,6 +67,7 @@ export const SearchIndex: React.FC = () => {
}>();
const { indexName } = useValues(IndexNameLogic);
const { errorConnectingMessage } = useValues(HttpLogic);
/**
* Guided Onboarding needs us to mark the add data step as complete as soon as the user has data in an index.
@ -72,6 +75,7 @@ export const SearchIndex: React.FC = () => {
* Putting it here guarantees that if a user is viewing an index with data, it'll be marked as complete
*/
const {
config,
guidedOnboarding,
productAccess: { hasAppSearchAccess },
productFeatures: { hasDefaultIngestPipeline },
@ -216,6 +220,8 @@ export const SearchIndex: React.FC = () => {
>
{isCrawlerIndex(index) && !index.connector ? (
<NoConnectorRecord />
) : isCrawlerIndex(index) && (Boolean(errorConnectingMessage) || !config.host) ? (
<ErrorStatePrompt />
) : (
<>
{indexName === index?.name && (

View file

@ -30,12 +30,16 @@ import { AddContentEmptyPrompt } from '../../../shared/add_content_empty_prompt'
import { docLinks } from '../../../shared/doc_links';
import { ElasticsearchResources } from '../../../shared/elasticsearch_resources';
import { GettingStartedSteps } from '../../../shared/getting_started_steps';
import { EuiLinkTo } from '../../../shared/react_router_helpers';
import { HttpLogic } from '../../../shared/http/http_logic';
import { KibanaLogic } from '../../../shared/kibana';
import { EuiButtonTo, EuiLinkTo } from '../../../shared/react_router_helpers';
import { handlePageChange } from '../../../shared/table_pagination';
import { useLocalStorage } from '../../../shared/use_local_storage';
import { NEW_INDEX_PATH } from '../../routes';
import { EnterpriseSearchContentPageTemplate } from '../layout/page_template';
import { CannotConnect } from '../search_index/components/cannot_connect';
import { DeleteIndexModal } from './delete_index_modal';
import { IndicesLogic } from './indices_logic';
import { IndicesStats } from './indices_stats';
@ -55,6 +59,8 @@ export const SearchIndices: React.FC = () => {
const [showHiddenIndices, setShowHiddenIndices] = useState(false);
const [onlyShowSearchOptimizedIndices, setOnlyShowSearchOptimizedIndices] = useState(false);
const [searchQuery, setSearchValue] = useState('');
const { config } = useValues(KibanaLogic);
const { errorConnectingMessage } = useValues(HttpLogic);
const [calloutDismissed, setCalloutDismissed] = useLocalStorage<boolean>(
'enterprise-search-indices-callout-dismissed',
@ -123,6 +129,37 @@ export const SearchIndices: React.FC = () => {
],
}}
>
{config.host && config.canDeployEntSearch && errorConnectingMessage && (
<>
<CannotConnect />
<EuiSpacer />
</>
)}
{!config.host && config.canDeployEntSearch && (
<>
<EuiCallOut
title={i18n.translate('xpack.enterpriseSearch.noEntSearchConfigured.title', {
defaultMessage: 'Enterprise Search has not been configured',
})}
iconType="warning"
color="warning"
>
<p>
<FormattedMessage
id="xpack.enterpriseSearch.noEntSearch.noCrawler"
defaultMessage="The Elastic web crawler is not available without Enterprise Search."
/>
</p>
<EuiButtonTo iconType="help" fill to="/setup_guide" color="warning">
<FormattedMessage
id="xpack.enterpriseSearch.noEntSearch.setupGuideCta"
defaultMessage="Review setup guide"
/>
</EuiButtonTo>
</EuiCallOut>
<EuiSpacer />
</>
)}
{!hasNoIndices ? (
<EuiFlexGroup direction="column">
{!calloutDismissed && (
@ -259,7 +296,7 @@ export const SearchIndices: React.FC = () => {
<AddContentEmptyPrompt />
<EuiSpacer size="xxl" />
<>
<EuiTitle>
<EuiTitle data-test-subj="search-indices-empty-title">
<h2>
{i18n.translate(
'xpack.enterpriseSearch.content.searchIndices.searchIndices.stepsTitle',

View file

@ -12,22 +12,15 @@ import '../__mocks__/enterprise_search_url.mock';
import React from 'react';
import { Redirect } from 'react-router-dom';
import { shallow } from 'enzyme';
import { SetupGuide } from '../enterprise_search_overview/components/setup_guide';
import { VersionMismatchPage } from '../shared/version_mismatch';
import { ErrorConnecting } from './components/error_connecting';
import { SearchIndicesRouter } from './components/search_indices';
import { Settings } from './components/settings';
import {
EnterpriseSearchContent,
EnterpriseSearchContentUnconfigured,
EnterpriseSearchContentConfigured,
} from '.';
import { EnterpriseSearchContent, EnterpriseSearchContentConfigured } from '.';
describe('EnterpriseSearchContent', () => {
it('always renders the Setup Guide', () => {
@ -37,6 +30,7 @@ describe('EnterpriseSearchContent', () => {
});
it('renders VersionMismatchPage when there are mismatching versions', () => {
setMockValues({ config: { canDeployEntSearch: true, host: 'host' } });
const wrapper = shallow(
<EnterpriseSearchContent enterpriseSearchVersion="7.15.0" kibanaVersion="7.16.0" />
);
@ -44,21 +38,6 @@ describe('EnterpriseSearchContent', () => {
expect(wrapper.find(VersionMismatchPage)).toHaveLength(1);
});
it('renders EnterpriseSearchContentUnconfigured when config.host is not set', () => {
setMockValues({ config: { canDeployEntSearch: true, host: '' } });
const wrapper = shallow(<EnterpriseSearchContent />);
expect(wrapper.find(EnterpriseSearchContentUnconfigured)).toHaveLength(1);
});
it('renders ErrorConnecting when Enterprise Search is unavailable', () => {
setMockValues({ errorConnectingMessage: '502 Bad Gateway' });
const wrapper = shallow(<EnterpriseSearchContent />);
const errorConnection = wrapper.find(ErrorConnecting);
expect(errorConnection).toHaveLength(1);
});
it('renders EnterpriseSearchContentConfigured when config.host is set & available', () => {
setMockValues({
config: { canDeployEntSearch: true, host: 'some.url' },
@ -77,14 +56,6 @@ describe('EnterpriseSearchContent', () => {
});
});
describe('EnterpriseSearchContentUnconfigured', () => {
it('redirects to the Setup Guide', () => {
const wrapper = shallow(<EnterpriseSearchContentUnconfigured />);
expect(wrapper.find(Redirect)).toHaveLength(1);
});
});
describe('EnterpriseSearchContentConfigured', () => {
const wrapper = shallow(<EnterpriseSearchContentConfigured {...DEFAULT_INITIAL_APP_DATA} />);

View file

@ -15,15 +15,21 @@ import { Routes, Route } from '@kbn/shared-ux-router';
import { isVersionMismatch } from '../../../common/is_version_mismatch';
import { InitialAppData } from '../../../common/types';
import { SetupGuide } from '../enterprise_search_overview/components/setup_guide';
import { ErrorStatePrompt } from '../shared/error_state';
import { HttpLogic } from '../shared/http';
import { KibanaLogic } from '../shared/kibana';
import { VersionMismatchPage } from '../shared/version_mismatch';
import { ErrorConnecting } from './components/error_connecting';
import { NotFound } from './components/not_found';
import { SearchIndicesRouter } from './components/search_indices';
import { Settings } from './components/settings';
import { SETUP_GUIDE_PATH, ROOT_PATH, SEARCH_INDICES_PATH, SETTINGS_PATH } from './routes';
import {
SETUP_GUIDE_PATH,
ROOT_PATH,
SEARCH_INDICES_PATH,
SETTINGS_PATH,
ERROR_STATE_PATH,
} from './routes';
export const EnterpriseSearchContent: React.FC<InitialAppData> = (props) => {
const { config } = useValues(KibanaLogic);
@ -32,17 +38,13 @@ export const EnterpriseSearchContent: React.FC<InitialAppData> = (props) => {
const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion);
const showView = () => {
if (!config.host && config.canDeployEntSearch) {
return <EnterpriseSearchContentUnconfigured />;
} else if (incompatibleVersions) {
if (config.host && config.canDeployEntSearch && incompatibleVersions) {
return (
<VersionMismatchPage
enterpriseSearchVersion={enterpriseSearchVersion}
kibanaVersion={kibanaVersion}
/>
);
} else if (errorConnectingMessage) {
return <ErrorConnecting />;
}
return <EnterpriseSearchContentConfigured {...(props as Required<InitialAppData>)} />;
@ -53,19 +55,18 @@ export const EnterpriseSearchContent: React.FC<InitialAppData> = (props) => {
<Route exact path={SETUP_GUIDE_PATH}>
<SetupGuide />
</Route>
<Route exact path={ERROR_STATE_PATH}>
{config.host && config.canDeployEntSearch && errorConnectingMessage ? (
<ErrorStatePrompt />
) : (
<Redirect to={SEARCH_INDICES_PATH} />
)}
</Route>
<Route>{showView()}</Route>
</Routes>
);
};
export const EnterpriseSearchContentUnconfigured: React.FC = () => (
<Routes>
<Route>
<Redirect to={SETUP_GUIDE_PATH} />
</Route>
</Routes>
);
export const EnterpriseSearchContentConfigured: React.FC<Required<InitialAppData>> = () => {
return (
<Routes>

View file

@ -8,6 +8,7 @@
export const ROOT_PATH = '/';
export const SETUP_GUIDE_PATH = '/setup_guide';
export const ERROR_STATE_PATH = '/error_state';
export const SEARCH_INDICES_PATH = `${ROOT_PATH}search_indices`;
export const SETTINGS_PATH = `${ROOT_PATH}settings`;

View file

@ -59,13 +59,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
describe('Content', () => {
before(async () => {
await common.navigateToApp('enterprise_search/content');
await common.navigateToApp('enterprise_search/content/search_indices');
});
it('loads a setup guide', async function () {
it('loads the indices page', async function () {
await retry.waitFor(
'setup guide visible',
async () => await testSubjects.exists('setupGuide')
'create index button visible',
async () => await testSubjects.exists('entSearchContent-searchIndices-createButton')
);
await a11y.testAppSnapshot();
});

View file

@ -53,13 +53,13 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
// require.resolve('./apps/cross_cluster_replication'),
require.resolve('./apps/reporting'),
require.resolve('./apps/enterprise_search'),
require.resolve('./apps/license_management'),
require.resolve('./apps/tags'),
require.resolve('./apps/search_sessions'),
require.resolve('./apps/stack_monitoring'),
require.resolve('./apps/watcher'),
require.resolve('./apps/rollup_jobs'),
require.resolve('./apps/observability'),
// require.resolve('./apps/license_management'),
// require.resolve('./apps/tags'),
// require.resolve('./apps/search_sessions'),
// require.resolve('./apps/stack_monitoring'),
// require.resolve('./apps/watcher'),
// require.resolve('./apps/rollup_jobs'),
// require.resolve('./apps/observability'),
],
pageObjects,