[Enterprise Search] Support pages without ent-search (#152969)

## Summary

Introduces new config properties related to running `enterprise_search`
plugin without the `ent-search` service running. All three default to
`true` and retain the current behavior when they are set to `true`.

`canDeployEntSearch` - Setting to false will result in not showing the
Setup Guide for content pages when `config.host` is not set. Essentially
ungating indices, engines etc. pages.
`hasConnectors` - setting to `false` will result in hiding the connector
ingestion method when creating a new index.
`hasDefaultIngestPipeline` - setting to `false` will result in hiding
the index Pipelines and Content Settings page, until we can migrate
management of default ingest pipeline to the ES module
`hasNativeConnectors` - setting to `false` will result in hiding the
native connector ingestion method when creating a new index.
`hasWebCrawler` - setting to `false` will result in hiding the web
crawler ingestion method when creating a new index.

`hasNativeConnectors` & `hasWebCrawler` are intended to be temporary and
can be removed once we are sure the connectors service and web crawler
will be available in serverless.
This commit is contained in:
Rodney Norris 2023-03-14 12:09:27 -04:00 committed by GitHub
parent e7de8060d4
commit 425c236962
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 372 additions and 183 deletions

View file

@ -103,6 +103,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'data.search.sessions.management.refreshTimeout (duration)',
'data.search.sessions.maxUpdateRetries (number)',
'data.search.sessions.notTouchedTimeout (duration)',
'enterpriseSearch.canDeployEntSearch (boolean)',
'enterpriseSearch.host (string)',
'home.disableWelcomeScreen (boolean)',
'map.emsFileApiUrl (string)',

View file

@ -32,6 +32,13 @@ export const DEFAULT_INITIAL_APP_DATA = {
hasSearchEnginesAccess: false,
hasWorkplaceSearchAccess: true,
},
features: {
hasConnectors: true,
hasDefaultIngestPipeline: true,
hasNativeConnectors: true,
hasSearchApplications: false,
hasWebCrawler: true,
},
appSearch: {
accountId: 'some-id-string',
onboardingComplete: true,

View file

@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import { ProductFeatures } from './types';
import { IngestPipelineParams } from './types/connectors';
export const ENTERPRISE_SEARCH_OVERVIEW_PLUGIN = {
@ -161,3 +162,11 @@ export enum INGESTION_METHOD_IDS {
crawler = 'crawler',
native_connector = 'native_connector',
}
export const DEFAULT_PRODUCT_FEATURES: ProductFeatures = {
hasConnectors: true,
hasDefaultIngestPipeline: true,
hasNativeConnectors: true,
hasSearchApplications: false,
hasWebCrawler: true,
};

View file

@ -19,6 +19,7 @@ export interface InitialAppData {
appSearch?: AppSearchAccount;
configuredLimits?: ConfiguredLimits;
enterpriseSearchVersion?: string;
features?: ProductFeatures;
kibanaVersion?: string;
readOnlyMode?: boolean;
searchOAuth?: SearchOAuth;
@ -36,6 +37,14 @@ export interface ProductAccess {
hasWorkplaceSearchAccess: boolean;
}
export interface ProductFeatures {
hasConnectors: boolean;
hasDefaultIngestPipeline: boolean;
hasNativeConnectors: boolean;
hasSearchApplications: boolean;
hasWebCrawler: boolean;
}
export interface SearchOAuth {
clientId: string;
redirectUrl: string;
@ -52,5 +61,10 @@ export interface Meta {
page: MetaPage;
}
export interface ClientConfigType {
canDeployEntSearch: boolean;
host?: string;
}
export type { ElasticsearchIndexWithPrivileges } from './indices';
export type { KibanaDeps } from './kibana_deps';

View file

@ -37,6 +37,11 @@ export const mockKibanaValues = {
hasSearchEnginesAccess: false,
hasWorkplaceSearchAccess: true,
},
productFeatures: {
hasNativeConnectors: true,
hasSearchApplications: false,
hasWebCrawler: true,
},
uiSettings: uiSettingsServiceMock.createStartContract(),
security: securityMock.createStart(),
setBreadcrumbs: jest.fn(),

View file

@ -9,6 +9,8 @@ import React, { useState } from 'react';
import { useLocation } from 'react-router-dom';
import { useValues } from 'kea';
import {
EuiBadge,
EuiFlexGroup,
@ -22,7 +24,9 @@ import { i18n } from '@kbn/i18n';
import { INGESTION_METHOD_IDS } from '../../../../../common/constants';
import { ProductFeatures } from '../../../../../common/types';
import { BETA_LABEL } from '../../../shared/constants/labels';
import { KibanaLogic } from '../../../shared/kibana/kibana_logic';
import { parseQueryParams } from '../../../shared/query_params';
import { EuiLinkTo } from '../../../shared/react_router_helpers';
@ -41,8 +45,8 @@ const betaBadge = (
</EuiBadge>
);
const METHOD_BUTTON_GROUP_OPTIONS: ButtonGroupOption[] = [
{
const METHOD_BUTTON_GROUP_OPTIONS: Record<INGESTION_METHOD_IDS, ButtonGroupOption> = {
[INGESTION_METHOD_IDS.crawler]: {
description: i18n.translate(
'xpack.enterpriseSearch.content.newIndex.buttonGroup.crawler.description',
{
@ -58,7 +62,7 @@ const METHOD_BUTTON_GROUP_OPTIONS: ButtonGroupOption[] = [
defaultMessage: 'Use the web crawler',
}),
},
{
[INGESTION_METHOD_IDS.native_connector]: {
badge: betaBadge,
description: i18n.translate(
'xpack.enterpriseSearch.content.newIndex.buttonGroup.nativeConnector.description',
@ -82,7 +86,7 @@ const METHOD_BUTTON_GROUP_OPTIONS: ButtonGroupOption[] = [
}
),
},
{
[INGESTION_METHOD_IDS.api]: {
description: i18n.translate(
'xpack.enterpriseSearch.content.newIndex.buttonGroup.api.description',
{
@ -98,7 +102,7 @@ const METHOD_BUTTON_GROUP_OPTIONS: ButtonGroupOption[] = [
defaultMessage: 'Use the API',
}),
},
{
[INGESTION_METHOD_IDS.connector]: {
badge: betaBadge,
description: i18n.translate(
'xpack.enterpriseSearch.content.newIndex.buttonGroup.connector.description',
@ -116,15 +120,32 @@ const METHOD_BUTTON_GROUP_OPTIONS: ButtonGroupOption[] = [
defaultMessage: 'Build a connector',
}),
},
];
};
const getAvailableMethodOptions = (productFeatures: ProductFeatures): ButtonGroupOption[] => {
return [
...(productFeatures.hasWebCrawler
? [METHOD_BUTTON_GROUP_OPTIONS[INGESTION_METHOD_IDS.crawler]]
: []),
...(productFeatures.hasNativeConnectors
? [METHOD_BUTTON_GROUP_OPTIONS[INGESTION_METHOD_IDS.native_connector]]
: []),
METHOD_BUTTON_GROUP_OPTIONS[INGESTION_METHOD_IDS.api],
...(productFeatures.hasConnectors
? [METHOD_BUTTON_GROUP_OPTIONS[INGESTION_METHOD_IDS.connector]]
: []),
];
};
export const NewIndex: React.FC = () => {
const { search } = useLocation();
const { capabilities, productFeatures } = useValues(KibanaLogic);
const { method: methodParam } = parseQueryParams(search);
const availableIngestionMethodOptions = getAvailableMethodOptions(productFeatures);
const initialSelectedMethod =
METHOD_BUTTON_GROUP_OPTIONS.find((option) => option.id === methodParam) ??
METHOD_BUTTON_GROUP_OPTIONS[0];
availableIngestionMethodOptions.find((option) => option.id === methodParam) ??
availableIngestionMethodOptions[0];
const [selectedMethod, setSelectedMethod] = useState<ButtonGroupOption>(initialSelectedMethod);
@ -168,16 +189,20 @@ export const NewIndex: React.FC = () => {
</EuiText>
<EuiSpacer size="m" />
<ButtonGroup
options={METHOD_BUTTON_GROUP_OPTIONS}
options={availableIngestionMethodOptions}
selected={selectedMethod}
onChange={setSelectedMethod}
/>
<EuiSpacer size="xxl" />
<EuiLinkTo to="/app/integrations" shouldNotCreateHref>
{i18n.translate('xpack.enterpriseSearch.content.newIndex.viewIntegrationsLink', {
defaultMessage: 'View additional integrations',
})}
</EuiLinkTo>
{capabilities.navLinks.integrations && (
<>
<EuiSpacer size="xxl" />
<EuiLinkTo to="/app/integrations" shouldNotCreateHref>
{i18n.translate('xpack.enterpriseSearch.content.newIndex.viewIntegrationsLink', {
defaultMessage: 'View additional integrations',
})}
</EuiLinkTo>
</>
)}
</EuiPanel>
</EuiFlexItem>
<EuiFlexItem>

View file

@ -16,6 +16,9 @@ import { flashIndexCreatedToast } from './new_index_created_toast';
import { NewSearchIndexLogic, NewSearchIndexValues } from './new_search_index_logic';
jest.mock('./new_index_created_toast', () => ({ flashIndexCreatedToast: jest.fn() }));
jest.mock('../../../shared/kibana/kibana_logic', () => ({
KibanaLogic: { values: { productAccess: { hasAppSearchAccess: true } } },
}));
const DEFAULT_VALUES: NewSearchIndexValues = {
data: undefined as any,
@ -35,14 +38,12 @@ describe('NewSearchIndexLogic', () => {
});
it('has expected default values', () => {
mount();
expect(NewSearchIndexLogic.values).toEqual(DEFAULT_VALUES);
});
describe('actions', () => {
describe('setLanguageSelectValue', () => {
it('sets language to the provided value', () => {
mount();
NewSearchIndexLogic.actions.setLanguageSelectValue('en');
expect(NewSearchIndexLogic.values).toEqual({
...DEFAULT_VALUES,

View file

@ -8,6 +8,7 @@
import { kea, MakeLogicType } from 'kea';
import { Actions } from '../../../shared/api_logic/create_api_logic';
import { KibanaLogic } from '../../../shared/kibana/kibana_logic';
import {
AddConnectorApiLogic,
AddConnectorApiLogicArgs,
@ -84,12 +85,15 @@ export const NewSearchIndexLogic = kea<MakeLogicType<NewSearchIndexValues, NewSe
},
listeners: ({ actions, values }) => ({
apiIndexCreated: () => {
if (!KibanaLogic.values.productAccess.hasAppSearchAccess) return;
flashIndexCreatedToast();
},
connectorIndexCreated: () => {
if (!KibanaLogic.values.productAccess.hasAppSearchAccess) return;
flashIndexCreatedToast();
},
crawlerIndexCreated: () => {
if (!KibanaLogic.values.productAccess.hasAppSearchAccess) return;
flashIndexCreatedToast();
},
setRawName: async (_, breakpoint) => {

View file

@ -17,12 +17,13 @@ import { SyncsContextMenu } from './syncs_context_menu';
describe('Header Actions', () => {
it('renders api index', () => {
expect(getHeaderActions(apiIndex)).toEqual([
expect(getHeaderActions(apiIndex, true)).toEqual([
<SearchEnginesPopover indexName="api" ingestionMethod="api" isHiddenIndex={false} />,
]);
expect(getHeaderActions(apiIndex, false)).toEqual([]);
});
it('renders connector index', () => {
expect(getHeaderActions(connectorIndex)).toEqual([
expect(getHeaderActions(connectorIndex, true)).toEqual([
<SyncsContextMenu />,
<SearchEnginesPopover
indexName="connector"
@ -30,11 +31,13 @@ describe('Header Actions', () => {
isHiddenIndex={false}
/>,
]);
expect(getHeaderActions(connectorIndex, false)).toEqual([<SyncsContextMenu />]);
});
it('renders crawler index', () => {
expect(getHeaderActions(crawlerIndex)).toEqual([
expect(getHeaderActions(crawlerIndex, true)).toEqual([
<CrawlerStatusIndicator />,
<SearchEnginesPopover indexName="crawler" ingestionMethod="crawler" isHiddenIndex={false} />,
]);
expect(getHeaderActions(crawlerIndex, false)).toEqual([<CrawlerStatusIndicator />]);
});
});

View file

@ -15,15 +15,22 @@ import { SearchEnginesPopover } from './search_engines_popover';
import { SyncsContextMenu } from './syncs_context_menu';
// Used to populate rightSideItems of an EuiPageTemplate, which is rendered right-to-left
export const getHeaderActions = (indexData?: ElasticsearchIndexWithIngestion) => {
export const getHeaderActions = (
indexData: ElasticsearchIndexWithIngestion | undefined,
hasAppSearchAccess: boolean
) => {
const ingestionMethod = getIngestionMethod(indexData);
return [
...(isCrawlerIndex(indexData) && indexData.connector ? [<CrawlerStatusIndicator />] : []),
...(isConnectorIndex(indexData) ? [<SyncsContextMenu />] : []),
<SearchEnginesPopover
indexName={indexData?.name}
ingestionMethod={ingestionMethod}
isHiddenIndex={indexData?.hidden}
/>,
...(hasAppSearchAccess
? [
<SearchEnginesPopover
indexName={indexData?.name}
ingestionMethod={ingestionMethod}
isHiddenIndex={indexData?.hidden}
/>,
]
: []),
];
};

View file

@ -46,6 +46,7 @@ export const ManageKeysPopover: React.FC = () => {
size="s"
items={[
<EuiContextMenuItem
key="viewApiKeys"
data-telemetry-id={`entSearchContent-${ingestionMethod}-overview-generateApiKeys-viewApiKeys`}
icon="eye"
onClick={() =>
@ -64,6 +65,7 @@ export const ManageKeysPopover: React.FC = () => {
</EuiText>
</EuiContextMenuItem>,
<EuiContextMenuItem
key="createNewApiKey"
data-telemetry-id={`entSearchContent-${ingestionMethod}-overview-generateApiKeys-createNewApiKey`}
icon="plusInCircle"
onClick={openGenerateModal}

View file

@ -75,7 +75,11 @@ export const SearchIndex: React.FC = () => {
* This needs to be checked for any of the 3 registered search guideIds
* Putting it here guarantees that if a user is viewing an index with data, it'll be marked as complete
*/
const { guidedOnboarding } = useValues(KibanaLogic);
const {
guidedOnboarding,
productAccess: { hasAppSearchAccess },
productFeatures: { hasDefaultIngestPipeline },
} = useValues(KibanaLogic);
const isAppGuideActive = useObservable(
guidedOnboarding.guidedOnboardingApi!.isGuideStepActive$('appSearch', 'add_data')
);
@ -199,7 +203,7 @@ export const SearchIndex: React.FC = () => {
...ALL_INDICES_TABS,
...(isConnectorIndex(index) ? CONNECTOR_TABS : []),
...(isCrawlerIndex(index) ? CRAWLER_TABS : []),
PIPELINES_TAB,
...(hasDefaultIngestPipeline ? [PIPELINES_TAB] : []),
];
const selectedTab = tabs.find((tab) => tab.id === tabId);
@ -222,7 +226,7 @@ export const SearchIndex: React.FC = () => {
isLoading={isInitialLoading}
pageHeader={{
pageTitle: indexName,
rightSideItems: getHeaderActions(index),
rightSideItems: getHeaderActions(index, hasAppSearchAccess),
}}
>
{isCrawlerIndex(index) && !index.connector ? (

View file

@ -14,6 +14,7 @@ import { Status } from '../../../../../common/types/api';
import { IngestPipelineParams } from '../../../../../common/types/connectors';
import { Actions } from '../../../shared/api_logic/create_api_logic';
import { KibanaLogic } from '../../../shared/kibana';
import {
FetchDefaultPipelineApiLogic,
@ -70,6 +71,7 @@ export const SettingsLogic = kea<MakeLogicType<PipelinesValues, PipelinesActions
},
events: ({ actions }) => ({
afterMount: () => {
if (KibanaLogic.values.productFeatures.hasDefaultIngestPipeline === false) return;
actions.fetchDefaultPipeline(undefined);
},
}),

View file

@ -45,7 +45,7 @@ describe('EnterpriseSearchContent', () => {
});
it('renders EnterpriseSearchContentUnconfigured when config.host is not set', () => {
setMockValues({ config: { host: '' } });
setMockValues({ config: { canDeployEntSearch: true, host: '' } });
const wrapper = shallow(<EnterpriseSearchContent />);
expect(wrapper.find(EnterpriseSearchContentUnconfigured)).toHaveLength(1);
@ -60,7 +60,17 @@ describe('EnterpriseSearchContent', () => {
});
it('renders EnterpriseSearchContentConfigured when config.host is set & available', () => {
setMockValues({ errorConnectingMessage: '', config: { host: 'some.url' } });
setMockValues({
config: { canDeployEntSearch: true, host: 'some.url' },
errorConnectingMessage: '',
});
const wrapper = shallow(<EnterpriseSearchContent />);
expect(wrapper.find(EnterpriseSearchContentConfigured)).toHaveLength(1);
});
it('renders EnterpriseSearchContentConfigured when config.host is not set & Ent Search cannot be deployed', () => {
setMockValues({ errorConnectingMessage: '', config: { canDeployEntSearch: false, host: '' } });
const wrapper = shallow(<EnterpriseSearchContent />);
expect(wrapper.find(EnterpriseSearchContentConfigured)).toHaveLength(1);

View file

@ -39,7 +39,7 @@ export const EnterpriseSearchContent: React.FC<InitialAppData> = (props) => {
const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion);
const showView = () => {
if (!config.host) {
if (!config.host && config.canDeployEntSearch) {
return <EnterpriseSearchContentUnconfigured />;
} else if (incompatibleVersions) {
return (

View file

@ -28,7 +28,7 @@ const props = {
describe('ProductSelector', () => {
it('renders the overview page, product cards, & setup guide CTAs with no host set', () => {
setMockValues({ config: { host: '' } });
setMockValues({ config: { canDeployEntSearch: true, host: '' } });
const wrapper = shallow(<ProductSelector {...props} />);
expect(wrapper.find(ProductCard)).toHaveLength(3);
@ -36,14 +36,14 @@ describe('ProductSelector', () => {
});
it('renders the trial callout', () => {
setMockValues({ config: { host: 'localhost' } });
setMockValues({ config: { canDeployEntSearch: true, host: 'localhost' } });
const wrapper = shallow(<ProductSelector {...props} />);
expect(wrapper.find(TrialCallout)).toHaveLength(1);
});
it('passes correct URL when Workplace Search user is not an admin', () => {
setMockValues({ config: { host: '' } });
setMockValues({ config: { canDeployEntSearch: true, host: '' } });
const wrapper = shallow(<ProductSelector {...props} isWorkplaceSearchAdmin={false} />);
expect(wrapper.find(ProductCard).last().prop('url')).toEqual(
@ -53,7 +53,7 @@ describe('ProductSelector', () => {
describe('access checks when host is set', () => {
beforeEach(() => {
setMockValues({ config: { host: 'localhost' } });
setMockValues({ config: { canDeployEntSearch: true, host: 'localhost' } });
});
it('does not render the App Search card if the user does not have access to AS', () => {

View file

@ -56,12 +56,14 @@ export const ProductSelector: React.FC<ProductSelectorProps> = ({
const { config } = useValues(KibanaLogic);
// If Enterprise Search hasn't been set up yet, show all products. Otherwise, only show products the user has access to
const shouldShowAppSearchCard = !config.host || hasAppSearchAccess;
const shouldShowWorkplaceSearchCard = !config.host || hasWorkplaceSearchAccess;
const shouldShowAppSearchCard = (!config.host && config.canDeployEntSearch) || hasAppSearchAccess;
const shouldShowWorkplaceSearchCard =
(!config.host && config.canDeployEntSearch) || hasWorkplaceSearchAccess;
// If Enterprise Search has been set up and the user does not have access to either product, show a message saying they
// need to contact an administrator to get access to one of the products.
const shouldShowEnterpriseSearchCards = shouldShowAppSearchCard || shouldShowWorkplaceSearchCard;
const shouldShowEnterpriseSearchCards =
shouldShowAppSearchCard || shouldShowWorkplaceSearchCard || !config.canDeployEntSearch;
const WORKPLACE_SEARCH_URL = isWorkplaceSearchAdmin
? WORKPLACE_SEARCH_PLUGIN.URL
@ -297,8 +299,12 @@ export const ProductSelector: React.FC<ProductSelectorProps> = ({
/>
</EuiFlexItem>
)}
{!config.host && config.canDeployEntSearch && (
<EuiFlexItem>
<SetupGuideCta />
</EuiFlexItem>
)}
</EuiFlexGroup>
{!config.host && <SetupGuideCta />}
</>
);

View file

@ -18,8 +18,9 @@ import { I18nProvider } from '@kbn/i18n-react';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { InitialAppData, ProductAccess } from '../../common/types';
import { PluginsStart, ClientConfigType, ClientData } from '../plugin';
import { DEFAULT_PRODUCT_FEATURES } from '../../common/constants';
import { ClientConfigType, InitialAppData, ProductAccess } from '../../common/types';
import { PluginsStart, ClientData } from '../plugin';
import { externalUrl } from './shared/enterprise_search_url';
import { mountFlashMessagesLogic, Toasts } from './shared/flash_messages';
@ -49,6 +50,7 @@ export const renderApp = (
hasWorkplaceSearchAccess: false,
};
const productAccess = data.access || noProductAccess;
const productFeatures = data.features ?? { ...DEFAULT_PRODUCT_FEATURES };
const EmptyContext: FC = ({ children }) => <>{children}</>;
const CloudContext = plugins.cloud?.CloudContextProvider || EmptyContext;
@ -61,6 +63,7 @@ export const renderApp = (
capabilities: core.application.capabilities,
config,
productAccess,
productFeatures,
charts: plugins.charts,
cloud: plugins.cloud,
uiSettings: core.uiSettings,

View file

@ -19,7 +19,7 @@ describe('KibanaLogic', () => {
describe('mounts', () => {
it('sets values from props', () => {
mountKibanaLogic(mockKibanaValues);
mountKibanaLogic(mockKibanaValues as any);
expect(KibanaLogic.values).toEqual({
...mockKibanaValues,
@ -41,7 +41,7 @@ describe('KibanaLogic', () => {
});
describe('navigateToUrl()', () => {
beforeEach(() => mountKibanaLogic(mockKibanaValues));
beforeEach(() => mountKibanaLogic(mockKibanaValues as any));
it('runs paths through createHref before calling navigateToUrl', () => {
KibanaLogic.values.navigateToUrl('/test');

View file

@ -21,7 +21,7 @@ import {
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
import { SecurityPluginStart } from '@kbn/security-plugin/public';
import { ProductAccess } from '../../../../common/types';
import { ClientConfigType, ProductAccess, ProductFeatures } from '../../../../common/types';
import { HttpLogic } from '../http';
import { createHref, CreateHrefOptions } from '../react_router_helpers';
@ -31,8 +31,9 @@ type RequiredFieldsOnly<T> = {
};
interface KibanaLogicProps {
application: ApplicationStart;
config: { host?: string };
config: ClientConfigType;
productAccess: ProductAccess;
productFeatures: ProductFeatures;
// Kibana core
capabilities: Capabilities;
history: ScopedHistory;
@ -74,6 +75,7 @@ export const KibanaLogic = kea<MakeLogicType<KibanaValues>>({
{},
],
productAccess: [props.productAccess, {}],
productFeatures: [props.productFeatures, {}],
renderHeaderActions: [props.renderHeaderActions, {}],
security: [props.security, {}],
setBreadcrumbs: [props.setBreadcrumbs, {}],

View file

@ -13,10 +13,17 @@ import { setMockValues, mockKibanaValues } from '../../__mocks__/kea_logic';
import { EuiSideNavItemType } from '@elastic/eui';
import { DEFAULT_PRODUCT_FEATURES } from '../../../../common/constants';
import { ProductAccess } from '../../../../common/types';
import { useEnterpriseSearchNav, useEnterpriseSearchEngineNav } from './nav';
const DEFAULT_PRODUCT_ACCESS: ProductAccess = {
hasAppSearchAccess: true,
hasSearchEnginesAccess: false,
hasWorkplaceSearchAccess: true,
};
describe('useEnterpriseSearchContentNav', () => {
beforeEach(() => {
jest.clearAllMocks();
@ -24,12 +31,8 @@ describe('useEnterpriseSearchContentNav', () => {
});
it('returns an array of top-level Enterprise Search nav items', () => {
const fullProductAccess: ProductAccess = {
hasAppSearchAccess: true,
hasSearchEnginesAccess: false,
hasWorkplaceSearchAccess: true,
};
setMockValues({ productAccess: fullProductAccess });
const fullProductAccess: ProductAccess = DEFAULT_PRODUCT_ACCESS;
setMockValues({ productAccess: fullProductAccess, productFeatures: DEFAULT_PRODUCT_FEATURES });
expect(useEnterpriseSearchNav()).toEqual([
{
@ -96,16 +99,16 @@ describe('useEnterpriseSearchContentNav', () => {
it('excludes legacy products when the user has no access to them', () => {
const noProductAccess: ProductAccess = {
...DEFAULT_PRODUCT_ACCESS,
hasAppSearchAccess: false,
hasSearchEnginesAccess: false,
hasWorkplaceSearchAccess: false,
};
setMockValues({ productAccess: noProductAccess });
setMockValues({ productAccess: noProductAccess, productFeatures: DEFAULT_PRODUCT_FEATURES });
mockKibanaValues.uiSettings.get.mockReturnValue(false);
const esNav = useEnterpriseSearchNav();
const searchNav = esNav.find((item) => item.id === 'search');
const searchNav = esNav?.find((item) => item.id === 'search');
expect(searchNav).not.toBeUndefined();
expect(searchNav).toEqual({
id: 'search',
@ -127,15 +130,18 @@ describe('useEnterpriseSearchContentNav', () => {
it('excludes App Search when the user has no access to it', () => {
const workplaceSearchProductAccess: ProductAccess = {
...DEFAULT_PRODUCT_ACCESS,
hasAppSearchAccess: false,
hasSearchEnginesAccess: false,
hasWorkplaceSearchAccess: true,
};
setMockValues({ productAccess: workplaceSearchProductAccess });
setMockValues({
productAccess: workplaceSearchProductAccess,
productFeatures: DEFAULT_PRODUCT_FEATURES,
});
const esNav = useEnterpriseSearchNav();
const searchNav = esNav.find((item) => item.id === 'search');
const searchNav = esNav?.find((item) => item.id === 'search');
expect(searchNav).not.toBeUndefined();
expect(searchNav).toEqual({
id: 'search',
@ -162,15 +168,17 @@ describe('useEnterpriseSearchContentNav', () => {
it('excludes Workplace Search when the user has no access to it', () => {
const appSearchProductAccess: ProductAccess = {
hasAppSearchAccess: true,
hasSearchEnginesAccess: false,
...DEFAULT_PRODUCT_ACCESS,
hasWorkplaceSearchAccess: false,
};
setMockValues({ productAccess: appSearchProductAccess });
setMockValues({
productAccess: appSearchProductAccess,
productFeatures: DEFAULT_PRODUCT_FEATURES,
});
const esNav = useEnterpriseSearchNav();
const searchNav = esNav.find((item) => item.id === 'search');
const searchNav = esNav?.find((item) => item.id === 'search');
expect(searchNav).not.toBeUndefined();
expect(searchNav).toEqual({
id: 'search',
@ -196,15 +204,11 @@ describe('useEnterpriseSearchContentNav', () => {
});
it('excludes engines when feature flag is off', () => {
const fullProductAccess: ProductAccess = {
hasAppSearchAccess: true,
hasSearchEnginesAccess: false,
hasWorkplaceSearchAccess: true,
};
setMockValues({ productAccess: fullProductAccess });
const fullProductAccess: ProductAccess = DEFAULT_PRODUCT_ACCESS;
setMockValues({ productAccess: fullProductAccess, productFeatures: DEFAULT_PRODUCT_FEATURES });
const esNav = useEnterpriseSearchNav();
expect(esNav.find((item) => item.id === 'enginesSearch')).toBeUndefined();
expect(esNav?.find((item) => item.id === 'enginesSearch')).toBeUndefined();
});
});
@ -215,11 +219,10 @@ describe('useEnterpriseSearchContentNav Engines feature flag', () => {
it('returns an array of top-level Enterprise Search nav items', () => {
const fullProductAccess: ProductAccess = {
hasAppSearchAccess: true,
...DEFAULT_PRODUCT_ACCESS,
hasSearchEnginesAccess: true,
hasWorkplaceSearchAccess: true,
};
setMockValues({ productAccess: fullProductAccess });
setMockValues({ productAccess: fullProductAccess, productFeatures: DEFAULT_PRODUCT_FEATURES });
expect(useEnterpriseSearchNav()).toEqual([
{
@ -297,25 +300,26 @@ describe('useEnterpriseSearchContentNav Engines feature flag', () => {
it('excludes standalone experiences when the user has no access to them', () => {
const fullProductAccess: ProductAccess = {
...DEFAULT_PRODUCT_ACCESS,
hasAppSearchAccess: false,
hasSearchEnginesAccess: true,
hasWorkplaceSearchAccess: false,
};
setMockValues({ productAccess: fullProductAccess });
setMockValues({ productAccess: fullProductAccess, productFeatures: DEFAULT_PRODUCT_FEATURES });
const esNav = useEnterpriseSearchNav();
expect(esNav.find((item) => item.id === 'standaloneExperiences')).toBeUndefined();
expect(esNav?.find((item) => item.id === 'standaloneExperiences')).toBeUndefined();
});
it('excludes App Search when the user has no access to it', () => {
const fullProductAccess: ProductAccess = {
...DEFAULT_PRODUCT_ACCESS,
hasAppSearchAccess: false,
hasSearchEnginesAccess: true,
hasWorkplaceSearchAccess: true,
};
setMockValues({ productAccess: fullProductAccess });
setMockValues({ productAccess: fullProductAccess, productFeatures: DEFAULT_PRODUCT_FEATURES });
const esNav = useEnterpriseSearchNav();
const standAloneNav = esNav.find((item) => item.id === 'standaloneExperiences');
const standAloneNav = esNav?.find((item) => item.id === 'standaloneExperiences');
expect(standAloneNav).not.toBeUndefined();
expect(standAloneNav).toEqual({
id: 'standaloneExperiences',
@ -331,14 +335,15 @@ describe('useEnterpriseSearchContentNav Engines feature flag', () => {
});
it('excludes Workplace Search when the user has no access to it', () => {
const fullProductAccess: ProductAccess = {
...DEFAULT_PRODUCT_ACCESS,
hasAppSearchAccess: true,
hasSearchEnginesAccess: true,
hasWorkplaceSearchAccess: false,
};
setMockValues({ productAccess: fullProductAccess });
setMockValues({ productAccess: fullProductAccess, productFeatures: DEFAULT_PRODUCT_FEATURES });
const esNav = useEnterpriseSearchNav();
const standAloneNav = esNav.find((item) => item.id === 'standaloneExperiences');
const standAloneNav = esNav?.find((item) => item.id === 'standaloneExperiences');
expect(standAloneNav).not.toBeUndefined();
expect(standAloneNav).toEqual({
id: 'standaloneExperiences',
@ -359,11 +364,10 @@ describe('useEnterpriseSearchEngineNav', () => {
jest.clearAllMocks();
mockKibanaValues.uiSettings.get.mockReturnValue(true);
const fullProductAccess: ProductAccess = {
hasAppSearchAccess: true,
...DEFAULT_PRODUCT_ACCESS,
hasSearchEnginesAccess: true,
hasWorkplaceSearchAccess: true,
};
setMockValues({ productAccess: fullProductAccess });
setMockValues({ productAccess: fullProductAccess, productFeatures: DEFAULT_PRODUCT_FEATURES });
});
it('returns an array of top-level Enterprise Search nav items', () => {
@ -444,14 +448,14 @@ describe('useEnterpriseSearchEngineNav', () => {
it('returns selected engine sub nav items', () => {
const engineName = 'my-test-engine';
const navItems = useEnterpriseSearchEngineNav(engineName);
expect(navItems.map((ni) => ni.name)).toEqual([
expect(navItems?.map((ni) => ni.name)).toEqual([
'Overview',
'Content',
'Search',
'Behavioral Analytics',
'Standalone Experiences',
]);
const searchItem = navItems.find((ni) => ni.id === 'enginesSearch');
const searchItem = navItems?.find((ni) => ni.id === 'enginesSearch');
expect(searchItem).not.toBeUndefined();
expect(searchItem!.items).not.toBeUndefined();
// @ts-ignore
@ -501,14 +505,14 @@ describe('useEnterpriseSearchEngineNav', () => {
it('returns selected engine without tabs when isEmpty', () => {
const engineName = 'my-test-engine';
const navItems = useEnterpriseSearchEngineNav(engineName, true);
expect(navItems.map((ni) => ni.name)).toEqual([
expect(navItems?.map((ni) => ni.name)).toEqual([
'Overview',
'Content',
'Search',
'Behavioral Analytics',
'Standalone Experiences',
]);
const searchItem = navItems.find((ni) => ni.id === 'enginesSearch');
const searchItem = navItems?.find((ni) => ni.id === 'enginesSearch');
expect(searchItem).not.toBeUndefined();
expect(searchItem!.items).not.toBeUndefined();
// @ts-ignore

View file

@ -30,7 +30,7 @@ import { KibanaLogic } from '../kibana';
import { generateNavLink } from './nav_link_helpers';
export const useEnterpriseSearchNav = () => {
const { productAccess } = useValues(KibanaLogic);
const { productAccess, productFeatures } = useValues(KibanaLogic);
const enginesSectionEnabled = productAccess.hasSearchEnginesAccess;
@ -59,17 +59,21 @@ export const useEnterpriseSearchNav = () => {
to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + SEARCH_INDICES_PATH,
}),
},
{
id: 'settings',
name: i18n.translate('xpack.enterpriseSearch.nav.contentSettingsTitle', {
defaultMessage: 'Settings',
}),
...generateNavLink({
shouldNotCreateHref: true,
shouldShowActiveForSubroutes: true,
to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + SETTINGS_PATH,
}),
},
...(productFeatures.hasDefaultIngestPipeline
? [
{
id: 'settings',
name: i18n.translate('xpack.enterpriseSearch.nav.contentSettingsTitle', {
defaultMessage: 'Settings',
}),
...generateNavLink({
shouldNotCreateHref: true,
shouldShowActiveForSubroutes: true,
to: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL + SETTINGS_PATH,
}),
},
]
: []),
],
name: i18n.translate('xpack.enterpriseSearch.nav.contentTitle', {
defaultMessage: 'Content',

View file

@ -32,14 +32,10 @@ import {
WORKPLACE_SEARCH_PLUGIN,
SEARCH_EXPERIENCES_PLUGIN,
} from '../common/constants';
import { InitialAppData } from '../common/types';
import { ClientConfigType, InitialAppData } from '../common/types';
import { docLinks } from './applications/shared/doc_links';
export interface ClientConfigType {
host?: string;
}
export interface ClientData extends InitialAppData {
publicUrl?: string;
errorConnectingMessage?: string;
@ -71,6 +67,7 @@ export class EnterpriseSearchPlugin implements Plugin {
public setup(core: CoreSetup, plugins: PluginsSetup) {
const { cloud } = plugins;
const { config } = this;
core.application.register({
id: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.ID,
@ -162,50 +159,52 @@ export class EnterpriseSearchPlugin implements Plugin {
},
});
core.application.register({
id: APP_SEARCH_PLUGIN.ID,
title: APP_SEARCH_PLUGIN.NAME,
euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO,
appRoute: APP_SEARCH_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
mount: async (params: AppMountParameters) => {
const kibanaDeps = await this.getKibanaDeps(core, params, cloud);
const { chrome, http } = kibanaDeps.core;
chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME);
if (config.canDeployEntSearch) {
core.application.register({
id: APP_SEARCH_PLUGIN.ID,
title: APP_SEARCH_PLUGIN.NAME,
euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO,
appRoute: APP_SEARCH_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
mount: async (params: AppMountParameters) => {
const kibanaDeps = await this.getKibanaDeps(core, params, cloud);
const { chrome, http } = kibanaDeps.core;
chrome.docTitle.change(APP_SEARCH_PLUGIN.NAME);
await this.getInitialData(http);
const pluginData = this.getPluginData();
await this.getInitialData(http);
const pluginData = this.getPluginData();
const { renderApp } = await import('./applications');
const { AppSearch } = await import('./applications/app_search');
const { renderApp } = await import('./applications');
const { AppSearch } = await import('./applications/app_search');
return renderApp(AppSearch, kibanaDeps, pluginData);
},
});
return renderApp(AppSearch, kibanaDeps, pluginData);
},
});
core.application.register({
id: WORKPLACE_SEARCH_PLUGIN.ID,
title: WORKPLACE_SEARCH_PLUGIN.NAME,
euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO,
appRoute: WORKPLACE_SEARCH_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
mount: async (params: AppMountParameters) => {
const kibanaDeps = await this.getKibanaDeps(core, params, cloud);
const { chrome, http } = kibanaDeps.core;
chrome.docTitle.change(WORKPLACE_SEARCH_PLUGIN.NAME);
core.application.register({
id: WORKPLACE_SEARCH_PLUGIN.ID,
title: WORKPLACE_SEARCH_PLUGIN.NAME,
euiIconType: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.LOGO,
appRoute: WORKPLACE_SEARCH_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
mount: async (params: AppMountParameters) => {
const kibanaDeps = await this.getKibanaDeps(core, params, cloud);
const { chrome, http } = kibanaDeps.core;
chrome.docTitle.change(WORKPLACE_SEARCH_PLUGIN.NAME);
// The Workplace Search Personal dashboard needs the chrome hidden. We hide it globally
// here first to prevent a flash of chrome on the Personal dashboard and unhide it for admin routes.
if (this.config.host) chrome.setIsVisible(false);
await this.getInitialData(http);
const pluginData = this.getPluginData();
// The Workplace Search Personal dashboard needs the chrome hidden. We hide it globally
// here first to prevent a flash of chrome on the Personal dashboard and unhide it for admin routes.
if (this.config.host) chrome.setIsVisible(false);
await this.getInitialData(http);
const pluginData = this.getPluginData();
const { renderApp } = await import('./applications');
const { WorkplaceSearch } = await import('./applications/workplace_search');
const { renderApp } = await import('./applications');
const { WorkplaceSearch } = await import('./applications/workplace_search');
return renderApp(WorkplaceSearch, kibanaDeps, pluginData);
},
});
return renderApp(WorkplaceSearch, kibanaDeps, pluginData);
},
});
}
core.application.register({
id: SEARCH_EXPERIENCES_PLUGIN.ID,
@ -248,15 +247,27 @@ export class EnterpriseSearchPlugin implements Plugin {
showOnHomePage: false,
});
plugins.home.featureCatalogue.register({
id: APP_SEARCH_PLUGIN.ID,
title: APP_SEARCH_PLUGIN.NAME,
icon: 'appSearchApp',
description: APP_SEARCH_PLUGIN.DESCRIPTION,
path: APP_SEARCH_PLUGIN.URL,
category: 'data',
showOnHomePage: false,
});
if (config.canDeployEntSearch) {
plugins.home.featureCatalogue.register({
id: APP_SEARCH_PLUGIN.ID,
title: APP_SEARCH_PLUGIN.NAME,
icon: 'appSearchApp',
description: APP_SEARCH_PLUGIN.DESCRIPTION,
path: APP_SEARCH_PLUGIN.URL,
category: 'data',
showOnHomePage: false,
});
plugins.home.featureCatalogue.register({
id: WORKPLACE_SEARCH_PLUGIN.ID,
title: WORKPLACE_SEARCH_PLUGIN.NAME,
icon: 'workplaceSearchApp',
description: WORKPLACE_SEARCH_PLUGIN.DESCRIPTION,
path: WORKPLACE_SEARCH_PLUGIN.URL,
category: 'data',
showOnHomePage: false,
});
}
plugins.home.featureCatalogue.register({
id: ELASTICSEARCH_PLUGIN.ID,
@ -268,16 +279,6 @@ export class EnterpriseSearchPlugin implements Plugin {
showOnHomePage: false,
});
plugins.home.featureCatalogue.register({
id: WORKPLACE_SEARCH_PLUGIN.ID,
title: WORKPLACE_SEARCH_PLUGIN.NAME,
icon: 'workplaceSearchApp',
description: WORKPLACE_SEARCH_PLUGIN.DESCRIPTION,
path: WORKPLACE_SEARCH_PLUGIN.URL,
category: 'data',
showOnHomePage: false,
});
plugins.home.featureCatalogue.register({
id: SEARCH_EXPERIENCES_PLUGIN.ID,
title: SEARCH_EXPERIENCES_PLUGIN.NAME,
@ -321,7 +322,7 @@ export class EnterpriseSearchPlugin implements Plugin {
}
private async getInitialData(http: HttpSetup) {
if (!this.config.host) return; // No API to call
if (!this.config.host && this.config.canDeployEntSearch) return; // No API to call
if (this.hasInitialized) return; // We've already made an initial call
try {

View file

@ -17,7 +17,12 @@ export const plugin = (initializerContext: PluginInitializerContext) => {
export const configSchema = schema.object({
accessCheckTimeout: schema.number({ defaultValue: 5000 }),
accessCheckTimeoutWarning: schema.number({ defaultValue: 300 }),
canDeployEntSearch: schema.boolean({ defaultValue: true }),
customHeaders: schema.maybe(schema.object({}, { unknowns: 'allow' })),
hasConnectors: schema.boolean({ defaultValue: true }),
hasDefaultIngestPipeline: schema.boolean({ defaultValue: true }),
hasNativeConnectors: schema.boolean({ defaultValue: true }),
hasWebCrawler: schema.boolean({ defaultValue: true }),
host: schema.maybe(schema.string()),
ssl: schema.object({
certificateAuthorities: schema.maybe(
@ -34,6 +39,7 @@ export type ConfigType = TypeOf<typeof configSchema>;
export const config: PluginConfigDescriptor<ConfigType> = {
exposeToBrowser: {
canDeployEntSearch: true,
host: true,
},
schema: configSchema,

View file

@ -47,7 +47,10 @@ describe('checkAccess', () => {
const mockSpaces = spacesMock.createStart();
const mockDependencies = {
request: { auth: { isAuthenticated: true } },
config: { host: 'http://localhost:3002' },
config: {
canDeployEntSearch: true,
host: 'http://localhost:3002',
},
security: mockSecurity,
spaces: mockSpaces,
} as any;

View file

@ -29,6 +29,8 @@ describe('callEnterpriseSearchConfigAPI', () => {
host: 'http://localhost:3002',
accessCheckTimeout: 200,
accessCheckTimeoutWarning: 100,
hasNativeConnectors: true,
hasWebCrawler: true,
};
const mockRequest = {
headers: { authorization: '==someAuth' },
@ -127,6 +129,11 @@ describe('callEnterpriseSearchConfigAPI', () => {
hasSearchEnginesAccess: true,
hasWorkplaceSearchAccess: false,
},
features: {
hasNativeConnectors: true,
hasSearchApplications: true,
hasWebCrawler: true,
},
publicUrl: 'http://some.vanity.url',
});
});
@ -141,6 +148,11 @@ describe('callEnterpriseSearchConfigAPI', () => {
hasSearchEnginesAccess: false,
hasWorkplaceSearchAccess: false,
},
features: {
hasNativeConnectors: true,
hasSearchApplications: false,
hasWebCrawler: true,
},
publicUrl: undefined,
readOnlyMode: false,
searchOAuth: {
@ -193,10 +205,31 @@ describe('callEnterpriseSearchConfigAPI', () => {
});
});
it('returns early if config.host is not set', async () => {
const config = { host: '' };
it('returns access & features if config.host is not set', async () => {
const config = {
hasConnectors: false,
hasDefaultIngestPipeline: false,
hasNativeConnectors: false,
hasSearchApplications: false,
hasWebCrawler: false,
host: '',
};
expect(await callEnterpriseSearchConfigAPI({ ...mockDependencies, config })).toEqual({});
expect(await callEnterpriseSearchConfigAPI({ ...mockDependencies, config })).toEqual({
access: {
hasAppSearchAccess: false,
hasSearchEnginesAccess: false,
hasWorkplaceSearchAccess: false,
},
features: {
hasConnectors: false,
hasDefaultIngestPipeline: false,
hasNativeConnectors: false,
hasSearchApplications: false,
hasWebCrawler: false,
},
kibanaVersion: '1.0.0',
});
expect(fetch).not.toHaveBeenCalled();
});

View file

@ -43,7 +43,23 @@ export const callEnterpriseSearchConfigAPI = async ({
log,
request,
}: Params): Promise<Return | ResponseError> => {
if (!config.host) return {};
if (!config.host)
// Return Access and Features for when running without `ent-search`
return {
access: {
hasAppSearchAccess: false,
hasSearchEnginesAccess: false, // TODO: update to read ES feature flag, or just refactor out
hasWorkplaceSearchAccess: false,
},
features: {
hasConnectors: config.hasConnectors,
hasDefaultIngestPipeline: config.hasDefaultIngestPipeline,
hasNativeConnectors: config.hasNativeConnectors,
hasSearchApplications: false, // TODO: update to read ES feature flag, or just refactor out
hasWebCrawler: config.hasWebCrawler,
},
kibanaVersion: kibanaPackageJson.version,
};
const TIMEOUT_WARNING = `Enterprise Search access check took over ${config.accessCheckTimeoutWarning}ms. Please ensure your Enterprise Search server is responding normally and not adversely impacting Kibana load speeds.`;
const TIMEOUT_MESSAGE = `Exceeded ${config.accessCheckTimeout}ms timeout while checking ${config.host}. Please consider increasing your enterpriseSearch.accessCheckTimeout value so that users aren't prevented from accessing Enterprise Search plugins due to slow responses.`;
@ -90,6 +106,13 @@ export const callEnterpriseSearchConfigAPI = async ({
hasSearchEnginesAccess: !!data?.current_user?.access?.search_engines,
hasWorkplaceSearchAccess: !!data?.current_user?.access?.workplace_search,
},
features: {
hasConnectors: config.hasConnectors,
hasDefaultIngestPipeline: config.hasDefaultIngestPipeline,
hasNativeConnectors: config.hasNativeConnectors,
hasSearchApplications: !!data?.current_user?.access?.search_engines,
hasWebCrawler: config.hasWebCrawler,
},
publicUrl: stripTrailingSlash(data?.settings?.external_url),
readOnlyMode: !!data?.settings?.read_only_mode,
searchOAuth: {

View file

@ -131,8 +131,7 @@ export class EnterpriseSearchPlugin implements Plugin {
ENTERPRISE_SEARCH_CONTENT_PLUGIN.ID,
ELASTICSEARCH_PLUGIN.ID,
ANALYTICS_PLUGIN.ID,
APP_SEARCH_PLUGIN.ID,
WORKPLACE_SEARCH_PLUGIN.ID,
...(config.canDeployEntSearch ? [APP_SEARCH_PLUGIN.ID, WORKPLACE_SEARCH_PLUGIN.ID] : []),
SEARCH_EXPERIENCES_PLUGIN.ID,
];
@ -172,7 +171,8 @@ export class EnterpriseSearchPlugin implements Plugin {
const dependencies = { config, security, spaces, request, log, ml };
const { hasAppSearchAccess, hasWorkplaceSearchAccess } = await checkAccess(dependencies);
const showEnterpriseSearch = hasAppSearchAccess || hasWorkplaceSearchAccess;
const showEnterpriseSearch =
hasAppSearchAccess || hasWorkplaceSearchAccess || !config.canDeployEntSearch;
return {
navLinks: {
@ -180,8 +180,8 @@ export class EnterpriseSearchPlugin implements Plugin {
enterpriseSearchContent: showEnterpriseSearch,
enterpriseSearchAnalytics: showEnterpriseSearch,
elasticsearch: showEnterpriseSearch,
appSearch: hasAppSearchAccess,
workplaceSearch: hasWorkplaceSearchAccess,
appSearch: hasAppSearchAccess && config.canDeployEntSearch,
workplaceSearch: hasWorkplaceSearchAccess && config.canDeployEntSearch,
searchExperiences: showEnterpriseSearch,
},
catalogue: {
@ -189,8 +189,8 @@ export class EnterpriseSearchPlugin implements Plugin {
enterpriseSearchContent: showEnterpriseSearch,
enterpriseSearchAnalytics: showEnterpriseSearch,
elasticsearch: showEnterpriseSearch,
appSearch: hasAppSearchAccess,
workplaceSearch: hasWorkplaceSearchAccess,
appSearch: hasAppSearchAccess && config.canDeployEntSearch,
workplaceSearch: hasWorkplaceSearchAccess && config.canDeployEntSearch,
searchExperiences: showEnterpriseSearch,
},
};
@ -204,12 +204,12 @@ export class EnterpriseSearchPlugin implements Plugin {
const dependencies = { router, config, log, enterpriseSearchRequestHandler, ml };
registerConfigDataRoute(dependencies);
registerAppSearchRoutes(dependencies);
if (config.canDeployEntSearch) registerAppSearchRoutes(dependencies);
registerEnterpriseSearchRoutes(dependencies);
registerWorkplaceSearchRoutes(dependencies);
if (config.canDeployEntSearch) registerWorkplaceSearchRoutes(dependencies);
// Enterprise Search Routes
registerConnectorRoutes(dependencies);
registerCrawlerRoutes(dependencies);
if (config.hasNativeConnectors) registerConnectorRoutes(dependencies);
if (config.hasWebCrawler) registerCrawlerRoutes(dependencies);
registerStatsRoutes(dependencies);
// Analytics Routes (stand-alone product)
@ -225,8 +225,10 @@ export class EnterpriseSearchPlugin implements Plugin {
* Bootstrap the routes, saved objects, and collector for telemetry
*/
savedObjects.registerType(enterpriseSearchTelemetryType);
savedObjects.registerType(appSearchTelemetryType);
savedObjects.registerType(workplaceSearchTelemetryType);
if (config.canDeployEntSearch) {
savedObjects.registerType(appSearchTelemetryType);
savedObjects.registerType(workplaceSearchTelemetryType);
}
let savedObjectsStarted: SavedObjectsServiceStart;
getStartServices().then(([coreStart]) => {
@ -234,8 +236,10 @@ export class EnterpriseSearchPlugin implements Plugin {
if (usageCollection) {
registerESTelemetryUsageCollector(usageCollection, savedObjectsStarted, this.logger);
registerASTelemetryUsageCollector(usageCollection, savedObjectsStarted, this.logger);
registerWSTelemetryUsageCollector(usageCollection, savedObjectsStarted, this.logger);
if (config.canDeployEntSearch) {
registerASTelemetryUsageCollector(usageCollection, savedObjectsStarted, this.logger);
registerWSTelemetryUsageCollector(usageCollection, savedObjectsStarted, this.logger);
}
}
});
registerTelemetryRoute({ ...dependencies, getSavedObjectsService: () => savedObjectsStarted });
@ -271,9 +275,15 @@ export class EnterpriseSearchPlugin implements Plugin {
/**
* Register a config for the search guide
*/
guidedOnboarding.registerGuideConfig(appSearchGuideId, appSearchGuideConfig);
guidedOnboarding.registerGuideConfig(websiteSearchGuideId, websiteSearchGuideConfig);
guidedOnboarding.registerGuideConfig(databaseSearchGuideId, databaseSearchGuideConfig);
if (config.canDeployEntSearch) {
guidedOnboarding.registerGuideConfig(appSearchGuideId, appSearchGuideConfig);
}
if (config.hasWebCrawler) {
guidedOnboarding.registerGuideConfig(websiteSearchGuideId, websiteSearchGuideConfig);
}
if (config.hasNativeConnectors) {
guidedOnboarding.registerGuideConfig(databaseSearchGuideId, databaseSearchGuideConfig);
}
}
public start() {}