[Search][Fix] Inference Endpoints deep link & Side Nav access (#197461)

This commit is contained in:
Rodney Norris 2024-10-23 18:44:23 -05:00 committed by GitHub
parent 292a7d384e
commit a124493b8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 151 additions and 23 deletions

View file

@ -9,7 +9,7 @@
export const ENTERPRISE_SEARCH_APP_ID = 'enterpriseSearch';
export const ENTERPRISE_SEARCH_CONTENT_APP_ID = 'enterpriseSearchContent';
export const ENTERPRISE_SEARCH_RELEVANCE_APP_ID = 'enterpriseSearchRelevance';
export const ENTERPRISE_SEARCH_RELEVANCE_APP_ID = 'searchInferenceEndpoints';
export const ENTERPRISE_SEARCH_APPLICATIONS_APP_ID = 'enterpriseSearchApplications';
export const ENTERPRISE_SEARCH_ANALYTICS_APP_ID = 'enterpriseSearchAnalytics';
export const ENTERPRISE_SEARCH_APPSEARCH_APP_ID = 'appSearch';

View file

@ -136,7 +136,7 @@ export const applicationUsageSchema = {
canvas: commonSchema,
enterpriseSearch: commonSchema,
enterpriseSearchContent: commonSchema,
enterpriseSearchRelevance: commonSchema,
searchInferenceEndpoints: commonSchema,
enterpriseSearchAnalytics: commonSchema,
enterpriseSearchApplications: commonSchema,
enterpriseSearchAISearch: commonSchema,

View file

@ -2098,7 +2098,7 @@
}
}
},
"enterpriseSearchRelevance": {
"searchInferenceEndpoints": {
"properties": {
"appId": {
"type": "keyword",

View file

@ -0,0 +1,96 @@
/*
* 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 type { ILicense } from '@kbn/licensing-plugin/public';
import { hasEnterpriseLicense } from './licensing';
describe('licensing utils', () => {
const baseLicense: ILicense = {
isActive: true,
type: 'trial',
isAvailable: true,
signature: 'fake',
toJSON: jest.fn(),
getUnavailableReason: jest.fn().mockReturnValue(undefined),
hasAtLeast: jest.fn().mockReturnValue(false),
check: jest.fn().mockReturnValue({ state: 'valid' }),
getFeature: jest.fn().mockReturnValue({ isAvailable: false, isEnabled: false }),
};
describe('hasEnterpriseLicense', () => {
let license: ILicense;
beforeEach(() => {
jest.resetAllMocks();
license = {
...baseLicense,
};
});
it('returns true for active enterprise license', () => {
license.type = 'enterprise';
expect(hasEnterpriseLicense(license)).toEqual(true);
});
it('returns true for active trial license', () => {
expect(hasEnterpriseLicense(license)).toEqual(true);
});
it('returns false for active basic license', () => {
license.type = 'basic';
expect(hasEnterpriseLicense(license)).toEqual(false);
});
it('returns false for active gold license', () => {
license.type = 'gold';
expect(hasEnterpriseLicense(license)).toEqual(false);
});
it('returns false for active platinum license', () => {
license.type = 'platinum';
expect(hasEnterpriseLicense(license)).toEqual(false);
});
it('returns false for inactive enterprise license', () => {
license.type = 'enterprise';
license.isActive = false;
expect(hasEnterpriseLicense(license)).toEqual(false);
});
it('returns false for inactive trial license', () => {
license.isActive = false;
expect(hasEnterpriseLicense(license)).toEqual(false);
});
it('returns false for inactive basic license', () => {
license.type = 'basic';
license.isActive = false;
expect(hasEnterpriseLicense(license)).toEqual(false);
});
it('returns false for inactive gold license', () => {
license.type = 'gold';
license.isActive = false;
expect(hasEnterpriseLicense(license)).toEqual(false);
});
it('returns false for inactive platinum license', () => {
license.type = 'platinum';
license.isActive = false;
expect(hasEnterpriseLicense(license)).toEqual(false);
});
it('returns false for active license is missing type', () => {
delete license.type;
expect(hasEnterpriseLicense(license)).toEqual(false);
});
it('returns false for null license', () => {
expect(hasEnterpriseLicense(null)).toEqual(false);
});
it('returns false for undefined license', () => {
expect(hasEnterpriseLicense(undefined)).toEqual(false);
});
});
});

View file

@ -0,0 +1,16 @@
/*
* 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 type { ILicense } from '@kbn/licensing-plugin/public';
/* hasEnterpriseLicense return if the given license is an active `enterprise` or `trial` license
*/
export function hasEnterpriseLicense(license: ILicense | null | undefined): boolean {
if (license === undefined || license === null) return false;
const qualifyingLicenses = ['enterprise', 'trial'];
return license.isActive && qualifyingLicenses.includes(license?.type ?? '');
}

View file

@ -209,7 +209,7 @@ export const getNavigationTreeDefinition = ({
}),
},
{
children: [{ link: 'enterpriseSearchRelevance:inferenceEndpoints' }],
children: [{ link: 'searchInferenceEndpoints:inferenceEndpoints' }],
id: 'relevance',
title: i18n.translate('xpack.enterpriseSearch.searchNav.relevance', {
defaultMessage: 'Relevance',

View file

@ -19,6 +19,8 @@ import {
PluginInitializerContext,
DEFAULT_APP_CATEGORIES,
AppDeepLink,
type AppUpdater,
AppStatus,
} from '@kbn/core/public';
import { DataPublicPluginStart } from '@kbn/data-plugin/public';
@ -53,8 +55,8 @@ import {
SEARCH_RELEVANCE_PLUGIN,
} from '../common/constants';
import { registerLocators } from '../common/locators';
import { ClientConfigType, InitialAppData } from '../common/types';
import { hasEnterpriseLicense } from '../common/utils/licensing';
import { ENGINES_PATH } from './applications/app_search/routes';
import { SEARCH_APPLICATIONS_PATH, PLAYGROUND_PATH } from './applications/applications/routes';
@ -134,7 +136,7 @@ const contentLinks: AppDeepLink[] = [
const relevanceLinks: AppDeepLink[] = [
{
id: 'searchInferenceEndpoints',
id: 'inferenceEndpoints',
path: `/${INFERENCE_ENDPOINTS_PATH}`,
title: i18n.translate(
'xpack.enterpriseSearch.navigation.relevanceInferenceEndpointsLinkLabel',
@ -180,6 +182,7 @@ const appSearchLinks: AppDeepLink[] = [
export class EnterpriseSearchPlugin implements Plugin {
private config: ClientConfigType;
private enterpriseLicenseAppUpdater$ = new BehaviorSubject<AppUpdater>(() => ({}));
constructor(initializerContext: PluginInitializerContext) {
this.config = initializerContext.config.get<ClientConfigType>();
@ -440,6 +443,8 @@ export class EnterpriseSearchPlugin implements Plugin {
deepLinks: relevanceLinks,
euiIconType: SEARCH_RELEVANCE_PLUGIN.LOGO,
id: SEARCH_RELEVANCE_PLUGIN.ID,
status: AppStatus.inaccessible,
updater$: this.enterpriseLicenseAppUpdater$,
mount: async (params: AppMountParameters) => {
const kibanaDeps = await this.getKibanaDeps(core, params, cloud);
const { chrome, http } = kibanaDeps.core;
@ -615,6 +620,18 @@ export class EnterpriseSearchPlugin implements Plugin {
);
});
plugins.licensing?.license$.subscribe((license) => {
if (hasEnterpriseLicense(license)) {
this.enterpriseLicenseAppUpdater$.next(() => ({
status: AppStatus.accessible,
}));
} else {
this.enterpriseLicenseAppUpdater$.next(() => ({
status: AppStatus.inaccessible,
}));
}
});
// Return empty start contract rather than void in order for plugins
// that depend on the enterprise search plugin to determine whether it is enabled or not
return {};

View file

@ -47,6 +47,7 @@ export default function searchSolutionNavigation({
await solutionNavigation.sidenav.expectLinkExists({ text: 'Playground' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Search applications' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Behavioral Analytics' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Inference Endpoints' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'App Search' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Workplace Search' });
await solutionNavigation.sidenav.expectLinkExists({ text: 'Other tools' });
@ -184,20 +185,19 @@ export default function searchSolutionNavigation({
// check Relevance
// > Inference Endpoints
// TODO: FTRs don't have enterprise license, so inference endpoints not shown
// await solutionNavigation.sidenav.clickLink({
// deepLinkId: 'enterpriseSearchRelevance:inferenceEndpoints',
// });
// await solutionNavigation.sidenav.expectLinkActive({
// deepLinkId: 'enterpriseSearchRelevance:inferenceEndpoints',
// });
// await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Relevance' });
// await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
// text: 'Inference Endpoints',
// });
// await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
// deepLinkId: 'enterpriseSearchRelevance:inferenceEndpoints',
// });
await solutionNavigation.sidenav.clickLink({
deepLinkId: 'searchInferenceEndpoints:inferenceEndpoints',
});
await solutionNavigation.sidenav.expectLinkActive({
deepLinkId: 'searchInferenceEndpoints:inferenceEndpoints',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({ text: 'Relevance' });
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
text: 'Inference Endpoints',
});
await solutionNavigation.breadcrumbs.expectBreadcrumbExists({
deepLinkId: 'searchInferenceEndpoints:inferenceEndpoints',
});
// check Enterprise Search
// > App Search
@ -296,8 +296,8 @@ export default function searchSolutionNavigation({
'enterpriseSearchApplications:playground',
'enterpriseSearchApplications:searchApplications',
'enterpriseSearchAnalytics',
// 'relevance',
// 'enterpriseSearchRelevance:inferenceEndpoints',
'relevance',
'searchInferenceEndpoints:inferenceEndpoints',
'entsearch',
'appSearch:engines',
'workplaceSearch',

View file

@ -93,7 +93,6 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
'enterpriseSearchVectorSearch',
'enterpriseSearchSemanticSearch',
'enterpriseSearchElasticsearch',
'enterpriseSearchRelevance',
'searchInferenceEndpoints',
'appSearch',
'observabilityAIAssistant',