[Enterprise Search] Vary Enterprise Search navigation options based on product access (#138775)

* Added an EnterpriseSearchProvider to propagate access data

Co-authored-by: Brian McGue <mcgue.brian@gmail.com>
Co-authored-by: Rodney Norris <rodney.norris@elastic.co>

* Move productAccess into KibanaLogic

* Fix PageTemplate tests

* Add tests for access based navigation

* Push useValues for productAccess down into useEnterpriseSearchNav

Co-authored-by: Brian McGue <mcgue.brian@gmail.com>
Co-authored-by: Rodney Norris <rodney.norris@elastic.co>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
James Rucker 2022-08-16 09:05:29 -07:00 committed by GitHub
parent b6fce4df84
commit c7ee95079c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 21 deletions

View file

@ -20,6 +20,10 @@ export const mockKibanaValues = {
},
history: mockHistory,
navigateToUrl: jest.fn(),
productAccess: {
hasAppSearchAccess: true,
hasWorkplaceSearchAccess: true,
},
security: securityMock.createStart(),
setBreadcrumbs: jest.fn(),
setChromeIsVisible: jest.fn(),

View file

@ -18,7 +18,7 @@ import { I18nProvider } from '@kbn/i18n-react';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { InitialAppData } from '../../common/types';
import { InitialAppData, ProductAccess } from '../../common/types';
import { PluginsStart, ClientConfigType, ClientData } from '../plugin';
import { externalUrl } from './shared/enterprise_search_url';
@ -41,6 +41,12 @@ export const renderApp = (
const { publicUrl, errorConnectingMessage, ...initialData } = data;
externalUrl.enterpriseSearchUrl = publicUrl || config.host || '';
const noProductAccess: ProductAccess = {
hasAppSearchAccess: false,
hasWorkplaceSearchAccess: false,
};
const productAccess = data.access || noProductAccess;
const EmptyContext: FC = ({ children }) => <>{children}</>;
const CloudContext = plugins.cloud?.CloudContextProvider || EmptyContext;
@ -49,6 +55,7 @@ export const renderApp = (
const unmountKibanaLogic = mountKibanaLogic({
config,
productAccess,
charts: plugins.charts,
cloud: plugins.cloud,
history: params.history,

View file

@ -14,6 +14,8 @@ import { CloudSetup } from '@kbn/cloud-plugin/public';
import { ApplicationStart, ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public';
import { SecurityPluginStart } from '@kbn/security-plugin/public';
import { ProductAccess } from '../../../../common/types';
import { HttpLogic } from '../http';
import { createHref, CreateHrefOptions } from '../react_router_helpers';
@ -22,6 +24,7 @@ type RequiredFieldsOnly<T> = {
};
interface KibanaLogicProps {
config: { host?: string };
productAccess: ProductAccess;
// Kibana core
history: ScopedHistory;
navigateToUrl: RequiredFieldsOnly<ApplicationStart['navigateToUrl']>;
@ -55,6 +58,7 @@ export const KibanaLogic = kea<MakeLogicType<KibanaValues>>({
},
{},
],
productAccess: [props.productAccess, {}],
security: [props.security, {}],
setBreadcrumbs: [props.setBreadcrumbs, {}],
setChromeIsVisible: [props.setChromeIsVisible, {}],

View file

@ -9,10 +9,24 @@ jest.mock('./nav_link_helpers', () => ({
generateNavLink: jest.fn(({ to, items }) => ({ href: to, items })),
}));
import { setMockValues } from '../../__mocks__/kea_logic';
import { ProductAccess } from '../../../../common/types';
import { useEnterpriseSearchNav } from './nav';
describe('useEnterpriseSearchContentNav', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('returns an array of top-level Enterprise Search nav items', () => {
const fullProductAccess: ProductAccess = {
hasAppSearchAccess: true,
hasWorkplaceSearchAccess: true,
};
setMockValues({ productAccess: fullProductAccess });
expect(useEnterpriseSearchNav()).toEqual([
{
href: '/app/enterprise_search/overview',
@ -53,4 +67,77 @@ describe('useEnterpriseSearchContentNav', () => {
},
]);
});
it('excludes legacy products when the user has no access to them', () => {
const noProductAccess: ProductAccess = {
hasAppSearchAccess: false,
hasWorkplaceSearchAccess: false,
};
setMockValues({ productAccess: noProductAccess });
expect(useEnterpriseSearchNav()[2]).toEqual({
id: 'search',
items: [
{
href: '/app/enterprise_search/elasticsearch',
id: 'elasticsearch',
name: 'Elasticsearch',
},
],
name: 'Search',
});
});
it('excludes App Search when the user has no access to it', () => {
const workplaceSearchProductAccess: ProductAccess = {
hasAppSearchAccess: false,
hasWorkplaceSearchAccess: true,
};
setMockValues({ productAccess: workplaceSearchProductAccess });
expect(useEnterpriseSearchNav()[2]).toEqual({
id: 'search',
items: [
{
href: '/app/enterprise_search/elasticsearch',
id: 'elasticsearch',
name: 'Elasticsearch',
},
{
href: '/app/enterprise_search/workplace_search',
id: 'workplace_search',
name: 'Workplace Search',
},
],
name: 'Search',
});
});
it('excludes Workplace Search when the user has no access to it', () => {
const appSearchProductAccess: ProductAccess = {
hasAppSearchAccess: true,
hasWorkplaceSearchAccess: false,
};
setMockValues({ productAccess: appSearchProductAccess });
expect(useEnterpriseSearchNav()[2]).toEqual({
id: 'search',
items: [
{
href: '/app/enterprise_search/elasticsearch',
id: 'elasticsearch',
name: 'Elasticsearch',
},
{
href: '/app/enterprise_search/app_search',
id: 'app_search',
name: 'App Search',
},
],
name: 'Search',
});
});
});

View file

@ -5,6 +5,8 @@
* 2.0.
*/
import { useValues } from 'kea';
import { EuiSideNavItemType } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@ -16,10 +18,13 @@ import {
WORKPLACE_SEARCH_PLUGIN,
} from '../../../../common/constants';
import { SEARCH_INDICES_PATH } from '../../enterprise_search_content/routes';
import { KibanaLogic } from '../kibana';
import { generateNavLink } from './nav_link_helpers';
export const useEnterpriseSearchNav = () => {
const { productAccess } = useValues(KibanaLogic);
const navItems: Array<EuiSideNavItemType<unknown>> = [
{
id: 'es_overview',
@ -63,26 +68,34 @@ export const useEnterpriseSearchNav = () => {
to: ELASTICSEARCH_PLUGIN.URL,
}),
},
{
id: 'app_search',
name: i18n.translate('xpack.enterpriseSearch.nav.appSearchTitle', {
defaultMessage: 'App Search',
}),
...generateNavLink({
shouldNotCreateHref: true,
to: APP_SEARCH_PLUGIN.URL,
}),
},
{
id: 'workplace_search',
name: i18n.translate('xpack.enterpriseSearch.nav.workplaceSearchTitle', {
defaultMessage: 'Workplace Search',
}),
...generateNavLink({
shouldNotCreateHref: true,
to: WORKPLACE_SEARCH_PLUGIN.URL,
}),
},
...(productAccess.hasAppSearchAccess
? [
{
id: 'app_search',
name: i18n.translate('xpack.enterpriseSearch.nav.appSearchTitle', {
defaultMessage: 'App Search',
}),
...generateNavLink({
shouldNotCreateHref: true,
to: APP_SEARCH_PLUGIN.URL,
}),
},
]
: []),
...(productAccess.hasWorkplaceSearchAccess
? [
{
id: 'workplace_search',
name: i18n.translate('xpack.enterpriseSearch.nav.workplaceSearchTitle', {
defaultMessage: 'Workplace Search',
}),
...generateNavLink({
shouldNotCreateHref: true,
to: WORKPLACE_SEARCH_PLUGIN.URL,
}),
},
]
: []),
],
name: i18n.translate('xpack.enterpriseSearch.nav.searchExperiencesTitle', {
defaultMessage: 'Search',