mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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:
parent
e7de8060d4
commit
425c236962
28 changed files with 372 additions and 183 deletions
|
@ -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)',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 />]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}
|
||||
/>,
|
||||
]
|
||||
: []),
|
||||
];
|
||||
};
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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 />}
|
||||
</>
|
||||
);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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, {}],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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() {}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue