[8.9] Add ESRE landing page placeholder and navigation (#159589)

## Summary

This PR adds the ESRE landing page to the left hand nav and the
hamburger menu. The page is just a placeholder for now, but it will be
filled with a complete step-by-step guide in follow-up PRs.

cc @julianrosado on ordering of menu items.

![Screenshot 2023-06-13 at 10 33
07](07cbcb84-6494-44e2-a09b-fcaf4d816b5b)
![Screenshot 2023-06-13 at 10 32
56](978ad272-3735-42ac-a3f8-20649485d687)

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Adam Demjen 2023-06-15 09:49:05 -04:00 committed by GitHub
parent b18d8d4c43
commit 4573874fab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 451 additions and 0 deletions

View file

@ -137,6 +137,7 @@ export const applicationUsageSchema = {
enterpriseSearchContent: commonSchema,
enterpriseSearchAnalytics: commonSchema,
enterpriseSearchApplications: commonSchema,
enterpriseSearchEsre: commonSchema,
elasticsearch: commonSchema,
appSearch: commonSchema,
workplaceSearch: commonSchema,

View file

@ -2360,6 +2360,137 @@
}
}
},
"enterpriseSearchEsre": {
"properties": {
"appId": {
"type": "keyword",
"_meta": {
"description": "The application being tracked"
}
},
"viewId": {
"type": "keyword",
"_meta": {
"description": "Always `main`"
}
},
"clicks_total": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application since we started counting them"
}
},
"clicks_7_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 7 days"
}
},
"clicks_30_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 30 days"
}
},
"clicks_90_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 90 days"
}
},
"minutes_on_screen_total": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen since we started counting them."
}
},
"minutes_on_screen_7_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 7 days"
}
},
"minutes_on_screen_30_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 30 days"
}
},
"minutes_on_screen_90_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 90 days"
}
},
"views": {
"type": "array",
"items": {
"properties": {
"appId": {
"type": "keyword",
"_meta": {
"description": "The application being tracked"
}
},
"viewId": {
"type": "keyword",
"_meta": {
"description": "The application view being tracked"
}
},
"clicks_total": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application sub view since we started counting them"
}
},
"clicks_7_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 7 days"
}
},
"clicks_30_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 30 days"
}
},
"clicks_90_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 90 days"
}
},
"minutes_on_screen_total": {
"type": "float",
"_meta": {
"description": "Minutes the application sub view is active and on-screen since we started counting them."
}
},
"minutes_on_screen_7_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
}
},
"minutes_on_screen_30_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
}
},
"minutes_on_screen_90_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
}
}
}
}
}
}
},
"elasticsearch": {
"properties": {
"appId": {

View file

@ -42,6 +42,22 @@ export const ENTERPRISE_SEARCH_CONTENT_PLUGIN = {
SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/',
};
export const ESRE_PLUGIN = {
ID: 'enterpriseSearchEsre',
NAME: i18n.translate('xpack.enterpriseSearch.esre.productName', {
defaultMessage: 'ESRE',
}),
NAV_TITLE: i18n.translate('xpack.enterpriseSearch.esre.navTitle', {
defaultMessage: 'ESRE',
}),
DESCRIPTION: i18n.translate('xpack.enterpriseSearch.esre.description', {
defaultMessage:
'Toolkit for enabling developers to build AI search-powered applications using the Elastic platform.',
}),
URL: '/app/enterprise_search/esre',
LOGO: 'logoEnterpriseSearch',
};
export const ANALYTICS_PLUGIN = {
ID: 'enterpriseSearchAnalytics',
NAME: i18n.translate('xpack.enterpriseSearch.analytics.productName', {

View file

@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { SetEsreChrome as SetPageChrome } from '../../../shared/kibana_chrome';
import { EnterpriseSearchEsrePageTemplate } from '../layout/page_template';
export const EsreGuide: React.FC = () => {
return (
<EnterpriseSearchEsrePageTemplate
restrictWidth
pageHeader={{
pageTitle: i18n.translate('xpack.enterpriseSearch.esre.guide.pageTitle', {
defaultMessage: 'Enhance your search with ESRE',
}),
}}
>
<SetPageChrome />
<p>ESRE placeholder</p>
</EnterpriseSearchEsrePageTemplate>
);
};

View file

@ -0,0 +1,73 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
jest.mock('../../../shared/layout/nav', () => ({
useEnterpriseSearchNav: () => [],
}));
import React from 'react';
import { shallow } from 'enzyme';
import { SetEsreChrome } from '../../../shared/kibana_chrome';
import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout';
import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry';
import { EnterpriseSearchEsrePageTemplate } from './page_template';
describe('EnterpriseSearchEsrePageTemplate', () => {
it('renders', () => {
const wrapper = shallow(
<EnterpriseSearchEsrePageTemplate>
<div className="hello">world</div>
</EnterpriseSearchEsrePageTemplate>
);
expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper);
expect(wrapper.prop('solutionNav')).toEqual({ name: 'ESRE', items: [] });
expect(wrapper.find('.hello').text()).toEqual('world');
});
describe('page chrome', () => {
it('takes a breadcrumb array & renders a product-specific page chrome', () => {
const wrapper = shallow(<EnterpriseSearchEsrePageTemplate pageChrome={['Some page']} />);
const setPageChrome = wrapper
.find(EnterpriseSearchPageTemplateWrapper)
.prop('setPageChrome') as any;
expect(setPageChrome.type).toEqual(SetEsreChrome);
expect(setPageChrome.props.trail).toEqual(['Some page']);
});
});
describe('page telemetry', () => {
it('takes a metric & renders product-specific telemetry viewed event', () => {
const wrapper = shallow(<EnterpriseSearchEsrePageTemplate pageViewTelemetry="some_page" />);
expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('action')).toEqual('viewed');
expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('metric')).toEqual('some_page');
});
});
describe('props', () => {
it('passes down any ...pageTemplateProps that EnterpriseSearchPageTemplateWrapper accepts', () => {
const wrapper = shallow(
<EnterpriseSearchEsrePageTemplate
pageHeader={{ pageTitle: 'hello world' }}
isLoading={false}
emptyState={<div />}
/>
);
expect(
wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('pageHeader')!.pageTitle
).toEqual('hello world');
expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('isLoading')).toEqual(false);
expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('emptyState')).toEqual(<div />);
});
});
});

View file

@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { ESRE_PLUGIN } from '../../../../../common/constants';
import { SetEsreChrome } from '../../../shared/kibana_chrome';
import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout';
import { useEnterpriseSearchNav } from '../../../shared/layout';
import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry';
export const EnterpriseSearchEsrePageTemplate: React.FC<PageTemplateProps> = ({
children,
pageChrome,
pageViewTelemetry,
...pageTemplateProps
}) => {
return (
<EnterpriseSearchPageTemplateWrapper
{...pageTemplateProps}
solutionNav={{
name: ESRE_PLUGIN.NAME,
items: useEnterpriseSearchNav(),
}}
setPageChrome={pageChrome && <SetEsreChrome trail={pageChrome} />}
>
{pageViewTelemetry && (
<SendEnterpriseSearchTelemetry action="viewed" metric={pageViewTelemetry} />
)}
{children}
</EnterpriseSearchPageTemplateWrapper>
);
};

View file

@ -0,0 +1,28 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { setMockValues } from '../__mocks__/kea_logic';
import React from 'react';
import { shallow } from 'enzyme';
import { EsreGuide } from './components/esre_guide/esre_guide';
import { EnterpriseSearchEsre } from '.';
describe('SearchExperiences', () => {
it('renders the ESRE guide', () => {
setMockValues({
errorConnectingMessage: '',
config: { host: 'localhost' },
});
const wrapper = shallow(<EnterpriseSearchEsre />);
expect(wrapper.find(EsreGuide)).toHaveLength(1);
});
});

View file

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { Switch } from 'react-router-dom';
import { Route } from '@kbn/shared-ux-router';
import { isVersionMismatch } from '../../../common/is_version_mismatch';
import { InitialAppData } from '../../../common/types';
import { VersionMismatchPage } from '../shared/version_mismatch';
import { EsreGuide } from './components/esre_guide/esre_guide';
import { ROOT_PATH } from './routes';
export const EnterpriseSearchEsre: React.FC<InitialAppData> = (props) => {
const { enterpriseSearchVersion, kibanaVersion } = props;
const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion);
const showView = () => {
if (incompatibleVersions) {
return (
<VersionMismatchPage
enterpriseSearchVersion={enterpriseSearchVersion}
kibanaVersion={kibanaVersion}
/>
);
}
return <EsreGuide />;
};
return (
<Switch>
<Route exact path={ROOT_PATH}>
{showView()}
</Route>
</Switch>
);
};

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const ROOT_PATH = '/';

View file

@ -16,6 +16,7 @@ import {
WORKPLACE_SEARCH_PLUGIN,
ENTERPRISE_SEARCH_CONTENT_PLUGIN,
SEARCH_EXPERIENCES_PLUGIN,
ESRE_PLUGIN,
} from '../../../../common/constants';
import { stripLeadingSlash } from '../../../../common/strip_slashes';
@ -139,3 +140,6 @@ export const useSearchExperiencesBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =
export const useEnterpriseSearchEnginesBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
useEnterpriseSearchBreadcrumbs(breadcrumbs);
export const useEsreBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
useEnterpriseSearchBreadcrumbs([{ text: ESRE_PLUGIN.NAME, path: '/' }, ...breadcrumbs]);

View file

@ -11,6 +11,7 @@ import {
APP_SEARCH_PLUGIN,
WORKPLACE_SEARCH_PLUGIN,
SEARCH_EXPERIENCES_PLUGIN,
ESRE_PLUGIN,
} from '../../../../common/constants';
/**
@ -47,3 +48,5 @@ export const workplaceSearchTitle = (page: Title = []) =>
export const searchExperiencesTitle = (page: Title = []) =>
generateTitle([...page, SEARCH_EXPERIENCES_PLUGIN.NAME]);
export const esreTitle = (page: Title = []) => generateTitle([...page, ESRE_PLUGIN.NAME]);

View file

@ -10,6 +10,7 @@ export {
SetAnalyticsChrome,
SetEnterpriseSearchContentChrome,
SetElasticsearchChrome,
SetEsreChrome,
SetAppSearchChrome,
SetWorkplaceSearchChrome,
SetSearchExperiencesChrome,

View file

@ -19,6 +19,7 @@ import {
useEnterpriseSearchEnginesBreadcrumbs,
useAnalyticsBreadcrumbs,
useEnterpriseSearchContentBreadcrumbs,
useEsreBreadcrumbs,
useElasticsearchBreadcrumbs,
useAppSearchBreadcrumbs,
useWorkplaceSearchBreadcrumbs,
@ -32,6 +33,7 @@ import {
appSearchTitle,
workplaceSearchTitle,
searchExperiencesTitle,
esreTitle,
} from './generate_title';
/**
@ -121,6 +123,23 @@ export const SetAppSearchChrome: React.FC<SetChromeProps> = ({ trail = [] }) =>
return null;
};
export const SetEsreChrome: React.FC<SetChromeProps> = ({ trail = [] }) => {
const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic);
const title = reverseArray(trail);
const docTitle = esreTitle(title);
const crumbs = useGenerateBreadcrumbs(trail);
const breadcrumbs = useEsreBreadcrumbs(crumbs);
useEffect(() => {
setBreadcrumbs(breadcrumbs);
setDocTitle(docTitle);
}, [trail]);
return null;
};
export const SetWorkplaceSearchChrome: React.FC<SetChromeProps> = ({ trail = [] }) => {
const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic);

View file

@ -51,6 +51,11 @@ describe('useEnterpriseSearchContentNav', () => {
id: 'elasticsearch',
name: 'Elasticsearch',
},
{
href: '/app/enterprise_search/esre',
id: 'esre',
name: 'ESRE',
},
{
href: '/app/enterprise_search/search_experiences',
id: 'searchExperiences',
@ -210,6 +215,11 @@ describe('useEnterpriseSearchEngineNav', () => {
id: 'elasticsearch',
name: 'Elasticsearch',
},
{
href: '/app/enterprise_search/esre',
id: 'esre',
name: 'ESRE',
},
{
href: '/app/enterprise_search/search_experiences',
id: 'searchExperiences',
@ -397,6 +407,11 @@ describe('useEnterpriseSearchAnalyticsNav', () => {
id: 'elasticsearch',
name: 'Elasticsearch',
},
{
href: '/app/enterprise_search/esre',
id: 'esre',
name: 'ESRE',
},
{
href: '/app/enterprise_search/search_experiences',
id: 'searchExperiences',

View file

@ -19,6 +19,7 @@ import {
ELASTICSEARCH_PLUGIN,
ENTERPRISE_SEARCH_CONTENT_PLUGIN,
ENTERPRISE_SEARCH_OVERVIEW_PLUGIN,
ESRE_PLUGIN,
SEARCH_EXPERIENCES_PLUGIN,
WORKPLACE_SEARCH_PLUGIN,
} from '../../../../common/constants';
@ -53,6 +54,16 @@ export const useEnterpriseSearchNav = () => {
to: ELASTICSEARCH_PLUGIN.URL,
}),
},
{
id: 'esre',
name: i18n.translate('xpack.enterpriseSearch.nav.esreTitle', {
defaultMessage: 'ESRE',
}),
...generateNavLink({
shouldNotCreateHref: true,
to: ESRE_PLUGIN.URL,
}),
},
{
id: 'searchExperiences',
name: i18n.translate('xpack.enterpriseSearch.nav.searchExperiencesTitle', {

View file

@ -30,6 +30,7 @@ import {
APPLICATIONS_PLUGIN,
APP_SEARCH_PLUGIN,
ELASTICSEARCH_PLUGIN,
ESRE_PLUGIN,
ENTERPRISE_SEARCH_CONTENT_PLUGIN,
ENTERPRISE_SEARCH_OVERVIEW_PLUGIN,
WORKPLACE_SEARCH_PLUGIN,
@ -141,6 +142,27 @@ export class EnterpriseSearchPlugin implements Plugin {
title: ENTERPRISE_SEARCH_OVERVIEW_PLUGIN.NAV_TITLE,
});
core.application.register({
appRoute: ESRE_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
euiIconType: ESRE_PLUGIN.LOGO,
id: ESRE_PLUGIN.ID,
mount: async (params: AppMountParameters) => {
const kibanaDeps = await this.getKibanaDeps(core, params, cloud);
const { chrome, http } = kibanaDeps.core;
chrome.docTitle.change(ESRE_PLUGIN.NAME);
await this.getInitialData(http);
const pluginData = this.getPluginData();
const { renderApp } = await import('./applications');
const { EnterpriseSearchEsre } = await import('./applications/esre');
return renderApp(EnterpriseSearchEsre, kibanaDeps, pluginData);
},
title: ESRE_PLUGIN.NAV_TITLE,
});
core.application.register({
appRoute: ENTERPRISE_SEARCH_CONTENT_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,

View file

@ -185,6 +185,7 @@ export class EnterpriseSearchPlugin implements Plugin {
enterpriseSearchContent: showEnterpriseSearch,
enterpriseSearchAnalytics: showEnterpriseSearch,
enterpriseSearchApplications: showEnterpriseSearch,
enterpriseSearchEsre: showEnterpriseSearch,
elasticsearch: showEnterpriseSearch,
appSearch: hasAppSearchAccess && config.canDeployEntSearch,
workplaceSearch: hasWorkplaceSearchAccess && config.canDeployEntSearch,
@ -195,6 +196,7 @@ export class EnterpriseSearchPlugin implements Plugin {
enterpriseSearchContent: showEnterpriseSearch,
enterpriseSearchAnalytics: showEnterpriseSearch,
enterpriseSearchApplications: showEnterpriseSearch,
enterpriseSearchEsre: showEnterpriseSearch,
elasticsearch: showEnterpriseSearch,
appSearch: hasAppSearchAccess && config.canDeployEntSearch,
workplaceSearch: hasWorkplaceSearchAccess && config.canDeployEntSearch,

View file

@ -67,6 +67,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
'enterpriseSearchContent',
'enterpriseSearchAnalytics',
'enterpriseSearchApplications',
'enterpriseSearchEsre',
'elasticsearch',
'appSearch',
'workplaceSearch',
@ -95,6 +96,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
'enterpriseSearchContent',
'enterpriseSearchAnalytics',
'enterpriseSearchApplications',
'enterpriseSearchEsre',
'elasticsearch',
'appSearch',
'workplaceSearch',

View file

@ -54,6 +54,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
'enterpriseSearchContent',
'enterpriseSearchAnalytics',
'enterpriseSearchApplications',
'enterpriseSearchEsre',
'appSearch',
'workplaceSearch'
)
@ -71,6 +72,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
'enterpriseSearchContent',
'enterpriseSearchAnalytics',
'enterpriseSearchApplications',
'enterpriseSearchEsre',
'appSearch',
'workplaceSearch',
'guidedOnboardingFeature'

View file

@ -31,6 +31,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
'enterpriseSearchContent',
'enterpriseSearchAnalytics',
'enterpriseSearchApplications',
'enterpriseSearchEsre',
'elasticsearch',
'appSearch',
'workplaceSearch',

View file

@ -23,6 +23,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
'enterpriseSearchContent',
'enterpriseSearchAnalytics',
'enterpriseSearchApplications',
'enterpriseSearchEsre',
'appSearch',
'workplaceSearch',
];