[ml] Optimize bundles, reduce page load async chunks by 74% (#179311)

## Summary

The `AnomalyExplorerChartsService` was importing the
`SWIM_LANE_LABEL_WIDTH` numerical constant from
`swimlane_container.tsx`. As a result, the _entirety_ of that file was
being included in the async bundle.

`AnomalyExplorerChartsService` is loaded asynchronously on page load by
the embeddable. By relocating the constant to its own file-- as well as
other optimizations (see below)-- we reduce the async page load by 74%,
(in dev mode).

### Before - `351k`
<img width="1220" alt="Screenshot 2024-03-24 at 10 09 31 AM"
src="6cea41f5-fb10-41c3-8951-a4be897174f9">

### After - `93.4k`
<img width="1471" alt="Screenshot 2024-04-05 at 11 45 45 AM"
src="a07e7a19-c1af-4b45-a195-69730fc61b0c">

Unfortunately, this change led to a bloating of async modules, the cause
of which is still unclear. The application async chunk weighed in at 2.2
MB compressed! To get this PR to a shippable state, I refactored
additional code to bring down duplication and bloat.

The result is an `ml` experience that fetches small bundles on demand as
someone interacts with it:

![Apr-05-2024
11-51-18](75ba423e-7071-4867-ae4b-05308bf319f3)

More work can be done to continue to optimize the plugin, of course, but
this set of changes is an excellent start, (and case study on bundle
load).

### Other optimizations in this PR

- Registration of some `start` services are conditional, and contain
their own async calls. I've removed these from the register helper,
(which itself is a brute-force offload of code from the plugin, but is
still loaded every time), and loaded them async if the conditions apply.
- Routing in `ml` use factories to create individual routes. In a lot of
cases, the pages these routes displayed were not asynchronously loaded,
adding tremendous amounts of React code to the root application.
  - I made all pages loaded by routes async.
- In some cases, the components themselves were colocated with the route
factory. I moved those to their own files for async import.
- Similarly, some state managers were colocated. Those were moved to
their own files, as well.
- Moved flyouts, modals, expanding rows to `React.lazy` async modules,
(using `dynamic` from `@kbn/shared-ux-utility`.
- Refactored `export * from` directives from `public/shared.ts` to
accurately reflect what is being consumed outside the `ml` plugin, (and
also reduce the size of _that_ bundle.
- Refactored `lodash` imports to submodule imports to enable
tree-shaking, (if ever enabled in webpack).
- Moved `getMlGlobalServices` off of the `app.tsx` file for import by
others, (to avoid depending on the rest of the code there).
- Moved some types to `/common` to avoid importing code, (though,
admittedly, types are compiled away). But it felt cleaner to move them
out.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Clint Andrew Hall 2024-04-11 15:15:14 -04:00 committed by GitHub
parent 7c6f4fe7d7
commit 67ab4f7732
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
74 changed files with 960 additions and 761 deletions

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import get from 'lodash/get';
import { get } from 'lodash';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import startsWith from 'lodash/startsWith';
import { startsWith } from 'lodash';
import type { Reducer, ReducerAction } from 'react';
import type { HttpSetup, HttpFetchOptions } from '@kbn/core/public';

View file

@ -10,3 +10,4 @@ export { getDefaultCapabilities as getDefaultMlCapabilities } from './types/capa
export { DATAFEED_STATE, JOB_STATE } from './constants/states';
export type { MlSummaryJob, SummaryJobState } from './types/anomaly_detection_jobs';
export { ML_ALERT_TYPES } from './constants/alerts';
export type { Job, Datafeed } from './types/anomaly_detection_jobs';

View file

@ -10,9 +10,7 @@ import './_index.scss';
import ReactDOM from 'react-dom';
import { pick } from 'lodash';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import type { AppMountParameters, CoreStart, HttpStart } from '@kbn/core/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { AppMountParameters, CoreStart } from '@kbn/core/public';
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
@ -21,21 +19,15 @@ import { StorageContextProvider } from '@kbn/ml-local-storage';
import useLifecycles from 'react-use/lib/useLifecycles';
import useObservable from 'react-use/lib/useObservable';
import type { ExperimentalFeatures, MlFeatures } from '../../common/constants/app';
import { MlLicense } from '../../common/license';
import { MlCapabilitiesService } from './capabilities/check_capabilities';
import { ML_STORAGE_KEYS } from '../../common/types/storage';
import type { MlSetupDependencies, MlStartDependencies } from '../plugin';
import { clearCache, setDependencyCache } from './util/dependency_cache';
import { setLicenseCache } from './license';
import { mlUsageCollectionProvider } from './services/usage_collection';
import { MlRouter } from './routing';
import { mlApiServicesProvider } from './services/ml_api_service';
import { HttpService } from './services/http_service';
import type { PageDependencies } from './routing/router';
import { EnabledFeaturesContextProvider } from './contexts/ml';
import type { StartServices } from './contexts/kibana';
import { fieldFormatServiceFactory } from './services/field_format_service_factory';
import { indexServiceFactory } from './util/index_service';
import { getMlGlobalServices } from './util/get_services';
export type MlDependencies = Omit<
MlSetupDependencies,
@ -54,38 +46,6 @@ interface AppProps {
const localStorage = new Storage(window.localStorage);
/**
* Provides global services available across the entire ML app.
*/
export function getMlGlobalServices(
httpStart: HttpStart,
dataViews: DataViewsContract,
usageCollection?: UsageCollectionSetup
) {
const httpService = new HttpService(httpStart);
const mlApiServices = mlApiServicesProvider(httpService);
// Note on the following services:
// - `mlIndexUtils` is just instantiated here to be passed on to `mlFieldFormatService`,
// but it's not being made available as part of global services. Since it's just
// some stateless utils `useMlIndexUtils()` should be used from within components.
// - `mlFieldFormatService` is a stateful legacy service that relied on "dependency cache",
// so because of its own state it needs to be made available as a global service.
// In the long run we should again try to get rid of it here and make it available via
// its own context or possibly without having a singleton like state at all, since the
// way this manages its own state right now doesn't consider React component lifecycles.
const mlIndexUtils = indexServiceFactory(dataViews);
const mlFieldFormatService = fieldFormatServiceFactory(mlApiServices, mlIndexUtils);
return {
httpService,
mlApiServices,
mlFieldFormatService,
mlUsageCollection: mlUsageCollectionProvider(usageCollection),
mlCapabilities: new MlCapabilitiesService(mlApiServices),
mlLicense: new MlLicense(),
};
}
export interface MlServicesContext {
mlServices: MlGlobalServices;
}

View file

@ -6,240 +6,30 @@
*/
import type { FC } from 'react';
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import React, { useState } from 'react';
import { EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import {
EuiFlyout,
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiFlexGroup,
EuiFlexItem,
EuiButtonEmpty,
EuiButton,
EuiFlyoutBody,
EuiTitle,
EuiSpacer,
EuiCheckbox,
EuiTabs,
EuiTab,
EuiLoadingSpinner,
EuiConfirmModal,
} from '@elastic/eui';
import { dynamic } from '@kbn/shared-ux-utility';
import { useMlKibana } from '../../../contexts/kibana';
import { ExportJobDependenciesWarningCallout } from './export_job_warning_callout';
import { JobsExportService } from './jobs_export_service';
import type { JobDependencies } from './jobs_export_service';
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
import type { JobType } from '../../../../../common/types/saved_objects';
import { useEnabledFeatures } from '../../../contexts/ml';
import type { ExportJobsFlyoutContentProps } from './export_jobs_flyout_content';
interface Props {
export interface Props extends Pick<ExportJobsFlyoutContentProps, 'currentTab'> {
isDisabled: boolean;
currentTab: JobType;
}
export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
const {
services: {
notifications: { toasts },
mlServices: { mlUsageCollection, mlApiServices },
},
} = useMlKibana();
const ExportJobsFlyoutContent = dynamic(async () => ({
default: (await import('./export_jobs_flyout_content')).ExportJobsFlyoutContent,
}));
const {
getJobs,
dataFrameAnalytics: { getDataFrameAnalytics },
} = mlApiServices;
const jobsExportService = useMemo(() => new JobsExportService(mlApiServices), [mlApiServices]);
const [loadingADJobs, setLoadingADJobs] = useState(true);
const [loadingDFAJobs, setLoadingDFAJobs] = useState(true);
export const ExportJobsFlyout: FC<Props> = ({ isDisabled, ...rest }) => {
const [showFlyout, setShowFlyout] = useState(false);
const [adJobIds, setAdJobIds] = useState<string[]>([]);
const [dfaJobIds, setDfaJobIds] = useState<string[]>([]);
const [selectedJobIds, setSelectedJobIds] = useState<string[]>([]);
const [exporting, setExporting] = useState(false);
const [selectedJobType, setSelectedJobType] = useState<JobType>(currentTab);
const [switchTabConfirmVisible, setSwitchTabConfirmVisible] = useState(false);
const [switchTabNextTab, setSwitchTabNextTab] = useState<JobType>(currentTab);
const { displayErrorToast, displaySuccessToast } = useMemo(
() => toastNotificationServiceProvider(toasts),
[toasts]
);
const { isADEnabled, isDFAEnabled } = useEnabledFeatures();
const [jobDependencies, setJobDependencies] = useState<JobDependencies>([]);
const [selectedJobDependencies, setSelectedJobDependencies] = useState<JobDependencies>([]);
const isMounted = useRef(true);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(
function onFlyoutChange() {
setLoadingADJobs(true);
setLoadingDFAJobs(true);
setAdJobIds([]);
setSelectedJobIds([]);
setExporting(false);
setSelectedJobType(currentTab);
setSwitchTabConfirmVisible(false);
if (showFlyout) {
if (isADEnabled) {
getJobs()
.then(({ jobs }) => {
if (isMounted.current === false) return;
setLoadingADJobs(false);
setAdJobIds(jobs.map((j) => j.job_id));
jobsExportService
.getJobDependencies(jobs)
.then((jobDeps) => {
if (isMounted.current === false) return;
setJobDependencies(jobDeps);
setLoadingADJobs(false);
})
.catch((error) => {
if (isMounted.current === false) return;
const errorTitle = i18n.translate(
'xpack.ml.importExport.exportFlyout.calendarsError',
{
defaultMessage: 'Could not load calendars',
}
);
displayErrorToast(error, errorTitle);
});
})
.catch((error) => {
if (isMounted.current === false) return;
const errorTitle = i18n.translate('xpack.ml.importExport.exportFlyout.adJobsError', {
defaultMessage: 'Could not load anomaly detection jobs',
});
displayErrorToast(error, errorTitle);
});
}
if (isDFAEnabled) {
getDataFrameAnalytics()
.then(({ data_frame_analytics: dataFrameAnalytics }) => {
if (isMounted.current === false) return;
setLoadingDFAJobs(false);
setDfaJobIds(dataFrameAnalytics.map((j) => j.id));
})
.catch((error) => {
if (isMounted.current === false) return;
const errorTitle = i18n.translate('xpack.ml.importExport.exportFlyout.dfaJobsError', {
defaultMessage: 'Could not load data frame analytics jobs',
});
displayErrorToast(error, errorTitle);
});
}
}
},
[
currentTab,
displayErrorToast,
getDataFrameAnalytics,
getJobs,
isADEnabled,
isDFAEnabled,
jobsExportService,
showFlyout,
]
);
function toggleFlyout() {
setShowFlyout(!showFlyout);
}
async function onExport() {
setExporting(true);
const title = i18n.translate('xpack.ml.importExport.exportFlyout.exportDownloading', {
defaultMessage: 'Your file is downloading in the background',
});
displaySuccessToast(title);
try {
if (selectedJobType === 'anomaly-detector') {
await jobsExportService.exportAnomalyDetectionJobs(selectedJobIds);
} else {
await jobsExportService.exportDataframeAnalyticsJobs(selectedJobIds);
}
mlUsageCollection.count(
selectedJobType === 'anomaly-detector'
? 'exported_anomaly_detector_jobs'
: 'exported_data_frame_analytics_jobs',
selectedJobIds.length
);
setExporting(false);
setShowFlyout(false);
} catch (error) {
const errorTitle = i18n.translate('xpack.ml.importExport.exportFlyout.exportError', {
defaultMessage: 'Could not export selected jobs',
});
displayErrorToast(error, errorTitle);
setExporting(false);
}
}
function toggleSelectedJob(checked: boolean, id: string) {
if (checked) {
setSelectedJobIds([...selectedJobIds, id]);
} else {
setSelectedJobIds(selectedJobIds.filter((id2) => id2 !== id));
}
}
const attemptTabSwitch = useCallback(
(jobType: JobType) => {
if (jobType === selectedJobType) {
return;
}
// if the user has already selected some jobs, open a confirm modal
// rather than changing tabs
if (selectedJobIds.length > 0) {
setSwitchTabNextTab(jobType);
setSwitchTabConfirmVisible(true);
return;
}
switchTab(jobType);
},
[selectedJobIds, selectedJobType]
);
useEffect(() => {
setSelectedJobDependencies(
jobDependencies.filter(({ jobId }) => selectedJobIds.includes(jobId))
);
}, [jobDependencies, selectedJobIds]);
function switchTab(jobType: JobType) {
setSwitchTabConfirmVisible(false);
setSelectedJobIds([]);
setSelectedJobType(jobType);
}
function onSelectAll() {
const ids = selectedJobType === 'anomaly-detector' ? adJobIds : dfaJobIds;
if (selectedJobIds.length === ids.length) {
setSelectedJobIds([]);
} else {
setSelectedJobIds([...ids]);
}
}
if (isADEnabled === false && isDFAEnabled === false) {
return null;
}
@ -249,182 +39,10 @@ export const ExportJobsFlyout: FC<Props> = ({ isDisabled, currentTab }) => {
<FlyoutButton onClick={toggleFlyout} isDisabled={isDisabled} />
{showFlyout === true && isDisabled === false && (
<>
<EuiFlyout
onClose={() => setShowFlyout(false)}
hideCloseButton
size="s"
data-test-subj="mlJobMgmtExportJobsFlyout"
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.flyoutHeader"
defaultMessage="Export jobs"
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<ExportJobDependenciesWarningCallout jobs={selectedJobDependencies} />
<EuiTabs size="s">
{isADEnabled === true ? (
<EuiTab
isSelected={selectedJobType === 'anomaly-detector'}
onClick={() => attemptTabSwitch('anomaly-detector')}
disabled={exporting}
data-test-subj="mlJobMgmtExportJobsADTab"
>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.adTab"
defaultMessage="Anomaly detection"
/>
</EuiTab>
) : null}
{isDFAEnabled === true ? (
<EuiTab
isSelected={selectedJobType === 'data-frame-analytics'}
onClick={() => attemptTabSwitch('data-frame-analytics')}
disabled={exporting}
data-test-subj="mlJobMgmtExportJobsDFATab"
>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.dfaTab"
defaultMessage="Analytics"
/>
</EuiTab>
) : null}
</EuiTabs>
<EuiSpacer size="s" />
<>
{isADEnabled === true && selectedJobType === 'anomaly-detector' && (
<>
{loadingADJobs === true ? (
<LoadingSpinner />
) : (
<>
<EuiButtonEmpty
size="xs"
onClick={onSelectAll}
isDisabled={isDisabled}
data-test-subj="mlJobMgmtExportJobsSelectAllButton"
>
{selectedJobIds.length === adJobIds.length ? (
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.adDeselectAllButton"
defaultMessage="Deselect all"
/>
) : (
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.adSelectAllButton"
defaultMessage="Select all"
/>
)}
</EuiButtonEmpty>
<EuiSpacer size="xs" />
<div data-test-subj="mlJobMgmtExportJobsADJobList">
{adJobIds.map((id) => (
<div key={id}>
<EuiCheckbox
id={id}
label={id}
checked={selectedJobIds.includes(id)}
onChange={(e) => toggleSelectedJob(e.target.checked, id)}
/>
<EuiSpacer size="s" />
</div>
))}
</div>
</>
)}
</>
)}
{isDFAEnabled === true && selectedJobType === 'data-frame-analytics' && (
<>
{loadingDFAJobs === true ? (
<LoadingSpinner />
) : (
<>
<EuiButtonEmpty
size="xs"
onClick={onSelectAll}
isDisabled={isDisabled}
data-test-subj="mlJobMgmtExportJobsSelectAllButton"
>
{selectedJobIds.length === dfaJobIds.length ? (
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.dfaDeselectAllButton"
defaultMessage="Deselect all"
/>
) : (
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.dfaSelectAllButton"
defaultMessage="Select all"
/>
)}
</EuiButtonEmpty>
<EuiSpacer size="xs" />
<div data-test-subj="mlJobMgmtExportJobsDFAJobList">
{dfaJobIds.map((id) => (
<div key={id}>
<EuiCheckbox
id={id}
label={id}
checked={selectedJobIds.includes(id)}
onChange={(e) => toggleSelectedJob(e.target.checked, id)}
/>
<EuiSpacer size="s" />
</div>
))}
</div>
</>
)}
</>
)}
</>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
iconType="cross"
onClick={setShowFlyout.bind(null, false)}
flush="left"
>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.closeButton"
defaultMessage="Close"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
disabled={selectedJobIds.length === 0 || exporting === true}
onClick={onExport}
fill
data-test-subj="mlJobMgmtExportExportButton"
>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.exportButton"
defaultMessage="Export"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
{switchTabConfirmVisible === true ? (
<SwitchTabsConfirm
onCancel={setSwitchTabConfirmVisible.bind(null, false)}
onConfirm={() => switchTab(switchTabNextTab)}
/>
) : null}
</>
<ExportJobsFlyoutContent
onClose={() => setShowFlyout(false)}
{...{ isADEnabled, isDFAEnabled, ...rest }}
/>
)}
</>
);
@ -442,47 +60,3 @@ const FlyoutButton: FC<{ isDisabled: boolean; onClick(): void }> = ({ isDisabled
</EuiButtonEmpty>
);
};
const LoadingSpinner: FC = () => (
<>
<EuiSpacer size="l" />
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="l" />
</EuiFlexItem>
</EuiFlexGroup>
</>
);
const SwitchTabsConfirm: FC<{ onCancel: () => void; onConfirm: () => void }> = ({
onCancel,
onConfirm,
}) => (
<EuiConfirmModal
title={i18n.translate('xpack.ml.importExport.exportFlyout.switchTabsConfirm.title', {
defaultMessage: 'Change tabs?',
})}
onCancel={onCancel}
onConfirm={onConfirm}
cancelButtonText={i18n.translate(
'xpack.ml.importExport.exportFlyout.switchTabsConfirm.cancelButton',
{
defaultMessage: 'Cancel',
}
)}
confirmButtonText={i18n.translate(
'xpack.ml.importExport.exportFlyout.switchTabsConfirm.confirmButton',
{
defaultMessage: 'Confirm',
}
)}
defaultFocusedButton="confirm"
>
<p>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.switchTabsConfirm.text"
defaultMessage="Changing tabs will clear currently selected jobs"
/>
</p>
</EuiConfirmModal>
);

View file

@ -0,0 +1,444 @@
/*
* 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 {
EuiButton,
EuiButtonEmpty,
EuiCheckbox,
EuiConfirmModal,
EuiFlexGroup,
EuiFlexItem,
EuiFlyout,
EuiFlyoutBody,
EuiFlyoutFooter,
EuiFlyoutHeader,
EuiLoadingSpinner,
EuiSpacer,
EuiTab,
EuiTabs,
EuiTitle,
} from '@elastic/eui';
import type { FC } from 'react';
import { useEffect, useMemo, useCallback } from 'react';
import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { ExportJobDependenciesWarningCallout } from './export_job_warning_callout';
import type { JobType } from '../../../../../common/types/saved_objects';
import { useMlKibana } from '../../../contexts/kibana';
import { JobsExportService } from './jobs_export_service';
import type { JobDependencies } from './jobs_export_service';
import { toastNotificationServiceProvider } from '../../../services/toast_notification_service';
const LoadingSpinner: FC = () => (
<>
<EuiSpacer size="l" />
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="l" />
</EuiFlexItem>
</EuiFlexGroup>
</>
);
const SwitchTabsConfirm: FC<{ onCancel: () => void; onConfirm: () => void }> = ({
onCancel,
onConfirm,
}) => (
<EuiConfirmModal
title={i18n.translate('xpack.ml.importExport.exportFlyout.switchTabsConfirm.title', {
defaultMessage: 'Change tabs?',
})}
onCancel={onCancel}
onConfirm={onConfirm}
cancelButtonText={i18n.translate(
'xpack.ml.importExport.exportFlyout.switchTabsConfirm.cancelButton',
{
defaultMessage: 'Cancel',
}
)}
confirmButtonText={i18n.translate(
'xpack.ml.importExport.exportFlyout.switchTabsConfirm.confirmButton',
{
defaultMessage: 'Confirm',
}
)}
defaultFocusedButton="confirm"
>
<p>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.switchTabsConfirm.text"
defaultMessage="Changing tabs will clear currently selected jobs"
/>
</p>
</EuiConfirmModal>
);
export interface ExportJobsFlyoutContentProps {
currentTab: JobType;
isADEnabled: boolean;
isDFAEnabled: boolean;
onClose: () => void;
}
export const ExportJobsFlyoutContent = ({
currentTab,
isADEnabled,
isDFAEnabled,
onClose,
}: ExportJobsFlyoutContentProps) => {
const {
services: {
notifications: { toasts },
mlServices: { mlUsageCollection, mlApiServices },
},
} = useMlKibana();
const {
getJobs,
dataFrameAnalytics: { getDataFrameAnalytics },
} = mlApiServices;
const jobsExportService = useMemo(() => new JobsExportService(mlApiServices), [mlApiServices]);
const [adJobIds, setAdJobIds] = useState<string[]>([]);
const [dfaJobIds, setDfaJobIds] = useState<string[]>([]);
const [selectedJobIds, setSelectedJobIds] = useState<string[]>([]);
const [selectedJobType, setSelectedJobType] = useState<JobType>(currentTab);
const { displayErrorToast, displaySuccessToast } = useMemo(
() => toastNotificationServiceProvider(toasts),
[toasts]
);
const [jobDependencies, setJobDependencies] = useState<JobDependencies>([]);
const [loadingADJobs, setLoadingADJobs] = useState(true);
const [loadingDFAJobs, setLoadingDFAJobs] = useState(true);
const [exporting, setExporting] = useState(false);
const [switchTabConfirmVisible, setSwitchTabConfirmVisible] = useState(false);
const [switchTabNextTab, setSwitchTabNextTab] = useState<JobType>(currentTab);
const [selectedJobDependencies, setSelectedJobDependencies] = useState<JobDependencies>([]);
async function onExport() {
setExporting(true);
const title = i18n.translate('xpack.ml.importExport.exportFlyout.exportDownloading', {
defaultMessage: 'Your file is downloading in the background',
});
displaySuccessToast(title);
try {
if (selectedJobType === 'anomaly-detector') {
await jobsExportService.exportAnomalyDetectionJobs(selectedJobIds);
} else {
await jobsExportService.exportDataframeAnalyticsJobs(selectedJobIds);
}
mlUsageCollection.count(
selectedJobType === 'anomaly-detector'
? 'exported_anomaly_detector_jobs'
: 'exported_data_frame_analytics_jobs',
selectedJobIds.length
);
setExporting(false);
onClose();
} catch (error) {
const errorTitle = i18n.translate('xpack.ml.importExport.exportFlyout.exportError', {
defaultMessage: 'Could not export selected jobs',
});
displayErrorToast(error, errorTitle);
setExporting(false);
}
}
function toggleSelectedJob(checked: boolean, id: string) {
if (checked) {
setSelectedJobIds([...selectedJobIds, id]);
} else {
setSelectedJobIds(selectedJobIds.filter((id2) => id2 !== id));
}
}
function switchTab(jobType: JobType) {
setSwitchTabConfirmVisible(false);
setSelectedJobIds([]);
setSelectedJobType(jobType);
}
function onSelectAll() {
const ids = selectedJobType === 'anomaly-detector' ? adJobIds : dfaJobIds;
if (selectedJobIds.length === ids.length) {
setSelectedJobIds([]);
} else {
setSelectedJobIds([...ids]);
}
}
const attemptTabSwitch = useCallback(
(jobType: JobType) => {
if (jobType === selectedJobType) {
return;
}
// if the user has already selected some jobs, open a confirm modal
// rather than changing tabs
if (selectedJobIds.length > 0) {
setSwitchTabNextTab(jobType);
setSwitchTabConfirmVisible(true);
return;
}
switchTab(jobType);
},
[selectedJobIds, selectedJobType]
);
useEffect(() => {
setSelectedJobDependencies(
jobDependencies.filter(({ jobId }) => selectedJobIds.includes(jobId))
);
}, [jobDependencies, selectedJobIds]);
useEffect(
function onFlyoutChange() {
setLoadingADJobs(true);
setLoadingDFAJobs(true);
setExporting(false);
setSwitchTabConfirmVisible(false);
setAdJobIds([]);
setSelectedJobIds([]);
setSelectedJobType(currentTab);
if (isADEnabled) {
getJobs()
.then(({ jobs }) => {
setLoadingADJobs(false);
setAdJobIds(jobs.map((j) => j.job_id));
jobsExportService
.getJobDependencies(jobs)
.then((jobDeps) => {
setJobDependencies(jobDeps);
setLoadingADJobs(false);
})
.catch((error) => {
const errorTitle = i18n.translate(
'xpack.ml.importExport.exportFlyout.calendarsError',
{
defaultMessage: 'Could not load calendars',
}
);
displayErrorToast(error, errorTitle);
});
})
.catch((error) => {
const errorTitle = i18n.translate('xpack.ml.importExport.exportFlyout.adJobsError', {
defaultMessage: 'Could not load anomaly detection jobs',
});
displayErrorToast(error, errorTitle);
});
}
if (isDFAEnabled) {
getDataFrameAnalytics()
.then(({ data_frame_analytics: dataFrameAnalytics }) => {
setLoadingDFAJobs(false);
setDfaJobIds(dataFrameAnalytics.map((j) => j.id));
})
.catch((error) => {
const errorTitle = i18n.translate('xpack.ml.importExport.exportFlyout.dfaJobsError', {
defaultMessage: 'Could not load data frame analytics jobs',
});
displayErrorToast(error, errorTitle);
});
}
},
[
currentTab,
displayErrorToast,
getDataFrameAnalytics,
getJobs,
isADEnabled,
isDFAEnabled,
jobsExportService,
]
);
return (
<>
<EuiFlyout
onClose={onClose}
hideCloseButton
size="s"
data-test-subj="mlJobMgmtExportJobsFlyout"
>
<EuiFlyoutHeader hasBorder>
<EuiTitle size="m">
<h2>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.flyoutHeader"
defaultMessage="Export jobs"
/>
</h2>
</EuiTitle>
</EuiFlyoutHeader>
<EuiFlyoutBody>
<ExportJobDependenciesWarningCallout jobs={selectedJobDependencies} />
<EuiTabs size="s">
{isADEnabled === true ? (
<EuiTab
isSelected={selectedJobType === 'anomaly-detector'}
onClick={() => attemptTabSwitch('anomaly-detector')}
disabled={exporting}
data-test-subj="mlJobMgmtExportJobsADTab"
>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.adTab"
defaultMessage="Anomaly detection"
/>
</EuiTab>
) : null}
{isDFAEnabled === true ? (
<EuiTab
isSelected={selectedJobType === 'data-frame-analytics'}
onClick={() => attemptTabSwitch('data-frame-analytics')}
disabled={exporting}
data-test-subj="mlJobMgmtExportJobsDFATab"
>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.dfaTab"
defaultMessage="Analytics"
/>
</EuiTab>
) : null}
</EuiTabs>
<EuiSpacer size="s" />
<>
{isADEnabled === true && selectedJobType === 'anomaly-detector' && (
<>
{loadingADJobs === true ? (
<LoadingSpinner />
) : (
<>
<EuiButtonEmpty
size="xs"
onClick={onSelectAll}
data-test-subj="mlJobMgmtExportJobsSelectAllButton"
>
{selectedJobIds.length === adJobIds.length ? (
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.adDeselectAllButton"
defaultMessage="Deselect all"
/>
) : (
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.adSelectAllButton"
defaultMessage="Select all"
/>
)}
</EuiButtonEmpty>
<EuiSpacer size="xs" />
<div data-test-subj="mlJobMgmtExportJobsADJobList">
{adJobIds.map((id) => (
<div key={id}>
<EuiCheckbox
id={id}
label={id}
checked={selectedJobIds.includes(id)}
onChange={(e) => toggleSelectedJob(e.target.checked, id)}
/>
<EuiSpacer size="s" />
</div>
))}
</div>
</>
)}
</>
)}
{isDFAEnabled === true && selectedJobType === 'data-frame-analytics' && (
<>
{loadingDFAJobs === true ? (
<LoadingSpinner />
) : (
<>
<EuiButtonEmpty
size="xs"
onClick={onSelectAll}
data-test-subj="mlJobMgmtExportJobsSelectAllButton"
>
{selectedJobIds.length === dfaJobIds.length ? (
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.dfaDeselectAllButton"
defaultMessage="Deselect all"
/>
) : (
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.dfaSelectAllButton"
defaultMessage="Select all"
/>
)}
</EuiButtonEmpty>
<EuiSpacer size="xs" />
<div data-test-subj="mlJobMgmtExportJobsDFAJobList">
{dfaJobIds.map((id) => (
<div key={id}>
<EuiCheckbox
id={id}
label={id}
checked={selectedJobIds.includes(id)}
onChange={(e) => toggleSelectedJob(e.target.checked, id)}
/>
<EuiSpacer size="s" />
</div>
))}
</div>
</>
)}
</>
)}
</>
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="cross" onClick={onClose.bind(null)} flush="left">
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.closeButton"
defaultMessage="Close"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButton
disabled={selectedJobIds.length === 0 || exporting === true}
onClick={onExport}
fill
data-test-subj="mlJobMgmtExportExportButton"
>
<FormattedMessage
id="xpack.ml.importExport.exportFlyout.exportButton"
defaultMessage="Export"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</EuiFlyout>
{switchTabConfirmVisible === true ? (
<SwitchTabsConfirm
onCancel={setSwitchTabConfirmVisible.bind(null, false)}
onConfirm={() => switchTab(switchTabNextTab)}
/>
) : null}
</>
);
};

View file

@ -5,4 +5,8 @@
* 2.0.
*/
export { ExportJobsFlyout } from './export_jobs_flyout';
import { dynamic } from '@kbn/shared-ux-utility';
export const ExportJobsFlyout = dynamic(async () => ({
default: (await import('./export_jobs_flyout')).ExportJobsFlyout,
}));

View file

@ -43,9 +43,10 @@ import { useValidateIds } from './validate';
import type { ImportedAdJob, JobIdObject, SkippedJobs } from './jobs_import_service';
import { useEnabledFeatures } from '../../../contexts/ml';
interface Props {
export interface Props {
isDisabled: boolean;
}
export const ImportJobsFlyout: FC<Props> = ({ isDisabled }) => {
const {
services: {

View file

@ -5,4 +5,8 @@
* 2.0.
*/
export { ImportJobsFlyout } from './import_jobs_flyout';
import { dynamic } from '@kbn/shared-ux-utility';
export const ImportJobsFlyout = dynamic(async () => ({
default: (await import('./import_jobs_flyout')).ImportJobsFlyout,
}));

View file

@ -5,4 +5,8 @@
* 2.0.
*/
export { JobSpacesSyncFlyout } from './job_spaces_sync_flyout';
import { dynamic } from '@kbn/shared-ux-utility';
export const JobSpacesSyncFlyout = dynamic(async () => ({
default: (await import('./job_spaces_sync_flyout')).JobSpacesSyncFlyout,
}));

View file

@ -29,9 +29,10 @@ import type { SyncSavedObjectResponse, SyncResult } from '../../../../common/typ
import { SyncList } from './sync_list';
import { useToastNotificationService } from '../../services/toast_notification_service';
interface Props {
export interface Props {
onClose: () => void;
}
export const JobSpacesSyncFlyout: FC<Props> = ({ onClose }) => {
const { displayErrorToast, displaySuccessToast } = useToastNotificationService();
const [loading, setLoading] = useState(false);

View file

@ -21,8 +21,7 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { MlInferenceState } from '../types';
import { PipelineDetailsTitle } from '../../shared';
import { PipelineNameAndDescription } from '../../shared';
import { PipelineDetailsTitle, PipelineNameAndDescription } from '../../shared';
interface Props {
handlePipelineConfigUpdate: (configUpdate: Partial<MlInferenceState>) => void;

View file

@ -28,7 +28,7 @@ import type { DataFrameAnalyticsConfig } from '@kbn/ml-data-frame-analytics-util
import { useTrainedModelsApiService } from '../../../../services/ml_api_service/trained_models';
import type { GetDataFrameAnalyticsResponse } from '../../../../services/ml_api_service/data_frame_analytics';
import { useToastNotificationService } from '../../../../services/toast_notification_service';
import { ModelsTableToConfigMapping } from '../../../../model_management';
import { ModelsTableToConfigMapping } from '../../../../model_management/config_mapping';
import { useMlApiContext } from '../../../../contexts/kibana';
import type { TrainedModelConfigResponse } from '../../../../../../common/types/trained_models';

View file

@ -11,9 +11,9 @@ import type { TypedLensByValueInput } from '@kbn/lens-plugin/public';
import useObservable from 'react-use/lib/useObservable';
import { css } from '@emotion/react';
import { i18n } from '@kbn/i18n';
import { Y_AXIS_LABEL_WIDTH } from '../swimlane_annotation_container';
import { useAnomalyExplorerContext } from '../anomaly_explorer_context';
import { useMlKibana } from '../../contexts/kibana';
import { Y_AXIS_LABEL_WIDTH } from '../constants';
export interface AnomalyDetectionAlertsOverviewChart {
seriesType?: 'bar_stacked' | 'line';

View file

@ -44,9 +44,9 @@ import {
import { useMlKibana } from '../../contexts/kibana';
import { useAnomalyExplorerContext } from '../anomaly_explorer_context';
import type { AppStateSelectedCells, SwimlaneData } from '../explorer_utils';
import { Y_AXIS_LABEL_WIDTH } from '../swimlane_annotation_container';
import { CELL_HEIGHT } from '../swimlane_container';
import { statusNameMap } from './const';
import { Y_AXIS_LABEL_WIDTH } from '../constants';
export interface SwimLaneWrapperProps {
selection?: AppStateSelectedCells | null;

View file

@ -10,6 +10,7 @@ import d3 from 'd3';
import { scaleTime } from 'd3-scale';
import { type ChartTooltipService, type TooltipData } from '../components/chart_tooltip';
import { useCurrentThemeVars } from '../contexts/kibana';
import { Y_AXIS_LABEL_PADDING, Y_AXIS_LABEL_WIDTH } from './constants';
export interface AnnotationTimelineProps<T extends { timestamp: number; end_timestamp?: number }> {
label: string;
@ -23,8 +24,6 @@ export interface AnnotationTimelineProps<T extends { timestamp: number; end_time
chartWidth: number;
}
export const Y_AXIS_LABEL_WIDTH = 170;
export const Y_AXIS_LABEL_PADDING = 8;
const ANNOTATION_CONTAINER_HEIGHT = 12;
const ANNOTATION_MIN_WIDTH = 8;

View file

@ -61,11 +61,12 @@ import { NoOverallData } from './components/no_overall_data';
import { SeverityControl } from '../components/severity_control';
import { AnomalyTimelineHelpPopover } from './anomaly_timeline_help_popover';
import { MlTooltipComponent } from '../components/chart_tooltip';
import { SwimlaneAnnotationContainer, Y_AXIS_LABEL_WIDTH } from './swimlane_annotation_container';
import { SwimlaneAnnotationContainer } from './swimlane_annotation_container';
import { AnomalyTimelineService } from '../services/anomaly_timeline_service';
import { useAnomalyExplorerContext } from './anomaly_explorer_context';
import { getTimeBoundsFromSelection } from './hooks/use_selected_cells';
import { SwimLaneWrapper } from './alerts';
import { Y_AXIS_LABEL_WIDTH } from './constants';
function mapSwimlaneOptionsToEuiOptions(options: string[]) {
return options.map((option) => ({

View file

@ -0,0 +1,10 @@
/*
* 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 const Y_AXIS_LABEL_WIDTH = 170;
export const Y_AXIS_LABEL_PADDING = 8;
export const SWIM_LANE_LABEL_WIDTH = Y_AXIS_LABEL_WIDTH + 2 * Y_AXIS_LABEL_PADDING;

View file

@ -35,8 +35,8 @@ import type { TimefilterContract } from '@kbn/data-plugin/public';
import { useStorage } from '@kbn/ml-local-storage';
import { isDefined } from '@kbn/ml-is-defined';
import type { TimeBuckets } from '@kbn/ml-time-buckets';
import { dynamic } from '@kbn/shared-ux-utility';
import { HelpPopover } from '../components/help_popover';
import { AnnotationFlyout } from '../components/annotations/annotation_flyout';
// @ts-ignore
import { AnnotationsTable } from '../components/annotations/annotations_table';
import { ExplorerNoJobsSelected, ExplorerNoResultsFound } from './components';
@ -66,14 +66,9 @@ import {
import { AnomalyTimeline } from './anomaly_timeline';
import type { FilterAction } from './explorer_constants';
import { FILTER_ACTION } from './explorer_constants';
// Explorer Charts
// @ts-ignore
import { ExplorerChartsContainer } from './explorer_charts/explorer_charts_container';
// Anomalies Table
// @ts-ignore
import { AnomaliesTable } from '../components/anomalies_table/anomalies_table';
// Anomalies Map
import { AnomaliesMap } from './anomalies_map';
import { ANOMALY_DETECTION_DEFAULT_TIME_RANGE } from '../../../common/constants/settings';
import { AnomalyContextMenu } from './anomaly_context_menu';
import type { JobSelectorProps } from '../components/job_selector/job_selector';
@ -85,6 +80,18 @@ import { ML_ANOMALY_EXPLORER_PANELS } from '../../../common/types/storage';
import { AlertsPanel } from './alerts';
import { useMlIndexUtils } from '../util/index_service';
const AnnotationFlyout = dynamic(async () => ({
default: (await import('../components/annotations/annotation_flyout')).AnnotationFlyout,
}));
const AnomaliesMap = dynamic(async () => ({
default: (await import('./anomalies_map')).AnomaliesMap,
}));
const ExplorerChartsContainer = dynamic(async () => ({
default: (await import('./explorer_charts/explorer_charts_container')).ExplorerChartsContainer,
}));
interface ExplorerPageProps {
jobSelectorProps: JobSelectorProps;
noInfluencersConfigured?: boolean;

View file

@ -14,9 +14,8 @@ import moment from 'moment';
import { useCurrentThemeVars } from '../contexts/kibana';
import type { Annotation, AnnotationsTable } from '../../../common/types/annotations';
import type { ChartTooltipService } from '../components/chart_tooltip';
import { Y_AXIS_LABEL_PADDING, Y_AXIS_LABEL_WIDTH } from './constants';
export const Y_AXIS_LABEL_WIDTH = 170;
export const Y_AXIS_LABEL_PADDING = 8;
const ANNOTATION_CONTAINER_HEIGHT = 12;
const ANNOTATION_MIN_WIDTH = 8;

View file

@ -61,7 +61,7 @@ import { mlEscape } from '../util/string_utils';
import { FormattedTooltip } from '../components/chart_tooltip/chart_tooltip';
import './_explorer.scss';
import { EMPTY_FIELD_VALUE_LABEL } from '../timeseriesexplorer/components/entity_control/entity_control';
import { Y_AXIS_LABEL_PADDING, Y_AXIS_LABEL_WIDTH } from './swimlane_annotation_container';
import { SWIM_LANE_LABEL_WIDTH, Y_AXIS_LABEL_PADDING } from './constants';
import { useCurrentThemeVars, useMlKibana } from '../contexts/kibana';
declare global {
@ -82,8 +82,6 @@ export const CELL_HEIGHT = 30;
const LEGEND_HEIGHT = 34;
const X_AXIS_HEIGHT = 24;
export const SWIM_LANE_LABEL_WIDTH = Y_AXIS_LABEL_WIDTH + 2 * Y_AXIS_LABEL_PADDING;
export function isViewBySwimLaneData(arg: any): arg is ViewBySwimLaneData {
return arg && arg.hasOwnProperty('cardinality');
}

View file

@ -12,8 +12,8 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { ML_DATA_PREVIEW_COUNT } from '../../../../../../common/util/job_utils';
import { useMlApiContext } from '../../../../contexts/kibana';
import { usePermissionCheck } from '../../../../capabilities/check_capabilities';
import type { CombinedJob } from '../../../../../shared';
import { MLJobEditor } from '../ml_job_editor';
import type { CombinedJob } from '../../../../../../common/types/anomaly_detection_jobs';
interface Props {
job: CombinedJob;

View file

@ -28,6 +28,7 @@ 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 { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { getMlGlobalServices } from '../../../../util/get_services';
import { EnabledFeaturesContextProvider } from '../../../../contexts/ml';
import { type MlFeatures, PLUGIN_ID } from '../../../../../../common/constants/app';
@ -36,7 +37,6 @@ import { checkGetManagementMlJobsResolver } from '../../../../capabilities/check
import { AccessDeniedPage } from '../access_denied_page';
import { InsufficientLicensePage } from '../insufficient_license_page';
import { JobSpacesSyncFlyout } from '../../../../components/job_spaces_sync';
import { getMlGlobalServices } from '../../../../app';
import { ExportJobsFlyout, ImportJobsFlyout } from '../../../../components/import_export_jobs';
import type { MlSavedObjectType } from '../../../../../../common/types/saved_objects';

View file

@ -7,7 +7,7 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import type { EuiBasicTableColumn } from '@elastic/eui';
import { TrainedModelLink } from '../../../../../model_management';
import { TrainedModelLink } from '../../../../../model_management/model_link';
import type { MlSavedObjectType } from '../../../../../../../common/types/saved_objects';
import type {
AnalyticsManagementItems,

View file

@ -5,8 +5,6 @@
* 2.0.
*/
export * from './models_list';
export const ModelsTableToConfigMapping = {
id: 'model_id',
description: 'description',
@ -14,5 +12,3 @@ export const ModelsTableToConfigMapping = {
type: 'type',
modelType: 'model_type',
} as const;
export { TrainedModelLink } from './model_link';

View file

@ -27,8 +27,8 @@ import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { isDefined } from '@kbn/ml-is-defined';
import { TRAINED_MODEL_TYPE } from '@kbn/ml-trained-models-utils';
import { dynamic } from '@kbn/shared-ux-utility';
import { InferenceApi } from './inference_api_tab';
import { JobMap } from '../data_frame_analytics/pages/job_map';
import type { ModelItemFull } from './models_list';
import { ModelPipelines } from './pipelines';
import { AllocatedModels } from '../memory_usage/nodes_overview/allocated_models';
@ -40,6 +40,10 @@ interface ExpandedRowProps {
item: ModelItemFull;
}
const JobMap = dynamic(async () => ({
default: (await import('../data_frame_analytics/pages/job_map')).JobMap,
}));
const useBadgeFormatter = () => {
const xs = useEuiPaddingSize('xs');

View file

@ -46,12 +46,12 @@ import {
} from '@kbn/ml-trained-models-utils';
import { isDefined } from '@kbn/ml-is-defined';
import { useStorage } from '@kbn/ml-local-storage';
import { AddModelFlyout } from './add_model_flyout';
import { dynamic } from '@kbn/shared-ux-utility';
import { getModelStateColor } from './get_model_state_color';
import { ML_ELSER_CALLOUT_DISMISSED } from '../../../common/types/storage';
import { TechnicalPreviewBadge } from '../components/technical_preview_badge';
import { useModelActions } from './model_actions';
import { ModelsTableToConfigMapping } from '.';
import { ModelsTableToConfigMapping } from './config_mapping';
import type { ModelsBarStats } from '../components/stats_bar';
import { StatsBar } from '../components/stats_bar';
import { useMlKibana } from '../contexts/kibana';
@ -65,7 +65,6 @@ import type {
import { DeleteModelsModal } from './delete_models_modal';
import { ML_PAGES } from '../../../common/constants/locator';
import type { ListingPageUrlState } from '../../../common/types/common';
import { ExpandedRow } from './expanded_row';
import { useTableSettings } from '../data_frame_analytics/pages/analytics_management/components/analytics_list/use_table_settings';
import { useToastNotificationService } from '../services/toast_notification_service';
import { useFieldFormatter } from '../contexts/kibana/use_field_formatter';
@ -104,6 +103,14 @@ interface PageUrlState {
pageUrlState: ListingPageUrlState;
}
const ExpandedRow = dynamic(async () => ({
default: (await import('./expanded_row')).ExpandedRow,
}));
const AddModelFlyout = dynamic(async () => ({
default: (await import('./add_model_flyout')).AddModelFlyout,
}));
const modelIdColumnName = i18n.translate('xpack.ml.trainedModels.modelsList.modelIdHeader', {
defaultMessage: 'ID',
});

View file

@ -9,6 +9,7 @@ import { CHANGE_POINT_DETECTION_ENABLED } from '@kbn/aiops-change-point-detectio
import { i18n } from '@kbn/i18n';
import type { FC } from 'react';
import React from 'react';
import { dynamic } from '@kbn/shared-ux-utility';
import { DataSourceContextProvider } from '../../../contexts/ml';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
@ -16,7 +17,10 @@ import type { MlRoute } from '../..';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { ChangePointDetectionPage as Page } from '../../../aiops';
const Page = dynamic(async () => ({
default: (await import('../../../aiops')).ChangePointDetectionPage,
}));
export const changePointDetectionRouteFactory = (
navigateToPath: NavigateToPath,

View file

@ -8,15 +8,19 @@
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { LogCategorizationPage as Page } from '../../../aiops/log_categorization';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { DataSourceContextProvider } from '../../../contexts/ml';
const Page = dynamic(async () => ({
default: (await import('../../../aiops/log_categorization')).LogCategorizationPage,
}));
export const logCategorizationRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -9,15 +9,19 @@ import type { FC } from 'react';
import React from 'react';
import { useLocation, Redirect } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { LogRateAnalysisPage as Page } from '../../../aiops/log_rate_analysis';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { DataSourceContextProvider } from '../../../contexts/ml';
const Page = dynamic(async () => ({
default: (await import('../../../aiops/log_rate_analysis')).LogRateAnalysisPage,
}));
export const logRateAnalysisRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -9,6 +9,7 @@ import type { FC } from 'react';
import React from 'react';
import { parse } from 'query-string';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { DataSourceContextProvider } from '../../../contexts/ml';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
@ -17,13 +18,15 @@ import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/analytics_creation';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import {
DATA_FRAME_ANALYTICS,
loadNewJobCapabilities,
} from '../../../services/new_job_capabilities/load_new_job_capabilities';
const Page = dynamic(async () => ({
default: (await import('../../../data_frame_analytics/pages/analytics_creation')).Page,
}));
export const analyticsJobsCreationRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -10,15 +10,19 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { useUrlState } from '@kbn/ml-url-state';
import type { DataFrameAnalysisConfigType } from '@kbn/ml-data-frame-analytics-utils';
import { dynamic } from '@kbn/shared-ux-utility';
import { basicResolvers } from '../../resolvers';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { Page } from '../../../data_frame_analytics/pages/analytics_exploration';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
const Page = dynamic(async () => ({
default: (await import('../../../data_frame_analytics/pages/analytics_exploration')).Page,
}));
export const analyticsJobExplorationRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -8,15 +8,19 @@
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/analytics_management';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
const Page = dynamic(async () => ({
default: (await import('../../../data_frame_analytics/pages/analytics_management')).Page,
}));
export const analyticsJobsListRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -8,15 +8,19 @@
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/job_map/page';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
const Page = dynamic(async () => ({
default: (await import('../../../data_frame_analytics/pages/job_map/page')).Page,
}));
export const analyticsMapRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -8,15 +8,19 @@
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/source_selection';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
const Page = dynamic(async () => ({
default: (await import('../../../data_frame_analytics/pages/source_selection')).Page,
}));
export const analyticsSourceSelectionRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -8,7 +8,7 @@
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { DataDriftPage } from '../../../datavisualizer/data_drift/data_drift_page';
import { dynamic } from '@kbn/shared-ux-utility';
import { DataSourceContextProvider } from '../../../contexts/ml';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
@ -23,6 +23,10 @@ import {
} from '../../breadcrumbs';
import { basicResolvers } from '../../resolvers';
const DataDriftPage = dynamic(async () => ({
default: (await import('../../../datavisualizer/data_drift/data_drift_page')).DataDriftPage,
}));
export const dataDriftRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -8,14 +8,18 @@
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { DatavisualizerSelector } from '../../../datavisualizer';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
const DatavisualizerSelector = dynamic(async () => ({
default: (await import('../../../datavisualizer')).DatavisualizerSelector,
}));
export const selectorRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -8,15 +8,19 @@
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { FileDataVisualizerPage } from '../../../datavisualizer/file_based';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
const FileDataVisualizerPage = dynamic(async () => ({
default: (await import('../../../datavisualizer/file_based')).FileDataVisualizerPage,
}));
export const fileBasedRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -8,15 +8,20 @@
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { IndexDataVisualizerPage as Page } from '../../../datavisualizer/index_based/index_data_visualizer';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { DataSourceContextProvider } from '../../../contexts/ml';
const Page = dynamic(async () => ({
default: (await import('../../../datavisualizer/index_based/index_data_visualizer'))
.IndexDataVisualizerPage,
}));
export const indexBasedRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -0,0 +1,82 @@
/*
* 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, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { basicResolvers } from '../../resolvers';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import { useMlKibana } from '../../../contexts/kibana';
import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { mlJobService } from '../../../services/job_service';
import { getDateFormatTz } from '../../../explorer/explorer_utils';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context';
import { AnnotationUpdatesService } from '../../../services/annotations_service';
import { AnomalyExplorerContextProvider } from '../../../explorer/anomaly_explorer_context';
const ExplorerUrlStateManager = dynamic(async () => ({
default: (await import('./state_manager')).ExplorerUrlStateManager,
}));
export const explorerRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string
): MlRoute => ({
id: 'explorer',
path: createPath(ML_PAGES.ANOMALY_EXPLORER),
title: i18n.translate('xpack.ml.anomalyDetection.anomalyExplorer.docTitle', {
defaultMessage: 'Anomaly Explorer',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.anomalyDetection.anomalyExplorerLabel', {
defaultMessage: 'Anomaly Explorer',
}),
},
],
enableDatePicker: true,
'data-test-subj': 'mlPageAnomalyExplorer',
});
const PageWrapper: FC<PageProps> = () => {
const {
services: {
mlServices: { mlApiServices },
},
} = useMlKibana();
const { context, results } = useRouteResolver('full', ['canGetJobs'], {
...basicResolvers(),
jobs: mlJobService.loadJobsWrapper,
jobsWithTimeRange: () => mlApiServices.jobs.jobsWithTimerange(getDateFormatTz()),
});
const annotationUpdatesService = useMemo(() => new AnnotationUpdatesService(), []);
return (
<PageLoader context={context}>
<MlAnnotationUpdatesContext.Provider value={annotationUpdatesService}>
<AnomalyExplorerContextProvider>
{results ? (
<ExplorerUrlStateManager jobsWithTimeRange={results.jobsWithTimeRange.jobs} />
) : null}
</AnomalyExplorerContextProvider>
</MlAnnotationUpdatesContext.Provider>
</PageLoader>
);
};

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 { explorerRouteFactory } from './explorer';

View file

@ -6,7 +6,7 @@
*/
import type { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { i18n } from '@kbn/i18n';
@ -17,93 +17,29 @@ import { useUrlState } from '@kbn/ml-url-state';
import { useTimefilter } from '@kbn/ml-date-picker';
import { ML_JOB_ID } from '@kbn/ml-anomaly-utils';
import { useTimeBuckets } from '@kbn/ml-time-buckets';
import { basicResolvers } from '../resolvers';
import { ML_PAGES } from '../../../locator';
import type { NavigateToPath } from '../../contexts/kibana';
import { useMlKibana } from '../../contexts/kibana';
import { useMlKibana } from '../../../contexts/kibana';
import type { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs';
import type { MlJobWithTimeRange } from '../../../../../common/types/anomaly_detection_jobs';
import { useRefresh } from '../../use_refresh';
import { Explorer } from '../../../explorer';
import { ml } from '../../../services/ml_api_service';
import { useExplorerData } from '../../../explorer/actions';
import { useJobSelection } from '../../../components/job_selector/use_job_selection';
import { useTableInterval } from '../../../components/controls/select_interval';
import { useTableSeverity } from '../../../components/controls/select_severity';
import { MlPageHeader } from '../../../components/page_header';
import { PageTitle } from '../../../components/page_title';
import { AnomalyResultsViewSelector } from '../../../components/anomaly_results_view_selector';
import { AnomalyDetectionEmptyState } from '../../../jobs/jobs_list/components/anomaly_detection_empty_state';
import { useAnomalyExplorerContext } from '../../../explorer/anomaly_explorer_context';
import type { MlRoute, PageProps } from '../router';
import { createPath, PageLoader } from '../router';
import { useRefresh } from '../use_refresh';
import { useRouteResolver } from '../use_resolver';
import { Explorer } from '../../explorer';
import { mlJobService } from '../../services/job_service';
import { ml } from '../../services/ml_api_service';
import { useExplorerData } from '../../explorer/actions';
import { getDateFormatTz } from '../../explorer/explorer_utils';
import { useJobSelection } from '../../components/job_selector/use_job_selection';
import { useTableInterval } from '../../components/controls/select_interval';
import { useTableSeverity } from '../../components/controls/select_severity';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context';
import { AnnotationUpdatesService } from '../../services/annotations_service';
import { MlPageHeader } from '../../components/page_header';
import { PageTitle } from '../../components/page_title';
import { AnomalyResultsViewSelector } from '../../components/anomaly_results_view_selector';
import { AnomalyDetectionEmptyState } from '../../jobs/jobs_list/components/anomaly_detection_empty_state';
import {
AnomalyExplorerContextProvider,
useAnomalyExplorerContext,
} from '../../explorer/anomaly_explorer_context';
export const explorerRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string
): MlRoute => ({
id: 'explorer',
path: createPath(ML_PAGES.ANOMALY_EXPLORER),
title: i18n.translate('xpack.ml.anomalyDetection.anomalyExplorer.docTitle', {
defaultMessage: 'Anomaly Explorer',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.anomalyDetection.anomalyExplorerLabel', {
defaultMessage: 'Anomaly Explorer',
}),
},
],
enableDatePicker: true,
'data-test-subj': 'mlPageAnomalyExplorer',
});
const PageWrapper: FC<PageProps> = () => {
const {
services: {
mlServices: { mlApiServices },
},
} = useMlKibana();
const { context, results } = useRouteResolver('full', ['canGetJobs'], {
...basicResolvers(),
jobs: mlJobService.loadJobsWrapper,
jobsWithTimeRange: () => mlApiServices.jobs.jobsWithTimerange(getDateFormatTz()),
});
const annotationUpdatesService = useMemo(() => new AnnotationUpdatesService(), []);
return (
<PageLoader context={context}>
<MlAnnotationUpdatesContext.Provider value={annotationUpdatesService}>
<AnomalyExplorerContextProvider>
{results ? (
<ExplorerUrlStateManager jobsWithTimeRange={results.jobsWithTimeRange.jobs} />
) : null}
</AnomalyExplorerContextProvider>
</MlAnnotationUpdatesContext.Provider>
</PageLoader>
);
};
interface ExplorerUrlStateManagerProps {
export interface ExplorerUrlStateManagerProps {
jobsWithTimeRange: MlJobWithTimeRange[];
}
const ExplorerUrlStateManager: FC<ExplorerUrlStateManagerProps> = ({ jobsWithTimeRange }) => {
export const ExplorerUrlStateManager: FC<ExplorerUrlStateManagerProps> = ({
jobsWithTimeRange,
}) => {
const {
services: { cases, presentationUtil, uiSettings },
} = useMlKibana();

View file

@ -14,18 +14,22 @@ import {
useRefreshIntervalUpdates,
useTimefilter,
} from '@kbn/ml-date-picker';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../locator';
import type { NavigateToPath } from '../../contexts/kibana';
import { DEFAULT_REFRESH_INTERVAL_MS } from '../../../../common/constants/jobs_list';
import type { MlRoute } from '../router';
import { createPath, PageLoader } from '../router';
import { useRouteResolver } from '../use_resolver';
import { JobsPage } from '../../jobs/jobs_list';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
import { AnnotationUpdatesService } from '../../services/annotations_service';
import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context';
import { basicResolvers } from '../resolvers';
const JobsPage = dynamic(async () => ({
default: (await import('../../jobs/jobs_list')).JobsPage,
}));
export const jobListRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({
id: 'anomaly_detection',
title: i18n.translate('xpack.ml.anomalyDetection.jobs.docTitle', {

View file

@ -8,6 +8,7 @@
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { basicResolvers } from '../resolvers';
import { ML_PAGES } from '../../../locator';
import type { NavigateToPath } from '../../contexts/kibana';
@ -15,7 +16,10 @@ import type { MlRoute } from '../router';
import { createPath, PageLoader } from '../router';
import { useRouteResolver } from '../use_resolver';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
import { MemoryUsagePage } from '../../memory_usage';
const MemoryUsagePage = dynamic(async () => ({
default: (await import('../../memory_usage')).MemoryUsagePage,
}));
export const nodesListRouteFactory = (
navigateToPath: NavigateToPath,

View file

@ -10,6 +10,7 @@ import React from 'react';
import { Redirect } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import { useMlKibana } from '../../../contexts/kibana';
@ -17,7 +18,7 @@ import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page, preConfiguredJobRedirect } from '../../../jobs/new_job/pages/index_or_search';
import { preConfiguredJobRedirect } from '../../../jobs/new_job/pages/index_or_search';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { NavigateToPageButton } from '../../components/navigate_to_page_button';
@ -26,6 +27,10 @@ enum MODE {
DATAVISUALIZER,
}
const Page = dynamic(async () => ({
default: (await import('../../../jobs/new_job/pages/index_or_search')).Page,
}));
interface IndexOrSearchPageProps extends PageProps {
nextStepPath: string;
mode: MODE;

View file

@ -8,16 +8,20 @@
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { basicResolvers } from '../../resolvers';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { Page } from '../../../jobs/new_job/pages/job_type';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { DataSourceContextProvider } from '../../../contexts/ml';
const Page = dynamic(async () => ({
default: (await import('../../../jobs/new_job/pages/job_type')).Page,
}));
export const jobTypeRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({
path: createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE),
render: () => <PageWrapper />,

View file

@ -9,6 +9,7 @@ import { parse } from 'query-string';
import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { basicResolvers } from '../../resolvers';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
@ -16,12 +17,15 @@ import { useMlKibana, useNavigateToPath } from '../../../contexts/kibana';
import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { Page } from '../../../jobs/new_job/recognize';
import { mlJobService } from '../../../services/job_service';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { useCreateADLinks } from '../../../components/custom_hooks/use_create_ad_links';
import { DataSourceContextProvider } from '../../../contexts/ml';
const Page = dynamic(async () => ({
default: (await import('../../../jobs/new_job/recognize')).Page,
}));
export const recognizeRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -10,6 +10,7 @@ import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { Redirect } from 'react-router-dom';
import { dynamic } from '@kbn/shared-ux-utility';
import { DataSourceContextProvider } from '../../../contexts/ml/data_source_context';
import type { NavigateToPath } from '../../../contexts/kibana';
import { useMlKibana } from '../../../contexts/kibana';
@ -17,7 +18,6 @@ import { basicResolvers } from '../../resolvers';
import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { Page } from '../../../jobs/new_job/pages/new_job';
import { JOB_TYPE } from '../../../../../common/constants/new_job';
import { mlJobService } from '../../../services/job_service';
import {
@ -33,6 +33,10 @@ interface WizardPageProps extends PageProps {
jobType: JOB_TYPE;
}
const Page = dynamic(async () => ({
default: (await import('../../../jobs/new_job/pages/new_job')).Page,
}));
const getBaseBreadcrumbs = (navigateToPath: NavigateToPath, basePath: string) => [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),

View file

@ -9,6 +9,7 @@ import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { useTimefilter } from '@kbn/ml-date-picker';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute, PageProps } from '../../router';
@ -16,9 +17,12 @@ import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { usePermissionCheck } from '../../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { CalendarsList } from '../../../settings/calendars';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
const CalendarsList = dynamic(async () => ({
default: (await import('../../../settings/calendars')).CalendarsList,
}));
export const calendarListRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -9,12 +9,12 @@ import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { useTimefilter } from '@kbn/ml-date-picker';
import { dynamic } from '@kbn/shared-ux-utility';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { usePermissionCheck } from '../../../capabilities/check_capabilities';
import { NewCalendar } from '../../../settings/calendars';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { getMlNodeCount } from '../../../ml_nodes_check';
@ -28,6 +28,10 @@ interface NewCalendarPageProps extends PageProps {
mode: MODE;
}
const NewCalendar = dynamic(async () => ({
default: (await import('../../../settings/calendars')).NewCalendar,
}));
export const newCalendarRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -9,6 +9,7 @@ import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { useTimefilter } from '@kbn/ml-date-picker';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
@ -16,9 +17,12 @@ import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { usePermissionCheck } from '../../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { FilterLists } from '../../../settings/filter_lists';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
const FilterLists = dynamic(async () => ({
default: (await import('../../../settings/filter_lists')).FilterLists,
}));
export const filterListRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -9,10 +9,10 @@ import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { useTimefilter } from '@kbn/ml-date-picker';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { usePermissionCheck } from '../../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { EditFilterList } from '../../../settings/filter_lists';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
@ -28,6 +28,10 @@ interface NewFilterPageProps extends PageProps {
mode: MODE;
}
const EditFilterList = dynamic(async () => ({
default: (await import('../../../settings/filter_lists')).EditFilterList,
}));
export const newFilterListRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -9,6 +9,7 @@ import type { FC } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { useTimefilter } from '@kbn/ml-date-picker';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
@ -16,9 +17,13 @@ import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { usePermissionCheck } from '../../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { AnomalyDetectionSettingsContext, Settings } from '../../../settings';
import { AnomalyDetectionSettingsContext } from '../../../settings';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
const Settings = dynamic(async () => ({
default: (await import('../../../settings')).Settings,
}));
export const settingsRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

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 { timeSeriesExplorerRouteFactory } from './timeseriesexplorer';

View file

@ -7,100 +7,37 @@
import { isEqual } from 'lodash';
import type { FC } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import usePrevious from 'react-use/lib/usePrevious';
import moment from 'moment';
import { i18n } from '@kbn/i18n';
import { useUrlState } from '@kbn/ml-url-state';
import { useTimefilter } from '@kbn/ml-date-picker';
import type { IUiSettingsClient } from '@kbn/core/public';
import type { TimeRangeBounds } from '@kbn/ml-time-buckets';
import { ML_PAGES } from '../../../locator';
import { getViewableDetectors } from '../../timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors';
import type { NavigateToPath } from '../../contexts/kibana';
import {
useMlApiContext,
useMlKibana,
useNotifications,
useUiSettings,
} from '../../contexts/kibana';
import type { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_jobs';
import { isTimeSeriesViewJob } from '../../../../common/util/job_utils';
import { TimeSeriesExplorer } from '../../timeseriesexplorer';
import { getDateFormatTz } from '../../explorer/explorer_utils';
import { mlJobService } from '../../services/job_service';
import { useForecastService } from '../../services/forecast_service';
import { useTimeSeriesExplorerService } from '../../util/time_series_explorer_service';
import { APP_STATE_ACTION } from '../../timeseriesexplorer/timeseriesexplorer_constants';
import { validateJobSelection } from '../../timeseriesexplorer/timeseriesexplorer_utils';
import { TimeSeriesExplorerPage } from '../../timeseriesexplorer/timeseriesexplorer_page';
import { TimeseriesexplorerNoJobsFound } from '../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found';
import { useTableInterval } from '../../components/controls/select_interval';
import { useTableSeverity } from '../../components/controls/select_severity';
import type { MlRoute, PageProps } from '../router';
import { createPath, PageLoader } from '../router';
import { useRouteResolver } from '../use_resolver';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
import { useToastNotificationService } from '../../services/toast_notification_service';
import { AnnotationUpdatesService } from '../../services/annotations_service';
import { MlAnnotationUpdatesContext } from '../../contexts/ml/ml_annotation_updates_context';
import { useTimeSeriesExplorerUrlState } from '../../timeseriesexplorer/hooks/use_timeseriesexplorer_url_state';
import type { TimeSeriesExplorerAppState } from '../../../../common/types/locator';
import { useJobSelectionFlyout } from '../../contexts/ml/use_job_selection_flyout';
import { useRefresh } from '../use_refresh';
import { TimeseriesexplorerNoChartData } from '../../timeseriesexplorer/components/timeseriesexplorer_no_chart_data';
import { basicResolvers } from '../resolvers';
export const timeSeriesExplorerRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string
): MlRoute => ({
id: 'timeseriesexplorer',
path: createPath(ML_PAGES.SINGLE_METRIC_VIEWER),
title: i18n.translate('xpack.ml.anomalyDetection.singleMetricViewerLabel', {
defaultMessage: 'Single Metric Viewer',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.anomalyDetection.singleMetricViewerLabel', {
defaultMessage: 'Single Metric Viewer',
}),
},
],
enableDatePicker: true,
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const mlApi = useMlApiContext();
const uiSettings = useUiSettings();
const { context, results } = useRouteResolver('full', ['canGetJobs'], {
...basicResolvers(),
jobs: mlJobService.loadJobsWrapper,
jobsWithTimeRange: () => mlApi.jobs.jobsWithTimerange(getDateFormatTz()),
});
const annotationUpdatesService = useMemo(() => new AnnotationUpdatesService(), []);
return (
<PageLoader context={context}>
<MlAnnotationUpdatesContext.Provider value={annotationUpdatesService}>
{results ? (
<TimeSeriesExplorerUrlStateManager
config={uiSettings}
jobsWithTimeRange={results.jobsWithTimeRange.jobs}
/>
) : null}
</MlAnnotationUpdatesContext.Provider>
</PageLoader>
);
};
import { getViewableDetectors } from '../../../timeseriesexplorer/timeseriesexplorer_utils/get_viewable_detectors';
import { useMlKibana, useNotifications } from '../../../contexts/kibana';
import type { MlJobWithTimeRange } from '../../../../../common/types/anomaly_detection_jobs';
import { isTimeSeriesViewJob } from '../../../../../common/util/job_utils';
import { TimeSeriesExplorer } from '../../../timeseriesexplorer';
import { mlJobService } from '../../../services/job_service';
import { useForecastService } from '../../../services/forecast_service';
import { useTimeSeriesExplorerService } from '../../../util/time_series_explorer_service';
import { APP_STATE_ACTION } from '../../../timeseriesexplorer/timeseriesexplorer_constants';
import { validateJobSelection } from '../../../timeseriesexplorer/timeseriesexplorer_utils';
import { TimeSeriesExplorerPage } from '../../../timeseriesexplorer/timeseriesexplorer_page';
import { TimeseriesexplorerNoJobsFound } from '../../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found';
import { useTableInterval } from '../../../components/controls/select_interval';
import { useTableSeverity } from '../../../components/controls/select_severity';
import { useToastNotificationService } from '../../../services/toast_notification_service';
import { useTimeSeriesExplorerUrlState } from '../../../timeseriesexplorer/hooks/use_timeseriesexplorer_url_state';
import type { TimeSeriesExplorerAppState } from '../../../../../common/types/locator';
import { useJobSelectionFlyout } from '../../../contexts/ml/use_job_selection_flyout';
import { useRefresh } from '../../use_refresh';
import { TimeseriesexplorerNoChartData } from '../../../timeseriesexplorer/components/timeseriesexplorer_no_chart_data';
type AppStateZoom = Exclude<TimeSeriesExplorerAppState['mlTimeSeriesExplorer'], undefined>['zoom'];
interface TimeSeriesExplorerUrlStateManager {
export interface TimeSeriesExplorerUrlStateManager {
config: IUiSettingsClient;
jobsWithTimeRange: MlJobWithTimeRange[];
}

View file

@ -8,28 +8,28 @@
import React from 'react';
import { render } from '@testing-library/react';
import { I18nProvider } from '@kbn/i18n-react';
import { TimeSeriesExplorerUrlStateManager } from './timeseriesexplorer';
import { TimeSeriesExplorer } from '../../timeseriesexplorer';
import { TimeSeriesExplorerPage } from '../../timeseriesexplorer/timeseriesexplorer_page';
import { TimeseriesexplorerNoJobsFound } from '../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found';
import { TimeSeriesExplorerUrlStateManager } from './state_manager';
import { TimeSeriesExplorer } from '../../../timeseriesexplorer';
import { TimeSeriesExplorerPage } from '../../../timeseriesexplorer/timeseriesexplorer_page';
import { TimeseriesexplorerNoJobsFound } from '../../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found';
import { DatePickerContextProvider, type DatePickerDependencies } from '@kbn/ml-date-picker';
import type { IUiSettingsClient } from '@kbn/core/public';
jest.mock('../../services/toast_notification_service');
jest.mock('../../../services/toast_notification_service');
jest.mock('../../timeseriesexplorer', () => ({
jest.mock('../../../timeseriesexplorer', () => ({
TimeSeriesExplorer: jest.fn(() => {
return null;
}),
}));
jest.mock('../../timeseriesexplorer/timeseriesexplorer_page', () => ({
jest.mock('../../../timeseriesexplorer/timeseriesexplorer_page', () => ({
TimeSeriesExplorerPage: jest.fn(({ children }) => {
return <>{children}</>;
}),
}));
jest.mock('../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found', () => ({
jest.mock('../../../timeseriesexplorer/components/timeseriesexplorer_no_jobs_found', () => ({
TimeseriesexplorerNoJobsFound: jest.fn(() => {
return null;
}),
@ -90,13 +90,13 @@ jest.mock('@kbn/ml-url-state', () => {
};
});
jest.mock('../../timeseriesexplorer/hooks/use_timeseriesexplorer_url_state');
jest.mock('../../../timeseriesexplorer/hooks/use_timeseriesexplorer_url_state');
jest.mock('../../components/help_menu', () => ({
jest.mock('../../../components/help_menu', () => ({
HelpMenu: () => <div id="mockHelpMenu" />,
}));
jest.mock('../../contexts/kibana/kibana_context', () => {
jest.mock('../../../contexts/kibana/kibana_context', () => {
return {
useMlKibana: () => {
return {

View file

@ -0,0 +1,74 @@
/*
* 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, { useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import { useMlApiContext, useUiSettings } from '../../../contexts/kibana';
import { getDateFormatTz } from '../../../explorer/explorer_utils';
import { mlJobService } from '../../../services/job_service';
import type { MlRoute, PageProps } from '../../router';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { AnnotationUpdatesService } from '../../../services/annotations_service';
import { MlAnnotationUpdatesContext } from '../../../contexts/ml/ml_annotation_updates_context';
import { basicResolvers } from '../../resolvers';
const TimeSeriesExplorerUrlStateManager = dynamic(async () => ({
default: (await import('./state_manager')).TimeSeriesExplorerUrlStateManager,
}));
export const timeSeriesExplorerRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string
): MlRoute => ({
id: 'timeseriesexplorer',
path: createPath(ML_PAGES.SINGLE_METRIC_VIEWER),
title: i18n.translate('xpack.ml.anomalyDetection.singleMetricViewerLabel', {
defaultMessage: 'Single Metric Viewer',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
{
text: i18n.translate('xpack.ml.anomalyDetection.singleMetricViewerLabel', {
defaultMessage: 'Single Metric Viewer',
}),
},
],
enableDatePicker: true,
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const mlApi = useMlApiContext();
const uiSettings = useUiSettings();
const { context, results } = useRouteResolver('full', ['canGetJobs'], {
...basicResolvers(),
jobs: mlJobService.loadJobsWrapper,
jobsWithTimeRange: () => mlApi.jobs.jobsWithTimerange(getDateFormatTz()),
});
const annotationUpdatesService = useMemo(() => new AnnotationUpdatesService(), []);
return (
<PageLoader context={context}>
<MlAnnotationUpdatesContext.Provider value={annotationUpdatesService}>
{results ? (
<TimeSeriesExplorerUrlStateManager
config={uiSettings}
jobsWithTimeRange={results.jobsWithTimeRange.jobs}
/>
) : null}
</MlAnnotationUpdatesContext.Provider>
</PageLoader>
);
};

View file

@ -10,6 +10,7 @@ import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { dynamic } from '@kbn/shared-ux-utility';
import { ML_PAGES } from '../../../../locator';
import type { NavigateToPath } from '../../../contexts/kibana';
import type { MlRoute } from '../../router';
@ -17,9 +18,12 @@ import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { ModelsList } from '../../../model_management';
import { MlPageHeader } from '../../../components/page_header';
const ModelsList = dynamic(async () => ({
default: (await import('../../../model_management/models_list')).ModelsList,
}));
export const modelsListRouteFactory = (
navigateToPath: NavigateToPath,
basePath: string

View file

@ -23,7 +23,7 @@ import type { SeriesConfigWithMetadata } from '../../../common/types/results';
import type { ExplorerChartsData } from '../explorer/explorer_charts/explorer_charts_container_service';
import type { AppStateSelectedCells } from '../explorer/explorer_utils';
import { SWIM_LANE_LABEL_WIDTH } from '../explorer/swimlane_container';
import { SWIM_LANE_LABEL_WIDTH } from '../explorer/constants';
import type { MlApiServices } from './ml_api_service';
import type { MlResultsService } from './results_service';

View file

@ -0,0 +1,51 @@
/*
* 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 { HttpStart } from '@kbn/core-http-browser';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { MlLicense } from '../../../common/license';
import { MlCapabilitiesService } from '../capabilities/check_capabilities';
import { fieldFormatServiceFactory } from '../services/field_format_service_factory';
import { HttpService } from '../services/http_service';
import { mlApiServicesProvider } from '../services/ml_api_service';
import { mlUsageCollectionProvider } from '../services/usage_collection';
import { indexServiceFactory } from './index_service';
/**
* Provides global services available across the entire ML app.
*/
export function getMlGlobalServices(
httpStart: HttpStart,
dataViews: DataViewsContract,
usageCollection?: UsageCollectionSetup
) {
const httpService = new HttpService(httpStart);
const mlApiServices = mlApiServicesProvider(httpService);
// Note on the following services:
// - `mlIndexUtils` is just instantiated here to be passed on to `mlFieldFormatService`,
// but it's not being made available as part of global services. Since it's just
// some stateless utils `useMlIndexUtils()` should be used from within components.
// - `mlFieldFormatService` is a stateful legacy service that relied on "dependency cache",
// so because of its own state it needs to be made available as a global service.
// In the long run we should again try to get rid of it here and make it available via
// its own context or possibly without having a singleton like state at all, since the
// way this manages its own state right now doesn't consider React component lifecycles.
const mlIndexUtils = indexServiceFactory(dataViews);
const mlFieldFormatService = fieldFormatServiceFactory(mlApiServices, mlIndexUtils);
return {
httpService,
mlApiServices,
mlFieldFormatService,
mlUsageCollection: mlUsageCollectionProvider(usageCollection),
mlCapabilities: new MlCapabilitiesService(mlApiServices),
mlLicense: new MlLicense(),
};
}

View file

@ -7,9 +7,9 @@
import type { MlEntityField } from '@kbn/ml-anomaly-utils';
import type { HasType, PublishingSubject } from '@kbn/presentation-publishing';
import type { JobId } from '../../shared';
import type { AnomalyExplorerChartsEmbeddableType } from '../constants';
import type { MlEmbeddableBaseApi } from '../types';
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
export interface AnomalyChartsFieldSelectionApi {
jobIds: PublishingSubject<JobId[]>;

View file

@ -13,7 +13,6 @@ import type { Observable } from 'rxjs';
import type { CoreStart } from '@kbn/core/public';
import { FormattedMessage } from '@kbn/i18n-react';
import { css } from '@emotion/react';
import { Y_AXIS_LABEL_WIDTH } from '../../application/explorer/swimlane_annotation_container';
import { useEmbeddableExecutionContext } from '../common/use_embeddable_execution_context';
import type { IAnomalySwimlaneEmbeddable } from './anomaly_swimlane_embeddable';
import { useSwimlaneInputResolver } from './swimlane_input_resolver';
@ -31,6 +30,7 @@ import type {
AnomalySwimlaneServices,
} from '..';
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE } from '..';
import { Y_AXIS_LABEL_WIDTH } from '../../application/explorer/constants';
export interface ExplorerSwimlaneContainerProps {
id: string;

View file

@ -13,11 +13,11 @@ import type {
import { apiIsOfType } from '@kbn/presentation-publishing';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import type { SwimlaneType } from '../../application/explorer/explorer_constants';
import type { JobId } from '../../shared';
import type { AnomalySwimLaneEmbeddableType } from '../constants';
import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE } from '../constants';
import type { AnomalySwimlaneEmbeddableUserInput, MlEmbeddableBaseApi } from '../types';
import type { AppStateSelectedCells } from '../../application/explorer/explorer_utils';
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
export interface AnomalySwimLaneComponentApi {
jobIds: PublishingSubject<JobId[]>;

View file

@ -16,7 +16,7 @@ import {
import type { BehaviorSubject } from 'rxjs';
import { firstValueFrom } from 'rxjs';
import { type AnomalyDetectorService } from '../../application/services/anomaly_detector_service';
import type { JobId } from '../../shared';
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
export type CommonInput = { jobIds: string[] } & EmbeddableInput;

View file

@ -13,9 +13,9 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { toMountPoint } from '@kbn/react-kibana-mount';
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import { getInitialGroupsMap } from '../../application/components/job_selector/job_selector';
import { getMlGlobalServices } from '../../application/app';
import type { JobId } from '../../../common/types/anomaly_detection_jobs';
import { JobSelectorFlyout } from './components/job_selector_flyout';
import { getMlGlobalServices } from '../../application/util/get_services';
/**
* Handles Anomaly detection jobs selection by a user.

View file

@ -15,8 +15,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import type { LensPublicStart } from '@kbn/lens-plugin/public';
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
import { getMlGlobalServices } from '../../../application/app';
import { getMlGlobalServices } from '../../../application/util/get_services';
export interface FlyoutComponentProps {
onClose: () => void;

View file

@ -26,13 +26,11 @@ export const plugin: PluginInitializer<
> = (initializerContext: PluginInitializerContext) => new MlPlugin(initializerContext);
export type { MlPluginSetup, MlPluginStart };
export type {
DataRecognizerConfigResponse,
JobExistResult,
JobStat,
MlCapabilitiesResponse,
MlSummaryJob,
} from './shared';
export type { MlCapabilitiesResponse } from '../common/types/capabilities';
export type { MlSummaryJob } from '../common/types/anomaly_detection_jobs';
export type { JobExistResult, JobStat } from '../common/types/data_recognizer';
export type { DataRecognizerConfigResponse } from '../common/types/modules';
export type { AnomalySwimlaneEmbeddableInput } from './embeddables';
@ -47,12 +45,10 @@ export { useMlHref, ML_PAGES, MlLocatorDefinition } from './locator';
export const getMlSharedImports = async () => {
return await import('./shared');
};
// Helper to get Type returned by getMlSharedImports.
type AwaitReturnType<T> = T extends PromiseLike<infer U> ? U : T;
export type GetMlSharedImportsReturnType = AwaitReturnType<ReturnType<typeof getMlSharedImports>>;
export { MLJobsAwaitingNodeWarning } from './application/components/jobs_awaiting_node_warning/new_job_awaiting_node_shared';
export {
MlNodeAvailableWarningShared,
useMlNodeAvailableCheck,
} from './application/components/node_available_warning';
export { MlNodeAvailableWarningShared } from './application/components/node_available_warning';

View file

@ -68,9 +68,9 @@ import {
type ExperimentalFeatures,
initExperimentalFeatures,
} from '../common/constants/app';
import type { MlCapabilities } from './shared';
import type { ElasticModels } from './application/services/elastic_models_service';
import type { MlApiServices } from './application/services/ml_api_service';
import type { MlCapabilities } from '../common/types/capabilities';
export interface MlStartDependencies {
cases?: CasesPublicStart;
@ -233,8 +233,6 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
registerEmbeddables,
registerMlUiActions,
registerSearchLinks,
registerMlAlerts,
registerMapExtension,
registerCasesAttachments,
} = await import('./register_helper');
registerSearchLinks(this.appUpdater$, fullLicense, mlCapabilities, this.isServerless);
@ -245,6 +243,10 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
// Register rules for basic license to show them in the UI as disabled
!fullLicense)
) {
// This module contains async imports itself, and it is conditionally loaded based on the license. We'll save
// traffic if we load it async.
const { registerMlAlerts } = await import('./alerting/register_ml_alerts');
registerMlAlerts(
pluginsSetup.triggersActionsUi,
core.getStartServices,
@ -264,6 +266,10 @@ export class MlPlugin implements Plugin<MlPluginSetup, MlPluginStart> {
}
if (pluginsSetup.maps) {
// This module contains async imports itself, and it is conditionally loaded if maps is enabled. We'll save
// traffic if we load it async.
const { registerMapExtension } = await import('./maps/register_map_extension');
// Pass canGetJobs as minimum permission to show anomalies card in maps layers
await registerMapExtension(pluginsSetup.maps, core, {
canGetJobs: mlCapabilities.canGetJobs,

View file

@ -5,10 +5,9 @@
* 2.0.
*/
// These register helper functions have no async imports themselves, so they
// can be bundled together for a single async chunk.
export { registerEmbeddables } from '../embeddables';
export { registerManagementSection } from '../application/management';
export { registerMlUiActions } from '../ui_actions';
export { registerSearchLinks } from './register_search_links';
export { registerMlAlerts } from '../alerting';
export { registerMapExtension } from '../maps/register_map_extension';
export { registerCasesAttachments } from '../cases';

View file

@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n';
import type { BehaviorSubject } from 'rxjs';
import type { AppUpdater } from '@kbn/core/public';
import type { MlCapabilities } from '../../../common/types/capabilities';
import { getDeepLinks } from './search_deep_links';
import type { MlCapabilities } from '../../shared';
export function registerSearchLinks(
appUpdater: BehaviorSubject<AppUpdater>,

View file

@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n';
import type { LinkId } from '@kbn/deeplinks-ml';
import { type AppDeepLink } from '@kbn/core/public';
import type { MlCapabilities } from '../../../common/types/capabilities';
import { ML_PAGES } from '../../../common/constants/locator';
import type { MlCapabilities } from '../../shared';
function createDeepLinks(
mlCapabilities: MlCapabilities,

View file

@ -5,16 +5,7 @@
* 2.0.
*/
export * from '../common/types/data_recognizer';
export * from '../common/types/capabilities';
export * from '../common/types/anomaly_detection_jobs';
export * from '../common/types/modules';
export * from '../common/types/audit_message';
export * from '../common/util/validators';
export * from '../common/util/metric_change_description';
export * from './application/components/field_stats_flyout';
export * from './application/data_frame_analytics/common';
export { FieldStatsInfoButton } from './application/components/field_stats_flyout/field_stats_info_button';
export { useFieldStatsTrigger } from './application/components/field_stats_flyout/use_field_stats_trigger';
export { FieldStatsFlyoutProvider } from './application/components/field_stats_flyout/field_stats_flyout_provider';
export { useFieldStatsFlyoutContext } from './application/components/field_stats_flyout/use_field_stats_flytout_context';

View file

@ -120,5 +120,6 @@
"@kbn/core-elasticsearch-client-server-mocks",
"@kbn/ml-time-buckets",
"@kbn/aiops-change-point-detection",
"@kbn/shared-ux-utility",
],
}

View file

@ -7,7 +7,7 @@
import expect from '@kbn/expect';
import { Job, Datafeed } from '@kbn/ml-plugin/public/shared';
import { Job, Datafeed } from '@kbn/ml-plugin/common';
import { DATAFEED_STATE, JOB_STATE } from '@kbn/ml-plugin/common';
import { FtrProviderContext } from '../../../ftr_provider_context';
import { USER } from '../../../../functional/services/ml/security_common';