[ML] Removing global isServerless flag (#166651)

Removed `isServerless` flag which lived in our global context and had to
be passed about to the various components which create their own version
of the context using `getMlGlobalServices`
This PR adds a new context which contains flags for all of the features
which can be toggled when in serverless mode.
Flags added:
```
 showNodeInfo
 showMLNavMenu
 showLicenseInfo
 isADEnabled
 isDFAEnabled
 isNLPEnabled
```
The enabled features flags are now read from the config file client
side, rather than using capabilities.

Additional changes:
- Changes the wording of the awaiting ML node callout in serverless.
- In the search project, the default ML page is the trained models list
and not Overview
- Reenables the Memory Usage page for all projects

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
James Gowdy 2023-09-27 09:32:17 +01:00 committed by GitHub
parent e522f0a178
commit c18513e23d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 399 additions and 379 deletions

View file

@ -258,6 +258,9 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.license_management.ui.enabled (boolean)', 'xpack.license_management.ui.enabled (boolean)',
'xpack.maps.preserveDrawingBuffer (boolean)', 'xpack.maps.preserveDrawingBuffer (boolean)',
'xpack.maps.showMapsInspectorAdapter (boolean)', 'xpack.maps.showMapsInspectorAdapter (boolean)',
'xpack.ml.ad.enabled (boolean)',
'xpack.ml.dfa.enabled (boolean)',
'xpack.ml.nlp.enabled (boolean)',
'xpack.osquery.actionEnabled (boolean)', 'xpack.osquery.actionEnabled (boolean)',
'xpack.remote_clusters.ui.enabled (boolean)', 'xpack.remote_clusters.ui.enabled (boolean)',
/** /**

View file

@ -72,10 +72,6 @@ export interface FullTimeRangeSelectorProps {
* @param value - The time field range response. * @param value - The time field range response.
*/ */
apiPath?: SetFullTimeRangeApiPath; apiPath?: SetFullTimeRangeApiPath;
/**
* Optional flag to disable the frozen data tier choice.
*/
hideFrozenDataTierChoice?: boolean;
} }
/** /**
@ -96,13 +92,12 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
disabled, disabled,
callback, callback,
apiPath, apiPath,
hideFrozenDataTierChoice = false,
} = props; } = props;
const { const {
http, http,
notifications: { toasts }, notifications: { toasts },
isServerless, showFrozenDataTierChoice,
} = useDatePickerContext(); } = useDatePickerContext();
// wrapper around setFullTimeRange to allow for the calling of the optional callBack prop // wrapper around setFullTimeRange to allow for the calling of the optional callBack prop
@ -114,9 +109,7 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
toasts, toasts,
http, http,
query, query,
isServerless || hideFrozenDataTierChoice showFrozenDataTierChoice ? frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE : false,
? false
: frozenDataPreference === FROZEN_TIER_PREFERENCE.EXCLUDE,
apiPath apiPath
); );
if (typeof callback === 'function' && fullTimeRange !== undefined) { if (typeof callback === 'function' && fullTimeRange !== undefined) {
@ -138,8 +131,7 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
toasts, toasts,
http, http,
query, query,
isServerless, showFrozenDataTierChoice,
hideFrozenDataTierChoice,
frozenDataPreference, frozenDataPreference,
apiPath, apiPath,
callback, callback,
@ -230,7 +222,7 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
/> />
</EuiButton> </EuiButton>
</EuiToolTip> </EuiToolTip>
{isServerless || hideFrozenDataTierChoice ? null : ( {showFrozenDataTierChoice ? (
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<EuiPopover <EuiPopover
id={'mlFullTimeRangeSelectorOption'} id={'mlFullTimeRangeSelectorOption'}
@ -256,7 +248,7 @@ export const FullTimeRangeSelector: FC<FullTimeRangeSelectorProps> = (props) =>
{popoverContent} {popoverContent}
</EuiPopover> </EuiPopover>
</EuiFlexItem> </EuiFlexItem>
)} ) : null}
</EuiFlexGroup> </EuiFlexGroup>
); );
}; };

View file

@ -45,9 +45,9 @@ export interface DatePickerDependencies {
*/ */
i18n: I18nStart; i18n: I18nStart;
/** /**
* Optional flag to indicate whether kibana is running in serverless * Optional flag to disable the frozen data tier choice.
*/ */
isServerless?: boolean; showFrozenDataTierChoice?: boolean;
} }
/** /**

View file

@ -51,19 +51,19 @@ export interface ChangePointDetectionAppStateProps {
/** App dependencies */ /** App dependencies */
appDependencies: AiopsAppDependencies; appDependencies: AiopsAppDependencies;
/** Optional flag to indicate whether kibana is running in serverless */ /** Optional flag to indicate whether kibana is running in serverless */
isServerless?: boolean; showFrozenDataTierChoice?: boolean;
} }
export const ChangePointDetectionAppState: FC<ChangePointDetectionAppStateProps> = ({ export const ChangePointDetectionAppState: FC<ChangePointDetectionAppStateProps> = ({
dataView, dataView,
savedSearch, savedSearch,
appDependencies, appDependencies,
isServerless = false, showFrozenDataTierChoice = true,
}) => { }) => {
const datePickerDeps: DatePickerDependencies = { const datePickerDeps: DatePickerDependencies = {
...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
uiSettingsKeys: UI_SETTINGS, uiSettingsKeys: UI_SETTINGS,
isServerless, showFrozenDataTierChoice,
}; };
const warning = timeSeriesDataViewWarning(dataView, 'change_point_detection'); const warning = timeSeriesDataViewWarning(dataView, 'change_point_detection');

View file

@ -36,14 +36,14 @@ export interface LogCategorizationAppStateProps {
/** App dependencies */ /** App dependencies */
appDependencies: AiopsAppDependencies; appDependencies: AiopsAppDependencies;
/** Optional flag to indicate whether kibana is running in serverless */ /** Optional flag to indicate whether kibana is running in serverless */
isServerless?: boolean; showFrozenDataTierChoice?: boolean;
} }
export const LogCategorizationAppState: FC<LogCategorizationAppStateProps> = ({ export const LogCategorizationAppState: FC<LogCategorizationAppStateProps> = ({
dataView, dataView,
savedSearch, savedSearch,
appDependencies, appDependencies,
isServerless = false, showFrozenDataTierChoice = true,
}) => { }) => {
if (!dataView) return null; if (!dataView) return null;
@ -56,7 +56,7 @@ export const LogCategorizationAppState: FC<LogCategorizationAppStateProps> = ({
const datePickerDeps: DatePickerDependencies = { const datePickerDeps: DatePickerDependencies = {
...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
uiSettingsKeys: UI_SETTINGS, uiSettingsKeys: UI_SETTINGS,
isServerless, showFrozenDataTierChoice,
}; };
return ( return (

View file

@ -41,7 +41,7 @@ export interface LogRateAnalysisAppStateProps {
/** Option to make main histogram sticky */ /** Option to make main histogram sticky */
stickyHistogram?: boolean; stickyHistogram?: boolean;
/** Optional flag to indicate whether kibana is running in serverless */ /** Optional flag to indicate whether kibana is running in serverless */
isServerless?: boolean; showFrozenDataTierChoice?: boolean;
} }
export const LogRateAnalysisAppState: FC<LogRateAnalysisAppStateProps> = ({ export const LogRateAnalysisAppState: FC<LogRateAnalysisAppStateProps> = ({
@ -49,7 +49,7 @@ export const LogRateAnalysisAppState: FC<LogRateAnalysisAppStateProps> = ({
savedSearch, savedSearch,
appDependencies, appDependencies,
stickyHistogram, stickyHistogram,
isServerless = false, showFrozenDataTierChoice = true,
}) => { }) => {
if (!dataView) return null; if (!dataView) return null;
@ -62,7 +62,7 @@ export const LogRateAnalysisAppState: FC<LogRateAnalysisAppStateProps> = ({
const datePickerDeps: DatePickerDependencies = { const datePickerDeps: DatePickerDependencies = {
...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
uiSettingsKeys: UI_SETTINGS, uiSettingsKeys: UI_SETTINGS,
isServerless, showFrozenDataTierChoice,
}; };
return ( return (

View file

@ -58,7 +58,7 @@ export interface LogRateAnalysisContentWrapperProps {
*/ */
onAnalysisCompleted?: (d: LogRateAnalysisResultsData) => void; onAnalysisCompleted?: (d: LogRateAnalysisResultsData) => void;
/** Optional flag to indicate whether kibana is running in serverless */ /** Optional flag to indicate whether kibana is running in serverless */
isServerless?: boolean; showFrozenDataTierChoice?: boolean;
} }
export const LogRateAnalysisContentWrapper: FC<LogRateAnalysisContentWrapperProps> = ({ export const LogRateAnalysisContentWrapper: FC<LogRateAnalysisContentWrapperProps> = ({
@ -72,7 +72,7 @@ export const LogRateAnalysisContentWrapper: FC<LogRateAnalysisContentWrapperProp
barColorOverride, barColorOverride,
barHighlightColorOverride, barHighlightColorOverride,
onAnalysisCompleted, onAnalysisCompleted,
isServerless = false, showFrozenDataTierChoice = true,
}) => { }) => {
if (!dataView) return null; if (!dataView) return null;
@ -85,6 +85,7 @@ export const LogRateAnalysisContentWrapper: FC<LogRateAnalysisContentWrapperProp
const datePickerDeps = { const datePickerDeps = {
...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), ...pick(appDependencies, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
uiSettingsKeys: UI_SETTINGS, uiSettingsKeys: UI_SETTINGS,
showFrozenDataTierChoice,
}; };
return ( return (

View file

@ -265,10 +265,13 @@ export const DataVisualizerStateContextProvider: FC<DataVisualizerStateContextPr
interface Props { interface Props {
getAdditionalLinks?: GetAdditionalLinks; getAdditionalLinks?: GetAdditionalLinks;
isServerless?: boolean; showFrozenDataTierChoice?: boolean;
} }
export const IndexDataVisualizer: FC<Props> = ({ getAdditionalLinks, isServerless = false }) => { export const IndexDataVisualizer: FC<Props> = ({
getAdditionalLinks,
showFrozenDataTierChoice = true,
}) => {
const coreStart = getCoreStart(); const coreStart = getCoreStart();
const { const {
data, data,
@ -302,7 +305,7 @@ export const IndexDataVisualizer: FC<Props> = ({ getAdditionalLinks, isServerles
const datePickerDeps: DatePickerDependencies = { const datePickerDeps: DatePickerDependencies = {
...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), ...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
uiSettingsKeys: UI_SETTINGS, uiSettingsKeys: UI_SETTINGS,
isServerless, showFrozenDataTierChoice,
}; };
return ( return (

View file

@ -16,3 +16,23 @@ export const ML_APP_NAME = i18n.translate('xpack.ml.navMenu.mlAppNameText', {
export const ML_APP_ROUTE = '/app/ml'; export const ML_APP_ROUTE = '/app/ml';
export const ML_INTERNAL_BASE_PATH = '/internal/ml'; export const ML_INTERNAL_BASE_PATH = '/internal/ml';
export const ML_EXTERNAL_BASE_PATH = '/api/ml'; export const ML_EXTERNAL_BASE_PATH = '/api/ml';
export type MlFeatures = Record<'ad' | 'dfa' | 'nlp', boolean>;
export interface ConfigSchema {
ad?: { enabled: boolean };
dfa?: { enabled: boolean };
nlp?: { enabled: boolean };
}
export function initEnabledFeatures(enabledFeatures: MlFeatures, config: ConfigSchema) {
if (config.ad?.enabled !== undefined) {
enabledFeatures.ad = config.ad.enabled;
}
if (config.dfa?.enabled !== undefined) {
enabledFeatures.dfa = config.dfa.enabled;
}
if (config.nlp?.enabled !== undefined) {
enabledFeatures.nlp = config.nlp.enabled;
}
}

View file

@ -15,15 +15,16 @@ import { ChangePointDetection } from '@kbn/aiops-plugin/public';
import { useDataSource } from '../contexts/ml/data_source_context'; import { useDataSource } from '../contexts/ml/data_source_context';
import { useFieldStatsTrigger, FieldStatsFlyoutProvider } from '../components/field_stats_flyout'; import { useFieldStatsTrigger, FieldStatsFlyoutProvider } from '../components/field_stats_flyout';
import { useMlKibana, useIsServerless } from '../contexts/kibana'; import { useMlKibana } from '../contexts/kibana';
import { HelpMenu } from '../components/help_menu'; import { HelpMenu } from '../components/help_menu';
import { TechnicalPreviewBadge } from '../components/technical_preview_badge'; import { TechnicalPreviewBadge } from '../components/technical_preview_badge';
import { MlPageHeader } from '../components/page_header'; import { MlPageHeader } from '../components/page_header';
import { useEnabledFeatures } from '../contexts/ml/serverless_context';
export const ChangePointDetectionPage: FC = () => { export const ChangePointDetectionPage: FC = () => {
const { services } = useMlKibana(); const { services } = useMlKibana();
const isServerless = useIsServerless(); const { showNodeInfo } = useEnabledFeatures();
const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource();
@ -46,7 +47,7 @@ export const ChangePointDetectionPage: FC = () => {
<ChangePointDetection <ChangePointDetection
dataView={dataView} dataView={dataView}
savedSearch={savedSearch} savedSearch={savedSearch}
isServerless={isServerless} showFrozenDataTierChoice={showNodeInfo}
appDependencies={{ appDependencies={{
...pick(services, [ ...pick(services, [
'application', 'application',

View file

@ -11,14 +11,15 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { LogCategorization } from '@kbn/aiops-plugin/public'; import { LogCategorization } from '@kbn/aiops-plugin/public';
import { useDataSource } from '../contexts/ml/data_source_context'; import { useDataSource } from '../contexts/ml/data_source_context';
import { useMlKibana, useIsServerless } from '../contexts/kibana'; import { useMlKibana } from '../contexts/kibana';
import { useEnabledFeatures } from '../contexts/ml';
import { HelpMenu } from '../components/help_menu'; import { HelpMenu } from '../components/help_menu';
import { TechnicalPreviewBadge } from '../components/technical_preview_badge'; import { TechnicalPreviewBadge } from '../components/technical_preview_badge';
import { MlPageHeader } from '../components/page_header'; import { MlPageHeader } from '../components/page_header';
export const LogCategorizationPage: FC = () => { export const LogCategorizationPage: FC = () => {
const { services } = useMlKibana(); const { services } = useMlKibana();
const isServerless = useIsServerless(); const { showNodeInfo } = useEnabledFeatures();
const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource();
@ -41,7 +42,7 @@ export const LogCategorizationPage: FC = () => {
<LogCategorization <LogCategorization
dataView={dataView} dataView={dataView}
savedSearch={savedSearch} savedSearch={savedSearch}
isServerless={isServerless} showFrozenDataTierChoice={showNodeInfo}
appDependencies={pick(services, [ appDependencies={pick(services, [
'application', 'application',
'data', 'data',

View file

@ -12,14 +12,15 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { LogRateAnalysis } from '@kbn/aiops-plugin/public'; import { LogRateAnalysis } from '@kbn/aiops-plugin/public';
import { useDataSource } from '../contexts/ml/data_source_context'; import { useDataSource } from '../contexts/ml/data_source_context';
import { useMlKibana, useIsServerless } from '../contexts/kibana'; import { useMlKibana } from '../contexts/kibana';
import { HelpMenu } from '../components/help_menu'; import { HelpMenu } from '../components/help_menu';
import { TechnicalPreviewBadge } from '../components/technical_preview_badge'; import { TechnicalPreviewBadge } from '../components/technical_preview_badge';
import { MlPageHeader } from '../components/page_header'; import { MlPageHeader } from '../components/page_header';
import { useEnabledFeatures } from '../contexts/ml';
export const LogRateAnalysisPage: FC = () => { export const LogRateAnalysisPage: FC = () => {
const { services } = useMlKibana(); const { services } = useMlKibana();
const isServerless = useIsServerless(); const { showNodeInfo } = useEnabledFeatures();
const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource(); const { selectedDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource();
@ -44,7 +45,7 @@ export const LogRateAnalysisPage: FC = () => {
stickyHistogram={false} stickyHistogram={false}
dataView={dataView} dataView={dataView}
savedSearch={savedSearch} savedSearch={savedSearch}
isServerless={isServerless} showFrozenDataTierChoice={showNodeInfo}
appDependencies={pick(services, [ appDependencies={pick(services, [
'application', 'application',
'data', 'data',

View file

@ -19,6 +19,7 @@ import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-pl
import { StorageContextProvider } from '@kbn/ml-local-storage'; import { StorageContextProvider } from '@kbn/ml-local-storage';
import useLifecycles from 'react-use/lib/useLifecycles'; import useLifecycles from 'react-use/lib/useLifecycles';
import useObservable from 'react-use/lib/useObservable'; import useObservable from 'react-use/lib/useObservable';
import type { MlFeatures } from '../../common/constants/app';
import { MlLicense } from '../../common/license'; import { MlLicense } from '../../common/license';
import { MlCapabilitiesService } from './capabilities/check_capabilities'; import { MlCapabilitiesService } from './capabilities/check_capabilities';
import { ML_STORAGE_KEYS } from '../../common/types/storage'; import { ML_STORAGE_KEYS } from '../../common/types/storage';
@ -30,6 +31,7 @@ import { MlRouter } from './routing';
import { mlApiServicesProvider } from './services/ml_api_service'; import { mlApiServicesProvider } from './services/ml_api_service';
import { HttpService } from './services/http_service'; import { HttpService } from './services/http_service';
import type { PageDependencies } from './routing/router'; import type { PageDependencies } from './routing/router';
import { EnabledFeaturesContextProvider } from './contexts/ml';
export type MlDependencies = Omit< export type MlDependencies = Omit<
MlSetupDependencies, MlSetupDependencies,
@ -42,6 +44,7 @@ interface AppProps {
deps: MlDependencies; deps: MlDependencies;
appMountParams: AppMountParameters; appMountParams: AppMountParameters;
isServerless: boolean; isServerless: boolean;
mlFeatures: MlFeatures;
} }
const localStorage = new Storage(window.localStorage); const localStorage = new Storage(window.localStorage);
@ -49,11 +52,7 @@ const localStorage = new Storage(window.localStorage);
/** /**
* Provides global services available across the entire ML app. * Provides global services available across the entire ML app.
*/ */
export function getMlGlobalServices( export function getMlGlobalServices(httpStart: HttpStart, usageCollection?: UsageCollectionSetup) {
httpStart: HttpStart,
isServerless: boolean,
usageCollection?: UsageCollectionSetup
) {
const httpService = new HttpService(httpStart); const httpService = new HttpService(httpStart);
const mlApiServices = mlApiServicesProvider(httpService); const mlApiServices = mlApiServicesProvider(httpService);
@ -63,7 +62,6 @@ export function getMlGlobalServices(
mlUsageCollection: mlUsageCollectionProvider(usageCollection), mlUsageCollection: mlUsageCollectionProvider(usageCollection),
mlCapabilities: new MlCapabilitiesService(mlApiServices), mlCapabilities: new MlCapabilitiesService(mlApiServices),
mlLicense: new MlLicense(), mlLicense: new MlLicense(),
isServerless,
}; };
} }
@ -73,7 +71,7 @@ export interface MlServicesContext {
export type MlGlobalServices = ReturnType<typeof getMlGlobalServices>; export type MlGlobalServices = ReturnType<typeof getMlGlobalServices>;
const App: FC<AppProps> = ({ coreStart, deps, appMountParams, isServerless }) => { const App: FC<AppProps> = ({ coreStart, deps, appMountParams, isServerless, mlFeatures }) => {
const pageDeps: PageDependencies = { const pageDeps: PageDependencies = {
history: appMountParams.history, history: appMountParams.history,
setHeaderActionMenu: appMountParams.setHeaderActionMenu, setHeaderActionMenu: appMountParams.setHeaderActionMenu,
@ -106,9 +104,9 @@ const App: FC<AppProps> = ({ coreStart, deps, appMountParams, isServerless }) =>
contentManagement: deps.contentManagement, contentManagement: deps.contentManagement,
presentationUtil: deps.presentationUtil, presentationUtil: deps.presentationUtil,
...coreStart, ...coreStart,
mlServices: getMlGlobalServices(coreStart.http, isServerless, deps.usageCollection), mlServices: getMlGlobalServices(coreStart.http, deps.usageCollection),
}; };
}, [deps, coreStart, isServerless]); }, [deps, coreStart]);
useLifecycles( useLifecycles(
function setupLicenseOnMount() { function setupLicenseOnMount() {
@ -132,7 +130,7 @@ const App: FC<AppProps> = ({ coreStart, deps, appMountParams, isServerless }) =>
const datePickerDeps: DatePickerDependencies = { const datePickerDeps: DatePickerDependencies = {
...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']), ...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings', 'i18n']),
uiSettingsKeys: UI_SETTINGS, uiSettingsKeys: UI_SETTINGS,
isServerless, showFrozenDataTierChoice: !isServerless,
}; };
const I18nContext = coreStart.i18n.Context; const I18nContext = coreStart.i18n.Context;
@ -146,7 +144,9 @@ const App: FC<AppProps> = ({ coreStart, deps, appMountParams, isServerless }) =>
<KibanaContextProvider services={services}> <KibanaContextProvider services={services}>
<StorageContextProvider storage={localStorage} storageKeys={ML_STORAGE_KEYS}> <StorageContextProvider storage={localStorage} storageKeys={ML_STORAGE_KEYS}>
<DatePickerContextProvider {...datePickerDeps}> <DatePickerContextProvider {...datePickerDeps}>
<EnabledFeaturesContextProvider isServerless={isServerless} mlFeatures={mlFeatures}>
<MlRouter pageDeps={pageDeps} /> <MlRouter pageDeps={pageDeps} />
</EnabledFeaturesContextProvider>
</DatePickerContextProvider> </DatePickerContextProvider>
</StorageContextProvider> </StorageContextProvider>
</KibanaContextProvider> </KibanaContextProvider>
@ -160,7 +160,8 @@ export const renderApp = (
coreStart: CoreStart, coreStart: CoreStart,
deps: MlDependencies, deps: MlDependencies,
appMountParams: AppMountParameters, appMountParams: AppMountParameters,
isServerless: boolean isServerless: boolean,
mlFeatures: MlFeatures
) => { ) => {
setDependencyCache({ setDependencyCache({
timefilter: deps.data.query.timefilter, timefilter: deps.data.query.timefilter,
@ -194,6 +195,7 @@ export const renderApp = (
deps={deps} deps={deps}
appMountParams={appMountParams} appMountParams={appMountParams}
isServerless={isServerless} isServerless={isServerless}
mlFeatures={mlFeatures}
/>, />,
appMountParams.element appMountParams.element
); );

View file

@ -32,7 +32,7 @@ import { JobsExportService } from './jobs_export_service';
import type { JobDependencies } from './jobs_export_service'; import type { JobDependencies } from './jobs_export_service';
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service'; import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
import type { JobType } from '../../../../../common/types/saved_objects'; import type { JobType } from '../../../../../common/types/saved_objects';
import { usePermissionCheck } from '../../../capabilities/check_capabilities'; import { useEnabledFeatures } from '../../../contexts/ml';
interface Props { interface Props {
isDisabled: boolean; isDisabled: boolean;
@ -68,7 +68,7 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
() => toastNotificationServiceProvider(toasts), () => toastNotificationServiceProvider(toasts),
[toasts] [toasts]
); );
const [isADEnabled, isDFAEnabled] = usePermissionCheck(['isADEnabled', 'isDFAEnabled']); const { isADEnabled, isDFAEnabled } = useEnabledFeatures();
const [jobDependencies, setJobDependencies] = useState<JobDependencies>([]); const [jobDependencies, setJobDependencies] = useState<JobDependencies>([]);
const [selectedJobDependencies, setSelectedJobDependencies] = useState<JobDependencies>([]); const [selectedJobDependencies, setSelectedJobDependencies] = useState<JobDependencies>([]);

View file

@ -40,7 +40,7 @@ import { toastNotificationServiceProvider } from '../../../services/toast_notifi
import { JobImportService } from './jobs_import_service'; import { JobImportService } from './jobs_import_service';
import { useValidateIds } from './validate'; import { useValidateIds } from './validate';
import type { ImportedAdJob, JobIdObject, SkippedJobs } from './jobs_import_service'; import type { ImportedAdJob, JobIdObject, SkippedJobs } from './jobs_import_service';
import { usePermissionCheck } from '../../../capabilities/check_capabilities'; import { useEnabledFeatures } from '../../../contexts/ml';
interface Props { interface Props {
isDisabled: boolean; isDisabled: boolean;
@ -82,7 +82,7 @@ export const ImportJobsFlyout: FC<Props> = ({ isDisabled }) => {
() => toastNotificationServiceProvider(toasts), () => toastNotificationServiceProvider(toasts),
[toasts] [toasts]
); );
const [isADEnabled, isDFAEnabled] = usePermissionCheck(['isADEnabled', 'isDFAEnabled']); const { isADEnabled, isDFAEnabled } = useEnabledFeatures();
const [validateIds] = useValidateIds( const [validateIds] = useValidateIds(
jobType, jobType,

View file

@ -25,7 +25,7 @@ import { JobMessage } from '../../../../common/types/audit_message';
import { blurButtonOnClick } from '../../util/component_utils'; import { blurButtonOnClick } from '../../util/component_utils';
import { JobIcon } from '../job_message_icon'; import { JobIcon } from '../job_message_icon';
import { useIsServerless } from '../../contexts/kibana'; import { useEnabledFeatures } from '../../contexts/ml';
interface JobMessagesProps { interface JobMessagesProps {
messages: JobMessage[]; messages: JobMessage[];
@ -46,7 +46,7 @@ export const JobMessages: FC<JobMessagesProps> = ({
refreshMessage, refreshMessage,
actionHandler, actionHandler,
}) => { }) => {
const isServerless = useIsServerless(); const { showNodeInfo } = useEnabledFeatures();
const columns: Array<EuiBasicTableColumn<JobMessage>> = useMemo(() => { const columns: Array<EuiBasicTableColumn<JobMessage>> = useMemo(() => {
const cols = [ const cols = [
{ {
@ -90,7 +90,7 @@ export const JobMessages: FC<JobMessagesProps> = ({
}, },
]; ];
if (isServerless === false) { if (showNodeInfo) {
cols.splice(2, 0, { cols.splice(2, 0, {
field: 'node_name', field: 'node_name',
name: i18n.translate('xpack.ml.jobMessages.nodeLabel', { name: i18n.translate('xpack.ml.jobMessages.nodeLabel', {
@ -101,7 +101,7 @@ export const JobMessages: FC<JobMessagesProps> = ({
} }
return cols; return cols;
}, [isServerless, refreshMessage]); }, [showNodeInfo, refreshMessage]);
if (typeof actionHandler === 'function') { if (typeof actionHandler === 'function') {
columns.push({ columns.push({

View file

@ -18,10 +18,10 @@ import {
} from '@elastic/eui'; } from '@elastic/eui';
import type { SyncSavedObjectResponse, SyncResult } from '../../../../common/types/saved_objects'; import type { SyncSavedObjectResponse, SyncResult } from '../../../../common/types/saved_objects';
import { usePermissionCheck } from '../../capabilities/check_capabilities'; import { useEnabledFeatures } from '../../contexts/ml';
export const SyncList: FC<{ syncItems: SyncSavedObjectResponse | null }> = ({ syncItems }) => { export const SyncList: FC<{ syncItems: SyncSavedObjectResponse | null }> = ({ syncItems }) => {
const [isADEnabled] = usePermissionCheck(['isADEnabled']); const { isADEnabled } = useEnabledFeatures();
if (syncItems === null) { if (syncItems === null) {
return null; return null;

View file

@ -10,15 +10,15 @@ import React, { FC } from 'react';
import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { lazyMlNodesAvailable } from '../../ml_nodes_check'; import { lazyMlNodesAvailable } from '../../ml_nodes_check';
import { useIsServerless } from '../../contexts/kibana'; import { useEnabledFeatures } from '../../contexts/ml';
interface Props { interface Props {
jobCount: number; jobCount: number;
} }
export const JobsAwaitingNodeWarning: FC<Props> = ({ jobCount }) => { export const JobsAwaitingNodeWarning: FC<Props> = ({ jobCount }) => {
const isServerless = useIsServerless(); const { showNodeInfo } = useEnabledFeatures();
if (isServerless || lazyMlNodesAvailable() === false || jobCount === 0) { if (showNodeInfo === false || lazyMlNodesAvailable() === false || jobCount === 0) {
return null; return null;
} }

View file

@ -11,33 +11,19 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { JobType } from '../../../../common/types/saved_objects'; import { JobType } from '../../../../common/types/saved_objects';
import { lazyMlNodesAvailable } from '../../ml_nodes_check'; import { lazyMlNodesAvailable } from '../../ml_nodes_check';
import { useIsServerless } from '../../contexts/kibana'; import { useEnabledFeatures } from '../../contexts/ml';
interface Props { interface Props {
jobType: JobType; jobType: JobType;
} }
export const NewJobAwaitingNodeWarning: FC<Props> = () => { export const NewJobAwaitingNodeWarning: FC<Props> = () => {
const isServerless = useIsServerless(); const { showNodeInfo } = useEnabledFeatures();
if (lazyMlNodesAvailable() === false) { if (lazyMlNodesAvailable() === false) {
return null; return null;
} }
return isServerless ? ( return showNodeInfo ? (
<>
<EuiCallOut
title={
<FormattedMessage
id="xpack.ml.jobsAwaitingNodeWarning.serverless.title"
defaultMessage="Machine learning is starting..."
/>
}
color="primary"
iconType="iInCircle"
/>
<EuiSpacer size="m" />
</>
) : (
<> <>
<EuiCallOut <EuiCallOut
title={ title={
@ -58,5 +44,19 @@ export const NewJobAwaitingNodeWarning: FC<Props> = () => {
</EuiCallOut> </EuiCallOut>
<EuiSpacer size="m" /> <EuiSpacer size="m" />
</> </>
) : (
<>
<EuiCallOut
title={
<FormattedMessage
id="xpack.ml.jobsAwaitingNodeWarning.serverless.title"
defaultMessage="Machine learning is starting..."
/>
}
color="primary"
iconType="iInCircle"
/>
<EuiSpacer size="m" />
</>
); );
}; };

View file

@ -17,7 +17,7 @@ import { countBy } from 'lodash';
import useMount from 'react-use/lib/useMount'; import useMount from 'react-use/lib/useMount';
import { useMlApiContext } from '../../contexts/kibana'; import { useMlApiContext } from '../../contexts/kibana';
import { useToastNotificationService } from '../../services/toast_notification_service'; import { useToastNotificationService } from '../../services/toast_notification_service';
import { usePermissionCheck } from '../../capabilities/check_capabilities'; import { useEnabledFeatures } from '../../contexts/ml';
type EntityType = 'anomaly_detector' | 'data_frame_analytics' | 'trained_models'; type EntityType = 'anomaly_detector' | 'data_frame_analytics' | 'trained_models';
@ -61,11 +61,7 @@ export const MlEntitySelector: FC<MlEntitySelectorProps> = ({
onSelectionChange, onSelectionChange,
handleDuplicates = false, handleDuplicates = false,
}) => { }) => {
const [isADEnabled, isDFAEnabled, isNLPEnabled] = usePermissionCheck([ const { isADEnabled, isDFAEnabled, isNLPEnabled } = useEnabledFeatures();
'isADEnabled',
'isDFAEnabled',
'isNLPEnabled',
]);
const { jobs: jobsApi, trainedModels, dataFrameAnalytics } = useMlApiContext(); const { jobs: jobsApi, trainedModels, dataFrameAnalytics } = useMlApiContext();
const { displayErrorToast } = useToastNotificationService(); const { displayErrorToast } = useToastNotificationService();
const visColorsBehindText = euiPaletteColorBlindBehindText(); const visColorsBehindText = euiPaletteColorBlindBehindText();

View file

@ -20,7 +20,7 @@ import { DatePickerWrapper } from '@kbn/ml-date-picker';
import * as routes from '../../routing/routes'; import * as routes from '../../routing/routes';
import { MlPageWrapper } from '../../routing/ml_page_wrapper'; import { MlPageWrapper } from '../../routing/ml_page_wrapper';
import { useMlKibana, useNavigateToPath, useIsServerless } from '../../contexts/kibana'; import { useMlKibana, useNavigateToPath } from '../../contexts/kibana';
import type { MlRoute, PageDependencies } from '../../routing/router'; import type { MlRoute, PageDependencies } from '../../routing/router';
import { useActiveRoute } from '../../routing/use_active_route'; import { useActiveRoute } from '../../routing/use_active_route';
import { useDocTitle } from '../../routing/use_doc_title'; import { useDocTitle } from '../../routing/use_doc_title';
@ -28,6 +28,7 @@ import { useDocTitle } from '../../routing/use_doc_title';
import { MlPageHeaderRenderer } from '../page_header/page_header'; import { MlPageHeaderRenderer } from '../page_header/page_header';
import { useSideNavItems } from './side_nav'; import { useSideNavItems } from './side_nav';
import { useEnabledFeatures } from '../../contexts/ml';
const ML_APP_SELECTOR = '[data-test-subj="mlApp"]'; const ML_APP_SELECTOR = '[data-test-subj="mlApp"]';
@ -55,7 +56,7 @@ export const MlPage: FC<{ pageDeps: PageDependencies }> = React.memo(({ pageDeps
mlServices: { httpService }, mlServices: { httpService },
}, },
} = useMlKibana(); } = useMlKibana();
const isServerless = useIsServerless(); const { showMLNavMenu } = useEnabledFeatures();
const headerPortalNode = useMemo(() => createHtmlPortalNode(), []); const headerPortalNode = useMemo(() => createHtmlPortalNode(), []);
const [isHeaderMounted, setIsHeaderMounted] = useState(false); const [isHeaderMounted, setIsHeaderMounted] = useState(false);
@ -127,7 +128,7 @@ export const MlPage: FC<{ pageDeps: PageDependencies }> = React.memo(({ pageDeps
data-test-subj={'mlApp'} data-test-subj={'mlApp'}
restrictWidth={false} restrictWidth={false}
solutionNav={ solutionNav={
isServerless === false showMLNavMenu
? { ? {
name: i18n.translate('xpack.ml.plugin.title', { name: i18n.translate('xpack.ml.plugin.title', {
defaultMessage: 'Machine Learning', defaultMessage: 'Machine Learning',

View file

@ -16,4 +16,3 @@ export { useMlApiContext } from './use_ml_api_context';
export { useFieldFormatter } from './use_field_formatter'; export { useFieldFormatter } from './use_field_formatter';
export { useCurrentThemeVars } from './use_current_theme'; export { useCurrentThemeVars } from './use_current_theme';
export { useMlLicenseInfo } from './use_ml_license'; export { useMlLicenseInfo } from './use_ml_license';
export { useIsServerless } from './use_is_serverless';

View file

@ -1,12 +0,0 @@
/*
* 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 { useMlKibana } from './kibana_context';
export function useIsServerless(): boolean {
return useMlKibana().services.mlServices.isServerless;
}

View file

@ -6,3 +6,4 @@
*/ */
export { DataSourceContextProvider, useDataSource } from './data_source_context'; export { DataSourceContextProvider, useDataSource } from './data_source_context';
export { EnabledFeaturesContextProvider, useEnabledFeatures } from './serverless_context';

View file

@ -0,0 +1,57 @@
/*
* 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, { createContext, FC, useContext, useMemo } from 'react';
import type { MlFeatures } from '../../../../common/constants/app';
export interface EnabledFeatures {
showNodeInfo: boolean;
showMLNavMenu: boolean;
showLicenseInfo: boolean;
isADEnabled: boolean;
isDFAEnabled: boolean;
isNLPEnabled: boolean;
}
export const EnabledFeaturesContext = createContext({
showNodeInfo: true,
showMLNavMenu: true,
showLicenseInfo: true,
isADEnabled: true,
isDFAEnabled: true,
isNLPEnabled: true,
});
interface Props {
isServerless: boolean;
mlFeatures: MlFeatures;
}
export const EnabledFeaturesContextProvider: FC<Props> = ({
children,
isServerless,
mlFeatures,
}) => {
const features: EnabledFeatures = {
showNodeInfo: !isServerless,
showMLNavMenu: !isServerless,
showLicenseInfo: !isServerless,
isADEnabled: mlFeatures.ad,
isDFAEnabled: mlFeatures.dfa,
isNLPEnabled: mlFeatures.nlp,
};
return (
<EnabledFeaturesContext.Provider value={features}>{children}</EnabledFeaturesContext.Provider>
);
};
export function useEnabledFeatures() {
const context = useContext(EnabledFeaturesContext);
return useMemo(() => {
return context;
}, [context]);
}

View file

@ -37,8 +37,8 @@ import {
useNotifications, useNotifications,
useNavigateToPath, useNavigateToPath,
useMlKibana, useMlKibana,
useIsServerless,
} from '../../../../contexts/kibana'; } from '../../../../contexts/kibana';
import { useEnabledFeatures } from '../../../../contexts/ml';
import { getDataViewIdFromName } from '../../../../util/index_utils'; import { getDataViewIdFromName } from '../../../../util/index_utils';
import { useNavigateToWizardWithClonedJob } from '../../analytics_management/components/action_clone/clone_action_name'; import { useNavigateToWizardWithClonedJob } from '../../analytics_management/components/action_clone/clone_action_name';
import { import {
@ -55,9 +55,9 @@ interface Props {
refreshJobsCallback: () => void; refreshJobsCallback: () => void;
} }
function getListItemsFactory(isServerless: boolean) { function getListItemsFactory(showLicenseInfo: boolean) {
return (details: Record<string, any>): EuiDescriptionListProps['listItems'] => { return (details: Record<string, any>): EuiDescriptionListProps['listItems'] => {
if (isServerless) { if (showLicenseInfo === false) {
delete details.license_level; delete details.license_level;
} }
@ -94,8 +94,8 @@ export const Controls: FC<Props> = React.memo(
const canCreateDataFrameAnalytics: boolean = usePermissionCheck('canCreateDataFrameAnalytics'); const canCreateDataFrameAnalytics: boolean = usePermissionCheck('canCreateDataFrameAnalytics');
const canDeleteDataFrameAnalytics: boolean = usePermissionCheck('canDeleteDataFrameAnalytics'); const canDeleteDataFrameAnalytics: boolean = usePermissionCheck('canDeleteDataFrameAnalytics');
const deleteAction = useDeleteAction(canDeleteDataFrameAnalytics); const deleteAction = useDeleteAction(canDeleteDataFrameAnalytics);
const isServerless = useIsServerless(); const { showLicenseInfo } = useEnabledFeatures();
const getListItems = useMemo(() => getListItemsFactory(isServerless), [isServerless]); const getListItems = useMemo(() => getListItemsFactory(showLicenseInfo), [showLicenseInfo]);
const { const {
closeDeleteJobCheckModal, closeDeleteJobCheckModal,

View file

@ -15,13 +15,14 @@ import type {
GetAdditionalLinksParams, GetAdditionalLinksParams,
} from '@kbn/data-visualizer-plugin/public'; } from '@kbn/data-visualizer-plugin/public';
import { useTimefilter } from '@kbn/ml-date-picker'; import { useTimefilter } from '@kbn/ml-date-picker';
import { useMlKibana, useMlLocator, useIsServerless } from '../../contexts/kibana'; import { useMlKibana, useMlLocator } from '../../contexts/kibana';
import { HelpMenu } from '../../components/help_menu'; import { HelpMenu } from '../../components/help_menu';
import { ML_PAGES } from '../../../../common/constants/locator'; import { ML_PAGES } from '../../../../common/constants/locator';
import { isFullLicense } from '../../license'; import { isFullLicense } from '../../license';
import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes'; import { mlNodesAvailable, getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes';
import { checkPermission } from '../../capabilities/check_capabilities'; import { checkPermission } from '../../capabilities/check_capabilities';
import { MlPageHeader } from '../../components/page_header'; import { MlPageHeader } from '../../components/page_header';
import { useEnabledFeatures } from '../../contexts/ml';
export const IndexDataVisualizerPage: FC = () => { export const IndexDataVisualizerPage: FC = () => {
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false }); useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
@ -37,7 +38,7 @@ export const IndexDataVisualizerPage: FC = () => {
}, },
}, },
} = useMlKibana(); } = useMlKibana();
const isServerless = useIsServerless(); const { showNodeInfo } = useEnabledFeatures();
const mlLocator = useMlLocator()!; const mlLocator = useMlLocator()!;
const mlFeaturesDisabled = !isFullLicense(); const mlFeaturesDisabled = !isFullLicense();
getMlNodeCount(); getMlNodeCount();
@ -191,7 +192,7 @@ export const IndexDataVisualizerPage: FC = () => {
</MlPageHeader> </MlPageHeader>
<IndexDataVisualizer <IndexDataVisualizer
getAdditionalLinks={getAdditionalLinks} getAdditionalLinks={getAdditionalLinks}
isServerless={isServerless} showFrozenDataTierChoice={showNodeInfo}
/> />
</> </>
) : null} ) : null}

View file

@ -137,7 +137,7 @@ export class JobsListView extends Component {
loadFullJob(jobId) loadFullJob(jobId)
.then((job) => { .then((job) => {
const fullJobsList = { ...this.state.fullJobsList }; const fullJobsList = { ...this.state.fullJobsList };
if (this.props.isServerless) { if (this.props.showNodeInfo === false) {
job = removeNodeInfo(job); job = removeNodeInfo(job);
} }
fullJobsList[jobId] = job; fullJobsList[jobId] = job;
@ -318,7 +318,7 @@ export class JobsListView extends Component {
const fullJobsList = {}; const fullJobsList = {};
const jobsSummaryList = jobs.map((job) => { const jobsSummaryList = jobs.map((job) => {
if (job.fullJob !== undefined) { if (job.fullJob !== undefined) {
if (this.props.isServerless) { if (this.props.showNodeInfo === false) {
job.fullJob = removeNodeInfo(job.fullJob); job.fullJob = removeNodeInfo(job.fullJob);
} }
fullJobsList[job.id] = job.fullJob; fullJobsList[job.id] = job.fullJob;
@ -417,7 +417,7 @@ export class JobsListView extends Component {
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
<JobStatsBar <JobStatsBar
jobsSummaryList={jobsSummaryList} jobsSummaryList={jobsSummaryList}
isServerless={this.props.isServerless} showNodeInfo={this.props.showNodeInfo}
/> />
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>

View file

@ -12,11 +12,9 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
function createJobStats(jobsSummaryList, isServerless) { function createJobStats(jobsSummaryList, showNodeInfo) {
const displayNodeInfo = isServerless === false;
const jobStats = { const jobStats = {
...(displayNodeInfo ...(showNodeInfo
? { ? {
activeNodes: { activeNodes: {
label: i18n.translate('xpack.ml.jobsList.statsBar.activeMLNodesLabel', { label: i18n.translate('xpack.ml.jobsList.statsBar.activeMLNodesLabel', {
@ -100,20 +98,20 @@ function createJobStats(jobsSummaryList, isServerless) {
jobStats.failed.show = false; jobStats.failed.show = false;
} }
if (displayNodeInfo) { if (showNodeInfo) {
jobStats.activeNodes.value = Object.keys(mlNodes).length; jobStats.activeNodes.value = Object.keys(mlNodes).length;
} }
return jobStats; return jobStats;
} }
export const JobStatsBar = ({ jobsSummaryList, isServerless }) => { export const JobStatsBar = ({ jobsSummaryList, showNodeInfo }) => {
const jobStats = createJobStats(jobsSummaryList, isServerless); const jobStats = createJobStats(jobsSummaryList, showNodeInfo);
return <StatsBar stats={jobStats} dataTestSub={'mlJobStatsBar'} />; return <StatsBar stats={jobStats} dataTestSub={'mlJobStatsBar'} />;
}; };
JobStatsBar.propTypes = { JobStatsBar.propTypes = {
jobsSummaryList: PropTypes.array.isRequired, jobsSummaryList: PropTypes.array.isRequired,
isServerless: PropTypes.bool.isRequired, showNodeInfo: PropTypes.bool.isRequired,
}; };

View file

@ -12,10 +12,11 @@ import { JobsListView } from './components/jobs_list_view';
import { ML_PAGES } from '../../../../common/constants/locator'; import { ML_PAGES } from '../../../../common/constants/locator';
import { ListingPageUrlState } from '../../../../common/types/common'; import { ListingPageUrlState } from '../../../../common/types/common';
import { HelpMenu } from '../../components/help_menu'; import { HelpMenu } from '../../components/help_menu';
import { useIsServerless, useMlKibana } from '../../contexts/kibana'; import { useMlKibana } from '../../contexts/kibana';
import { MlPageHeader } from '../../components/page_header'; import { MlPageHeader } from '../../components/page_header';
import { HeaderMenuPortal } from '../../components/header_menu_portal'; import { HeaderMenuPortal } from '../../components/header_menu_portal';
import { JobsActionMenu } from '../components/jobs_action_menu'; import { JobsActionMenu } from '../components/jobs_action_menu';
import { useEnabledFeatures } from '../../contexts/ml';
interface PageUrlState { interface PageUrlState {
pageKey: typeof ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE; pageKey: typeof ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE;
@ -42,7 +43,8 @@ export const JobsPage: FC<JobsPageProps> = ({ isMlEnabledInSpace, lastRefresh })
const { const {
services: { docLinks }, services: { docLinks },
} = useMlKibana(); } = useMlKibana();
const isServerless = useIsServerless();
const { showNodeInfo } = useEnabledFeatures();
const helpLink = docLinks.links.ml.anomalyDetection; const helpLink = docLinks.links.ml.anomalyDetection;
return ( return (
<> <>
@ -57,7 +59,7 @@ export const JobsPage: FC<JobsPageProps> = ({ isMlEnabledInSpace, lastRefresh })
lastRefresh={lastRefresh} lastRefresh={lastRefresh}
jobsViewState={pageState} jobsViewState={pageState}
onJobsViewStateUpdate={setPageState} onJobsViewStateUpdate={setPageState}
isServerless={isServerless} showNodeInfo={showNodeInfo}
/> />
<HelpMenu docLink={helpLink} /> <HelpMenu docLink={helpLink} />
</> </>

View file

@ -22,7 +22,7 @@ import { EventRateChart } from '../charts/event_rate_chart';
import { LineChartPoint } from '../../../common/chart_loader'; import { LineChartPoint } from '../../../common/chart_loader';
import { JOB_TYPE } from '../../../../../../../common/constants/new_job'; import { JOB_TYPE } from '../../../../../../../common/constants/new_job';
import { TimeRangePicker, TimeRange } from '../../../common/components'; import { TimeRangePicker, TimeRange } from '../../../common/components';
import { useMlKibana, useIsServerless } from '../../../../../contexts/kibana'; import { useMlKibana } from '../../../../../contexts/kibana';
import { import {
ML_FROZEN_TIER_PREFERENCE, ML_FROZEN_TIER_PREFERENCE,
type MlStorageKey, type MlStorageKey,
@ -33,7 +33,6 @@ export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
const timefilter = useTimefilter(); const timefilter = useTimefilter();
const { services } = useMlKibana(); const { services } = useMlKibana();
const dataSourceContext = useDataSource(); const dataSourceContext = useDataSource();
const isServerless = useIsServerless();
const { jobCreator, jobCreatorUpdate, jobCreatorUpdated, chartLoader, chartInterval } = const { jobCreator, jobCreatorUpdate, jobCreatorUpdated, chartLoader, chartInterval } =
useContext(JobCreatorContext); useContext(JobCreatorContext);
@ -138,7 +137,6 @@ export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep })
callback={fullTimeRangeCallback} callback={fullTimeRangeCallback}
timefilter={timefilter} timefilter={timefilter}
apiPath={`${ML_INTERNAL_BASE_PATH}/fields_service/time_field_range`} apiPath={`${ML_INTERNAL_BASE_PATH}/fields_service/time_field_range`}
hideFrozenDataTierChoice={isServerless}
/> />
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem /> <EuiFlexItem />

View file

@ -11,13 +11,15 @@ import type { CoreSetup } from '@kbn/core/public';
import type { ManagementSetup } from '@kbn/management-plugin/public'; import type { ManagementSetup } from '@kbn/management-plugin/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { ManagementAppMountParams } from '@kbn/management-plugin/public'; import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
import type { MlFeatures } from '../../../common/constants/app';
import type { MlStartDependencies } from '../../plugin'; import type { MlStartDependencies } from '../../plugin';
export function registerManagementSection( export function registerManagementSection(
management: ManagementSetup, management: ManagementSetup,
core: CoreSetup<MlStartDependencies>, core: CoreSetup<MlStartDependencies>,
deps: { usageCollection?: UsageCollectionSetup }, deps: { usageCollection?: UsageCollectionSetup },
isServerless: boolean isServerless: boolean,
mlFeatures: MlFeatures
) { ) {
return management.sections.section.insightsAndAlerting.registerApp({ return management.sections.section.insightsAndAlerting.registerApp({
id: 'jobsListLink', id: 'jobsListLink',
@ -27,7 +29,7 @@ export function registerManagementSection(
order: 4, order: 4,
async mount(params: ManagementAppMountParams) { async mount(params: ManagementAppMountParams) {
const { mountApp } = await import('./jobs_list'); const { mountApp } = await import('./jobs_list');
return mountApp(core, params, deps, isServerless); return mountApp(core, params, deps, isServerless, mlFeatures);
}, },
}); });
} }

View file

@ -30,7 +30,8 @@ import {
import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { SpacesContextProps, SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { SpacesContextProps, SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { PLUGIN_ID } from '../../../../../../common/constants/app'; import { EnabledFeaturesContextProvider } from '../../../../contexts/ml';
import { type MlFeatures, PLUGIN_ID } from '../../../../../../common/constants/app';
import { checkGetManagementMlJobsResolver } from '../../../../capabilities/check_capabilities'; import { checkGetManagementMlJobsResolver } from '../../../../capabilities/check_capabilities';
@ -46,7 +47,7 @@ import { DocsLink } from './docs_link';
const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>; const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>;
export const JobsListPage: FC<{ interface Props {
coreStart: CoreStart; coreStart: CoreStart;
share: SharePluginStart; share: SharePluginStart;
history: ManagementAppMountParams['history']; history: ManagementAppMountParams['history'];
@ -55,7 +56,10 @@ export const JobsListPage: FC<{
usageCollection?: UsageCollectionSetup; usageCollection?: UsageCollectionSetup;
fieldFormats: FieldFormatsStart; fieldFormats: FieldFormatsStart;
isServerless: boolean; isServerless: boolean;
}> = ({ mlFeatures: MlFeatures;
}
export const JobsListPage: FC<Props> = ({
coreStart, coreStart,
share, share,
history, history,
@ -64,6 +68,7 @@ export const JobsListPage: FC<{
usageCollection, usageCollection,
fieldFormats, fieldFormats,
isServerless, isServerless,
mlFeatures,
}) => { }) => {
const [initialized, setInitialized] = useState(false); const [initialized, setInitialized] = useState(false);
const [accessDenied, setAccessDenied] = useState(false); const [accessDenied, setAccessDenied] = useState(false);
@ -74,8 +79,8 @@ export const JobsListPage: FC<{
const theme$ = coreStart.theme.theme$; const theme$ = coreStart.theme.theme$;
const mlServices = useMemo( const mlServices = useMemo(
() => getMlGlobalServices(coreStart.http, isServerless, usageCollection), () => getMlGlobalServices(coreStart.http, usageCollection),
[coreStart.http, isServerless, usageCollection] [coreStart.http, usageCollection]
); );
const check = async () => { const check = async () => {
@ -134,6 +139,7 @@ export const JobsListPage: FC<{
}} }}
> >
<ContextWrapper feature={PLUGIN_ID}> <ContextWrapper feature={PLUGIN_ID}>
<EnabledFeaturesContextProvider isServerless={isServerless} mlFeatures={mlFeatures}>
<Router history={history}> <Router history={history}>
<EuiPageTemplate.Header <EuiPageTemplate.Header
pageTitle={ pageTitle={
@ -190,6 +196,7 @@ export const JobsListPage: FC<{
<SpaceManagement spacesApi={spacesApi} setCurrentTab={setCurrentTabId} /> <SpaceManagement spacesApi={spacesApi} setCurrentTab={setCurrentTabId} />
</EuiPageTemplate.Section> </EuiPageTemplate.Section>
</Router> </Router>
</EnabledFeaturesContextProvider>
</ContextWrapper> </ContextWrapper>
</KibanaContextProvider> </KibanaContextProvider>
</KibanaThemeProvider> </KibanaThemeProvider>

View file

@ -21,7 +21,7 @@ import {
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import { useTableState } from '@kbn/ml-in-memory-table'; import { useTableState } from '@kbn/ml-in-memory-table';
import { usePermissionCheck } from '../../../../../capabilities/check_capabilities'; import { useEnabledFeatures } from '../../../../../contexts/ml';
import type { JobType, MlSavedObjectType } from '../../../../../../../common/types/saved_objects'; import type { JobType, MlSavedObjectType } from '../../../../../../../common/types/saved_objects';
import type { import type {
ManagementListResponse, ManagementListResponse,
@ -46,11 +46,7 @@ export const SpaceManagement: FC<Props> = ({ spacesApi, setCurrentTab }) => {
const [filters, setFilters] = useState<SearchFilterConfig[] | undefined>(); const [filters, setFilters] = useState<SearchFilterConfig[] | undefined>();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [isADEnabled, isDFAEnabled, isNLPEnabled] = usePermissionCheck([ const { isADEnabled, isDFAEnabled, isNLPEnabled } = useEnabledFeatures();
'isADEnabled',
'isDFAEnabled',
'isNLPEnabled',
]);
const { onTableChange, pagination, sorting, setPageIndex } = useTableState<ManagementItems>( const { onTableChange, pagination, sorting, setPageIndex } = useTableState<ManagementItems>(
items ?? [], items ?? [],

View file

@ -14,6 +14,7 @@ import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { MlFeatures } from '../../../../common/constants/app';
import type { MlStartDependencies } from '../../../plugin'; import type { MlStartDependencies } from '../../../plugin';
import { JobsListPage } from './components'; import { JobsListPage } from './components';
import { getJobsListBreadcrumbs } from '../breadcrumbs'; import { getJobsListBreadcrumbs } from '../breadcrumbs';
@ -26,6 +27,7 @@ const renderApp = (
data: DataPublicPluginStart, data: DataPublicPluginStart,
fieldFormats: FieldFormatsStart, fieldFormats: FieldFormatsStart,
isServerless: boolean, isServerless: boolean,
mlFeatures: MlFeatures,
spacesApi?: SpacesPluginStart, spacesApi?: SpacesPluginStart,
usageCollection?: UsageCollectionSetup usageCollection?: UsageCollectionSetup
) => { ) => {
@ -39,6 +41,7 @@ const renderApp = (
usageCollection, usageCollection,
fieldFormats, fieldFormats,
isServerless, isServerless,
mlFeatures,
}), }),
element element
); );
@ -51,7 +54,8 @@ export async function mountApp(
core: CoreSetup<MlStartDependencies>, core: CoreSetup<MlStartDependencies>,
params: ManagementAppMountParams, params: ManagementAppMountParams,
deps: { usageCollection?: UsageCollectionSetup }, deps: { usageCollection?: UsageCollectionSetup },
isServerless: boolean isServerless: boolean,
mlFeatures: MlFeatures
) { ) {
const [coreStart, pluginsStart] = await core.getStartServices(); const [coreStart, pluginsStart] = await core.getStartServices();
@ -64,6 +68,7 @@ export async function mountApp(
pluginsStart.data, pluginsStart.data,
pluginsStart.fieldFormats, pluginsStart.fieldFormats,
isServerless, isServerless,
mlFeatures,
pluginsStart.spaces, pluginsStart.spaces,
deps.usageCollection deps.usageCollection
); );

View file

@ -29,7 +29,7 @@ import { useFieldFormatter, useMlKibana } from '../../contexts/kibana';
import { useRefresh } from '../../routing/use_refresh'; import { useRefresh } from '../../routing/use_refresh';
import { getMemoryItemColor } from '../memory_item_colors'; import { getMemoryItemColor } from '../memory_item_colors';
import { useToastNotificationService } from '../../services/toast_notification_service'; import { useToastNotificationService } from '../../services/toast_notification_service';
import { usePermissionCheck } from '../../capabilities/check_capabilities'; import { useEnabledFeatures } from '../../contexts/ml';
interface Props { interface Props {
node?: string; node?: string;
@ -73,11 +73,7 @@ export const JobMemoryTreeMap: FC<Props> = ({ node, type, height }) => {
[isDarkTheme] [isDarkTheme]
); );
const [isADEnabled, isDFAEnabled, isNLPEnabled] = usePermissionCheck([ const { isADEnabled, isDFAEnabled, isNLPEnabled } = useEnabledFeatures();
'isADEnabled',
'isDFAEnabled',
'isNLPEnabled',
]);
const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES); const bytesFormatter = useFieldFormatter(FIELD_FORMAT_IDS.BYTES);
const { displayErrorToast } = useToastNotificationService(); const { displayErrorToast } = useToastNotificationService();

View file

@ -13,7 +13,7 @@ import { NodesList } from './nodes_overview';
import { MlPageHeader } from '../components/page_header'; import { MlPageHeader } from '../components/page_header';
import { MemoryPage, JobMemoryTreeMap } from './memory_tree_map'; import { MemoryPage, JobMemoryTreeMap } from './memory_tree_map';
import { SavedObjectsWarning } from '../components/saved_objects_warning'; import { SavedObjectsWarning } from '../components/saved_objects_warning';
import { useIsServerless } from '../contexts/kibana'; import { useEnabledFeatures } from '../contexts/ml';
enum TAB { enum TAB {
NODES, NODES,
@ -24,7 +24,7 @@ export const MemoryUsagePage: FC = () => {
const [selectedTab, setSelectedTab] = useState<TAB>(TAB.NODES); const [selectedTab, setSelectedTab] = useState<TAB>(TAB.NODES);
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true }); useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true });
const isServerless = useIsServerless(); const { showNodeInfo } = useEnabledFeatures();
const refresh = useCallback(() => { const refresh = useCallback(() => {
mlTimefilterRefresh$.next({ mlTimefilterRefresh$.next({
@ -47,7 +47,7 @@ export const MemoryUsagePage: FC = () => {
<SavedObjectsWarning onCloseFlyout={refresh} /> <SavedObjectsWarning onCloseFlyout={refresh} />
{isServerless === false ? ( {showNodeInfo ? (
<> <>
<EuiTabs> <EuiTabs>
<EuiTab <EuiTab

View file

@ -33,7 +33,7 @@ import { ModelPipelines } from './pipelines';
import { AllocatedModels } from '../memory_usage/nodes_overview/allocated_models'; import { AllocatedModels } from '../memory_usage/nodes_overview/allocated_models';
import type { AllocatedModel, TrainedModelStat } from '../../../common/types/trained_models'; import type { AllocatedModel, TrainedModelStat } from '../../../common/types/trained_models';
import { useFieldFormatter } from '../contexts/kibana/use_field_formatter'; import { useFieldFormatter } from '../contexts/kibana/use_field_formatter';
import { useIsServerless } from '../contexts/kibana'; import { useEnabledFeatures } from '../contexts/ml';
interface ExpandedRowProps { interface ExpandedRowProps {
item: ModelItemFull; item: ModelItemFull;
@ -114,7 +114,7 @@ export function useListItemsFormatter() {
export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => { export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
const formatToListItems = useListItemsFormatter(); const formatToListItems = useListItemsFormatter();
const isServerless = useIsServerless(); const { showLicenseInfo, showNodeInfo } = useEnabledFeatures();
const { const {
inference_config: inferenceConfig, inference_config: inferenceConfig,
@ -151,17 +151,17 @@ export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
estimated_operations, estimated_operations,
estimated_heap_memory_usage_bytes, estimated_heap_memory_usage_bytes,
default_field_map, default_field_map,
...(isServerless ? {} : { license_level }), ...(showLicenseInfo ? { license_level } : {}),
}; };
}, [ }, [
default_field_map,
description, description,
estimated_heap_memory_usage_bytes,
estimated_operations,
license_level,
tags, tags,
version, version,
isServerless, estimated_operations,
estimated_heap_memory_usage_bytes,
default_field_map,
showLicenseInfo,
license_level,
]); ]);
const deploymentStatItems: AllocatedModel[] = useMemo<AllocatedModel[]>(() => { const deploymentStatItems: AllocatedModel[] = useMemo<AllocatedModel[]>(() => {
@ -200,8 +200,8 @@ export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
}, [stats]); }, [stats]);
const hideColumns = useMemo(() => { const hideColumns = useMemo(() => {
return isServerless ? ['model_id', 'node_name'] : ['model_id']; return showNodeInfo ? ['model_id'] : ['model_id', 'node_name'];
}, [isServerless]); }, [showNodeInfo]);
const tabs = useMemo<EuiTabbedContentTab[]>(() => { const tabs = useMemo<EuiTabbedContentTab[]>(() => {
return [ return [

View file

@ -63,7 +63,7 @@ import { useRefresh } from '../routing/use_refresh';
import { SavedObjectsWarning } from '../components/saved_objects_warning'; import { SavedObjectsWarning } from '../components/saved_objects_warning';
import { TestTrainedModelFlyout } from './test_models'; import { TestTrainedModelFlyout } from './test_models';
import { AddInferencePipelineFlyout } from '../components/ml_inference'; import { AddInferencePipelineFlyout } from '../components/ml_inference';
import { usePermissionCheck } from '../capabilities/check_capabilities'; import { useEnabledFeatures } from '../contexts/ml';
type Stats = Omit<TrainedModelStat, 'model_id' | 'deployment_stats'>; type Stats = Omit<TrainedModelStat, 'model_id' | 'deployment_stats'>;
@ -105,7 +105,7 @@ export const ModelsList: FC<Props> = ({
}, },
} = useMlKibana(); } = useMlKibana();
const [isNLPEnabled] = usePermissionCheck(['isNLPEnabled']); const { isNLPEnabled } = useEnabledFeatures();
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true }); useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true });

View file

@ -40,7 +40,7 @@ import type {
NotificationItem, NotificationItem,
} from '../../../../common/types/notifications'; } from '../../../../common/types/notifications';
import { useMlKibana } from '../../contexts/kibana'; import { useMlKibana } from '../../contexts/kibana';
import { usePermissionCheck } from '../../capabilities/check_capabilities'; import { useEnabledFeatures } from '../../contexts/ml';
const levelBadgeMap: Record<MlNotificationMessageLevel, IconColor> = { const levelBadgeMap: Record<MlNotificationMessageLevel, IconColor> = {
[ML_NOTIFICATIONS_MESSAGE_LEVEL.ERROR]: 'danger', [ML_NOTIFICATIONS_MESSAGE_LEVEL.ERROR]: 'danger',
@ -67,11 +67,7 @@ export const NotificationsList: FC = () => {
}, },
} = useMlKibana(); } = useMlKibana();
const [isADEnabled, isDFAEnabled, isNLPEnabled] = usePermissionCheck([ const { isADEnabled, isDFAEnabled, isNLPEnabled } = useEnabledFeatures();
'isADEnabled',
'isDFAEnabled',
'isNLPEnabled',
]);
const { displayErrorToast } = useToastNotificationService(); const { displayErrorToast } = useToastNotificationService();

View file

@ -19,7 +19,7 @@ import {
import { ML_PAGES } from '../../../../../common/constants/locator'; import { ML_PAGES } from '../../../../../common/constants/locator';
import { OverviewStatsBar } from '../../../components/collapsible_panel/collapsible_panel'; import { OverviewStatsBar } from '../../../components/collapsible_panel/collapsible_panel';
import { CollapsiblePanel } from '../../../components/collapsible_panel'; import { CollapsiblePanel } from '../../../components/collapsible_panel';
import { useMlKibana, useMlLink, useIsServerless } from '../../../contexts/kibana'; import { useMlKibana, useMlLink } from '../../../contexts/kibana';
import { AnomalyDetectionTable } from './table'; import { AnomalyDetectionTable } from './table';
import { ml } from '../../../services/ml_api_service'; import { ml } from '../../../services/ml_api_service';
import { getGroupsFromJobs, getStatsBarData } from './utils'; import { getGroupsFromJobs, getStatsBarData } from './utils';
@ -31,6 +31,7 @@ import { AnomalyTimelineService } from '../../../services/anomaly_timeline_servi
import type { OverallSwimlaneData } from '../../../explorer/explorer_utils'; import type { OverallSwimlaneData } from '../../../explorer/explorer_utils';
import { AnomalyDetectionEmptyState } from '../../../jobs/jobs_list/components/anomaly_detection_empty_state'; import { AnomalyDetectionEmptyState } from '../../../jobs/jobs_list/components/anomaly_detection_empty_state';
import { overviewPanelDefaultState } from '../../overview_page'; import { overviewPanelDefaultState } from '../../overview_page';
import { useEnabledFeatures } from '../../../contexts/ml';
export type GroupsDictionary = Dictionary<Group>; export type GroupsDictionary = Dictionary<Group>;
@ -57,7 +58,7 @@ export const AnomalyDetectionPanel: FC<Props> = ({ anomalyTimelineService, setLa
} = useMlKibana(); } = useMlKibana();
const { displayErrorToast } = useToastNotificationService(); const { displayErrorToast } = useToastNotificationService();
const isServerless = useIsServerless(); const { showNodeInfo } = useEnabledFeatures();
const refresh = useRefresh(); const refresh = useRefresh();
@ -92,7 +93,7 @@ export const AnomalyDetectionPanel: FC<Props> = ({ anomalyTimelineService, setLa
return job; return job;
}); });
const { groups: jobsGroups, count } = getGroupsFromJobs(jobsSummaryList); const { groups: jobsGroups, count } = getGroupsFromJobs(jobsSummaryList);
const stats = getStatsBarData(jobsSummaryList, isServerless); const stats = getStatsBarData(jobsSummaryList, showNodeInfo);
const statGroups = groupBy( const statGroups = groupBy(
Object.entries(stats) Object.entries(stats)

View file

@ -74,7 +74,7 @@ export function getGroupsFromJobs(jobs: MlSummaryJobs): {
return { groups, count }; return { groups, count };
} }
export function getStatsBarData(jobsList: MlSummaryJob[] | undefined, isServerless: boolean) { export function getStatsBarData(jobsList: MlSummaryJob[] | undefined, showNodeInfo: boolean) {
const jobStats = { const jobStats = {
total: { total: {
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.totalJobsLabel', { label: i18n.translate('xpack.ml.overviewJobsList.statsBar.totalJobsLabel', {
@ -108,9 +108,8 @@ export function getStatsBarData(jobsList: MlSummaryJob[] | undefined, isServerle
show: false, show: false,
group: 0, group: 0,
}, },
...(isServerless ...(showNodeInfo
? {} ? {
: {
activeNodes: { activeNodes: {
label: i18n.translate('xpack.ml.overviewJobsList.statsBar.activeMLNodesLabel', { label: i18n.translate('xpack.ml.overviewJobsList.statsBar.activeMLNodesLabel', {
defaultMessage: 'Active ML nodes', defaultMessage: 'Active ML nodes',
@ -119,7 +118,8 @@ export function getStatsBarData(jobsList: MlSummaryJob[] | undefined, isServerle
show: true, show: true,
group: 1, group: 1,
}, },
}), }
: {}),
activeDatafeeds: { activeDatafeeds: {
label: i18n.translate('xpack.ml.jobsList.statsBar.activeDatafeedsLabel', { label: i18n.translate('xpack.ml.jobsList.statsBar.activeDatafeedsLabel', {
defaultMessage: 'Active datafeeds', defaultMessage: 'Active datafeeds',
@ -166,7 +166,7 @@ export function getStatsBarData(jobsList: MlSummaryJob[] | undefined, isServerle
jobStats.failed.show = false; jobStats.failed.show = false;
} }
if (isServerless === false) { if (showNodeInfo) {
jobStats.activeNodes!.value = Object.keys(mlNodes).length; jobStats.activeNodes!.value = Object.keys(mlNodes).length;
} }

View file

@ -13,7 +13,7 @@ import { AnalyticsPanel } from './analytics_panel';
import { AnomalyTimelineService } from '../../services/anomaly_timeline_service'; import { AnomalyTimelineService } from '../../services/anomaly_timeline_service';
import { mlResultsServiceProvider } from '../../services/results_service'; import { mlResultsServiceProvider } from '../../services/results_service';
import { useMlKibana } from '../../contexts/kibana'; import { useMlKibana } from '../../contexts/kibana';
import { usePermissionCheck } from '../../capabilities/check_capabilities'; import { useEnabledFeatures } from '../../contexts/ml';
interface Props { interface Props {
createAnomalyDetectionJobDisabled: boolean; createAnomalyDetectionJobDisabled: boolean;
@ -33,7 +33,7 @@ export const OverviewContent: FC<Props> = ({
}, },
} = useMlKibana(); } = useMlKibana();
const [isADEnabled, isDFAEnabled] = usePermissionCheck(['isADEnabled', 'isDFAEnabled']); const { isADEnabled, isDFAEnabled } = useEnabledFeatures();
const timefilter = useTimefilter(); const timefilter = useTimefilter();

View file

@ -36,16 +36,7 @@ export const nodesListRouteFactory = (
}); });
const PageWrapper: FC = () => { const PageWrapper: FC = () => {
const { context } = useRouteResolver( const { context } = useRouteResolver('full', [], basicResolvers());
'full',
// only enabled in non-serverless mode
// if a serverless project ever contains all three features
// this check will have to be changed to an
// explicit isServerless check which will probably
// require a change in useRouteResolver
['isADEnabled', 'isDFAEnabled', 'isNLPEnabled'],
basicResolvers()
);
return ( return (
<PageLoader context={context}> <PageLoader context={context}>

View file

@ -11,6 +11,7 @@ import React, { FC, Suspense } from 'react';
import { Redirect } from 'react-router-dom'; import { Redirect } from 'react-router-dom';
import { ML_PAGES } from '../../../locator'; import { ML_PAGES } from '../../../locator';
import type { NavigateToPath } from '../../contexts/kibana'; import type { NavigateToPath } from '../../contexts/kibana';
import { useEnabledFeatures } from '../../contexts/ml/serverless_context';
import { getMlNodeCount } from '../../ml_nodes_check'; import { getMlNodeCount } from '../../ml_nodes_check';
import { loadMlServerInfo } from '../../services/ml_server_info'; import { loadMlServerInfo } from '../../services/ml_server_info';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs'; import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
@ -67,5 +68,13 @@ export const appRootRouteFactory = (navigateToPath: NavigateToPath, basePath: st
}); });
const Page: FC = () => { const Page: FC = () => {
const { isADEnabled, isDFAEnabled, isNLPEnabled } = useEnabledFeatures();
if (isADEnabled === false && isDFAEnabled === false && isNLPEnabled === true) {
// if only NLP is enabled, redirect to the trained models page.
// in the search serverless project, the overview page is blank, so we
// need to redirect to the trained models page instead
return <Redirect to={createPath(ML_PAGES.TRAINED_MODELS_MANAGE)} />;
}
return <Redirect to={createPath(ML_PAGES.OVERVIEW)} />; return <Redirect to={createPath(ML_PAGES.OVERVIEW)} />;
}; };

View file

@ -27,7 +27,7 @@ describe('AnomalyChartsEmbeddableFactory', () => {
const [coreStart, pluginsStart] = await getStartServices(); const [coreStart, pluginsStart] = await getStartServices();
// act // act
const factory = new AnomalyChartsEmbeddableFactory(getStartServices, false); const factory = new AnomalyChartsEmbeddableFactory(getStartServices);
await factory.create({ await factory.create({
jobIds: ['test-job'], jobIds: ['test-job'],

View file

@ -35,8 +35,7 @@ export class AnomalyChartsEmbeddableFactory
]; ];
constructor( constructor(
private getStartServices: StartServicesAccessor<MlStartDependencies, MlPluginStart>, private getStartServices: StartServicesAccessor<MlStartDependencies, MlPluginStart>
private isServerless: boolean
) {} ) {}
public async isEditable() { public async isEditable() {
@ -62,7 +61,7 @@ export class AnomalyChartsEmbeddableFactory
const { resolveEmbeddableAnomalyChartsUserInput } = await import( const { resolveEmbeddableAnomalyChartsUserInput } = await import(
'./anomaly_charts_setup_flyout' './anomaly_charts_setup_flyout'
); );
return await resolveEmbeddableAnomalyChartsUserInput(coreStart, this.isServerless); return await resolveEmbeddableAnomalyChartsUserInput(coreStart);
} catch (e) { } catch (e) {
return Promise.reject(); return Promise.reject();
} }

View file

@ -19,7 +19,6 @@ import { mlApiServicesProvider } from '../../application/services/ml_api_service
export async function resolveEmbeddableAnomalyChartsUserInput( export async function resolveEmbeddableAnomalyChartsUserInput(
coreStart: CoreStart, coreStart: CoreStart,
isServerless: boolean,
input?: AnomalyChartsEmbeddableInput input?: AnomalyChartsEmbeddableInput
): Promise<Partial<AnomalyChartsEmbeddableInput>> { ): Promise<Partial<AnomalyChartsEmbeddableInput>> {
const { http, overlays, theme, i18n } = coreStart; const { http, overlays, theme, i18n } = coreStart;
@ -28,7 +27,7 @@ export async function resolveEmbeddableAnomalyChartsUserInput(
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const { jobIds } = await resolveJobSelection(coreStart, isServerless, input?.jobIds); const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds);
const title = input?.title ?? getDefaultExplorerChartsPanelTitle(jobIds); const title = input?.title ?? getDefaultExplorerChartsPanelTitle(jobIds);
const { jobs } = await getJobs({ jobId: jobIds.join(',') }); const { jobs } = await getJobs({ jobId: jobIds.join(',') });
const influencers = extractInfluencers(jobs); const influencers = extractInfluencers(jobs);

View file

@ -27,7 +27,7 @@ describe('AnomalySwimlaneEmbeddableFactory', () => {
const [coreStart, pluginsStart] = await getStartServices(); const [coreStart, pluginsStart] = await getStartServices();
// act // act
const factory = new AnomalySwimlaneEmbeddableFactory(getStartServices, false); const factory = new AnomalySwimlaneEmbeddableFactory(getStartServices);
await factory.create({ await factory.create({
jobIds: ['test-job'], jobIds: ['test-job'],

View file

@ -35,8 +35,7 @@ export class AnomalySwimlaneEmbeddableFactory
]; ];
constructor( constructor(
private getStartServices: StartServicesAccessor<MlStartDependencies, MlPluginStart>, private getStartServices: StartServicesAccessor<MlStartDependencies, MlPluginStart>
private isServerless: boolean
) {} ) {}
public async isEditable() { public async isEditable() {
@ -60,7 +59,7 @@ export class AnomalySwimlaneEmbeddableFactory
try { try {
const { resolveAnomalySwimlaneUserInput } = await import('./anomaly_swimlane_setup_flyout'); const { resolveAnomalySwimlaneUserInput } = await import('./anomaly_swimlane_setup_flyout');
return await resolveAnomalySwimlaneUserInput(coreStart, this.isServerless); return await resolveAnomalySwimlaneUserInput(coreStart);
} catch (e) { } catch (e) {
return Promise.reject(); return Promise.reject();
} }

View file

@ -19,7 +19,6 @@ import { mlApiServicesProvider } from '../../application/services/ml_api_service
export async function resolveAnomalySwimlaneUserInput( export async function resolveAnomalySwimlaneUserInput(
coreStart: CoreStart, coreStart: CoreStart,
isServerless: boolean,
input?: AnomalySwimlaneEmbeddableInput input?: AnomalySwimlaneEmbeddableInput
): Promise<Partial<AnomalySwimlaneEmbeddableInput>> { ): Promise<Partial<AnomalySwimlaneEmbeddableInput>> {
const { http, overlays, theme, i18n } = coreStart; const { http, overlays, theme, i18n } = coreStart;
@ -28,7 +27,7 @@ export async function resolveAnomalySwimlaneUserInput(
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const { jobIds } = await resolveJobSelection(coreStart, isServerless, input?.jobIds); const { jobIds } = await resolveJobSelection(coreStart, input?.jobIds);
const title = input?.title ?? getDefaultSwimlanePanelTitle(jobIds); const title = input?.title ?? getDefaultSwimlanePanelTitle(jobIds);
const { jobs } = await getJobs({ jobId: jobIds.join(',') }); const { jobs } = await getJobs({ jobId: jobIds.join(',') });
const influencers = extractInfluencers(jobs); const influencers = extractInfluencers(jobs);

View file

@ -26,7 +26,6 @@ import { JobSelectorFlyout } from './components/job_selector_flyout';
*/ */
export async function resolveJobSelection( export async function resolveJobSelection(
coreStart: CoreStart, coreStart: CoreStart,
isServerless: boolean,
selectedJobIds?: JobId[] selectedJobIds?: JobId[]
): Promise<{ jobIds: string[]; groups: Array<{ groupId: string; jobIds: string[] }> }> { ): Promise<{ jobIds: string[]; groups: Array<{ groupId: string; jobIds: string[] }> }> {
const { const {
@ -70,9 +69,7 @@ export async function resolveJobSelection(
const flyoutSession = coreStart.overlays.openFlyout( const flyoutSession = coreStart.overlays.openFlyout(
toMountPoint( toMountPoint(
<KibanaContextProvider <KibanaContextProvider services={{ ...coreStart, mlServices: getMlGlobalServices(http) }}>
services={{ ...coreStart, mlServices: getMlGlobalServices(http, isServerless) }}
>
<JobSelectorFlyout <JobSelectorFlyout
selectedIds={selectedJobIds} selectedIds={selectedJobIds}
withTimeRangeSelector={false} withTimeRangeSelector={false}

View file

@ -15,24 +15,16 @@ export * from './types';
export { getEmbeddableComponent } from './get_embeddable_component'; export { getEmbeddableComponent } from './get_embeddable_component';
export function registerEmbeddables( export function registerEmbeddables(embeddable: EmbeddableSetup, core: MlCoreSetup) {
embeddable: EmbeddableSetup,
core: MlCoreSetup,
isServerless: boolean
) {
const anomalySwimlaneEmbeddableFactory = new AnomalySwimlaneEmbeddableFactory( const anomalySwimlaneEmbeddableFactory = new AnomalySwimlaneEmbeddableFactory(
core.getStartServices, core.getStartServices
isServerless
); );
embeddable.registerEmbeddableFactory( embeddable.registerEmbeddableFactory(
anomalySwimlaneEmbeddableFactory.type, anomalySwimlaneEmbeddableFactory.type,
anomalySwimlaneEmbeddableFactory anomalySwimlaneEmbeddableFactory
); );
const anomalyChartsFactory = new AnomalyChartsEmbeddableFactory( const anomalyChartsFactory = new AnomalyChartsEmbeddableFactory(core.getStartServices);
core.getStartServices,
isServerless
);
embeddable.registerEmbeddableFactory(anomalyChartsFactory.type, anomalyChartsFactory); embeddable.registerEmbeddableFactory(anomalyChartsFactory.type, anomalyChartsFactory);
} }

View file

@ -27,7 +27,6 @@ export function createFlyout(
share: SharePluginStart, share: SharePluginStart,
data: DataPublicPluginStart, data: DataPublicPluginStart,
dashboardService: DashboardStart, dashboardService: DashboardStart,
isServerless: boolean,
lens?: LensPublicStart lens?: LensPublicStart
): Promise<void> { ): Promise<void> {
const { const {
@ -54,7 +53,7 @@ export function createFlyout(
data, data,
lens, lens,
dashboardService, dashboardService,
mlServices: getMlGlobalServices(http, isServerless), mlServices: getMlGlobalServices(http),
}} }}
> >
<FlyoutComponent <FlyoutComponent

View file

@ -20,8 +20,7 @@ export async function showLensVisToADJobFlyout(
share: SharePluginStart, share: SharePluginStart,
data: DataPublicPluginStart, data: DataPublicPluginStart,
lens: LensPublicStart, lens: LensPublicStart,
dashboardService: DashboardStart, dashboardService: DashboardStart
isServerless: boolean
): Promise<void> { ): Promise<void> {
return createFlyout( return createFlyout(
LensLayerSelectionFlyout, LensLayerSelectionFlyout,
@ -30,7 +29,6 @@ export async function showLensVisToADJobFlyout(
share, share,
data, data,
dashboardService, dashboardService,
isServerless,
lens lens
); );
} }

View file

@ -19,16 +19,7 @@ export async function showMapVisToADJobFlyout(
coreStart: CoreStart, coreStart: CoreStart,
share: SharePluginStart, share: SharePluginStart,
data: DataPublicPluginStart, data: DataPublicPluginStart,
dashboardService: DashboardStart, dashboardService: DashboardStart
isServerless: boolean
): Promise<void> { ): Promise<void> {
return createFlyout( return createFlyout(GeoJobFlyout, embeddable, coreStart, share, data, dashboardService);
GeoJobFlyout,
embeddable,
coreStart,
share,
data,
dashboardService,
isServerless
);
} }

View file

@ -58,7 +58,14 @@ import { MlLocatorDefinition, type MlLocator } from './locator';
import { setDependencyCache } from './application/util/dependency_cache'; import { setDependencyCache } from './application/util/dependency_cache';
import { registerHomeFeature } from './register_home_feature'; import { registerHomeFeature } from './register_home_feature';
import { isFullLicense, isMlEnabled } from '../common/license'; import { isFullLicense, isMlEnabled } from '../common/license';
import { ML_APP_ROUTE, PLUGIN_ICON_SOLUTION, PLUGIN_ID } from '../common/constants/app'; import {
initEnabledFeatures,
type MlFeatures,
ML_APP_ROUTE,
PLUGIN_ICON_SOLUTION,
PLUGIN_ID,
type ConfigSchema,
} from '../common/constants/app';
import type { MlCapabilities } from './shared'; import type { MlCapabilities } from './shared';
export interface MlStartDependencies { export interface MlStartDependencies {
@ -113,9 +120,15 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
private sharedMlServices: MlSharedServices | undefined; private sharedMlServices: MlSharedServices | undefined;
private isServerless: boolean = false; private isServerless: boolean = false;
private enabledFeatures: MlFeatures = {
ad: true,
dfa: true,
nlp: true,
};
constructor(private initializerContext: PluginInitializerContext) { constructor(private initializerContext: PluginInitializerContext<ConfigSchema>) {
this.isServerless = initializerContext.env.packageInfo.buildFlavor === 'serverless'; this.isServerless = initializerContext.env.packageInfo.buildFlavor === 'serverless';
initEnabledFeatures(this.enabledFeatures, initializerContext.config.get());
} }
setup(core: MlCoreSetup, pluginsSetup: MlSetupDependencies) { setup(core: MlCoreSetup, pluginsSetup: MlSetupDependencies) {
@ -164,7 +177,8 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
presentationUtil: pluginsStart.presentationUtil, presentationUtil: pluginsStart.presentationUtil,
}, },
params, params,
this.isServerless this.isServerless,
this.enabledFeatures
); );
}, },
}); });
@ -180,7 +194,8 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
{ {
usageCollection: pluginsSetup.usageCollection, usageCollection: pluginsSetup.usageCollection,
}, },
this.isServerless this.isServerless,
this.enabledFeatures
).enable(); ).enable();
} }
@ -208,13 +223,13 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
registerMapExtension, registerMapExtension,
registerCasesAttachments, registerCasesAttachments,
} = await import('./register_helper'); } = await import('./register_helper');
registerSearchLinks(this.appUpdater$, fullLicense, mlCapabilities, this.isServerless); registerSearchLinks(this.appUpdater$, fullLicense, mlCapabilities, !this.isServerless);
if (fullLicense) { if (fullLicense) {
registerMlUiActions(pluginsSetup.uiActions, core, this.isServerless); registerMlUiActions(pluginsSetup.uiActions, core);
if (mlCapabilities.isADEnabled) { if (this.enabledFeatures.ad) {
registerEmbeddables(pluginsSetup.embeddable, core, this.isServerless); registerEmbeddables(pluginsSetup.embeddable, core);
if (pluginsSetup.cases) { if (pluginsSetup.cases) {
registerCasesAttachments(pluginsSetup.cases, coreStart, pluginStart); registerCasesAttachments(pluginsSetup.cases, coreStart, pluginStart);

View file

@ -16,7 +16,7 @@ export function registerSearchLinks(
appUpdater: BehaviorSubject<AppUpdater>, appUpdater: BehaviorSubject<AppUpdater>,
isFullLicense: boolean, isFullLicense: boolean,
mlCapabilities: MlCapabilities, mlCapabilities: MlCapabilities,
isServerless: boolean showMLNavMenu: boolean
) { ) {
appUpdater.next(() => ({ appUpdater.next(() => ({
keywords: [ keywords: [
@ -24,6 +24,6 @@ export function registerSearchLinks(
defaultMessage: 'ML', defaultMessage: 'ML',
}), }),
], ],
deepLinks: getDeepLinks(isFullLicense, mlCapabilities, isServerless), deepLinks: getDeepLinks(isFullLicense, mlCapabilities, showMLNavMenu),
})); }));
} }

View file

@ -15,13 +15,13 @@ import type { MlCapabilities } from '../../shared';
function createDeepLinks( function createDeepLinks(
mlCapabilities: MlCapabilities, mlCapabilities: MlCapabilities,
isFullLicense: boolean, isFullLicense: boolean,
isServerless: boolean showMLNavMenu: boolean
) { ) {
function getNavStatus( function getNavStatus(
visible: boolean, visible: boolean,
showInServerless: boolean = true showInServerless: boolean = true
): AppNavLinkStatus | undefined { ): AppNavLinkStatus | undefined {
if (isServerless) { if (showMLNavMenu === false) {
// in serverless the status needs to be "visible" rather than "default" // in serverless the status needs to be "visible" rather than "default"
// for the links to appear in the nav menu. // for the links to appear in the nav menu.
return showInServerless && visible ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden; return showInServerless && visible ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden;
@ -145,7 +145,7 @@ function createDeepLinks(
defaultMessage: 'Memory Usage', defaultMessage: 'Memory Usage',
}), }),
path: `/${ML_PAGES.MEMORY_USAGE}`, path: `/${ML_PAGES.MEMORY_USAGE}`,
navLinkStatus: getNavStatus(isFullLicense, false), navLinkStatus: getNavStatus(isFullLicense, true),
}; };
}, },
@ -279,8 +279,8 @@ function createDeepLinks(
export function getDeepLinks( export function getDeepLinks(
isFullLicense: boolean, isFullLicense: boolean,
mlCapabilities: MlCapabilities, mlCapabilities: MlCapabilities,
isServerless: boolean showMLNavMenu: boolean
) { ) {
const links = createDeepLinks(mlCapabilities, isFullLicense, isServerless); const links = createDeepLinks(mlCapabilities, isFullLicense, showMLNavMenu);
return Object.values(links).map((link) => link()); return Object.values(links).map((link) => link());
} }

View file

@ -17,8 +17,7 @@ import {
export const EDIT_ANOMALY_CHARTS_PANEL_ACTION = 'editAnomalyChartsPanelAction'; export const EDIT_ANOMALY_CHARTS_PANEL_ACTION = 'editAnomalyChartsPanelAction';
export function createEditAnomalyChartsPanelAction( export function createEditAnomalyChartsPanelAction(
getStartServices: MlCoreSetup['getStartServices'], getStartServices: MlCoreSetup['getStartServices']
isServerless: boolean
): UiActionsActionDefinition<EditAnomalyChartsPanelContext> { ): UiActionsActionDefinition<EditAnomalyChartsPanelContext> {
return { return {
id: 'edit-anomaly-charts', id: 'edit-anomaly-charts',
@ -44,7 +43,6 @@ export function createEditAnomalyChartsPanelAction(
const result = await resolveEmbeddableAnomalyChartsUserInput( const result = await resolveEmbeddableAnomalyChartsUserInput(
coreStart, coreStart,
isServerless,
embeddable.getInput() embeddable.getInput()
); );
embeddable.updateInput(result); embeddable.updateInput(result);

View file

@ -14,8 +14,7 @@ import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE, EditSwimlanePanelContext } from '../e
export const EDIT_SWIMLANE_PANEL_ACTION = 'editSwimlanePanelAction'; export const EDIT_SWIMLANE_PANEL_ACTION = 'editSwimlanePanelAction';
export function createEditSwimlanePanelAction( export function createEditSwimlanePanelAction(
getStartServices: MlCoreSetup['getStartServices'], getStartServices: MlCoreSetup['getStartServices']
isServerless: boolean
): UiActionsActionDefinition<EditSwimlanePanelContext> { ): UiActionsActionDefinition<EditSwimlanePanelContext> {
return { return {
id: 'edit-anomaly-swimlane', id: 'edit-anomaly-swimlane',
@ -39,11 +38,7 @@ export function createEditSwimlanePanelAction(
'../embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout' '../embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout'
); );
const result = await resolveAnomalySwimlaneUserInput( const result = await resolveAnomalySwimlaneUserInput(coreStart, embeddable.getInput());
coreStart,
isServerless,
embeddable.getInput()
);
embeddable.updateInput(result); embeddable.updateInput(result);
} catch (e) { } catch (e) {
return Promise.reject(); return Promise.reject();

View file

@ -34,24 +34,17 @@ export { SWIM_LANE_SELECTION_TRIGGER };
*/ */
export function registerMlUiActions( export function registerMlUiActions(
uiActions: UiActionsSetup, uiActions: UiActionsSetup,
core: CoreSetup<MlStartDependencies, MlPluginStart>, core: CoreSetup<MlStartDependencies, MlPluginStart>
isServerless: boolean
) { ) {
// Initialize actions // Initialize actions
const editSwimlanePanelAction = createEditSwimlanePanelAction( const editSwimlanePanelAction = createEditSwimlanePanelAction(core.getStartServices);
core.getStartServices,
isServerless
);
const openInExplorerAction = createOpenInExplorerAction(core.getStartServices); const openInExplorerAction = createOpenInExplorerAction(core.getStartServices);
const applyInfluencerFiltersAction = createApplyInfluencerFiltersAction(core.getStartServices); const applyInfluencerFiltersAction = createApplyInfluencerFiltersAction(core.getStartServices);
const applyEntityFieldFilterAction = createApplyEntityFieldFiltersAction(core.getStartServices); const applyEntityFieldFilterAction = createApplyEntityFieldFiltersAction(core.getStartServices);
const applyTimeRangeSelectionAction = createApplyTimeRangeSelectionAction(core.getStartServices); const applyTimeRangeSelectionAction = createApplyTimeRangeSelectionAction(core.getStartServices);
const clearSelectionAction = createClearSelectionAction(core.getStartServices); const clearSelectionAction = createClearSelectionAction(core.getStartServices);
const editExplorerPanelAction = createEditAnomalyChartsPanelAction( const editExplorerPanelAction = createEditAnomalyChartsPanelAction(core.getStartServices);
core.getStartServices, const visToAdJobAction = createVisToADJobAction(core.getStartServices);
isServerless
);
const visToAdJobAction = createVisToADJobAction(core.getStartServices, isServerless);
// Register actions // Register actions
uiActions.registerAction(editSwimlanePanelAction); uiActions.registerAction(editSwimlanePanelAction);

View file

@ -15,8 +15,7 @@ import { isLensEmbeddable, isMapEmbeddable } from '../application/jobs/new_job/j
export const CREATE_LENS_VIS_TO_ML_AD_JOB_ACTION = 'createMLADJobAction'; export const CREATE_LENS_VIS_TO_ML_AD_JOB_ACTION = 'createMLADJobAction';
export function createVisToADJobAction( export function createVisToADJobAction(
getStartServices: MlCoreSetup['getStartServices'], getStartServices: MlCoreSetup['getStartServices']
isServerless: boolean
): UiActionsActionDefinition<{ embeddable: Embeddable | MapEmbeddable }> { ): UiActionsActionDefinition<{ embeddable: Embeddable | MapEmbeddable }> {
return { return {
id: 'create-ml-ad-job-action', id: 'create-ml-ad-job-action',
@ -40,26 +39,11 @@ export function createVisToADJobAction(
if (lens === undefined) { if (lens === undefined) {
return; return;
} }
await showLensVisToADJobFlyout( await showLensVisToADJobFlyout(embeddable, coreStart, share, data, lens, dashboard);
embeddable,
coreStart,
share,
data,
lens,
dashboard,
isServerless
);
} else if (isMapEmbeddable(embeddable)) { } else if (isMapEmbeddable(embeddable)) {
const [{ showMapVisToADJobFlyout }, [coreStart, { share, data, dashboard }]] = const [{ showMapVisToADJobFlyout }, [coreStart, { share, data, dashboard }]] =
await Promise.all([import('../embeddables/job_creation/map'), getStartServices()]); await Promise.all([import('../embeddables/job_creation/map'), getStartServices()]);
await showMapVisToADJobFlyout( await showMapVisToADJobFlyout(embeddable, coreStart, share, data, dashboard);
embeddable,
coreStart,
share,
data,
dashboard,
isServerless
);
} }
} catch (e) { } catch (e) {
return Promise.reject(); return Promise.reject();

View file

@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import { schema, type TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema';
const enabledSchema = schema.maybe( const enabledSchema = schema.maybe(
schema.object({ schema.object({
@ -17,5 +17,3 @@ export const configSchema = schema.object({
dfa: enabledSchema, dfa: enabledSchema,
nlp: enabledSchema, nlp: enabledSchema,
}); });
export type ConfigSchema = TypeOf<typeof configSchema>;

View file

@ -6,6 +6,8 @@
*/ */
import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server'; import type { PluginConfigDescriptor, PluginInitializerContext } from '@kbn/core/server';
import { type ConfigSchema } from '../common/constants/app';
import { configSchema } from './config_schema';
import { MlServerPlugin } from './plugin'; import { MlServerPlugin } from './plugin';
export type { MlPluginSetup, MlPluginStart } from './plugin'; export type { MlPluginSetup, MlPluginStart } from './plugin';
export type { export type {
@ -26,10 +28,14 @@ export {
InsufficientMLCapabilities, InsufficientMLCapabilities,
MLPrivilegesUninitialized, MLPrivilegesUninitialized,
} from './shared'; } from './shared';
import { configSchema, type ConfigSchema } from './config_schema';
export const config: PluginConfigDescriptor<ConfigSchema> = { export const config: PluginConfigDescriptor<ConfigSchema> = {
schema: configSchema, schema: configSchema,
exposeToBrowser: {
ad: true,
dfa: true,
nlp: true,
},
}; };
export const plugin = (ctx: PluginInitializerContext<ConfigSchema>) => new MlServerPlugin(ctx); export const plugin = (ctx: PluginInitializerContext<ConfigSchema>) => new MlServerPlugin(ctx);

View file

@ -7,11 +7,11 @@
import type { Logger } from '@kbn/core/server'; import type { Logger } from '@kbn/core/server';
import type { AlertingPlugin } from '@kbn/alerting-plugin/server'; import type { AlertingPlugin } from '@kbn/alerting-plugin/server';
import type { MlFeatures } from '../../../common/constants/app';
import { registerAnomalyDetectionAlertType } from './register_anomaly_detection_alert_type'; import { registerAnomalyDetectionAlertType } from './register_anomaly_detection_alert_type';
import type { SharedServices } from '../../shared_services'; import type { SharedServices } from '../../shared_services';
import { registerJobsMonitoringRuleType } from './register_jobs_monitoring_rule_type'; import { registerJobsMonitoringRuleType } from './register_jobs_monitoring_rule_type';
import type { MlServicesProviders } from '../../shared_services/shared_services'; import type { MlServicesProviders } from '../../shared_services/shared_services';
import type { MlFeatures } from '../../types';
export interface RegisterAlertParams { export interface RegisterAlertParams {
alerting: AlertingPlugin['setup']; alerting: AlertingPlugin['setup'];

View file

@ -9,13 +9,13 @@ import { cloneDeep } from 'lodash';
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import type { CapabilitiesSwitcher, CoreSetup, Logger } from '@kbn/core/server'; import type { CapabilitiesSwitcher, CoreSetup, Logger } from '@kbn/core/server';
import type { ILicense } from '@kbn/licensing-plugin/common/types'; import type { ILicense } from '@kbn/licensing-plugin/common/types';
import type { MlFeatures } from '../../../common/constants/app';
import { isFullLicense, isMinimumLicense, isMlEnabled } from '../../../common/license'; import { isFullLicense, isMinimumLicense, isMlEnabled } from '../../../common/license';
import { import {
type MlCapabilities, type MlCapabilities,
basicLicenseMlCapabilities, basicLicenseMlCapabilities,
featureCapabilities, featureCapabilities,
} from '../../../common/types/capabilities'; } from '../../../common/types/capabilities';
import type { MlFeatures } from '../../types';
export const setupCapabilitiesSwitcher = ( export const setupCapabilitiesSwitcher = (
coreSetup: CoreSetup, coreSetup: CoreSetup,

View file

@ -6,11 +6,11 @@
*/ */
import type { CasesSetup } from '@kbn/cases-plugin/server'; import type { CasesSetup } from '@kbn/cases-plugin/server';
import type { MlFeatures } from '../../common/constants/app';
import { import {
CASE_ATTACHMENT_TYPE_ID_ANOMALY_EXPLORER_CHARTS, CASE_ATTACHMENT_TYPE_ID_ANOMALY_EXPLORER_CHARTS,
CASE_ATTACHMENT_TYPE_ID_ANOMALY_SWIMLANE, CASE_ATTACHMENT_TYPE_ID_ANOMALY_SWIMLANE,
} from '../../common/constants/cases'; } from '../../common/constants/cases';
import type { MlFeatures } from '../types';
export function registerCasesPersistableState(cases: CasesSetup, enabledFeatures: MlFeatures) { export function registerCasesPersistableState(cases: CasesSetup, enabledFeatures: MlFeatures) {
if (enabledFeatures.ad === true) { if (enabledFeatures.ad === true) {

View file

@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import type { HomeServerPluginSetup } from '@kbn/home-plugin/server'; import type { HomeServerPluginSetup } from '@kbn/home-plugin/server';
import type { MlFeatures } from '../types'; import type { MlFeatures } from '../../common/constants/app';
export function registerSampleDataSetLinks( export function registerSampleDataSetLinks(
home: HomeServerPluginSetup, home: HomeServerPluginSetup,

View file

@ -19,6 +19,7 @@ import {
type MapElements, type MapElements,
} from '@kbn/ml-data-frame-analytics-utils'; } from '@kbn/ml-data-frame-analytics-utils';
import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import type { MlFeatures } from '../../../common/constants/app';
import type { ModelService } from '../model_management/models_provider'; import type { ModelService } from '../model_management/models_provider';
import { modelsProvider } from '../model_management'; import { modelsProvider } from '../model_management';
import { import {
@ -36,7 +37,6 @@ import {
isTransformLinkReturnType, isTransformLinkReturnType,
} from './types'; } from './types';
import type { MlClient } from '../../lib/ml_client'; import type { MlClient } from '../../lib/ml_client';
import type { MlFeatures } from '../../types';
import { DEFAULT_TRAINED_MODELS_PAGE_SIZE } from '../../routes/trained_models'; import { DEFAULT_TRAINED_MODELS_PAGE_SIZE } from '../../routes/trained_models';
export class AnalyticsManager { export class AnalyticsManager {

View file

@ -9,6 +9,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import numeral from '@elastic/numeral'; import numeral from '@elastic/numeral';
import { pick } from 'lodash'; import { pick } from 'lodash';
import { isDefined } from '@kbn/ml-is-defined'; import { isDefined } from '@kbn/ml-is-defined';
import type { MlFeatures } from '../../../common/constants/app';
import type { import type {
MemoryUsageInfo, MemoryUsageInfo,
TrainedModelStatsResponse, TrainedModelStatsResponse,
@ -22,7 +23,6 @@ import type {
NodeDeploymentStatsResponse, NodeDeploymentStatsResponse,
NodesOverviewResponse, NodesOverviewResponse,
} from '../../../common/types/trained_models'; } from '../../../common/types/trained_models';
import type { MlFeatures } from '../../types';
// @ts-expect-error numeral missing value // @ts-expect-error numeral missing value
const AD_EXTRA_MEMORY = numeral('10MB').value(); const AD_EXTRA_MEMORY = numeral('10MB').value();

View file

@ -7,6 +7,7 @@
import { IScopedClusterClient } from '@kbn/core/server'; import { IScopedClusterClient } from '@kbn/core/server';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { MlFeatures } from '../../../common/constants/app';
import type { MLSavedObjectService } from '../../saved_objects'; import type { MLSavedObjectService } from '../../saved_objects';
import type { NotificationItem, NotificationSource } from '../../../common/types/notifications'; import type { NotificationItem, NotificationSource } from '../../../common/types/notifications';
import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns'; import { ML_NOTIFICATION_INDEX_PATTERN } from '../../../common/constants/index_patterns';
@ -19,7 +20,6 @@ import type {
NotificationsCountResponse, NotificationsCountResponse,
NotificationsSearchResponse, NotificationsSearchResponse,
} from '../../../common/types/notifications'; } from '../../../common/types/notifications';
import type { MlFeatures } from '../../types';
const MAX_NOTIFICATIONS_SIZE = 10000; const MAX_NOTIFICATIONS_SIZE = 10000;

View file

@ -25,12 +25,16 @@ import type { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server'; import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server';
import type { HomeServerPluginSetup } from '@kbn/home-plugin/server'; import type { HomeServerPluginSetup } from '@kbn/home-plugin/server';
import type { CasesSetup } from '@kbn/cases-plugin/server'; import type { CasesSetup } from '@kbn/cases-plugin/server';
import type { MlFeatures, PluginsSetup, PluginsStart, RouteInitialization } from './types'; import type { PluginsSetup, PluginsStart, RouteInitialization } from './types';
import type { MlCapabilities } from '../common/types/capabilities'; import type { MlCapabilities } from '../common/types/capabilities';
import type { ConfigSchema } from './config_schema';
import { jsonSchemaRoutes } from './routes/json_schema'; import { jsonSchemaRoutes } from './routes/json_schema';
import { notificationsRoutes } from './routes/notifications'; import { notificationsRoutes } from './routes/notifications';
import { PLUGIN_ID } from '../common/constants/app'; import {
type MlFeatures,
PLUGIN_ID,
type ConfigSchema,
initEnabledFeatures,
} from '../common/constants/app';
import { initMlServerLog } from './lib/log'; import { initMlServerLog } from './lib/log';
import { annotationRoutes } from './routes/annotations'; import { annotationRoutes } from './routes/annotations';
import { calendars } from './routes/calendars'; import { calendars } from './routes/calendars';
@ -102,7 +106,7 @@ export class MlServerPlugin
this.mlLicense = new MlLicense(); this.mlLicense = new MlLicense();
this.isMlReady = new Promise((resolve) => (this.setMlReady = resolve)); this.isMlReady = new Promise((resolve) => (this.setMlReady = resolve));
this.savedObjectsSyncService = new SavedObjectsSyncService(this.log); this.savedObjectsSyncService = new SavedObjectsSyncService(this.log);
this.initEnabledFeatures(ctx.config.get()); initEnabledFeatures(this.enabledFeatures, ctx.config.get());
} }
public setup(coreSetup: CoreSetup<PluginsStart>, plugins: PluginsSetup): MlPluginSetup { public setup(coreSetup: CoreSetup<PluginsStart>, plugins: PluginsSetup): MlPluginSetup {
@ -240,11 +244,11 @@ export class MlServerPlugin
// Register Trained Model Management routes // Register Trained Model Management routes
if (this.enabledFeatures.dfa || this.enabledFeatures.nlp) { if (this.enabledFeatures.dfa || this.enabledFeatures.nlp) {
modelManagementRoutes(routeInit);
trainedModelsRoutes(routeInit, plugins.cloud); trainedModelsRoutes(routeInit, plugins.cloud);
} }
// Register Miscellaneous routes // Register Miscellaneous routes
modelManagementRoutes(routeInit);
dataVisualizerRoutes(routeInit); dataVisualizerRoutes(routeInit);
fieldsService(routeInit); fieldsService(routeInit);
indicesRoutes(routeInit); indicesRoutes(routeInit);
@ -332,16 +336,4 @@ export class MlServerPlugin
public stop() { public stop() {
this.mlLicense.unsubscribe(); this.mlLicense.unsubscribe();
} }
private initEnabledFeatures(config: ConfigSchema) {
if (config.ad?.enabled !== undefined) {
this.enabledFeatures.ad = config.ad.enabled;
}
if (config.dfa?.enabled !== undefined) {
this.enabledFeatures.dfa = config.dfa.enabled;
}
if (config.nlp?.enabled !== undefined) {
this.enabledFeatures.nlp = config.nlp.enabled;
}
}
} }

View file

@ -12,10 +12,10 @@ import {
JOB_MAP_NODE_TYPES, JOB_MAP_NODE_TYPES,
type DeleteDataFrameAnalyticsWithIndexStatus, type DeleteDataFrameAnalyticsWithIndexStatus,
} from '@kbn/ml-data-frame-analytics-utils'; } from '@kbn/ml-data-frame-analytics-utils';
import { ML_INTERNAL_BASE_PATH } from '../../common/constants/app'; import { type MlFeatures, ML_INTERNAL_BASE_PATH } from '../../common/constants/app';
import { wrapError } from '../client/error_wrapper'; import { wrapError } from '../client/error_wrapper';
import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages'; import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages';
import type { MlFeatures, RouteInitialization } from '../types'; import type { RouteInitialization } from '../types';
import { import {
dataAnalyticsJobConfigSchema, dataAnalyticsJobConfigSchema,
dataAnalyticsJobUpdateSchema, dataAnalyticsJobUpdateSchema,

View file

@ -8,12 +8,11 @@
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { schema } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema';
import type { ErrorType } from '@kbn/ml-error-utils'; import type { ErrorType } from '@kbn/ml-error-utils';
import type { MlGetTrainedModelsRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { type ElserVersion } from '@kbn/ml-trained-models-utils';
import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server';
import type { ElserVersion } from '@kbn/ml-trained-models-utils';
import { isDefined } from '@kbn/ml-is-defined'; import { isDefined } from '@kbn/ml-is-defined';
import { ML_INTERNAL_BASE_PATH } from '../../common/constants/app'; import { type MlFeatures, ML_INTERNAL_BASE_PATH } from '../../common/constants/app';
import type { MlFeatures, RouteInitialization } from '../types'; import type { RouteInitialization } from '../types';
import { wrapError } from '../client/error_wrapper'; import { wrapError } from '../client/error_wrapper';
import { import {
deleteTrainedModelQuerySchema, deleteTrainedModelQuerySchema,
@ -101,7 +100,7 @@ export function trainedModelsRoutes(
...getTrainedModelsRequestParams, ...getTrainedModelsRequestParams,
...(modelId ? { model_id: modelId } : {}), ...(modelId ? { model_id: modelId } : {}),
size: DEFAULT_TRAINED_MODELS_PAGE_SIZE, size: DEFAULT_TRAINED_MODELS_PAGE_SIZE,
} as MlGetTrainedModelsRequest); } as estypes.MlGetTrainedModelsRequest);
// model_type is missing // model_type is missing
// @ts-ignore // @ts-ignore
const result = resp.trained_model_configs as TrainedModelConfigResponse[]; const result = resp.trained_model_configs as TrainedModelConfigResponse[];

View file

@ -30,6 +30,7 @@ import type { CasesSetup } from '@kbn/cases-plugin/server';
import type { RouteGuard } from './lib/route_guard'; import type { RouteGuard } from './lib/route_guard';
import type { ResolveMlCapabilities } from '../common/types/capabilities'; import type { ResolveMlCapabilities } from '../common/types/capabilities';
import type { MlLicense } from '../common/license'; import type { MlLicense } from '../common/license';
import type { MlFeatures } from '../common/constants/app';
export interface LicenseCheckResult { export interface LicenseCheckResult {
isAvailable: boolean; isAvailable: boolean;
@ -82,5 +83,3 @@ export interface RouteInitialization {
routeGuard: RouteGuard; routeGuard: RouteGuard;
getEnabledFeatures: () => MlFeatures; getEnabledFeatures: () => MlFeatures;
} }
export type MlFeatures = Record<'ad' | 'dfa' | 'nlp', boolean>;