[Semantic Text UI] Display semantic_text based on licensing (#185902)

This PR includes changes related to displaying semantic_text based on
the ml operations capacity of the users. The PR includes the following
changes:

 - Display a banner based on the user's capacity to run ML operations.
- Display semantic_text if the user has the capacity to run ML
operations; otherwise, hide the semantic_text field.

### Trial License
<img width="1052" alt="Screenshot 2024-06-10 at 4 06 16 PM"
src="56a492db-c181-44ca-a77d-ea14a54ed0a3">


### Basic License
<img width="1456" alt="Screenshot 2024-06-10 at 4 00 56 PM"
src="aa9e0e6c-7a5f-4637-896b-9c2c2a1e152a">


### Serverless
<img width="1083" alt="Screenshot 2024-06-10 at 3 52 19 PM"
src="bd1fe21d-aacb-4b6a-98d9-489fab62e506">


# How to test
- Enable semantic_text in config/kibana.yml.
`xpack.index_management.dev.enableSemanticText: true`
- For Basic license, we can run elastic_search using: `yarn es snapshot`
- For Trial license, we can run elastic_seach using: `yarn es snapshot
--license trial`
- For serverless, we can run elastic_search using: `yarn es serverless
--projectType es`
This commit is contained in:
Saikat Sarkar 2024-06-21 05:33:48 -06:00 committed by GitHub
parent dca0ea2ade
commit 385bb2b35b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 112 additions and 25 deletions

View file

@ -647,6 +647,10 @@ describe('<IndexDetailsPage />', () => {
});
describe('Add Semantic text field', () => {
const customInferenceModel = 'my-elser-model';
const mockLicense = {
isActive: true,
hasAtLeast: jest.fn((type) => true),
};
beforeEach(async () => {
httpRequestsMockHelpers.setInferenceModels({
data: [
@ -674,7 +678,18 @@ describe('<IndexDetailsPage />', () => {
enterpriseSearch: '',
},
},
core: {
application: { capabilities: { ml: { canGetTrainedModels: true } } },
},
plugins: {
licensing: {
license$: {
subscribe: jest.fn((callback) => {
callback(mockLicense);
return { unsubscribe: jest.fn() };
}),
},
},
ml: {
mlApi: {
trainedModels: {

View file

@ -20,7 +20,7 @@ describe('When semantic_text is enabled', () => {
getItemSpy = jest.spyOn(Storage.prototype, 'getItem');
setItemSpy = jest.spyOn(Storage.prototype, 'setItem');
const setup = registerTestBed(SemanticTextBanner, {
defaultProps: { isSemanticTextEnabled: true },
defaultProps: { isSemanticTextEnabled: true, isPlatinumLicense: true },
memoryRouter: { wrapComponent: false },
});
const testBed = setup();
@ -56,6 +56,21 @@ describe('When semantic_text is enabled', () => {
});
});
describe('when user does not have ML permissions', () => {
const setupWithNoMlPermission = registerTestBed(SemanticTextBanner, {
defaultProps: { isSemanticTextEnabled: true, isPlatinumLicense: false },
memoryRouter: { wrapComponent: false },
});
const { find } = setupWithNoMlPermission();
it('should contain content related to semantic_text', () => {
expect(find('indexDetailsMappingsSemanticTextBanner').text()).toContain(
'Semantic text now available for platinum license'
);
});
});
describe('When semantic_text is disabled', () => {
const setup = registerTestBed(SemanticTextBanner, {
defaultProps: { isSemanticTextEnabled: false },

View file

@ -8,7 +8,7 @@
"browser": true,
"configPath": ["xpack", "index_management"],
"requiredPlugins": ["home", "management", "features", "share"],
"optionalPlugins": ["security", "usageCollection", "fleet", "cloud", "ml", "console"],
"optionalPlugins": ["security", "usageCollection", "fleet", "cloud", "ml", "console","licensing"],
"requiredBundles": ["kibanaReact", "esUiShared", "runtimeFields"]
}
}

View file

@ -26,6 +26,7 @@ import type { CloudSetup } from '@kbn/cloud-plugin/public';
import type { ConsolePluginStart } from '@kbn/console-plugin/public';
import { EuiBreadcrumb } from '@elastic/eui';
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import { ExtensionsService } from '../services';
import { HttpService, NotificationService, UiMetricService } from './services';
import { IndexManagementBreadcrumb } from './services/breadcrumbs';
@ -48,6 +49,7 @@ export interface AppDependencies {
share: SharePluginStart;
cloud?: CloudSetup;
console?: ConsolePluginStart;
licensing?: LicensingPluginStart;
ml?: MlPluginStart;
};
services: {

View file

@ -83,6 +83,7 @@ export function getIndexManagementDependencies({
cloud,
console: startDependencies.console,
ml: startDependencies.ml,
licensing: startDependencies.licensing,
},
services: {
httpService,

View file

@ -27,6 +27,7 @@ import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react';
import { ILicense } from '@kbn/licensing-plugin/public';
import { Index } from '../../../../../../common';
import { useDetailsPageMappingsModelManagement } from '../../../../../hooks/use_details_page_mappings_model_management';
import { useAppContext } from '../../../../app_context';
@ -65,17 +66,33 @@ export const DetailsPageMappingsContent: FunctionComponent<{
}> = ({ index, data, jsonData, refetchMapping, showAboutMappings }) => {
const {
services: { extensionsService },
core: { getUrlForApp },
plugins: { ml },
core: {
getUrlForApp,
application: { capabilities },
},
plugins: { ml, licensing },
url,
config,
} = useAppContext();
const [isPlatinumLicense, setIsPlatinumLicense] = useState<boolean>(false);
useEffect(() => {
const subscription = licensing?.license$.subscribe((license: ILicense) => {
setIsPlatinumLicense(license.isActive && license.hasAtLeast('platinum'));
});
return () => subscription?.unsubscribe();
}, [licensing]);
const { enableSemanticText: isSemanticTextEnabled } = config;
const [errorsInTrainedModelDeployment, setErrorsInTrainedModelDeployment] = useState<string[]>(
[]
);
const hasMLPermissions = capabilities?.ml?.canGetTrainedModels ? true : false;
const semanticTextInfo = {
isSemanticTextEnabled,
isSemanticTextEnabled: isSemanticTextEnabled && hasMLPermissions && isPlatinumLicense,
indexName: index.name,
ml,
setErrorsInTrainedModelDeployment,
@ -164,7 +181,7 @@ export const DetailsPageMappingsContent: FunctionComponent<{
const [isModalVisible, setIsModalVisible] = useState(false);
useEffect(() => {
if (!isSemanticTextEnabled) {
if (!isSemanticTextEnabled || !hasMLPermissions) {
return;
}
@ -182,15 +199,19 @@ export const DetailsPageMappingsContent: FunctionComponent<{
return;
}
if (!hasMLPermissions) {
return;
}
await fetchInferenceToModelIdMap();
} catch (exception) {
setSaveMappingError(exception.message);
}
}, [fetchInferenceToModelIdMap, isSemanticTextEnabled]);
}, [fetchInferenceToModelIdMap, isSemanticTextEnabled, hasMLPermissions]);
const updateMappings = useCallback(async () => {
try {
if (isSemanticTextEnabled) {
if (isSemanticTextEnabled && hasMLPermissions) {
await fetchInferenceToModelIdMap();
if (pendingDeployments.length > 0) {
@ -487,7 +508,12 @@ export const DetailsPageMappingsContent: FunctionComponent<{
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexItem grow={true}>
<SemanticTextBanner isSemanticTextEnabled={isSemanticTextEnabled} />
{hasMLPermissions && (
<SemanticTextBanner
isSemanticTextEnabled={isSemanticTextEnabled}
isPlatinumLicense={isPlatinumLicense}
/>
)}
</EuiFlexItem>
{errorSavingMappings}
{isAddingFields && (

View file

@ -11,9 +11,47 @@ import useLocalStorage from 'react-use/lib/useLocalStorage';
interface SemanticTextBannerProps {
isSemanticTextEnabled: boolean;
isPlatinumLicense?: boolean;
}
export function SemanticTextBanner({ isSemanticTextEnabled }: SemanticTextBannerProps) {
const defaultLicenseMessage = (
<FormattedMessage
id="xpack.idxMgmt.indexDetails.mappings.semanticTextBanner.descriptionForPlatinumLicense"
defaultMessage="{label} Upgrade your license to add semantic_text field types to your indices.'"
values={{
label: (
<strong>
<FormattedMessage
id="xpack.idxMgmt.indexDetails.mappings.semanticTextBanner.semanticTextFieldAvailableForPlatinumLicense"
defaultMessage="Semantic text now available for platinum license."
/>
</strong>
),
}}
/>
);
const platinumLicenseMessage = (
<FormattedMessage
id="xpack.idxMgmt.indexDetails.mappings.semanticTextBanner.description"
defaultMessage="{label} Add a field to your mapping and choose 'semantic_text' to get started.'"
values={{
label: (
<strong>
<FormattedMessage
id="xpack.idxMgmt.indexDetails.mappings.semanticTextBanner.semanticTextFieldAvailable"
defaultMessage="semantic_text field type now available!"
/>
</strong>
),
}}
/>
);
export function SemanticTextBanner({
isSemanticTextEnabled,
isPlatinumLicense = false,
}: SemanticTextBannerProps) {
const [isSemanticTextBannerDisplayable, setIsSemanticTextBannerDisplayable] =
useLocalStorage<boolean>('semantic-text-banner-display', true);
@ -23,20 +61,7 @@ export function SemanticTextBanner({ isSemanticTextEnabled }: SemanticTextBanner
<EuiFlexGroup>
<EuiFlexItem>
<EuiText size="m" color="success">
<FormattedMessage
id="xpack.idxMgmt.indexDetails.mappings.semanticTextBanner.description"
defaultMessage="{label} Add a field to your mapping and choose 'semantic_text' to get started.'"
values={{
label: (
<strong>
<FormattedMessage
id="xpack.idxMgmt.indexDetails.mappings.semanticTextBanner.semanticTextFieldAvailable"
defaultMessage="semantic_text field type now available!"
/>
</strong>
),
}}
/>
{isPlatinumLicense ? platinumLicenseMessage : defaultLicenseMessage}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>

View file

@ -112,7 +112,7 @@ export class IndexMgmtUIPlugin
}
public start(coreStart: CoreStart, plugins: StartDependencies): IndexManagementPluginStart {
const { fleet, usageCollection, cloud, share, console, ml } = plugins;
const { fleet, usageCollection, cloud, share, console, ml, licensing } = plugins;
return {
extensionsService: this.extensionsService.setup(),
getIndexMappingComponent: (deps: { history: ScopedHistory<unknown> }) => {
@ -134,6 +134,7 @@ export class IndexMgmtUIPlugin
cloud,
console,
ml,
licensing,
},
services: {
extensionsService: this.extensionsService,

View file

@ -17,6 +17,7 @@ import { ManagementSetup } from '@kbn/management-plugin/public';
import { MlPluginStart } from '@kbn/ml-plugin/public';
import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { LicensingPluginStart } from '@kbn/licensing-plugin/public';
export interface IndexManagementStartServices {
analytics: Pick<AnalyticsServiceStart, 'reportEvent'>;
@ -40,6 +41,7 @@ export interface StartDependencies {
fleet?: unknown;
usageCollection: UsageCollectionSetup;
management: ManagementSetup;
licensing?: LicensingPluginStart;
ml?: MlPluginStart;
}