wip: dataVis to 1 item. Add ML section to management nav.

This commit is contained in:
Melissa 2024-12-04 15:02:18 -07:00
parent f349f61cbc
commit 817063d1aa
15 changed files with 742 additions and 87 deletions

View file

@ -34,6 +34,14 @@ const insightsAndAlertingTip = i18n.translate('management.sections.insightsAndAl
defaultMessage: 'Manage how to detect changes in your data',
});
const machineLearningTitle = i18n.translate('management.sections.machineLearningTitle', {
defaultMessage: 'Machine Learning',
});
const machineLearningTip = i18n.translate('management.sections.machineLearningTip', {
defaultMessage: 'Manage the detection of anomalies in your data',
});
const sectionTitle = i18n.translate('management.sections.section.title', {
defaultMessage: 'Security',
});
@ -79,6 +87,13 @@ export const InsightsAndAlertingSection = {
order: 2,
};
export const MachineLearningSection = {
id: ManagementSectionId.MachineLearning,
title: machineLearningTitle,
tip: machineLearningTip,
order: 4,
};
export const SecuritySection = {
id: 'security',
title: sectionTitle,
@ -104,6 +119,7 @@ export const managementSections = [
IngestSection,
DataSection,
InsightsAndAlertingSection,
MachineLearningSection,
SecuritySection,
KibanaSection,
StackSection,

View file

@ -13,6 +13,7 @@ import {
IngestSection,
DataSection,
InsightsAndAlertingSection,
MachineLearningSection,
SecuritySection,
KibanaSection,
StackSection,
@ -41,6 +42,7 @@ export class ManagementSectionsService {
ingest: this.registerSection(IngestSection),
data: this.registerSection(DataSection),
insightsAndAlerting: this.registerSection(InsightsAndAlertingSection),
machineLearning: this.registerSection(MachineLearningSection),
security: this.registerSection(SecuritySection),
kibana: this.registerSection(KibanaSection),
stack: this.registerSection(StackSection),

View file

@ -31,6 +31,7 @@ export interface DefinedSections {
ingest: ManagementSection;
data: ManagementSection;
insightsAndAlerting: ManagementSection;
machineLearning: ManagementSection;
security: ManagementSection;
kibana: ManagementSection;
stack: ManagementSection;
@ -65,6 +66,7 @@ export enum ManagementSectionId {
Ingest = 'ingest',
Data = 'data',
InsightsAndAlerting = 'insightsAndAlerting',
MachineLearning = 'ml',
Security = 'security',
Kibana = 'kibana',
Stack = 'stack',

View file

@ -84,6 +84,15 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) {
disabled: disableLinks,
testSubj: 'mlMainTab overview',
},
{
id: 'datavisualizer',
name: i18n.translate('xpack.ml.navMenu.dataVisualizerTabLinkText', {
defaultMessage: 'Data Visualizer',
}),
disabled: false,
pathId: ML_PAGES.DATA_VISUALIZER,
testSubj: 'mlMainTab dataVisualizer',
},
{
id: 'notifications',
pathId: ML_PAGES.NOTIFICATIONS,
@ -220,54 +229,6 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) {
},
],
},
{
id: 'datavisualizer',
name: i18n.translate('xpack.ml.navMenu.dataVisualizerTabLinkText', {
defaultMessage: 'Data Visualizer',
}),
disabled: false,
pathId: ML_PAGES.DATA_VISUALIZER,
testSubj: 'mlMainTab dataVisualizer',
items: [
{
id: 'filedatavisualizer',
pathId: ML_PAGES.DATA_VISUALIZER_FILE,
name: i18n.translate('xpack.ml.navMenu.fileDataVisualizerLinkText', {
defaultMessage: 'File',
}),
disabled: false,
testSubj: 'mlMainTab fileDataVisualizer',
},
{
id: 'data_view_datavisualizer',
pathId: ML_PAGES.DATA_VISUALIZER_INDEX_SELECT,
name: i18n.translate('xpack.ml.navMenu.dataViewDataVisualizerLinkText', {
defaultMessage: 'Data View',
}),
disabled: false,
testSubj: 'mlMainTab indexDataVisualizer',
},
{
id: 'esql_datavisualizer',
pathId: ML_PAGES.DATA_VISUALIZER_ESQL,
name: i18n.translate('xpack.ml.navMenu.esqlDataVisualizerLinkText', {
defaultMessage: 'ES|QL',
}),
disabled: false,
testSubj: 'mlMainTab esqlDataVisualizer',
},
{
id: 'data_drift',
pathId: ML_PAGES.DATA_DRIFT_INDEX_SELECT,
name: i18n.translate('xpack.ml.navMenu.dataComparisonText', {
defaultMessage: 'Data Drift',
}),
disabled: disableLinks,
testSubj: 'mlMainTab dataDrift',
},
],
},
];
mlTabs.push({

View file

@ -95,7 +95,7 @@ export const DatavisualizerSelector: FC = () => {
<EuiText color="subdued">
<FormattedMessage
id="xpack.ml.datavisualizer.selector.dataVisualizerDescription"
defaultMessage="The Machine Learning Data Visualizer tool helps you understand your data,
defaultMessage="The Machine Learning Data Visualizer tool helps you understand your data
by analyzing the metrics and fields in a log file or an existing Elasticsearch index."
/>
</EuiText>
@ -211,6 +211,52 @@ export const DatavisualizerSelector: FC = () => {
/>
</EuiFlexItem>
) : null}
<EuiFlexItem>
<EuiCard
hasBorder
icon={<EuiIcon size="xxl" type="visTagCloud" />}
title={
<>
<FormattedMessage
id="xpack.ml.datavisualizer.selector.selectDataDriftTitle"
defaultMessage="Visualize data using data drift"
/>{' '}
<EuiBetaBadge
label=""
iconType="beaker"
size="m"
color="hollow"
tooltipContent={
<FormattedMessage
id="xpack.ml.datavisualizer.selector.dataDriftTechnicalPreviewBadge.titleMsg"
defaultMessage="Data drift visualizer is in technical preview."
/>
}
tooltipPosition={'right'}
/>
</>
}
description={
<FormattedMessage
id="xpack.ml.datavisualizer.selector.dataDriftDescription"
defaultMessage="Visualize changes in the model input data."
/>
}
footer={
<EuiButton
target="_self"
onClick={() => navigateToPath('/data_drift_index_select')}
data-test-subj="mlDataVisualizerSelectDataDriftButton"
>
<FormattedMessage
id="xpack.ml.datavisualizer.selector.selectDataViewButtonLabel"
defaultMessage="Select data view"
/>
</EuiButton>
}
data-test-subj="mlDataVisualizerCardDataDriftData"
/>
</EuiFlexItem>
</EuiFlexGrid>
{startTrialVisible === true && (
<Fragment>

View file

@ -15,10 +15,10 @@ import { JobsListView } from './components/jobs_list_view';
import { ML_PAGES } from '../../../../common/constants/locator';
import { HelpMenu } from '../../components/help_menu';
import { useMlKibana } from '../../contexts/kibana';
import { MlPageHeader } from '../../components/page_header';
import { HeaderMenuPortal } from '../../components/header_menu_portal';
import { JobsActionMenu } from '../components/jobs_action_menu';
import { useEnabledFeatures } from '../../contexts/ml';
import { getMlNodeCount } from '../../ml_nodes_check/check_ml_nodes';
interface PageUrlState {
pageKey: typeof ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE;
@ -43,17 +43,18 @@ export const JobsPage: FC<JobsPageProps> = ({ isMlEnabledInSpace, lastRefresh })
getDefaultAnomalyDetectionJobsListState()
);
const {
services: { docLinks },
services: {
docLinks,
mlServices: { mlApi },
},
} = useMlKibana();
const { euiTheme } = useEuiTheme();
getMlNodeCount(mlApi);
const { showNodeInfo } = useEnabledFeatures();
const helpLink = docLinks.links.ml.anomalyDetection;
return (
<>
<MlPageHeader>
<FormattedMessage id="xpack.ml.jobsList.title" defaultMessage="Anomaly Detection Jobs" />
</MlPageHeader>
<HeaderMenuPortal>
<JobsActionMenu />
</HeaderMenuPortal>

View file

@ -14,34 +14,64 @@ import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
import type { MlFeatures } from '../../../common/constants/app';
import type { MlStartDependencies } from '../../plugin';
export function registerManagementSection(
export function registerManagementSections(
management: ManagementSetup,
core: CoreSetup<MlStartDependencies>,
deps: { usageCollection?: UsageCollectionSetup },
isServerless: boolean,
mlFeatures: MlFeatures
) {
const appName = i18n.translate('xpack.ml.management.jobsListTitle', {
defaultMessage: 'Machine Learning',
const overviewTitle = i18n.translate('xpack.ml.management.overviewTitle', {
defaultMessage: 'Overview',
});
const anomalyDetectionJobsTitle = i18n.translate(
'xpack.ml.management.anomalyDetectionJobsTitle',
{
defaultMessage: 'Anomaly Detection Jobs',
}
);
return management.sections.section.insightsAndAlerting.registerApp({
id: 'jobsListLink',
title: appName,
order: 4,
async mount(params: ManagementAppMountParams) {
const [{ chrome }] = await core.getStartServices();
const { docTitle } = chrome;
management.sections.section.machineLearning
.registerApp({
id: 'jobsListLink', // TODO: will need to update this
title: overviewTitle,
order: 1,
async mount(params: ManagementAppMountParams) {
const [{ chrome }] = await core.getStartServices();
const { docTitle } = chrome;
docTitle.change(appName);
docTitle.change(overviewTitle);
const { mountApp } = await import('./jobs_list');
const unmountAppCallback = await mountApp(core, params, deps, isServerless, mlFeatures);
const { mountApp } = await import('./overview');
const unmountAppCallback = await mountApp(core, params, deps, isServerless, mlFeatures);
return () => {
docTitle.reset();
unmountAppCallback();
};
},
});
return () => {
docTitle.reset();
unmountAppCallback();
};
},
})
.enable();
management.sections.section.machineLearning
.registerApp({
id: 'anomalyDetectionJobsLink',
title: anomalyDetectionJobsTitle,
order: 2,
async mount(params: ManagementAppMountParams) {
const [{ chrome }] = await core.getStartServices();
const { docTitle } = chrome;
docTitle.change(overviewTitle);
const { mountApp } = await import('./anomaly_detection_jobs');
const unmountAppCallback = await mountApp(core, params, deps, isServerless, mlFeatures);
return () => {
docTitle.reset();
unmountAppCallback();
};
},
})
.enable();
}

View file

@ -27,8 +27,6 @@ import { UpgradeWarning } from '../components/upgrade';
import { HelpMenu } from '../components/help_menu';
import { useMlKibana, useMlLink } from '../contexts/kibana';
import { NodesList } from '../memory_usage/nodes_overview';
import { MlPageHeader } from '../components/page_header';
import { PageTitle } from '../components/page_title';
import { getMlNodesCount } from '../ml_nodes_check/check_ml_nodes';
export const overviewPanelDefaultState = Object.freeze({
@ -62,13 +60,6 @@ export const OverviewPage: FC = () => {
return (
<div>
<MlPageHeader>
<PageTitle
title={i18n.translate('xpack.ml.overview.overviewLabel', {
defaultMessage: 'Overview',
})}
/>
</MlPageHeader>
<NodeAvailableWarning />
<JobsAwaitingNodeWarning jobCount={adLazyJobCount + dfaLazyJobCount} />
<SavedObjectsWarning
@ -82,7 +73,6 @@ export const OverviewPage: FC = () => {
}}
/>
<UpgradeWarning />
{canViewMlNodes ? (
<>
<CollapsiblePanel
@ -121,7 +111,6 @@ export const OverviewPage: FC = () => {
<EuiSpacer size="m" />
</>
) : null}
<OverviewContent
createAnomalyDetectionJobDisabled={disableCreateAnomalyDetectionJob}
setAdLazyJobCount={setAdLazyJobCount}

View file

@ -54,7 +54,7 @@ import type { FieldFormatsRegistry } from '@kbn/field-formats-plugin/common';
import { ENABLE_ESQL } from '@kbn/esql-utils';
import type { MlSharedServices } from './application/services/get_shared_ml_services';
import { getMlSharedServices } from './application/services/get_shared_ml_services';
import { registerManagementSection } from './application/management';
import { registerManagementSections } from './application/management';
import type { MlLocatorParams } from './locator';
import { MlLocatorDefinition, type MlLocator } from './locator';
import { registerHomeFeature } from './register_home_feature';
@ -227,7 +227,7 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
}
if (pluginsSetup.management) {
registerManagementSection(
registerManagementSections(
pluginsSetup.management,
core,
{
@ -235,7 +235,7 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
},
this.isServerless,
this.enabledFeatures
).enable();
);
}
const licensing = pluginsSetup.licensing.license$.pipe(take(1));

View file

@ -0,0 +1,217 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Router } from '@kbn/shared-ux-router';
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
import type { CoreStart } from '@kbn/core/public';
import { pick } from 'lodash';
import { EuiPageTemplate, EuiSpacer } from '@elastic/eui';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { SpacesContextProps, SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { UpgradeWarning } from '../../../components/upgrade/upgrade_warning';
import { getMlGlobalServices } from '../../../util/get_services';
import { EnabledFeaturesContextProvider } from '../../../contexts/ml';
import { type MlFeatures, PLUGIN_ID } from '../../../../../common/constants/app';
import { checkGetManagementMlJobsResolver } from '../../../capabilities/check_capabilities';
import { AccessDeniedPage } from '../../jobs_list/components/access_denied_page';
import { InsufficientLicensePage } from '../../jobs_list/components/insufficient_license_page';
// import { DocsLink } from '../../jobs_list/components/jobs_list_page/docs_link';
import { JobsPage } from '../../../jobs/jobs_list';
const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>;
interface Props {
coreStart: CoreStart;
share: SharePluginStart;
history: ManagementAppMountParams['history'];
spacesApi?: SpacesPluginStart;
data: DataPublicPluginStart;
charts: ChartsPluginStart;
usageCollection?: UsageCollectionSetup;
fieldFormats: FieldFormatsStart;
isServerless: boolean;
mlFeatures: MlFeatures;
}
export const AnomalyDetectionJobsPage: FC<Props> = ({
coreStart,
share,
history,
spacesApi,
data,
charts,
usageCollection,
fieldFormats,
isServerless,
mlFeatures,
}) => {
const [initialized, setInitialized] = useState(false);
const [accessDenied, setAccessDenied] = useState(false);
const [isUpgradeInProgress, setIsUpgradeInProgress] = useState(false);
const [isPlatinumOrTrialLicense, setIsPlatinumOrTrialLicense] = useState(true);
const mlServices = useMemo(
() => getMlGlobalServices(coreStart, data.dataViews, usageCollection),
[coreStart, data.dataViews, usageCollection]
);
const datePickerDeps: DatePickerDependencies = {
...pick(coreStart, ['http', 'notifications', 'theme', 'uiSettings', 'i18n']),
data,
uiSettingsKeys: UI_SETTINGS,
showFrozenDataTierChoice: false,
};
const check = async () => {
try {
await checkGetManagementMlJobsResolver(mlServices);
} catch (e) {
if (e.mlFeatureEnabledInSpace && e.isPlatinumOrTrialLicense === false) {
setIsPlatinumOrTrialLicense(false);
} else if (e.isUpgradeInProgress) {
setIsUpgradeInProgress(true);
} else {
setAccessDenied(true);
}
}
setInitialized(true);
};
useEffect(() => {
check();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// eslint-disable-next-line react-hooks/exhaustive-deps
const ContextWrapper = useCallback(
spacesApi ? spacesApi.ui.components.getSpacesContextProvider : getEmptyFunctionComponent,
[spacesApi]
);
if (initialized === false) {
return null;
}
if (isUpgradeInProgress) {
return (
<I18nProvider>
<KibanaRenderContextProvider {...coreStart}>
<KibanaContextProvider
services={{
...coreStart,
share,
data,
usageCollection,
fieldFormats,
spacesApi,
mlServices,
}}
>
<UpgradeWarning />
</KibanaContextProvider>
</KibanaRenderContextProvider>
</I18nProvider>
);
}
if (accessDenied) {
return (
<I18nProvider>
<KibanaRenderContextProvider {...coreStart}>
<AccessDeniedPage />
</KibanaRenderContextProvider>
</I18nProvider>
);
}
if (isPlatinumOrTrialLicense === false) {
return (
<I18nProvider>
<KibanaRenderContextProvider {...coreStart}>
<InsufficientLicensePage basePath={coreStart.http.basePath} />
</KibanaRenderContextProvider>
</I18nProvider>
);
}
return (
<I18nProvider>
<KibanaRenderContextProvider {...coreStart}>
<RedirectAppLinks
coreStart={{
application: coreStart.application,
}}
>
<KibanaContextProvider
services={{
...coreStart,
share,
data,
charts,
usageCollection,
fieldFormats,
spacesApi,
mlServices,
}}
>
<DatePickerContextProvider {...datePickerDeps}>
<ContextWrapper feature={PLUGIN_ID}>
<EnabledFeaturesContextProvider isServerless={isServerless} mlFeatures={mlFeatures}>
<Router history={history}>
<EuiPageTemplate.Header
pageTitle={
<FormattedMessage
id="xpack.ml.management.overview.anomalyDetectionJobsPageTitle"
defaultMessage="Anomaly Detection Jobs"
/>
}
description={
<FormattedMessage
id="xpack.ml.management.jobsList.jobsListTagline"
defaultMessage="Identify, analyze, and process your data using advanced analysis techniques."
/>
}
// rightSideItems={[<DocsLink currentTabId={currentTabId} />]}
bottomBorder
paddingSize={'none'}
/>
<EuiSpacer size="l" />
<EuiPageTemplate.Section
paddingSize={'none'}
id="kibanaManagementMLSection"
data-test-subj="mlPageStackManagementJobsList"
>
<JobsPage />
</EuiPageTemplate.Section>
</Router>
</EnabledFeaturesContextProvider>
</ContextWrapper>
</DatePickerContextProvider>
</KibanaContextProvider>
</RedirectAppLinks>
</KibanaRenderContextProvider>
</I18nProvider>
);
};

View file

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

View file

@ -0,0 +1,79 @@
/*
* 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 ReactDOM, { unmountComponentAtNode } from 'react-dom';
import React from 'react';
import type { CoreSetup, CoreStart } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
import type { MlFeatures } from '../../../../common/constants/app';
import type { MlStartDependencies } from '../../../plugin';
import { AnomalyDetectionJobsPage } from './components';
import { getJobsListBreadcrumbs } from '../breadcrumbs';
const renderApp = (
element: HTMLElement,
history: ManagementAppMountParams['history'],
coreStart: CoreStart,
share: SharePluginStart,
data: DataPublicPluginStart,
fieldFormats: FieldFormatsStart,
charts: ChartsPluginStart,
isServerless: boolean,
mlFeatures: MlFeatures,
spacesApi?: SpacesPluginStart,
usageCollection?: UsageCollectionSetup
) => {
ReactDOM.render(
React.createElement(AnomalyDetectionJobsPage, {
coreStart,
history,
share,
data,
charts,
spacesApi,
usageCollection,
fieldFormats,
isServerless,
mlFeatures,
}),
element
);
return () => {
unmountComponentAtNode(element);
};
};
export async function mountApp(
core: CoreSetup<MlStartDependencies>,
params: ManagementAppMountParams,
deps: { usageCollection?: UsageCollectionSetup },
isServerless: boolean,
mlFeatures: MlFeatures
) {
const [coreStart, pluginsStart] = await core.getStartServices();
params.setBreadcrumbs(getJobsListBreadcrumbs()); // TODO: update this
return renderApp(
params.element,
params.history,
coreStart,
pluginsStart.share,
pluginsStart.data,
pluginsStart.fieldFormats,
pluginsStart.charts,
isServerless,
mlFeatures,
pluginsStart.spaces,
deps.usageCollection
);
}

View file

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

View file

@ -0,0 +1,217 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Router } from '@kbn/shared-ux-router';
import { FormattedMessage, I18nProvider } from '@kbn/i18n-react';
import type { CoreStart } from '@kbn/core/public';
import { pick } from 'lodash';
import { EuiPageTemplate, EuiSpacer } from '@elastic/eui';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { SpacesContextProps, SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { UpgradeWarning } from '../../../components/upgrade/upgrade_warning';
import { getMlGlobalServices } from '../../../util/get_services';
import { EnabledFeaturesContextProvider } from '../../../contexts/ml';
import { type MlFeatures, PLUGIN_ID } from '../../../../../common/constants/app';
import { checkGetManagementMlJobsResolver } from '../../../capabilities/check_capabilities';
import { AccessDeniedPage } from '../../jobs_list/components/access_denied_page';
import { InsufficientLicensePage } from '../../jobs_list/components/insufficient_license_page';
// import { DocsLink } from '../../jobs_list/components/jobs_list_page/docs_link';
import { OverviewPage as OverviewPageContent } from '../../../overview/overview_page';
const getEmptyFunctionComponent: React.FC<SpacesContextProps> = ({ children }) => <>{children}</>;
interface Props {
coreStart: CoreStart;
share: SharePluginStart;
history: ManagementAppMountParams['history'];
spacesApi?: SpacesPluginStart;
data: DataPublicPluginStart;
charts: ChartsPluginStart;
usageCollection?: UsageCollectionSetup;
fieldFormats: FieldFormatsStart;
isServerless: boolean;
mlFeatures: MlFeatures;
}
export const OverviewPage: FC<Props> = ({
coreStart,
share,
history,
spacesApi,
data,
charts,
usageCollection,
fieldFormats,
isServerless,
mlFeatures,
}) => {
const [initialized, setInitialized] = useState(false);
const [accessDenied, setAccessDenied] = useState(false);
const [isUpgradeInProgress, setIsUpgradeInProgress] = useState(false);
const [isPlatinumOrTrialLicense, setIsPlatinumOrTrialLicense] = useState(true);
const mlServices = useMemo(
() => getMlGlobalServices(coreStart, data.dataViews, usageCollection),
[coreStart, data.dataViews, usageCollection]
);
const datePickerDeps: DatePickerDependencies = {
...pick(coreStart, ['http', 'notifications', 'theme', 'uiSettings', 'i18n']),
data,
uiSettingsKeys: UI_SETTINGS,
showFrozenDataTierChoice: false,
};
const check = async () => {
try {
await checkGetManagementMlJobsResolver(mlServices);
} catch (e) {
if (e.mlFeatureEnabledInSpace && e.isPlatinumOrTrialLicense === false) {
setIsPlatinumOrTrialLicense(false);
} else if (e.isUpgradeInProgress) {
setIsUpgradeInProgress(true);
} else {
setAccessDenied(true);
}
}
setInitialized(true);
};
useEffect(() => {
check();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// eslint-disable-next-line react-hooks/exhaustive-deps
const ContextWrapper = useCallback(
spacesApi ? spacesApi.ui.components.getSpacesContextProvider : getEmptyFunctionComponent,
[spacesApi]
);
if (initialized === false) {
return null;
}
if (isUpgradeInProgress) {
return (
<I18nProvider>
<KibanaRenderContextProvider {...coreStart}>
<KibanaContextProvider
services={{
...coreStart,
share,
data,
usageCollection,
fieldFormats,
spacesApi,
mlServices,
}}
>
<UpgradeWarning />
</KibanaContextProvider>
</KibanaRenderContextProvider>
</I18nProvider>
);
}
if (accessDenied) {
return (
<I18nProvider>
<KibanaRenderContextProvider {...coreStart}>
<AccessDeniedPage />
</KibanaRenderContextProvider>
</I18nProvider>
);
}
if (isPlatinumOrTrialLicense === false) {
return (
<I18nProvider>
<KibanaRenderContextProvider {...coreStart}>
<InsufficientLicensePage basePath={coreStart.http.basePath} />
</KibanaRenderContextProvider>
</I18nProvider>
);
}
return (
<I18nProvider>
<KibanaRenderContextProvider {...coreStart}>
<RedirectAppLinks
coreStart={{
application: coreStart.application,
}}
>
<KibanaContextProvider
services={{
...coreStart,
share,
data,
charts,
usageCollection,
fieldFormats,
spacesApi,
mlServices,
}}
>
<DatePickerContextProvider {...datePickerDeps}>
<ContextWrapper feature={PLUGIN_ID}>
<EnabledFeaturesContextProvider isServerless={isServerless} mlFeatures={mlFeatures}>
<Router history={history}>
<EuiPageTemplate.Header
pageTitle={
<FormattedMessage
id="xpack.ml.management.overview.overviewPageTitle"
defaultMessage="Overview"
/>
}
description={
<FormattedMessage
id="xpack.ml.management.jobsList.jobsListTagline"
defaultMessage="Identify, analyze, and process your data using advanced analysis techniques."
/>
}
// rightSideItems={[<DocsLink currentTabId={currentTabId} />]}
bottomBorder
paddingSize={'none'}
/>
<EuiSpacer size="l" />
<EuiPageTemplate.Section
paddingSize={'none'}
id="kibanaManagementMLSection"
data-test-subj="mlPageStackManagementJobsList"
>
<OverviewPageContent />
</EuiPageTemplate.Section>
</Router>
</EnabledFeaturesContextProvider>
</ContextWrapper>
</DatePickerContextProvider>
</KibanaContextProvider>
</RedirectAppLinks>
</KibanaRenderContextProvider>
</I18nProvider>
);
};

View file

@ -0,0 +1,79 @@
/*
* 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 ReactDOM, { unmountComponentAtNode } from 'react-dom';
import React from 'react';
import type { CoreSetup, CoreStart } from '@kbn/core/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { ManagementAppMountParams } from '@kbn/management-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
import type { MlFeatures } from '../../../../common/constants/app';
import type { MlStartDependencies } from '../../../plugin';
import { OverviewPage } from './components';
import { getJobsListBreadcrumbs } from '../breadcrumbs';
const renderApp = (
element: HTMLElement,
history: ManagementAppMountParams['history'],
coreStart: CoreStart,
share: SharePluginStart,
data: DataPublicPluginStart,
fieldFormats: FieldFormatsStart,
charts: ChartsPluginStart,
isServerless: boolean,
mlFeatures: MlFeatures,
spacesApi?: SpacesPluginStart,
usageCollection?: UsageCollectionSetup
) => {
ReactDOM.render(
React.createElement(OverviewPage, {
coreStart,
history,
share,
data,
charts,
spacesApi,
usageCollection,
fieldFormats,
isServerless,
mlFeatures,
}),
element
);
return () => {
unmountComponentAtNode(element);
};
};
export async function mountApp(
core: CoreSetup<MlStartDependencies>,
params: ManagementAppMountParams,
deps: { usageCollection?: UsageCollectionSetup },
isServerless: boolean,
mlFeatures: MlFeatures
) {
const [coreStart, pluginsStart] = await core.getStartServices();
params.setBreadcrumbs(getJobsListBreadcrumbs()); // TODO: update this
return renderApp(
params.element,
params.history,
coreStart,
pluginsStart.share,
pluginsStart.data,
pluginsStart.fieldFormats,
pluginsStart.charts,
isServerless,
mlFeatures,
pluginsStart.spaces,
deps.usageCollection
);
}