[ML] Update route resolvers (#159176)

## Summary

Resolves #153932

Updates route resolver callback to track license and ML capabilities
requirements.

- The logic for resolving the data view and saved search has been moved
to the dedicated context `DataSourceContextProvider` and only applies to
pages that need it. It also shows an error callout in case of an error
during the data view fetch.
- ML License class has been updated to track license changes and logic
for redirects has been moved to the route resolver
- `MlCapabilitiesService` has been updated to periodically fetch
capabilities
- Most of the static usages of `checkPermission` have been replaced with
`usePermissionCheck`


### Notes for reviewers 

- Now it's obvious what license and capabilities requirements each route
has. We should carefully review it because I assume legacy resolvers
were not entirely correct in the same cases.

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
This commit is contained in:
Dima Arnautov 2023-06-13 13:53:42 +02:00 committed by GitHub
parent 0c4906af89
commit bf6848888b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
117 changed files with 1202 additions and 1397 deletions

View file

@ -60,7 +60,6 @@ export const ML_PAGES = {
FILTER_LISTS_MANAGE: 'settings/filter_lists',
FILTER_LISTS_NEW: 'settings/filter_lists/new_filter_list',
FILTER_LISTS_EDIT: 'settings/filter_lists/edit_filter_list',
ACCESS_DENIED: 'access-denied',
OVERVIEW: 'overview',
NOTIFICATIONS: 'notifications',
AIOPS: 'aiops',

View file

@ -5,8 +5,10 @@
* 2.0.
*/
import { Observable, Subscription } from 'rxjs';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { ILicense } from '@kbn/licensing-plugin/common/types';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { isEqual } from 'lodash';
import { PLUGIN_ID } from '../constants/app';
export const MINIMUM_LICENSE = 'basic';
@ -19,6 +21,16 @@ export interface LicenseStatus {
message?: string;
}
export interface MlLicenseInfo {
license: ILicense | null;
isSecurityEnabled: boolean;
hasLicenseExpired: boolean;
isMlEnabled: boolean;
isMinimumLicense: boolean;
isFullLicense: boolean;
isTrialLicense: boolean;
}
export class MlLicense {
private _licenseSubscription: Subscription | null = null;
private _license: ILicense | null = null;
@ -29,17 +41,48 @@ export class MlLicense {
private _isFullLicense: boolean = false;
private _isTrialLicense: boolean = false;
private _licenseInfo$ = new BehaviorSubject<MlLicenseInfo>({
license: this._license,
isSecurityEnabled: this._isSecurityEnabled,
hasLicenseExpired: this._hasLicenseExpired,
isMlEnabled: this._isMlEnabled,
isMinimumLicense: this._isMinimumLicense,
isFullLicense: this._isFullLicense,
isTrialLicense: this._isTrialLicense,
});
public licenseInfo$: Observable<MlLicenseInfo> = this._licenseInfo$.pipe(
distinctUntilChanged(isEqual)
);
public isLicenseReady$: Observable<boolean> = this._licenseInfo$.pipe(
map((v) => !!v.license),
distinctUntilChanged()
);
public setup(license$: Observable<ILicense>, callback?: (lic: MlLicense) => void) {
this._licenseSubscription = license$.subscribe(async (license) => {
this._licenseSubscription = license$.subscribe((license) => {
const { isEnabled: securityIsEnabled } = license.getFeature('security');
const mlLicenseUpdate = {
license,
isSecurityEnabled: securityIsEnabled,
hasLicenseExpired: license.status === 'expired',
isMlEnabled: license.getFeature(PLUGIN_ID).isEnabled,
isMinimumLicense: isMinimumLicense(license),
isFullLicense: isFullLicense(license),
isTrialLicense: isTrialLicense(license),
};
this._licenseInfo$.next(mlLicenseUpdate);
this._license = license;
this._isSecurityEnabled = securityIsEnabled;
this._hasLicenseExpired = this._license.status === 'expired';
this._isMlEnabled = this._license.getFeature(PLUGIN_ID).isEnabled;
this._isMinimumLicense = isMinimumLicense(this._license);
this._isFullLicense = isFullLicense(this._license);
this._isTrialLicense = isTrialLicense(this._license);
this._isSecurityEnabled = mlLicenseUpdate.isSecurityEnabled;
this._hasLicenseExpired = mlLicenseUpdate.hasLicenseExpired;
this._isMlEnabled = mlLicenseUpdate.isMlEnabled;
this._isMinimumLicense = mlLicenseUpdate.isMinimumLicense;
this._isFullLicense = mlLicenseUpdate.isFullLicense;
this._isTrialLicense = mlLicenseUpdate.isTrialLicense;
if (callback !== undefined) {
callback(this);
@ -47,6 +90,10 @@ export class MlLicense {
});
}
public getLicenseInfo() {
return this._licenseInfo$.getValue();
}
public unsubscribe() {
if (this._licenseSubscription !== null) {
this._licenseSubscription.unsubscribe();

View file

@ -39,6 +39,8 @@ export const userMlCapabilities = {
canTestTrainedModels: false,
canGetFieldInfo: false,
canGetMlInfo: false,
// AIOps
canUseAiops: false,
};
export const adminMlCapabilities = {

View file

@ -58,7 +58,6 @@ export type MlGenericUrlState = MLPageState<
| typeof ML_PAGES.FILTER_LISTS_MANAGE
| typeof ML_PAGES.FILTER_LISTS_NEW
| typeof ML_PAGES.SETTINGS
| typeof ML_PAGES.ACCESS_DENIED
| typeof ML_PAGES.DATA_VISUALIZER
| typeof ML_PAGES.DATA_VISUALIZER_FILE
| typeof ML_PAGES.DATA_VISUALIZER_INDEX_SELECT

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React from 'react';
import React, { type FC } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
@ -16,14 +16,23 @@ import {
EuiPageContent_Deprecated as EuiPageContent,
EuiSpacer,
} from '@elastic/eui';
import { createPermissionFailureMessage } from '../capabilities/check_capabilities';
import { MlCapabilitiesKey } from '../../../common/types/capabilities';
import { HelpMenu } from '../components/help_menu';
import { useMlKibana } from '../contexts/kibana';
export const Page = () => {
export interface AccessDeniedCalloutProps {
missingCapabilities?: MlCapabilitiesKey[];
}
export const AccessDeniedCallout: FC<AccessDeniedCalloutProps> = ({ missingCapabilities }) => {
const {
services: { docLinks },
} = useMlKibana();
const helpLink = docLinks.links.ml.guide;
const errorMessages = (missingCapabilities ?? []).map((c) => createPermissionFailureMessage(c));
return (
<>
<EuiSpacer size="xxl" />
@ -41,12 +50,19 @@ export const Page = () => {
</h2>
}
body={
<p>
<div>
<FormattedMessage
id="xpack.ml.accessDenied.description"
defaultMessage="You dont have permission to view the Machine Learning plugin. Access to the plugin requires the Machine Learning feature to be visible in this space."
defaultMessage="You do not have permission to view this page."
/>
</p>
{errorMessages ? (
<ul>
{errorMessages.map((v) => (
<li key={v}>{v}</li>
))}
</ul>
) : null}
</div>
}
/>
</EuiPageContent>

View file

@ -5,4 +5,4 @@
* 2.0.
*/
export { Page } from './page';
export { AccessDeniedCallout } from './access_denied';

View file

@ -13,8 +13,8 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { ChangePointDetection } from '@kbn/aiops-plugin/public';
import { useDataSource } from '../contexts/ml/data_source_context';
import { useFieldStatsTrigger, FieldStatsFlyoutProvider } from '../components/field_stats_flyout';
import { useMlContext } from '../contexts/ml';
import { useMlKibana } from '../contexts/kibana';
import { HelpMenu } from '../components/help_menu';
import { TechnicalPreviewBadge } from '../components/technical_preview_badge';
@ -24,9 +24,7 @@ import { MlPageHeader } from '../components/page_header';
export const ChangePointDetectionPage: FC = () => {
const { services } = useMlKibana();
const context = useMlContext();
const dataView = context.currentDataView;
const savedSearch = context.selectedSavedSearch;
const { currentDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource();
return (
<>

View file

@ -9,23 +9,18 @@ import React, { FC } from 'react';
import { pick } from 'lodash';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { ExplainLogRateSpikes } from '@kbn/aiops-plugin/public';
import { useMlContext } from '../contexts/ml';
import { useDataSource } from '../contexts/ml/data_source_context';
import { useMlKibana } from '../contexts/kibana';
import { HelpMenu } from '../components/help_menu';
import { TechnicalPreviewBadge } from '../components/technical_preview_badge';
import { MlPageHeader } from '../components/page_header';
export const ExplainLogRateSpikesPage: FC = () => {
const { services } = useMlKibana();
const context = useMlContext();
const dataView = context.currentDataView;
const savedSearch = context.selectedSavedSearch;
const { currentDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource();
return (
<>

View file

@ -7,25 +7,19 @@
import React, { FC } from 'react';
import { pick } from 'lodash';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { LogCategorization } from '@kbn/aiops-plugin/public';
import { useMlContext } from '../contexts/ml';
import { useDataSource } from '../contexts/ml/data_source_context';
import { useMlKibana } from '../contexts/kibana';
import { HelpMenu } from '../components/help_menu';
import { TechnicalPreviewBadge } from '../components/technical_preview_badge';
import { MlPageHeader } from '../components/page_header';
export const LogCategorizationPage: FC = () => {
const { services } = useMlKibana();
const context = useMlContext();
const dataView = context.currentDataView;
const savedSearch = context.selectedSavedSearch;
const { currentDataView: dataView, selectedSavedSearch: savedSearch } = useDataSource();
return (
<>

View file

@ -5,28 +5,29 @@
* 2.0.
*/
import React, { FC } from 'react';
import React, { type FC, useMemo } from 'react';
import './_index.scss';
import ReactDOM from 'react-dom';
import { pick } from 'lodash';
import { AppMountParameters, CoreStart, HttpStart } from '@kbn/core/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { DatePickerContextProvider } from '@kbn/ml-date-picker';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { UI_SETTINGS } from '@kbn/data-plugin/common';
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import {
KibanaContextProvider,
KibanaThemeProvider,
toMountPoint,
wrapWithTheme,
} from '@kbn/kibana-react-plugin/public';
import { StorageContextProvider } from '@kbn/ml-local-storage';
import { firstValueFrom } from 'rxjs';
import { mlCapabilities } from './capabilities/check_capabilities';
import useLifecycles from 'react-use/lib/useLifecycles';
import useObservable from 'react-use/lib/useObservable';
import { MlLicense } from '../../common/license';
import { MlCapabilitiesService } from './capabilities/check_capabilities';
import { ML_STORAGE_KEYS } from '../../common/types/storage';
import { ML_APP_LOCATOR, ML_PAGES } from '../../common/constants/locator';
import type { MlSetupDependencies, MlStartDependencies } from '../plugin';
import { setDependencyCache, clearCache } from './util/dependency_cache';
import { clearCache, setDependencyCache } from './util/dependency_cache';
import { setLicenseCache } from './license';
import { mlUsageCollectionProvider } from './services/usage_collection';
import { MlRouter } from './routing';
@ -58,12 +59,15 @@ export function isServerless() {
*/
export function getMlGlobalServices(httpStart: HttpStart, usageCollection?: UsageCollectionSetup) {
const httpService = new HttpService(httpStart);
const mlApiServices = mlApiServicesProvider(httpService);
return {
httpService,
mlApiServices: mlApiServicesProvider(httpService),
mlApiServices,
mlUsageCollection: mlUsageCollectionProvider(usageCollection),
isServerless,
mlCapabilities,
mlCapabilities: new MlCapabilitiesService(mlApiServices),
mlLicense: new MlLicense(),
};
}
@ -74,54 +78,59 @@ export interface MlServicesContext {
export type MlGlobalServices = ReturnType<typeof getMlGlobalServices>;
const App: FC<AppProps> = ({ coreStart, deps, appMountParams }) => {
const redirectToMlAccessDeniedPage = async () => {
// access maybe be denied due to an expired license, so check the license status first
// if the license has expired, redirect to the license management page
const license = await firstValueFrom(deps.licensing.license$);
const redirectPage =
license.status === 'expired'
? deps.share.url.locators.get('LICENSE_MANAGEMENT_LOCATOR')!.getUrl({
page: 'dashboard',
})
: deps.share.url.locators.get(ML_APP_LOCATOR)!.getUrl({
page: ML_PAGES.ACCESS_DENIED,
});
await coreStart.application.navigateToUrl(await redirectPage);
};
const pageDeps = {
history: appMountParams.history,
setHeaderActionMenu: appMountParams.setHeaderActionMenu,
dataViewsContract: deps.data.dataViews,
config: coreStart.uiSettings!,
setBreadcrumbs: coreStart.chrome!.setBreadcrumbs,
redirectToMlAccessDeniedPage,
};
const services = {
kibanaVersion: deps.kibanaVersion,
share: deps.share,
data: deps.data,
security: deps.security,
licenseManagement: deps.licenseManagement,
storage: localStorage,
embeddable: deps.embeddable,
maps: deps.maps,
triggersActionsUi: deps.triggersActionsUi,
dataVisualizer: deps.dataVisualizer,
usageCollection: deps.usageCollection,
fieldFormats: deps.fieldFormats,
dashboard: deps.dashboard,
charts: deps.charts,
cases: deps.cases,
unifiedSearch: deps.unifiedSearch,
licensing: deps.licensing,
lens: deps.lens,
savedObjectsManagement: deps.savedObjectsManagement,
savedSearch: deps.savedSearch,
...coreStart,
};
const services = useMemo(() => {
return {
kibanaVersion: deps.kibanaVersion,
share: deps.share,
data: deps.data,
security: deps.security,
licenseManagement: deps.licenseManagement,
storage: localStorage,
embeddable: deps.embeddable,
maps: deps.maps,
triggersActionsUi: deps.triggersActionsUi,
dataVisualizer: deps.dataVisualizer,
usageCollection: deps.usageCollection,
fieldFormats: deps.fieldFormats,
dashboard: deps.dashboard,
charts: deps.charts,
cases: deps.cases,
unifiedSearch: deps.unifiedSearch,
licensing: deps.licensing,
lens: deps.lens,
savedObjectsManagement: deps.savedObjectsManagement,
savedSearch: deps.savedSearch,
...coreStart,
mlServices: getMlGlobalServices(coreStart.http, deps.usageCollection),
};
}, [deps, coreStart]);
useLifecycles(
function setupLicenseOnMount() {
setLicenseCache(services.mlServices.mlLicense);
services.mlServices.mlLicense.setup(deps.licensing.license$);
},
function destroyLicenseOnUnmount() {
services.mlServices.mlLicense.unsubscribe();
}
);
// Wait for license and capabilities to be retrieved before rendering the app.
const licenseReady = useObservable(services.mlServices.mlLicense.isLicenseReady$, false);
const mlCapabilities = useObservable(
services.mlServices.mlCapabilities.capabilities$,
services.mlServices.mlCapabilities.getCapabilities()
);
if (!licenseReady || !mlCapabilities) return null;
const datePickerDeps = {
...pick(services, ['data', 'http', 'notifications', 'theme', 'uiSettings']),
@ -138,12 +147,7 @@ const App: FC<AppProps> = ({ coreStart, deps, appMountParams }) => {
<ApplicationUsageTrackingProvider>
<I18nContext>
<KibanaThemeProvider theme$={appMountParams.theme$}>
<KibanaContextProvider
services={{
...services,
mlServices: getMlGlobalServices(coreStart.http, deps.usageCollection),
}}
>
<KibanaContextProvider services={services}>
<StorageContextProvider storage={localStorage} storageKeys={ML_STORAGE_KEYS}>
<DatePickerContextProvider {...datePickerDeps}>
<MlRouter pageDeps={pageDeps} />
@ -188,15 +192,12 @@ export const renderApp = (
appMountParams.onAppLeave((actions) => actions.default());
const mlLicense = setLicenseCache(deps.licensing, coreStart.application, () =>
ReactDOM.render(
<App coreStart={coreStart} deps={deps} appMountParams={appMountParams} />,
appMountParams.element
)
ReactDOM.render(
<App coreStart={coreStart} deps={deps} appMountParams={appMountParams} />,
appMountParams.element
);
return () => {
mlLicense.unsubscribe();
clearCache();
ReactDOM.unmountComponentAtNode(appMountParams.element);
deps.data.search.session.clear();

View file

@ -0,0 +1,13 @@
/*
* 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 usePermissionCheck = jest.fn((arg: string | string[]) => {
if (Array.isArray(arg)) {
return arg.map((v) => true);
}
return true;
});

View file

@ -6,57 +6,110 @@
*/
import { i18n } from '@kbn/i18n';
import { BehaviorSubject } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';
import { BehaviorSubject, combineLatest, from, type Subscription, timer } from 'rxjs';
import { distinctUntilChanged, retry, switchMap, tap } from 'rxjs/operators';
import { isEqual } from 'lodash';
import useObservable from 'react-use/lib/useObservable';
import { useMemo, useRef } from 'react';
import { useMlKibana } from '../contexts/kibana';
import { hasLicenseExpired } from '../license';
import { MlCapabilities, getDefaultCapabilities } from '../../../common/types/capabilities';
import {
getDefaultCapabilities,
MlCapabilities,
MlCapabilitiesKey,
} from '../../../common/types/capabilities';
import { getCapabilities } from './get_capabilities';
import type { MlApiServices } from '../services/ml_api_service';
import { type MlApiServices } from '../services/ml_api_service';
let _capabilities: MlCapabilities = getDefaultCapabilities();
const CAPABILITIES_REFRESH_INTERVAL = 60000;
export class MlCapabilitiesService {
private _capabilities$ = new BehaviorSubject<MlCapabilities>(getDefaultCapabilities());
private _isLoading$ = new BehaviorSubject<boolean>(true);
/**
* Updates on manual request, e.g. in the route resolver.
* @private
*/
private _updateRequested$ = new BehaviorSubject<number>(Date.now());
private _capabilities$ = new BehaviorSubject<MlCapabilities | null>(null);
public capabilities$ = this._capabilities$.pipe(distinctUntilChanged(isEqual));
public getCapabilities(): MlCapabilities {
private _subscription: Subscription | undefined;
constructor(private readonly mlApiServices: MlApiServices) {
this.init();
}
private init() {
this._subscription = combineLatest([
this._updateRequested$,
timer(0, CAPABILITIES_REFRESH_INTERVAL),
])
.pipe(
tap(() => {
this._isLoading$.next(true);
}),
switchMap(() => from(this.mlApiServices.checkMlCapabilities())),
retry({ delay: CAPABILITIES_REFRESH_INTERVAL })
)
.subscribe((results) => {
this._capabilities$.next(results.capabilities);
this._isLoading$.next(false);
/**
* To support legacy use of {@link checkPermission}
*/
_capabilities = results.capabilities;
});
}
public getCapabilities(): MlCapabilities | null {
return this._capabilities$.getValue();
}
public updateCapabilities(update: MlCapabilities) {
this._capabilities$.next(update);
public refreshCapabilities() {
this._updateRequested$.next(Date.now());
}
public destroy() {
if (this._subscription) {
this._subscription.unsubscribe();
}
}
}
/**
* TODO should be initialized in getMlGlobalServices
* Temp solution to make it work with the current setup.
*/
export const mlCapabilities = new MlCapabilitiesService();
/**
* Check the privilege type and the license to see whether a user has permission to access a feature.
*
* @param capability
*/
export function usePermissionCheck(capability: keyof MlCapabilities) {
export function usePermissionCheck<T extends MlCapabilitiesKey | MlCapabilitiesKey[]>(
capability: T
): T extends MlCapabilitiesKey ? boolean : boolean[] {
const {
services: {
mlServices: { mlCapabilities: mlCapabilitiesService },
},
} = useMlKibana();
const licenseHasExpired = hasLicenseExpired();
// Memoize argument, in case it's an array to preserve the reference.
const requestedCapabilities = useRef(capability);
const capabilities = useObservable(
mlCapabilitiesService.capabilities$,
mlCapabilitiesService.getCapabilities()
);
return capabilities[capability] && !licenseHasExpired;
return useMemo(() => {
return Array.isArray(requestedCapabilities.current)
? requestedCapabilities.current.map((c) => capabilities[c])
: capabilities[requestedCapabilities.current];
}, [capabilities]);
}
export function checkGetManagementMlJobsResolver({ checkMlCapabilities }: MlApiServices) {
@ -64,7 +117,6 @@ export function checkGetManagementMlJobsResolver({ checkMlCapabilities }: MlApiS
checkMlCapabilities()
.then(({ capabilities, isPlatinumOrTrialLicense, mlFeatureEnabledInSpace }) => {
_capabilities = capabilities;
mlCapabilities.updateCapabilities(capabilities);
// Loop through all capabilities to ensure they are all set to true.
const isManageML = Object.values(_capabilities).every((p) => p === true);
@ -80,33 +132,6 @@ export function checkGetManagementMlJobsResolver({ checkMlCapabilities }: MlApiS
});
}
export function checkGetJobsCapabilitiesResolver(
redirectToMlAccessDeniedPage: () => Promise<void>
): Promise<MlCapabilities> {
return new Promise((resolve, reject) => {
getCapabilities()
.then(async ({ capabilities, isPlatinumOrTrialLicense }) => {
_capabilities = capabilities;
mlCapabilities.updateCapabilities(capabilities);
// the minimum privilege for using ML with a platinum or trial license is being able to get the transforms list.
// all other functionality is controlled by the return capabilities object.
// if the license is basic (isPlatinumOrTrialLicense === false) then do not redirect,
// allow the promise to resolve as the separate license check will redirect then user to
// a basic feature
if (_capabilities.canGetJobs || isPlatinumOrTrialLicense === false) {
return resolve(_capabilities);
} else {
await redirectToMlAccessDeniedPage();
return reject();
}
})
.catch(async (e) => {
await redirectToMlAccessDeniedPage();
return reject();
});
});
}
export function checkCreateJobsCapabilitiesResolver(
redirectToJobsManagementPage: () => Promise<void>
): Promise<MlCapabilities> {
@ -114,7 +139,6 @@ export function checkCreateJobsCapabilitiesResolver(
getCapabilities()
.then(async ({ capabilities, isPlatinumOrTrialLicense }) => {
_capabilities = capabilities;
mlCapabilities.updateCapabilities(capabilities);
// if the license is basic (isPlatinumOrTrialLicense === false) then do not redirect,
// allow the promise to resolve as the separate license check will redirect then user to
// a basic feature
@ -134,30 +158,6 @@ export function checkCreateJobsCapabilitiesResolver(
});
}
export function checkFindFileStructurePrivilegeResolver(
redirectToMlAccessDeniedPage: () => Promise<void>
): Promise<MlCapabilities> {
return new Promise((resolve, reject) => {
getCapabilities()
.then(async ({ capabilities }) => {
_capabilities = capabilities;
mlCapabilities.updateCapabilities(capabilities);
// the minimum privilege for using ML with a basic license is being able to use the datavisualizer.
// all other functionality is controlled by the return _capabilities object
if (_capabilities.canFindFileStructure) {
return resolve(_capabilities);
} else {
await redirectToMlAccessDeniedPage();
return reject();
}
})
.catch(async (e) => {
await redirectToMlAccessDeniedPage();
return reject();
});
});
}
/**
* @deprecated use {@link usePermissionCheck} instead.
* @param capability

View file

@ -45,7 +45,7 @@ import {
getDateFormatTz,
SourceIndicesWithGeoFields,
} from '../../explorer/explorer_utils';
import { checkPermission } from '../../capabilities/check_capabilities';
import { usePermissionCheck } from '../../capabilities/check_capabilities';
import type { TimeRangeBounds } from '../../util/time_buckets';
import { useMlKibana } from '../../contexts/kibana';
// @ts-ignore
@ -617,7 +617,8 @@ export const LinksMenuUI = (props: LinksMenuProps) => {
};
const { anomaly, showViewSeriesLink } = props;
const canConfigureRules = isRuleSupported(anomaly.source) && checkPermission('canUpdateJob');
const canUpdateJob = usePermissionCheck('canUpdateJob');
const canConfigureRules = isRuleSupported(anomaly.source) && canUpdateJob;
const contextMenuItems = useMemo(() => {
const items = [];

View file

@ -5,27 +5,26 @@
* 2.0.
*/
import React, { FC, useEffect, useCallback, useState, useRef, useMemo } from 'react';
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiBasicTableColumn,
EuiFlexGroup,
EuiFlexItem,
EuiInMemoryTable,
EuiLoadingSpinner,
EuiBasicTableColumn,
} from '@elastic/eui';
import { timeFormatter } from '@kbn/ml-date-utils';
import { checkPermission } from '../../capabilities/check_capabilities';
import { usePermissionCheck } from '../../capabilities/check_capabilities';
import { EditModelSnapshotFlyout } from './edit_model_snapshot_flyout';
import { RevertModelSnapshotFlyout } from './revert_model_snapshot_flyout';
import { ml } from '../../services/ml_api_service';
import { JOB_STATE, DATAFEED_STATE } from '../../../../common/constants/states';
import { DATAFEED_STATE, JOB_STATE } from '../../../../common/constants/states';
import { CloseJobConfirm } from './close_job_confirm';
import {
ModelSnapshot,
CombinedJobWithStats,
ModelSnapshot,
} from '../../../../common/types/anomaly_detection_jobs';
interface Props {
@ -41,8 +40,10 @@ export enum COMBINED_JOB_STATE {
}
export const ModelSnapshotTable: FC<Props> = ({ job, refreshJobList }) => {
const canCreateJob = checkPermission('canCreateJob');
const canStartStopDatafeed = checkPermission('canStartStopDatafeed');
const [canCreateJob, canStartStopDatafeed] = usePermissionCheck([
'canCreateJob',
'canStartStopDatafeed',
]);
const [snapshots, setSnapshots] = useState<ModelSnapshot[]>([]);
const [snapshotsLoaded, setSnapshotsLoaded] = useState<boolean>(false);

View file

@ -5,13 +5,13 @@
* 2.0.
*/
import React, { FC, useEffect, useState, useCallback, useRef, useMemo } from 'react';
import React, { FC, useCallback, useEffect, useRef, useState } from 'react';
import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { MlSavedObjectType } from '../../../../common/types/saved_objects';
import { useMlApiContext } from '../../contexts/kibana';
import { JobSpacesSyncFlyout } from '../job_spaces_sync';
import { checkPermission } from '../../capabilities/check_capabilities';
import { usePermissionCheck } from '../../capabilities/check_capabilities';
interface Props {
mlSavedObjectType?: MlSavedObjectType;
@ -31,7 +31,7 @@ export const SavedObjectsWarning: FC<Props> = ({
const mounted = useRef(false);
const [showWarning, setShowWarning] = useState(false);
const [showSyncFlyout, setShowSyncFlyout] = useState(false);
const canCreateJob = useMemo(() => checkPermission('canCreateJob'), []);
const canCreateJob = usePermissionCheck('canCreateJob');
const checkStatus = useCallback(async () => {
try {

View file

@ -8,3 +8,4 @@
export { useMlKibana } from './kibana_context';
export { useTimefilter } from './use_timefilter';
export { useMlApiContext } from './use_ml_api_context';
export { useMlLicenseInfo } from './use_ml_license';

View file

@ -9,6 +9,7 @@ import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks';
import { BehaviorSubject } from 'rxjs';
import { mlApiServicesMock } from '../../../services/__mocks__/ml_api_services';
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
export const chartsServiceMock = {
theme: {
@ -37,13 +38,24 @@ export const kibanaContextMock = {
services: {
uiSettings: { get: jest.fn() },
chrome: { recentlyAccessed: { add: jest.fn() } },
application: { navigateToApp: jest.fn() },
application: { navigateToApp: jest.fn(), navigateToUrl: jest.fn() },
http: {
basePath: {
get: jest.fn(),
},
},
share: {
url: {
locators: {
get: jest.fn(() => {
return {
getUrl: jest.fn(() => {
return Promise.resolve('mock-url');
}),
};
}),
},
},
urlGenerators: { getUrlGenerator: jest.fn() },
},
data: dataPluginMock.createStartContract(),
@ -51,7 +63,11 @@ export const kibanaContextMock = {
fieldFormats: fieldFormatsServiceMock.createStartContract(),
mlServices: {
mlApiServices: mlApiServicesMock,
mlCapabilities: {
refreshCapabilities: jest.fn(),
},
},
notifications: notificationServiceMock.createStartContract(),
},
};

View file

@ -0,0 +1,18 @@
/*
* 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 useMlLicenseInfo = jest.fn(() => {
return {
isSecurityEnabled: true,
hasLicenseExpired: false,
isMlEnabled: true,
isMinimumLicense: true,
isFullLicense: true,
isTrialLicense: false,
license: {},
};
});

View file

@ -15,3 +15,4 @@ export { useMlLocator, useMlLink } from './use_create_url';
export { useMlApiContext } from './use_ml_api_context';
export { useFieldFormatter } from './use_field_formatter';
export { useCurrentThemeVars } from './use_current_theme';
export { useMlLicenseInfo } from './use_ml_license';

View file

@ -26,6 +26,7 @@ import type { CasesUiStart } from '@kbn/cases-plugin/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import type { LensPublicStart } from '@kbn/lens-plugin/public';
import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public';
import type { SavedSearchPublicPluginStart } from '@kbn/saved-search-plugin/public';
import type { MlServicesContext } from '../../app';
interface StartPlugins {
@ -49,6 +50,7 @@ interface StartPlugins {
appName: string;
lens: LensPublicStart;
savedObjectsManagement: SavedObjectsManagementPluginStart;
savedSearch: SavedSearchPublicPluginStart;
}
export type StartServices = CoreStart &
StartPlugins & {

View file

@ -0,0 +1,19 @@
/*
* 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 useObservable from 'react-use/lib/useObservable';
import { useMlKibana } from './kibana_context';
export const useMlLicenseInfo = () => {
const {
services: {
mlServices: { mlLicense },
},
} = useMlKibana();
return useObservable(mlLicense.licenseInfo$, mlLicense.getLicenseInfo());
};

View file

@ -0,0 +1,162 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { type FC, useCallback, useContext, useEffect, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { useLocation } from 'react-router-dom';
import { parse } from 'query-string';
import { i18n } from '@kbn/i18n';
import { DataView } from '@kbn/data-views-plugin/common';
import { SavedSearch } from '@kbn/saved-search-plugin/public';
import { EuiEmptyPrompt } from '@elastic/eui';
import { SavedSearchSavedObject } from '../../../../common/types/kibana';
import { DataViewAndSavedSearch } from '../../util/index_utils';
import { useMlKibana } from '../kibana';
import { createSearchItems } from '../../jobs/new_job/utils/new_job_utils';
export interface DataSourceContextValue {
combinedQuery: any;
currentDataView: DataView; // TODO this should be DataView or null
// @deprecated currentSavedSearch is of SavedSearchSavedObject type, change to selectedSavedSearch
deprecatedSavedSearchObj: SavedSearchSavedObject | null;
selectedSavedSearch: SavedSearch | null;
}
export const DataSourceContext = React.createContext<DataSourceContextValue>(
{} as DataSourceContextValue
);
/**
* Context provider that resolves current data view and the saved search from the URL state.
*
* @param children
* @constructor
*/
export const DataSourceContextProvider: FC = ({ children }) => {
const [value, setValue] = useState<DataSourceContextValue>();
const [error, setError] = useState<Error>();
const location = useLocation();
const {
services: {
data: { dataViews },
savedSearch: savedSearchService,
uiSettings,
},
} = useMlKibana();
const { index: dataViewId, savedSearchId } = parse(location.search, {
sort: false,
}) as { index: string; savedSearchId: string };
const getDataViewAndSavedSearchCallback = useCallback(
async (ssId: string) => {
const resp: DataViewAndSavedSearch = {
savedSearch: null,
dataView: null,
};
if (ssId === undefined) {
return resp;
}
const ss = await savedSearchService.get(ssId);
if (ss === null) {
return resp;
}
const dataViewIdTemp = ss.references?.find((r) => r.type === 'index-pattern')?.id;
resp.dataView = await dataViews.get(dataViewIdTemp!);
resp.savedSearch = ss;
return resp;
},
[savedSearchService, dataViews]
);
/**
* Resolve data view or saved search if exist in the URL.
*/
const resolveDataSource = useCallback(async () => {
if (dataViewId === '') {
throw new Error(
i18n.translate('xpack.ml.useResolver.errorIndexPatternIdEmptyString', {
defaultMessage: 'dataViewId must not be empty string.',
})
);
}
let dataViewAndSavedSearch: DataViewAndSavedSearch = {
savedSearch: null,
dataView: null,
};
let savedSearch = null;
if (savedSearchId !== undefined) {
savedSearch = await savedSearchService.get(savedSearchId);
dataViewAndSavedSearch = await getDataViewAndSavedSearchCallback(savedSearchId);
} else if (dataViewId !== undefined) {
dataViewAndSavedSearch.dataView = await dataViews.get(dataViewId);
}
const { savedSearch: deprecatedSavedSearchObj, dataView } = dataViewAndSavedSearch;
const { combinedQuery } = createSearchItems(
uiSettings,
dataView !== null ? dataView : undefined,
deprecatedSavedSearchObj
);
return {
combinedQuery,
currentDataView: dataView,
deprecatedSavedSearchObj,
selectedSavedSearch: savedSearch,
};
}, [
dataViewId,
savedSearchId,
uiSettings,
dataViews,
savedSearchService,
getDataViewAndSavedSearchCallback,
]);
useEffect(() => {
resolveDataSource()
.then((result) => {
setValue(result as DataSourceContextValue);
})
.catch((e) => {
setError(e);
});
}, [resolveDataSource]);
if (!value && !error) return null;
if (error) {
return (
<EuiEmptyPrompt
iconType="error"
color="danger"
title={
<h2>
<FormattedMessage
id="xpack.ml.dataSourceContext.errorTitle"
defaultMessage="Unable to fetch data view or saved search"
/>
</h2>
}
body={<p>{error.message}</p>}
/>
);
}
return <DataSourceContext.Provider value={value!}>{children}</DataSourceContext.Provider>;
};
export const useDataSource = () => {
return useContext(DataSourceContext);
};

View file

@ -5,6 +5,4 @@
* 2.0.
*/
export type { MlContextValue, SavedSearchQuery } from './ml_context';
export { MlContext } from './ml_context';
export { useMlContext } from './use_ml_context';
export { DataSourceContextProvider, useDataSource } from './data_source_context';

View file

@ -1,34 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public';
import type { SavedSearch } from '@kbn/saved-search-plugin/public';
import type { SavedSearchSavedObject } from '../../../../common/types/kibana';
import type { MlServicesContext } from '../../app';
export interface MlContextValue {
combinedQuery: any;
currentDataView: DataView; // TODO this should be DataView or null
// @deprecated currentSavedSearch is of SavedSearchSavedObject type, change to selectedSavedSearch
deprecatedSavedSearchObj: SavedSearchSavedObject | null;
selectedSavedSearch: SavedSearch | null;
dataViewsContract: DataViewsContract;
kibanaConfig: any; // IUiSettingsClient;
kibanaVersion: string;
}
export type SavedSearchQuery = object;
// In tests, these custom hooks must not be mocked,
// instead <UiChrome.Provider value="mocked-value">` needs
// to be used. This guarantees that we have both properly set up
// TypeScript support and runtime checks for these dependencies.
// Multiple custom hooks can be created to access subsets of
// the overall context value if necessary too,
// see useCurrentIndexPattern() for example.
export const MlContext = React.createContext<Partial<MlContextValue & MlServicesContext>>({});

View file

@ -1,20 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useContext } from 'react';
import { MlContext } from './ml_context';
export const useCurrentIndexPattern = () => {
const context = useContext(MlContext);
if (context.currentDataView === undefined) {
throw new Error('currentDataView is undefined');
}
return context.currentDataView;
};

View file

@ -1,29 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { useContext } from 'react';
import { MlContext, MlContextValue } from './ml_context';
export const useMlContext = () => {
const context = useContext(MlContext);
if (
context.combinedQuery === undefined ||
context.currentDataView === undefined ||
// @deprecated currentSavedSearch is of SavedSearchSavedObject type
// and should be migrated to selectedSavedSearch
context.deprecatedSavedSearchObj === undefined ||
context.selectedSavedSearch === undefined ||
context.dataViewsContract === undefined ||
context.kibanaConfig === undefined
) {
throw new Error('required attribute is undefined');
}
return context as MlContextValue;
};

View file

@ -17,15 +17,15 @@ import {
type TrackTotalHitsSearchResponse,
ANALYSIS_CONFIG_TYPE,
} from '@kbn/ml-data-frame-analytics-utils';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { ml } from '../../services/ml_api_service';
import { Dictionary } from '../../../../common/types/common';
import { SavedSearchQuery } from '../../contexts/ml';
export type IndexPattern = string;
export interface LoadExploreDataArg {
filterByIsTraining?: boolean;
searchQuery: SavedSearchQuery;
searchQuery: estypes.QueryDslQueryContainer;
}
export interface ClassificationMetricItem {
@ -53,7 +53,7 @@ export const getDefaultTrainingFilterQuery = (resultsField: string, isTraining:
export interface SearchQuery {
track_total_hits?: boolean;
query: SavedSearchQuery;
query: estypes.QueryDslQueryContainer;
sort?: any;
}
@ -226,7 +226,10 @@ interface QueryStringQuery {
query_string: Dictionary<any>;
}
export type ResultsSearchQuery = ResultsSearchBoolQuery | ResultsSearchTermQuery | SavedSearchQuery;
export type ResultsSearchQuery =
| ResultsSearchBoolQuery
| ResultsSearchTermQuery
| estypes.QueryDslQueryContainer;
export function getEvalQueryBody({
resultsField,
@ -369,7 +372,7 @@ export const loadEvalData = async ({
interface LoadDocsCountConfig {
ignoreDefaultQuery?: boolean;
isTraining?: boolean;
searchQuery: SavedSearchQuery;
searchQuery: estypes.QueryDslQueryContainer;
resultsField: string;
destIndex: string;
}

View file

@ -18,12 +18,10 @@ import {
import { ml } from '../../services/ml_api_service';
import { newJobCapsServiceAnalytics } from '../../services/new_job_capabilities/new_job_capabilities_service_analytics';
import { SavedSearchQuery } from '../../contexts/ml';
export const getIndexData = async (
jobConfig: DataFrameAnalyticsConfig | undefined,
dataGrid: UseDataGridReturnType,
searchQuery: SavedSearchQuery,
searchQuery: estypes.QueryDslQueryContainer,
options: { didCancel: boolean }
) => {
if (jobConfig !== undefined) {

View file

@ -19,10 +19,10 @@ import {
type TotalFeatureImportance,
} from '@kbn/ml-data-frame-analytics-utils';
import { useMlKibana } from '../../contexts/kibana';
import { getDataViewIdFromName } from '../../util/index_utils';
import { ml } from '../../services/ml_api_service';
import { newJobCapsServiceAnalytics } from '../../services/new_job_capabilities/new_job_capabilities_service_analytics';
import { useMlContext } from '../../contexts/ml';
import { isGetDataFrameAnalyticsStatsResponseOk } from '../pages/analytics_management/services/analytics_service/get_analytics';
import { useTrainedModelsApiService } from '../../services/ml_api_service/trained_models';
@ -30,7 +30,11 @@ import { getToastNotificationService } from '../../services/toast_notification_s
import { getDestinationIndex } from './get_destination_index';
export const useResultsViewConfig = (jobId: string) => {
const mlContext = useMlContext();
const {
services: {
data: { dataViews },
},
} = useMlKibana();
const trainedModelsApiService = useTrainedModelsApiService();
const [indexPattern, setIndexPattern] = useState<DataView | undefined>(undefined);
@ -99,13 +103,13 @@ export const useResultsViewConfig = (jobId: string) => {
let dataView: DataView | undefined;
try {
dataView = await mlContext.dataViewsContract.get(destDataViewId);
dataView = await dataViews.get(destDataViewId);
// Force refreshing the fields list here because a user directly coming
// from the job creation wizard might land on the page without the
// data view being fully initialized because it was created
// before the analytics job populated the destination index.
await mlContext.dataViewsContract.refreshFields(dataView);
await dataViews.refreshFields(dataView);
} catch (e) {
dataView = undefined;
}
@ -115,7 +119,7 @@ export const useResultsViewConfig = (jobId: string) => {
const sourceIndex = jobConfigUpdate.source.index[0];
const sourceDataViewId = (await getDataViewIdFromName(sourceIndex)) ?? sourceIndex;
try {
dataView = await mlContext.dataViewsContract.get(sourceDataViewId);
dataView = await dataViews.get(sourceDataViewId);
} catch (e) {
dataView = undefined;
}

View file

@ -15,11 +15,11 @@ import {
EuiSpacer,
} from '@elastic/eui';
import { ANALYSIS_CONFIG_TYPE } from '@kbn/ml-data-frame-analytics-utils';
import { useDataSource } from '../../../../../contexts/ml/data_source_context';
import {
State,
UNSET_CONFIG_ITEM,
} from '../../../analytics_management/hooks/use_create_analytics_form/state';
import { useMlContext } from '../../../../../contexts/ml';
import { ANALYTICS_STEPS } from '../../page';
const MAX_INCLUDES_LENGTH = 5;
@ -30,8 +30,7 @@ interface Props {
}
export const ConfigurationStepDetails: FC<Props> = ({ setCurrentStep, state }) => {
const mlContext = useMlContext();
const { currentDataView } = mlContext;
const { currentDataView } = useDataSource();
const { form, isJobCreated } = state;
const { dependentVariable, includes, jobConfigQueryString, jobType, trainingPercent } = form;

View file

@ -41,7 +41,7 @@ import {
} from '../../../../../components/field_stats_flyout';
import { FieldForStats } from '../../../../../components/field_stats_flyout/field_stats_info_button';
import { newJobCapsServiceAnalytics } from '../../../../../services/new_job_capabilities/new_job_capabilities_service_analytics';
import { useMlContext } from '../../../../../contexts/ml';
import { useDataSource } from '../../../../../contexts/ml';
import { getScatterplotMatrixLegendType } from '../../../../common/get_scatterplot_matrix_legend_type';
import { AnalyticsJobType } from '../../../analytics_management/hooks/use_create_analytics_form/state';
@ -120,8 +120,7 @@ export const ConfigurationStepForm: FC<ConfigurationStepProps> = ({
state,
setCurrentStep,
}) => {
const mlContext = useMlContext();
const { currentDataView, selectedSavedSearch } = mlContext;
const { currentDataView, selectedSavedSearch } = useDataSource();
const { savedSearchQuery, savedSearchQueryStr } = useSavedSearch();
const [fieldOptionsFetchFail, setFieldOptionsFetchFail] = useState<boolean>(false);

View file

@ -15,7 +15,8 @@ import {
Query,
toElasticsearchQuery,
} from '@kbn/es-query';
import { useMlContext } from '../../../../../contexts/ml';
import { useMlKibana } from '../../../../../contexts/kibana';
import { useDataSource } from '../../../../../contexts/ml';
import { SEARCH_QUERY_LANGUAGE } from '../../../../../../../common/constants/search';
// `undefined` is used for a non-initialized state
@ -33,8 +34,11 @@ export function useSavedSearch() {
const [savedSearchQuery, setSavedSearchQuery] = useState<SavedSearchQuery>(undefined);
const [savedSearchQueryStr, setSavedSearchQueryStr] = useState<SavedSearchQueryStr>(undefined);
const mlContext = useMlContext();
const { currentDataView, kibanaConfig, selectedSavedSearch } = mlContext;
const {
services: { uiSettings },
} = useMlKibana();
const { currentDataView, selectedSavedSearch } = useDataSource();
const getQueryData = () => {
let qry: any = {};
@ -75,7 +79,7 @@ export function useSavedSearch() {
qry.bool.must_not = [...qry.bool.must_not, ...filterQuery.must_not];
} else {
qry = buildEsQuery(currentDataView, [query], filter);
decorateQuery(qry, kibanaConfig.get('query:queryString:options'));
decorateQuery(qry, uiSettings.get('query:queryString:options'));
}
setSavedSearchQuery(qry);

View file

@ -17,7 +17,7 @@ import { JOB_ID_MAX_LENGTH } from '../../../../../../../common/constants/validat
import { ContinueButton } from '../continue_button';
import { ANALYTICS_STEPS } from '../../page';
import { ml } from '../../../../../services/ml_api_service';
import { useMlContext } from '../../../../../contexts/ml';
import { useDataSource } from '../../../../../contexts/ml';
import { DetailsStepTimeField } from './details_step_time_field';
const DEFAULT_RESULTS_FIELD = 'ml';
@ -39,8 +39,7 @@ export const DetailsStepForm: FC<CreateAnalyticsStepProps> = ({
services: { docLinks, notifications },
} = useMlKibana();
const mlContext = useMlContext();
const { currentDataView } = mlContext;
const { currentDataView } = useDataSource();
const createIndexLink = docLinks.links.apis.createIndex;
const { setFormState } = actions;

View file

@ -23,7 +23,7 @@ import { XJsonMode } from '@kbn/ace';
import { XJson } from '@kbn/es-ui-shared-plugin/public';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { getCombinedRuntimeMappings, isRuntimeMappings } from '@kbn/ml-runtime-field-utils';
import { useMlContext } from '../../../../../contexts/ml';
import { useDataSource } from '../../../../../contexts/ml';
import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/use_create_analytics_form';
import { RuntimeMappingsEditor } from './runtime_mappings_editor';
import { SwitchModal } from './switch_modal';
@ -93,8 +93,7 @@ export const RuntimeMappings: FC<Props> = ({ actions, state }) => {
xJson: advancedRuntimeMappingsConfig,
} = useXJsonMode(runtimeMappings || '');
const mlContext = useMlContext();
const { currentDataView } = mlContext;
const { currentDataView } = useDataSource();
const applyChanges = () => {
const removeRuntimeMappings = advancedRuntimeMappingsConfig === '';

View file

@ -19,7 +19,7 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { DataFrameAnalyticsId } from '@kbn/ml-data-frame-analytics-utils';
import { useMlContext } from '../../../contexts/ml';
import { useDataSource } from '../../../contexts/ml/data_source_context';
import { ml } from '../../../services/ml_api_service';
import { useCreateAnalyticsForm } from '../analytics_management/hooks/use_create_analytics_form';
import { CreateAnalyticsAdvancedEditor } from './components/create_analytics_advanced_editor';
@ -54,8 +54,7 @@ export const Page: FC<Props> = ({ jobId }) => {
false,
]);
const mlContext = useMlContext();
const { currentDataView } = mlContext;
const { currentDataView } = useDataSource();
const createAnalyticsForm = useCreateAnalyticsForm();
const { state } = createAnalyticsForm;

View file

@ -40,11 +40,11 @@ import {
type DataFrameAnalyticsConfig,
} from '@kbn/ml-data-frame-analytics-utils';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { SEARCH_QUERY_LANGUAGE } from '../../../../../../../common/constants/search';
import { getToastNotifications } from '../../../../../util/dependency_cache';
import { useColorRange, ColorRangeLegend } from '../../../../../components/color_range_legend';
import { SavedSearchQuery } from '../../../../../contexts/ml';
import { useMlKibana } from '../../../../../contexts/kibana';
import { defaultSearchQuery, renderCellPopoverFactory, SEARCH_SIZE } from '../../../../common';
@ -121,7 +121,7 @@ interface ExpandableSectionResultsProps {
jobConfig?: DataFrameAnalyticsConfig;
needsDestIndexPattern: boolean;
resultsField?: string;
searchQuery: SavedSearchQuery;
searchQuery: estypes.QueryDslQueryContainer;
}
export const ExpandableSectionResults: FC<ExpandableSectionResultsProps> = ({

View file

@ -24,14 +24,13 @@ import {
SearchQueryLanguage,
} from '../../../../../../../common/constants/search';
import { removeFilterFromQueryString } from '../../../../../explorer/explorer_utils';
import { SavedSearchQuery } from '../../../../../contexts/ml';
import { useMlKibana } from '../../../../../contexts/kibana';
export interface ExplorationQueryBarProps {
indexPattern: DataView;
setSearchQuery: (update: {
queryString: string;
query?: SavedSearchQuery;
query?: estypes.QueryDslQueryContainer;
language: SearchQueryLanguage;
}) => void;
includeQueryString?: boolean;

View file

@ -34,9 +34,9 @@ import {
type UseIndexDataReturnType,
} from '@kbn/ml-data-grid';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { MlApiServices } from '../../../../../services/ml_api_service';
import { DataLoader } from '../../../../../datavisualizer/index_based/data_loader';
import { SavedSearchQuery } from '../../../../../contexts/ml';
import { getIndexData, getIndexFields } from '../../../../common';
import { useTrainedModelsApiService } from '../../../../../services/ml_api_service/trained_models';
@ -45,7 +45,7 @@ import { useExplorationDataGrid } from './use_exploration_data_grid';
export const useExplorationResults = (
indexPattern: DataView | undefined,
jobConfig: DataFrameAnalyticsConfig | undefined,
searchQuery: SavedSearchQuery,
searchQuery: estypes.QueryDslQueryContainer,
toastNotifications: CoreSetup['notifications']['toasts'],
mlApiServices: MlApiServices
): UseIndexDataReturnType => {

View file

@ -5,12 +5,11 @@
* 2.0.
*/
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { shallow } from 'enzyme';
import React from 'react';
import { MlContext } from '../../../../../contexts/ml';
import { kibanaContextValueMock } from '../../../../../contexts/ml/__mocks__/kibana_context_value';
import { OutlierExploration } from './outlier_exploration';
import { kibanaContextMock } from '../../../../../contexts/kibana/__mocks__/kibana_context';
// workaround to make React.memo() work with enzyme
jest.mock('react', () => {
@ -21,9 +20,9 @@ jest.mock('react', () => {
describe('Data Frame Analytics: <Exploration />', () => {
test('Minimal initialization', () => {
const wrapper = shallow(
<MlContext.Provider value={kibanaContextValueMock}>
<KibanaContextProvider services={kibanaContextMock.services}>
<OutlierExploration jobId="the-job-id" />
</MlContext.Provider>
</KibanaContextProvider>
);
// Without the jobConfig being loaded, the component will just return empty.
expect(wrapper.text()).toMatch('');

View file

@ -12,13 +12,13 @@ import { EuiCallOut, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { isOutlierAnalysis, FEATURE_INFLUENCE } from '@kbn/ml-data-frame-analytics-utils';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import {
useColorRange,
COLOR_RANGE,
COLOR_RANGE_SCALE,
} from '../../../../../components/color_range_legend';
import { useScatterplotFieldOptions } from '../../../../../components/scatterplot_matrix';
import { SavedSearchQuery } from '../../../../../contexts/ml';
import { defaultSearchQuery, useResultsViewConfig, getDestinationIndex } from '../../../../common';
@ -45,7 +45,8 @@ export const OutlierExploration: FC<ExplorationProps> = React.memo(({ jobId }) =
const { indexPattern, indexPatternErrorMessage, jobConfig, needsDestIndexPattern } =
useResultsViewConfig(jobId);
const [pageUrlState, setPageUrlState] = useExplorationUrlState();
const [searchQuery, setSearchQuery] = useState<SavedSearchQuery>(defaultSearchQuery);
const [searchQuery, setSearchQuery] =
useState<estypes.QueryDslQueryContainer>(defaultSearchQuery);
const outlierData = useOutlierData(indexPattern, jobConfig, searchQuery);
const searchQueryUpdateHandler: ExplorationQueryBarProps['setSearchQuery'] = useCallback(

View file

@ -26,13 +26,13 @@ import {
type UseIndexDataReturnType,
} from '@kbn/ml-data-grid';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { DataLoader } from '../../../../../datavisualizer/index_based/data_loader';
import {
useColorRange,
COLOR_RANGE,
COLOR_RANGE_SCALE,
} from '../../../../../components/color_range_legend';
import { SavedSearchQuery } from '../../../../../contexts/ml';
import { getToastNotifications } from '../../../../../util/dependency_cache';
import { getIndexData, getIndexFields } from '../../../../common';
@ -43,7 +43,7 @@ import { useExplorationDataGrid } from '../exploration_results_table/use_explora
export const useOutlierData = (
indexPattern: DataView | undefined,
jobConfig: DataFrameAnalyticsConfig | undefined,
searchQuery: SavedSearchQuery
searchQuery: estypes.QueryDslQueryContainer
): UseIndexDataReturnType => {
const needsDestIndexFields =
indexPattern !== undefined && indexPattern.title === jobConfig?.source.index[0];

View file

@ -26,8 +26,8 @@ import {
ANALYSIS_CONFIG_TYPE,
} from '@kbn/ml-data-frame-analytics-utils';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { useMlKibana } from '../../../../../contexts/kibana';
import { SavedSearchQuery } from '../../../../../contexts/ml';
import { getValuesFromResponse, loadEvalData, loadDocsCount, Eval } from '../../../../common';
import {
@ -44,7 +44,7 @@ import { EvaluateStat } from './evaluate_stat';
interface Props {
jobConfig: DataFrameAnalyticsConfig;
jobStatus?: DataFrameTaskStateType;
searchQuery: SavedSearchQuery;
searchQuery: estypes.QueryDslQueryContainer;
}
const EMPTY_STATS = {

View file

@ -5,13 +5,13 @@
* 2.0.
*/
import React, { FC, useCallback, useState, useEffect } from 'react';
import React, { FC, useCallback, useEffect, useState } from 'react';
import { i18n } from '@kbn/i18n';
import {
EuiInMemoryTable,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiInMemoryTable,
EuiSearchBar,
EuiSearchBarProps,
EuiSpacer,
@ -22,7 +22,7 @@ import {
type DataFrameAnalyticsId,
} from '@kbn/ml-data-frame-analytics-utils';
import { useRefreshAnalyticsList } from '../../../../common';
import { checkPermission } from '../../../../../capabilities/check_capabilities';
import { usePermissionCheck } from '../../../../../capabilities/check_capabilities';
import { useNavigateToPath } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
@ -32,7 +32,7 @@ import {
ItemIdToExpandedRowMap,
} from './common';
import { getAnalyticsFactory } from '../../services/analytics_service';
import { getTaskStateBadge, getJobTypeBadge, useColumns } from './use_columns';
import { getJobTypeBadge, getTaskStateBadge, useColumns } from './use_columns';
import { ExpandedRow } from './expanded_row';
import { AnalyticStatsBarStats, StatsBar } from '../../../../../components/stats_bar';
import { CreateAnalyticsButton } from '../create_analytics_button';
@ -121,9 +121,12 @@ export const DataFrameAnalyticsList: FC<Props> = ({
const refreshObs = useRefresh();
const disabled =
!checkPermission('canCreateDataFrameAnalytics') ||
!checkPermission('canStartStopDataFrameAnalytics');
const [canCreateDataFrameAnalytics, canStartStopDataFrameAnalytics] = usePermissionCheck([
'canCreateDataFrameAnalytics',
'canStartStopDataFrameAnalytics',
]);
const disabled = !canCreateDataFrameAnalytics || !canStartStopDataFrameAnalytics;
const getAnalytics = getAnalyticsFactory(
setAnalytics,

View file

@ -6,11 +6,8 @@
*/
import React from 'react';
import { EuiTableActionsColumnType } from '@elastic/eui';
import { checkPermission } from '../../../../../capabilities/check_capabilities';
import { usePermissionCheck } from '../../../../../capabilities/check_capabilities';
import { DeleteSpaceAwareItemCheckModal } from '../../../../../components/delete_space_aware_item_check_modal';
import { useCloneAction } from '../action_clone';
import { useDeleteAction, DeleteActionModal } from '../action_delete';
@ -19,7 +16,6 @@ import { useStartAction, StartActionModal } from '../action_start';
import { useStopAction, StopActionModal } from '../action_stop';
import { useViewAction } from '../action_view';
import { useMapAction } from '../action_map';
import { DataFrameAnalyticsListRow } from './common';
import { useRefreshAnalyticsList } from '../../../../common/analytics';
@ -27,9 +23,12 @@ export const useActions = (): {
actions: EuiTableActionsColumnType<DataFrameAnalyticsListRow>['actions'];
modals: JSX.Element | null;
} => {
const canCreateDataFrameAnalytics: boolean = checkPermission('canCreateDataFrameAnalytics');
const canDeleteDataFrameAnalytics: boolean = checkPermission('canDeleteDataFrameAnalytics');
const canStartStopDataFrameAnalytics: boolean = checkPermission('canStartStopDataFrameAnalytics');
const [canCreateDataFrameAnalytics, canDeleteDataFrameAnalytics, canStartStopDataFrameAnalytics] =
usePermissionCheck([
'canCreateDataFrameAnalytics',
'canDeleteDataFrameAnalytics',
'canStartStopDataFrameAnalytics',
]);
const viewAction = useViewAction();
const mapAction = useMapAction();

View file

@ -20,10 +20,9 @@ import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import dfaImage from './data_frame_analytics_kibana.png';
import { mlNodesAvailable } from '../../../../../ml_nodes_check';
import { useMlKibana } from '../../../../../contexts/kibana';
import { useNavigateToPath } from '../../../../../contexts/kibana';
import { useMlKibana, useNavigateToPath } from '../../../../../contexts/kibana';
import { ML_PAGES } from '../../../../../../../common/constants/locator';
import { checkPermission } from '../../../../../capabilities/check_capabilities';
import { usePermissionCheck } from '../../../../../capabilities/check_capabilities';
export const AnalyticsEmptyPrompt: FC = () => {
const {
@ -32,10 +31,14 @@ export const AnalyticsEmptyPrompt: FC = () => {
http: { basePath },
},
} = useMlKibana();
const [canCreateDataFrameAnalytics, canStartStopDataFrameAnalytics] = usePermissionCheck([
'canCreateDataFrameAnalytics',
'canStartStopDataFrameAnalytics',
]);
const disabled =
!mlNodesAvailable() ||
!checkPermission('canCreateDataFrameAnalytics') ||
!checkPermission('canStartStopDataFrameAnalytics');
!mlNodesAvailable() || !canCreateDataFrameAnalytics || !canStartStopDataFrameAnalytics;
const transformsLink = `${basePath.get()}/app/management/data/transform`;
const navigateToPath = useNavigateToPath();

View file

@ -7,17 +7,17 @@
import React from 'react';
import { mountHook } from '@kbn/test-jest-helpers';
import { MlContext } from '../../../../../contexts/ml';
import { kibanaContextValueMock } from '../../../../../contexts/ml/__mocks__/kibana_context_value';
import { useCreateAnalyticsForm } from './use_create_analytics_form';
import { kibanaContextMock } from '../../../../../contexts/kibana/__mocks__/kibana_context';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
const getMountedHook = () =>
mountHook(
() => useCreateAnalyticsForm(),
({ children }) => (
<MlContext.Provider value={kibanaContextValueMock}>{children}</MlContext.Provider>
<KibanaContextProvider services={kibanaContextMock.services}>
{children}
</KibanaContextProvider>
)
);

View file

@ -13,9 +13,9 @@ import { DuplicateDataViewError } from '@kbn/data-plugin/public';
import { extractErrorMessage } from '@kbn/ml-error-utils';
import type { DataFrameAnalyticsConfig } from '@kbn/ml-data-frame-analytics-utils';
import { useMlKibana } from '../../../../../contexts/kibana';
import { DeepReadonly } from '../../../../../../../common/types/common';
import { ml } from '../../../../../services/ml_api_service';
import { useMlContext } from '../../../../../contexts/ml';
import { useRefreshAnalyticsList } from '../../../../common';
import { extractCloningConfig, isAdvancedConfig } from '../../components/action_clone';
@ -93,7 +93,11 @@ function delay(ms = 1000) {
}
export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
const mlContext = useMlContext();
const {
services: {
data: { dataViews },
},
} = useMlKibana();
const [state, dispatch] = useReducer(reducer, getInitialState());
const { refresh } = useRefreshAnalyticsList();
@ -175,7 +179,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
// index exists - create data view
if (exists?.indexExists === true) {
try {
await mlContext.dataViewsContract.createAndSave(
await dataViews.createAndSave(
{
title: dataViewName,
...(form.timeFieldName ? { timeFieldName: form.timeFieldName } : {}),
@ -264,7 +268,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
try {
// Set the existing data view names.
const indexPatternsMap: SourceIndexMap = {};
const savedObjects = (await mlContext.dataViewsContract.getCache()) || [];
const savedObjects = (await dataViews.getCache()) || [];
savedObjects.forEach((obj) => {
const title = obj?.attributes?.title;
if (title !== undefined) {
@ -286,7 +290,7 @@ export const useCreateAnalyticsForm = (): CreateAnalyticsFormProps => {
};
const initiateWizard = async () => {
await mlContext.dataViewsContract.clearCache();
await dataViews.clearCache();
await prepareFormValidation();
};

View file

@ -12,7 +12,7 @@ import { DEFAULT_SAMPLER_SHARD_SIZE } from '@kbn/ml-agg-utils';
import { OMIT_FIELDS } from '@kbn/ml-anomaly-utils';
import { type RuntimeMappings } from '@kbn/ml-runtime-field-utils';
import { SavedSearchQuery } from '../../../contexts/ml';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IndexPatternTitle } from '../../../../../common/types/kibana';
import { ml } from '../../../services/ml_api_service';
@ -35,7 +35,7 @@ export class DataLoader {
async loadFieldHistograms(
fields: FieldHistogramRequestConfig[],
query: string | SavedSearchQuery,
query: string | estypes.QueryDslQueryContainer,
samplerShardSize = DEFAULT_SAMPLER_SHARD_SIZE,
editorRuntimeMappings?: RuntimeMappings
): Promise<any[]> {

View file

@ -76,7 +76,6 @@ import type { ExplorerState } from './reducers';
import type { TimeBuckets } from '../util/time_buckets';
import { useToastNotificationService } from '../services/toast_notification_service';
import { useMlKibana, useMlLocator } from '../contexts/kibana';
import { useMlContext } from '../contexts/ml';
import { useAnomalyExplorerContext } from './anomaly_explorer_context';
import { ML_ANOMALY_EXPLORER_PANELS } from '../../../common/types/storage';
@ -355,12 +354,13 @@ export const Explorer: FC<ExplorerUIProps> = ({
}, []);
const {
services: { charts: chartsService },
services: {
charts: chartsService,
data: { dataViews: dataViewsService },
},
} = useMlKibana();
const { euiTheme } = useEuiTheme();
const mlLocator = useMlLocator();
const context = useMlContext();
const dataViewsService = context.dataViewsContract;
const {
annotations,

View file

@ -19,11 +19,12 @@ import {
import adImage from './anomaly_detection_kibana.png';
import { ML_PAGES } from '../../../../../../common/constants/locator';
import { useMlKibana, useMlLocator, useNavigateToPath } from '../../../../contexts/kibana';
import { checkPermission } from '../../../../capabilities/check_capabilities';
import { usePermissionCheck } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check';
export const AnomalyDetectionEmptyState: FC = () => {
const disableCreateAnomalyDetectionJob = !checkPermission('canCreateJob') || !mlNodesAvailable();
const canCreateJob = usePermissionCheck('canCreateJob');
const disableCreateAnomalyDetectionJob = !canCreateJob || !mlNodesAvailable();
const {
services: { docLinks },

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { checkPermission } from '../../../../capabilities/check_capabilities';
import { usePermissionCheck } from '../../../../capabilities/check_capabilities';
import { mlNodesAvailable } from '../../../../ml_nodes_check/check_ml_nodes';
import React from 'react';
@ -16,7 +16,8 @@ import { useCreateAndNavigateToMlLink } from '../../../../contexts/kibana/use_cr
import { ML_PAGES } from '../../../../../../common/constants/locator';
export function NewJobButton() {
const buttonEnabled = checkPermission('canCreateJob') && mlNodesAvailable();
const canCreateJob = usePermissionCheck('canCreateJob');
const buttonEnabled = canCreateJob && mlNodesAvailable();
const newJob = useCreateAndNavigateToMlLink(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX);
return (

View file

@ -9,7 +9,7 @@ import React, { FC, Fragment, useEffect, useState } from 'react';
import moment, { Moment } from 'moment';
import { i18n } from '@kbn/i18n';
import { EuiDatePicker, EuiDatePickerRange } from '@elastic/eui';
import { useMlContext } from '../../../../contexts/ml';
import { useMlKibana } from '../../../../contexts/kibana';
const WIDTH = '512px';
@ -24,8 +24,11 @@ interface Props {
}
export const TimeRangePicker: FC<Props> = ({ setTimeRange, timeRange }) => {
const mlContext = useMlContext();
const dateFormat: string = mlContext.kibanaConfig.get('dateFormat');
const {
services: { uiSettings },
} = useMlKibana();
// TODO should use fieldFormats instead
const dateFormat: string = uiSettings.get('dateFormat');
const [startMoment, setStartMoment] = useState<Moment | undefined>(moment(timeRange.start));
const [endMoment, setEndMoment] = useState<Moment | undefined>(moment(timeRange.end));

View file

@ -21,6 +21,9 @@ import {
} from '../../../util/dependency_cache';
import { getDefaultQuery } from '../utils/new_job_utils';
/**
* TODO update route resolver to use Kibana context instead of the deps cache
*/
export async function resolver(
lensSavedObjectId: string | undefined,
lensSavedObjectRisonString: string | undefined,

View file

@ -17,7 +17,7 @@ import {
isRareJobCreator,
} from '../../../../../common/job_creator';
import { ml } from '../../../../../../../services/ml_api_service';
import { useMlContext } from '../../../../../../../contexts/ml';
import { useDataSource } from '../../../../../../../contexts/ml';
import { getToastNotificationService } from '../../../../../../../services/toast_notification_service';
export enum ESTIMATE_STATUS {
@ -27,7 +27,7 @@ export enum ESTIMATE_STATUS {
export function useEstimateBucketSpan() {
const { jobCreator, jobCreatorUpdate } = useContext(JobCreatorContext);
const mlContext = useMlContext();
const mlContext = useDataSource();
const [status, setStatus] = useState(ESTIMATE_STATUS.NOT_RUNNING);

View file

@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n';
import moment from 'moment';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiFlexGroup, EuiFlexItem, EuiDescriptionList } from '@elastic/eui';
import { useMlKibana } from '../../../../../../../contexts/kibana';
import { JobCreatorContext } from '../../../job_creator_context';
import {
isMultiMetricJobCreator,
@ -18,12 +19,14 @@ import {
} from '../../../../../common/job_creator';
import { getNewJobDefaults } from '../../../../../../../services/ml_server_info';
import { ListItems, falseLabel, trueLabel, defaultLabel, Italic } from '../common';
import { useMlContext } from '../../../../../../../contexts/ml';
export const JobDetails: FC = () => {
const { jobCreator } = useContext(JobCreatorContext);
const mlContext = useMlContext();
const dateFormat: string = mlContext.kibanaConfig.get('dateFormat');
const {
services: { uiSettings },
} = useMlKibana();
// TODO should use fieldFormats instead
const dateFormat: string = uiSettings.get('dateFormat');
const { anomaly_detectors: anomalyDetectors } = getNewJobDefaults();
const isAdvanced = isAdvancedJobCreator(jobCreator);

View file

@ -17,7 +17,7 @@ import { ML_INTERNAL_BASE_PATH } from '../../../../../../../common/constants/app
import { WizardNav } from '../wizard_nav';
import { StepProps, WIZARD_STEPS } from '../step_types';
import { JobCreatorContext } from '../job_creator_context';
import { useMlContext } from '../../../../../contexts/ml';
import { useDataSource } from '../../../../../contexts/ml';
import { EventRateChart } from '../charts/event_rate_chart';
import { LineChartPoint } from '../../../common/chart_loader';
import { JOB_TYPE } from '../../../../../../../common/constants/new_job';
@ -32,7 +32,7 @@ import {
export const TimeRangeStep: FC<StepProps> = ({ setCurrentStep, isCurrentStep }) => {
const timefilter = useTimefilter();
const { services } = useMlKibana();
const mlContext = useMlContext();
const mlContext = useDataSource();
const { jobCreator, jobCreatorUpdate, jobCreatorUpdated, chartLoader, chartInterval } =
useContext(JobCreatorContext);

View file

@ -20,7 +20,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { ES_FIELD_TYPES } from '@kbn/field-types';
import { useMlKibana, useNavigateToPath } from '../../../../contexts/kibana';
import { useMlContext } from '../../../../contexts/ml';
import { useDataSource } from '../../../../contexts/ml';
import { DataRecognizer } from '../../../../components/data_recognizer';
import { addItemToRecentlyAccessed } from '../../../../util/recently_accessed';
import { timeBasedIndexCheck } from '../../../../util/index_utils';
@ -37,7 +37,7 @@ export const Page: FC = () => {
services: { share },
} = useMlKibana();
const mlContext = useMlContext();
const mlContext = useDataSource();
const navigateToPath = useNavigateToPath();
const onSelectDifferentIndex = useCreateAndNavigateToMlLink(
ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_INDEX

View file

@ -34,7 +34,7 @@ import { ChartLoader } from '../../common/chart_loader';
import { MapLoader } from '../../common/map_loader';
import { ResultsLoader } from '../../common/results_loader';
import { JobValidator } from '../../common/job_validator';
import { useMlContext } from '../../../../contexts/ml';
import { useDataSource } from '../../../../contexts/ml';
import { useMlKibana } from '../../../../contexts/kibana';
import { ExistingJobsAndGroups, mlJobService } from '../../../../services/job_service';
import { newJobCapsService } from '../../../../services/new_job_capabilities/new_job_capabilities_service';
@ -53,7 +53,7 @@ export interface PageProps {
export const Page: FC<PageProps> = ({ existingJobsAndGroups, jobType }) => {
const timefilter = useTimefilter();
const mlContext = useMlContext();
const mlContext = useDataSource();
const {
services: { maps: mapsPlugin },
} = useMlKibana();

View file

@ -24,7 +24,7 @@ import { JobDetailsStep } from '../components/job_details_step';
import { ValidationStep } from '../components/validation_step';
import { SummaryStep } from '../components/summary_step';
import { DatafeedStep } from '../components/datafeed_step';
import { useMlContext } from '../../../../contexts/ml';
import { useDataSource } from '../../../../contexts/ml';
interface Props {
currentStep: WIZARD_STEPS;
@ -32,7 +32,7 @@ interface Props {
}
export const WizardSteps: FC<Props> = ({ currentStep, setCurrentStep }) => {
const mlContext = useMlContext();
const mlContext = useDataSource();
const { services } = useMlKibana();
const fieldStatsServices: FieldStatsServices = useMemo(() => {
const { uiSettings, data, fieldFormats, charts } = services;

View file

@ -20,8 +20,8 @@ import {
EuiTextAlign,
} from '@elastic/eui';
import { getTimeFilterRange, useTimefilter } from '@kbn/ml-date-picker';
import { useDataSource } from '../../../../contexts/ml/data_source_context';
import { ModuleJobUI, SAVE_STATE } from '../page';
import { useMlContext } from '../../../../contexts/ml';
import {
composeValidators,
maxLengthValidator,
@ -53,7 +53,7 @@ export const JobSettingsForm: FC<JobSettingsFormProps> = ({
}) => {
const timefilter = useTimefilter();
const { from, to } = getTimeFilterRange(timefilter);
const { currentDataView: dataView } = useMlContext();
const { currentDataView: dataView } = useDataSource();
const jobPrefixValidator = useMemo(
() =>

View file

@ -23,8 +23,8 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { addExcludeFrozenToQuery } from '@kbn/ml-query-utils';
import { TIME_FORMAT } from '@kbn/ml-date-utils';
import { type RuntimeMappings } from '@kbn/ml-runtime-field-utils';
import { useDataSource } from '../../../contexts/ml';
import { useMlKibana, useMlLocator } from '../../../contexts/kibana';
import { useMlContext } from '../../../contexts/ml';
import {
DatafeedResponse,
JobOverride,
@ -92,7 +92,7 @@ export const Page: FC<PageProps> = ({ moduleId, existingGroupIds }) => {
const [jobsAwaitingNodeCount, setJobsAwaitingNodeCount] = useState(0);
// #endregion
const { selectedSavedSearch, currentDataView: dataView, combinedQuery } = useMlContext();
const { selectedSavedSearch, currentDataView: dataView, combinedQuery } = useDataSource();
const pageTitle = selectedSavedSearch
? i18n.translate('xpack.ml.newJob.recognize.savedSearchPageTitle', {
defaultMessage: 'saved search {savedSearchTitle}',

View file

@ -5,60 +5,8 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { getToastNotifications, getSavedObjectsClient } from '../../../util/dependency_cache';
import { ml } from '../../../services/ml_api_service';
import { getSavedObjectsClient } from '../../../util/dependency_cache';
import { KibanaObjects } from './page';
import { NavigateToPath } from '../../../contexts/kibana';
import { CreateLinkWithUserDefaults } from '../../../components/custom_hooks/use_create_ad_links';
/**
* Checks whether the jobs in a data recognizer module have been created.
* Redirects to the Anomaly Explorer to view the jobs if they have been created,
* or the recognizer job wizard for the module if not.
*/
export function checkViewOrCreateJobs(
moduleId: string,
dataViewId: string,
createLinkWithUserDefaults: CreateLinkWithUserDefaults,
navigateToPath: NavigateToPath
): Promise<any> {
return new Promise((resolve, reject) => {
// Load the module, and check if the job(s) in the module have been created.
// If so, load the jobs in the Anomaly Explorer.
// Otherwise open the data recognizer wizard for the module.
// Always want to call reject() so as not to load original page.
ml.dataRecognizerModuleJobsExist({ moduleId })
.then(async (resp: any) => {
if (resp.jobsExist === true) {
// also honor user's time filter setting in Advanced Settings
const url = createLinkWithUserDefaults('explorer', resp.jobs);
await navigateToPath(url);
reject();
} else {
await navigateToPath(`/jobs/new_job/recognize?id=${moduleId}&index=${dataViewId}`);
reject();
}
})
.catch(async (err: Error) => {
// eslint-disable-next-line no-console
console.error(`Error checking whether jobs in module ${moduleId} exists`, err);
const toastNotifications = getToastNotifications();
toastNotifications.addWarning({
title: i18n.translate('xpack.ml.newJob.recognize.moduleCheckJobsExistWarningTitle', {
defaultMessage: 'Error checking module {moduleId}',
values: { moduleId },
}),
text: i18n.translate('xpack.ml.newJob.recognize.moduleCheckJobsExistWarningDescription', {
defaultMessage:
'An error occurred trying to check whether the jobs in the module have been created.',
}),
});
await navigateToPath(`/jobs`);
reject();
});
});
}
/**
* Gets kibana objects with an existence check.

View file

@ -5,69 +5,22 @@
* 2.0.
*/
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import type { CoreStart } from '@kbn/core/public';
import { MlLicense } from '../../../common/license';
import { MlClientLicense } from './ml_client_license';
let mlLicense: MlClientLicense | null = null;
let mlLicense: MlLicense | null = null;
/**
* Create a new mlLicense and cache it for later checks
*
* @export
* @param {LicensingPluginStart} licensingStart
* @param application
* @param postInitFunctions
* @returns {MlClientLicense}
* Cache ml license to support legacy usage.
*/
export function setLicenseCache(
licensingStart: LicensingPluginStart,
application: CoreStart['application'],
callback?: (lic: MlLicense) => void
) {
mlLicense = new MlClientLicense(application);
mlLicense.setup(licensingStart.license$, callback);
export function setLicenseCache(mlLicenseInstance: MlLicense) {
mlLicense = mlLicenseInstance;
return mlLicense;
}
/**
* Used as routing resolver to stop the loading of a page if the current license
* is a trial, platinum or enterprise.
*
* @export
* @returns {Promise<void>} Promise which resolves if the license is trial, platinum or enterprise and rejects if it isn't.
*/
export async function checkFullLicense() {
if (mlLicense === null) {
// this should never happen
console.error('ML Licensing not initialized'); // eslint-disable-line no-console
return Promise.reject();
}
return mlLicense.fullLicenseResolver();
}
/**
* Used as routing resolver to stop the loading of a page if the current license
* is at least basic.
*
* @export
* @returns {Promise<void>} Promise resolves if the license is at least basic and rejects if it isn't.
*/
export async function checkBasicLicense() {
if (mlLicense === null) {
// this should never happen
console.error('ML Licensing not initialized'); // eslint-disable-line no-console
return Promise.reject();
}
return mlLicense.basicLicenseResolver();
}
/**
* Check to see if the current license has expired
*
* @deprecated
* @export
* @returns {boolean}
*/
@ -78,6 +31,7 @@ export function hasLicenseExpired() {
/**
* Check to see if the current license is trial, platinum or enterprise.
*
* @deprecated
* @export
* @returns {boolean}
*/
@ -90,6 +44,7 @@ export function isFullLicense() {
* Note, this is not accurate for cloud trials.
* For cloud trials use isCloudTrial returned from the mlInfo endpoint
*
* @deprecated
* @export
* @returns {boolean}
*/

View file

@ -1,34 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiCallOut } from '@elastic/eui';
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
import { getOverlays, getTheme } from '../util/dependency_cache';
let expiredLicenseBannerId: string;
export function showExpiredLicenseWarning() {
if (expiredLicenseBannerId === undefined) {
const message = i18n.translate('xpack.ml.checkLicense.licenseHasExpiredMessage', {
defaultMessage: 'Your Machine Learning license has expired.',
});
// Only show the banner once with no way to dismiss it
const overlays = getOverlays();
const theme = getTheme();
expiredLicenseBannerId = overlays.banners.add(
toMountPoint(
wrapWithTheme(
<EuiCallOut iconType="iInCircle" color="warning" title={message} />,
theme.theme$
)
)
);
}
}

View file

@ -5,10 +5,4 @@
* 2.0.
*/
export {
checkBasicLicense,
checkFullLicense,
hasLicenseExpired,
isFullLicense,
setLicenseCache,
} from './check_license';
export { hasLicenseExpired, isFullLicense, setLicenseCache } from './check_license';

View file

@ -1,61 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Observable, Subject } from 'rxjs';
import { ILicense } from '@kbn/licensing-plugin/common/types';
import { MlClientLicense } from './ml_client_license';
import { applicationServiceMock } from '@kbn/core/public/mocks';
describe('MlClientLicense', () => {
const startApplicationContractMock = applicationServiceMock.createStartContract();
test('should miss the license update when initialized without postInitFunction', () => {
const mlLicense = new MlClientLicense(startApplicationContractMock);
// upon instantiation the full license doesn't get set
expect(mlLicense.isFullLicense()).toBe(false);
const license$ = new Subject();
mlLicense.setup(license$ as Observable<ILicense>);
// if the observable wasn't triggered the full license is still not set
expect(mlLicense.isFullLicense()).toBe(false);
license$.next({
check: () => ({ state: 'valid' }),
getFeature: () => ({ isEnabled: true }),
status: 'valid',
});
// once the observable triggered the license should be set
expect(mlLicense.isFullLicense()).toBe(true);
});
test('should not miss the license update when initialized with postInitFunction', (done) => {
const mlLicense = new MlClientLicense(startApplicationContractMock);
// upon instantiation the full license doesn't get set
expect(mlLicense.isFullLicense()).toBe(false);
const license$ = new Subject();
mlLicense.setup(license$ as Observable<ILicense>, (license) => {
// when passed in via postInitFunction callback, the license should be valid
// even if the license$ observable gets triggered after this setup.
expect(license.isFullLicense()).toBe(true);
done();
});
license$.next({
check: () => ({ state: 'valid' }),
getFeature: () => ({ isEnabled: true }),
status: 'valid',
});
});
});

View file

@ -1,59 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CoreStart } from '@kbn/core/public';
import { ML_PAGES } from '../../../common/constants/locator';
import { MlLicense } from '../../../common/license';
import { showExpiredLicenseWarning } from './expired_warning';
import { PLUGIN_ID } from '../../../common/constants/app';
export class MlClientLicense extends MlLicense {
constructor(private application: CoreStart['application']) {
super();
}
private redirectToKibana() {
this.application.navigateToApp('home');
return Promise.reject();
}
private redirectToBasic() {
this.application.navigateToApp(PLUGIN_ID, { path: ML_PAGES.DATA_VISUALIZER });
return Promise.reject();
}
fullLicenseResolver(): Promise<void> {
if (this.isMlEnabled() === false || this.isMinimumLicense() === false) {
// ML is not enabled or the license isn't at least basic
return this.redirectToKibana();
}
if (this.isFullLicense() === false) {
// ML is enabled, but only with a basic or gold license
return this.redirectToBasic();
}
// ML is enabled
if (this.hasLicenseExpired()) {
showExpiredLicenseWarning();
}
return Promise.resolve();
}
basicLicenseResolver() {
if (this.isMlEnabled() === false || this.isMinimumLicense() === false) {
// ML is not enabled or the license isn't at least basic
return this.redirectToKibana();
}
// ML is enabled
if (this.hasLicenseExpired()) {
showExpiredLicenseWarning();
}
return Promise.resolve();
}
}

View file

@ -38,15 +38,10 @@ export interface Group {
interface Props {
anomalyTimelineService: AnomalyTimelineService;
jobCreationDisabled: boolean;
setLazyJobCount: React.Dispatch<React.SetStateAction<number>>;
}
export const AnomalyDetectionPanel: FC<Props> = ({
anomalyTimelineService,
jobCreationDisabled,
setLazyJobCount,
}) => {
export const AnomalyDetectionPanel: FC<Props> = ({ anomalyTimelineService, setLazyJobCount }) => {
const {
services: { charts: chartsService },
} = useMlKibana();

View file

@ -51,7 +51,6 @@ export const OverviewContent: FC<Props> = ({
<>
<AnomalyDetectionPanel
anomalyTimelineService={anomalyTimelineService}
jobCreationDisabled={createAnomalyDetectionJobDisabled}
setLazyJobCount={setAdLazyJobCount}
/>
<EuiSpacer size="m" />

View file

@ -9,7 +9,7 @@ import React, { FC, useState } from 'react';
import { EuiPanel, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { mlTimefilterRefresh$, useTimefilter } from '@kbn/ml-date-picker';
import { checkPermission } from '../capabilities/check_capabilities';
import { usePermissionCheck } from '../capabilities/check_capabilities';
import { mlNodesAvailable } from '../ml_nodes_check';
import { OverviewContent } from './components/content';
import { NodeAvailableWarning } from '../components/node_available_warning';
@ -25,9 +25,9 @@ import { useIsServerless } from '../contexts/kibana/use_is_serverless';
export const OverviewPage: FC = () => {
const serverless = useIsServerless();
const canViewMlNodes = checkPermission('canViewMlNodes');
const [canViewMlNodes, canCreateJob] = usePermissionCheck(['canViewMlNodes', 'canCreateJob']);
const disableCreateAnomalyDetectionJob = !checkPermission('canCreateJob') || !mlNodesAvailable();
const disableCreateAnomalyDetectionJob = !canCreateJob || !mlNodesAvailable();
const {
services: { docLinks },
} = useMlKibana();

View file

@ -5,33 +5,19 @@
* 2.0.
*/
import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import { cacheDataViewsContract, loadSavedSearches } from '../util/index_utils';
import { checkFullLicense } from '../license';
import { checkGetJobsCapabilitiesResolver } from '../capabilities/check_capabilities';
import { getMlNodeCount } from '../ml_nodes_check/check_ml_nodes';
import { loadMlServerInfo } from '../services/ml_server_info';
export interface Resolvers {
[name: string]: () => Promise<any>;
}
export interface ResolverResults {
[name: string]: any;
}
export type ResolverResults =
| {
[name: string]: any;
}
| undefined;
interface BasicResolverDependencies {
dataViewsContract: DataViewsContract;
redirectToMlAccessDeniedPage: () => Promise<void>;
}
export const basicResolvers = ({
dataViewsContract,
redirectToMlAccessDeniedPage,
}: BasicResolverDependencies): Resolvers => ({
checkFullLicense,
export const basicResolvers = (): Resolvers => ({
getMlNodeCount,
loadMlServerInfo,
cacheDataViewsContract: () => cacheDataViewsContract(dataViewsContract),
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
loadSavedSearches,
});

View file

@ -20,10 +20,10 @@ import type { DataViewsContract } from '@kbn/data-views-plugin/public';
import { EuiSkeletonText } from '@elastic/eui';
import { UrlStateProvider } from '@kbn/ml-url-state';
import { MlNotificationsContextProvider } from '../contexts/ml/ml_notifications_context';
import { MlContext, MlContextValue } from '../contexts/ml';
import { MlPage } from '../components/ml_page';
import { MlPages } from '../../locator';
import { type RouteResolverContext } from './use_resolver';
// custom RouteProps making location non-optional
interface MlRouteProps extends RouteProps {
@ -64,13 +64,18 @@ export interface PageDependencies {
setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'];
dataViewsContract: DataViewsContract;
setBreadcrumbs: ChromeStart['setBreadcrumbs'];
redirectToMlAccessDeniedPage: () => Promise<void>;
}
export const PageLoader: FC<{ context: MlContextValue }> = ({ context, children }) => {
export const PageLoader: FC<{ context: RouteResolverContext }> = ({ context, children }) => {
const isLoading = !context.initialized;
if (context?.resolvedComponent) {
return context.resolvedComponent;
}
return (
<EuiSkeletonText lines={10} isLoading={context === null}>
<MlContext.Provider value={context}>{children}</MlContext.Provider>
<EuiSkeletonText lines={10} isLoading={isLoading}>
{!isLoading ? children : null}
</EuiSkeletonText>
);
};

View file

@ -1,40 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { ML_PAGES } from '../../../locator';
import { createPath, MlRoute, PageLoader, PageProps } from '../router';
import { useResolver } from '../use_resolver';
import { Page } from '../../access_denied';
const breadcrumbs = [
{
text: i18n.translate('xpack.ml.accessDeniedLabel', {
defaultMessage: 'Access denied',
}),
},
];
export const accessDeniedRouteFactory = (): MlRoute => ({
path: createPath(ML_PAGES.ACCESS_DENIED),
title: i18n.translate('xpack.ml.accessDeniedLabel', {
defaultMessage: 'Access denied',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
breadcrumbs,
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {});
return (
<PageLoader context={context}>
<Page />
</PageLoader>
);
};

View file

@ -8,15 +8,13 @@
import { CHANGE_POINT_DETECTION_ENABLED } from '@kbn/aiops-plugin/common';
import { i18n } from '@kbn/i18n';
import React, { FC } from 'react';
import { parse } from 'query-string';
import { DataSourceContextProvider } from '../../../contexts/ml';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { MlRoute } from '../..';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { createPath, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { checkBasicLicense } from '../../../license';
import { cacheDataViewsContract } from '../../../util/index_utils';
import { createPath, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { ChangePointDetectionPage as Page } from '../../../aiops';
export const changePointDetectionRouteFactory = (
@ -28,7 +26,7 @@ export const changePointDetectionRouteFactory = (
title: i18n.translate('xpack.ml.aiops.changePointDetection.docTitle', {
defaultMessage: 'Change point detection',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_CHANGE_POINT_DETECTION', navigateToPath, basePath),
@ -41,16 +39,14 @@ export const changePointDetectionRouteFactory = (
disabled: !CHANGE_POINT_DETECTION_ENABLED,
});
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
});
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canUseAiops']);
return (
<PageLoader context={context}>
<Page />
<DataSourceContextProvider>
<Page />
</DataSourceContextProvider>
</PageLoader>
);
};

View file

@ -6,23 +6,15 @@
*/
import React, { FC } from 'react';
import { parse } from 'query-string';
import { i18n } from '@kbn/i18n';
import { AIOPS_ENABLED } from '@kbn/aiops-plugin/common';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { createPath, MlRoute, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { ExplainLogRateSpikesPage as Page } from '../../../aiops/explain_log_rate_spikes';
import { checkBasicLicense } from '../../../license';
import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
import { cacheDataViewsContract } from '../../../util/index_utils';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { DataSourceContextProvider } from '../../../contexts/ml';
export const explainLogRateSpikesRouteFactory = (
navigateToPath: NavigateToPath,
@ -33,7 +25,7 @@ export const explainLogRateSpikesRouteFactory = (
title: i18n.translate('xpack.ml.aiops.explainLogRateSpikes.docTitle', {
defaultMessage: 'Explain log rate spikes',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp(
@ -50,19 +42,14 @@ export const explainLogRateSpikesRouteFactory = (
disabled: !AIOPS_ENABLED,
});
const PageWrapper: FC<PageProps> = ({ location, deps, ...restProps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
});
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canUseAiops']);
return (
<PageLoader context={context}>
<Page />
<DataSourceContextProvider>
<Page />
</DataSourceContextProvider>
</PageLoader>
);
};

View file

@ -6,23 +6,15 @@
*/
import React, { FC } from 'react';
import { parse } from 'query-string';
import { i18n } from '@kbn/i18n';
import { AIOPS_ENABLED } from '@kbn/aiops-plugin/common';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { createPath, MlRoute, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { LogCategorizationPage as Page } from '../../../aiops/log_categorization';
import { checkBasicLicense } from '../../../license';
import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
import { cacheDataViewsContract } from '../../../util/index_utils';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { DataSourceContextProvider } from '../../../contexts/ml';
export const logCategorizationRouteFactory = (
navigateToPath: NavigateToPath,
@ -33,7 +25,7 @@ export const logCategorizationRouteFactory = (
title: i18n.translate('xpack.ml.aiops.logCategorization.docTitle', {
defaultMessage: 'Log Pattern Analysis',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('AIOPS_BREADCRUMB_LOG_PATTERN_ANALYSIS', navigateToPath, basePath),
@ -46,19 +38,14 @@ export const logCategorizationRouteFactory = (
disabled: !AIOPS_ENABLED,
});
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
});
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canUseAiops']);
return (
<PageLoader context={context}>
<Page />
<DataSourceContextProvider>
<Page />
</DataSourceContextProvider>
</PageLoader>
);
};

View file

@ -7,20 +7,18 @@
import React, { FC } from 'react';
import { parse } from 'query-string';
import { i18n } from '@kbn/i18n';
import { DataSourceContextProvider } from '../../../contexts/ml';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { useRouteResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page } from '../../../data_frame_analytics/pages/analytics_creation';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import {
loadNewJobCapabilities,
DATA_FRAME_ANALYTICS,
loadNewJobCapabilities,
} from '../../../services/new_job_capabilities/load_new_job_capabilities';
export const analyticsJobsCreationRouteFactory = (
@ -48,15 +46,21 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
sort: false,
});
const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, {
...basicResolvers(deps),
analyticsFields: () =>
loadNewJobCapabilities(index, savedSearchId, deps.dataViewsContract, DATA_FRAME_ANALYTICS),
});
const { context } = useRouteResolver(
'full',
['canGetDataFrameAnalytics', 'canCreateDataFrameAnalytics'],
{
...basicResolvers(),
analyticsFields: () =>
loadNewJobCapabilities(index, savedSearchId, deps.dataViewsContract, DATA_FRAME_ANALYTICS),
}
);
return (
<PageLoader context={context}>
<Page jobId={jobId} />
<DataSourceContextProvider>
<Page jobId={jobId} />
</DataSourceContextProvider>
</PageLoader>
);
};

View file

@ -6,17 +6,14 @@
*/
import React, { FC } 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 { basicResolvers } from '../../resolvers';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { createPath, MlRoute, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { Page } from '../../../data_frame_analytics/pages/analytics_exploration';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
@ -25,7 +22,7 @@ export const analyticsJobExplorationRouteFactory = (
basePath: string
): MlRoute => ({
path: createPath(ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
title: i18n.translate('xpack.ml.dataFrameAnalytics.exploration.docTitle', {
defaultMessage: 'Results Explorer',
}),
@ -40,14 +37,8 @@ export const analyticsJobExplorationRouteFactory = (
],
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
basicResolvers(deps)
);
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canGetDataFrameAnalytics'], basicResolvers());
const [globalState] = useUrlState('_g');
const jobId: string = globalState?.ml.jobId;

View file

@ -9,8 +9,8 @@ import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { createPath, MlRoute, 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';
@ -24,7 +24,7 @@ export const analyticsJobsListRouteFactory = (
title: i18n.translate('xpack.ml.dataFrameAnalytics.jobs.docTitle', {
defaultMessage: 'Data Frame Analytics Jobs',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('DATA_FRAME_ANALYTICS_BREADCRUMB', navigateToPath, basePath),
@ -38,14 +38,8 @@ export const analyticsJobsListRouteFactory = (
enableDatePicker: true,
});
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
basicResolvers(deps)
);
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canGetDataFrameAnalytics'], basicResolvers());
return (
<PageLoader context={context}>
<Page />

View file

@ -7,12 +7,10 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { createPath, MlRoute, 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';
@ -22,7 +20,7 @@ export const analyticsMapRouteFactory = (
basePath: string
): MlRoute => ({
path: createPath(ML_PAGES.DATA_FRAME_ANALYTICS_MAP),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
title: i18n.translate('xpack.ml.dataFrameAnalytics.analyticsMap.docTitle', {
defaultMessage: 'Analytics Map',
}),
@ -39,14 +37,8 @@ export const analyticsMapRouteFactory = (
'data-test-subj': 'mlPageAnalyticsMap',
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
basicResolvers(deps)
);
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canGetDataFrameAnalytics'], basicResolvers());
return (
<PageLoader context={context}>

View file

@ -6,14 +6,11 @@
*/
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { createPath, MlRoute, 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';
@ -23,7 +20,7 @@ export const analyticsSourceSelectionRouteFactory = (
basePath: string
): MlRoute => ({
path: createPath(ML_PAGES.DATA_FRAME_ANALYTICS_SOURCE_SELECTION),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
title: i18n.translate('xpack.ml.dataFrameAnalytics.sourceSelection.docTitle', {
defaultMessage: 'Source Selection',
}),
@ -38,14 +35,8 @@ export const analyticsSourceSelectionRouteFactory = (
],
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
basicResolvers(deps)
);
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canGetDataFrameAnalytics'], basicResolvers());
return (
<PageLoader context={context}>

View file

@ -9,11 +9,9 @@ import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { createPath, MlRoute, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { DatavisualizerSelector } from '../../../datavisualizer';
import { checkBasicLicense } from '../../../license';
import { checkFindFileStructurePrivilegeResolver } from '../../../capabilities/check_capabilities';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
export const selectorRouteFactory = (
@ -25,21 +23,16 @@ export const selectorRouteFactory = (
title: i18n.translate('xpack.ml.dataVisualizer.docTitle', {
defaultMessage: 'Data Visualizer',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB'),
],
});
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const PageWrapper: FC = () => {
const { context } = useRouteResolver('basic', ['canFindFileStructure']);
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkBasicLicense,
checkFindFileStructurePrivilege: () =>
checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage),
});
return (
<PageLoader context={context}>
<DatavisualizerSelector />

View file

@ -7,18 +7,12 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { createPath, MlRoute, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { FileDataVisualizerPage } from '../../../datavisualizer/file_based';
import { checkBasicLicense } from '../../../license';
import { checkFindFileStructurePrivilegeResolver } from '../../../capabilities/check_capabilities';
import { cacheDataViewsContract } from '../../../util/index_utils';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
export const fileBasedRouteFactory = (
@ -30,7 +24,7 @@ export const fileBasedRouteFactory = (
title: i18n.translate('xpack.ml.dataVisualizer.file.docTitle', {
defaultMessage: 'File Data Visualizer',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath),
@ -42,15 +36,8 @@ export const fileBasedRouteFactory = (
],
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkFindFileStructurePrivilege: () =>
checkFindFileStructurePrivilegeResolver(redirectToMlAccessDeniedPage),
});
const PageWrapper: FC = () => {
const { context } = useRouteResolver('basic', ['canFindFileStructure']);
return (
<PageLoader context={context}>

View file

@ -6,21 +6,14 @@
*/
import React, { FC } from 'react';
import { parse } from 'query-string';
import { i18n } from '@kbn/i18n';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { createPath, MlRoute, PageLoader } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { IndexDataVisualizerPage as Page } from '../../../datavisualizer/index_based/index_data_visualizer';
import { checkBasicLicense } from '../../../license';
import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
import { cacheDataViewsContract } from '../../../util/index_utils';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { DataSourceContextProvider } from '../../../contexts/ml';
export const indexBasedRouteFactory = (
navigateToPath: NavigateToPath,
@ -31,7 +24,7 @@ export const indexBasedRouteFactory = (
title: i18n.translate('xpack.ml.dataVisualizer.dataView.docTitle', {
defaultMessage: 'Index Data Visualizer',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('DATA_VISUALIZER_BREADCRUMB', navigateToPath, basePath),
@ -43,19 +36,14 @@ export const indexBasedRouteFactory = (
],
});
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(index, savedSearchId, deps.config, deps.dataViewsContract, {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
});
const PageWrapper: FC = () => {
const { context } = useRouteResolver('basic', []);
return (
<PageLoader context={context}>
<Page />
<DataSourceContextProvider>
<Page />
</DataSourceContextProvider>
</PageLoader>
);
};

View file

@ -15,6 +15,7 @@ import { EuiThemeProvider as StyledComponentsThemeProvider } from '@kbn/kibana-r
import { useUrlState } from '@kbn/ml-url-state';
import { useTimefilter } from '@kbn/ml-date-picker';
import { ML_JOB_ID } from '@kbn/ml-anomaly-utils';
import { basicResolvers } from '../resolvers';
import { ML_PAGES } from '../../../locator';
import { NavigateToPath, useMlKibana } from '../../contexts/kibana';
@ -22,8 +23,7 @@ import { MlJobWithTimeRange } from '../../../../common/types/anomaly_detection_j
import { createPath, MlRoute, PageLoader, PageProps } from '../router';
import { useRefresh } from '../use_refresh';
import { useResolver } from '../use_resolver';
import { basicResolvers } from '../resolvers';
import { useRouteResolver } from '../use_resolver';
import { Explorer } from '../../explorer';
import { mlJobService } from '../../services/job_service';
import { ml } from '../../services/ml_api_service';
@ -42,8 +42,8 @@ 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,
AnomalyExplorerContextProvider,
useAnomalyExplorerContext,
} from '../../explorer/anomaly_explorer_context';
export const explorerRouteFactory = (
@ -69,25 +69,28 @@ export const explorerRouteFactory = (
'data-test-subj': 'mlPageAnomalyExplorer',
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context, results } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
{
...basicResolvers(deps),
jobs: mlJobService.loadJobsWrapper,
jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()),
}
);
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>
<ExplorerUrlStateManager jobsWithTimeRange={results.jobsWithTimeRange.jobs} />
{results ? (
<ExplorerUrlStateManager jobsWithTimeRange={results.jobsWithTimeRange.jobs} />
) : null}
</AnomalyExplorerContextProvider>
</MlAnnotationUpdatesContext.Provider>
</PageLoader>

View file

@ -14,7 +14,6 @@ export * from './data_frame_analytics';
export * from './aiops';
export { timeSeriesExplorerRouteFactory } from './timeseriesexplorer';
export * from './explorer';
export * from './access_denied';
export * from './trained_models';
export * from './notifications';
export * from './memory_usage';

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { useEffect, FC, useMemo } from 'react';
import React, { FC, useEffect, useMemo } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { i18n } from '@kbn/i18n';
import {
@ -16,13 +16,13 @@ import {
import { ML_PAGES } from '../../../locator';
import { NavigateToPath } from '../../contexts/kibana';
import { DEFAULT_REFRESH_INTERVAL_MS } from '../../../../common/constants/jobs_list';
import { createPath, MlRoute, PageLoader, PageProps } from '../router';
import { useResolver } from '../use_resolver';
import { basicResolvers } from '../resolvers';
import { createPath, MlRoute, 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';
export const jobListRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({
id: 'anomaly_detection',
@ -30,7 +30,7 @@ export const jobListRouteFactory = (navigateToPath: NavigateToPath, basePath: st
defaultMessage: 'Anomaly Detection Jobs',
}),
path: createPath(ML_PAGES.ANOMALY_DETECTION_JOBS_MANAGE),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
@ -44,14 +44,9 @@ export const jobListRouteFactory = (navigateToPath: NavigateToPath, basePath: st
enableDatePicker: true,
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
basicResolvers(deps)
);
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canGetJobs'], basicResolvers());
const timefilter = useTimefilter({ timeRangeSelector: false, autoRefreshSelector: true });
const refresh = useRefreshIntervalUpdates();

View file

@ -7,11 +7,11 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { basicResolvers } from '../resolvers';
import { ML_PAGES } from '../../../locator';
import { NavigateToPath } from '../../contexts/kibana';
import { MlRoute, PageLoader, PageProps, createPath } from '../router';
import { useResolver } from '../use_resolver';
import { basicResolvers } from '../resolvers';
import { createPath, MlRoute, PageLoader } from '../router';
import { useRouteResolver } from '../use_resolver';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
import { MemoryUsagePage } from '../../memory_usage';
@ -20,7 +20,7 @@ export const nodesListRouteFactory = (
basePath: string
): MlRoute => ({
path: createPath(ML_PAGES.MEMORY_USAGE),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
title: i18n.translate('xpack.ml.modelManagement.memoryUsage.docTitle', {
defaultMessage: 'Memory Usage',
}),
@ -35,13 +35,11 @@ export const nodesListRouteFactory = (
enableDatePicker: true,
});
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
basicResolvers(deps)
const PageWrapper: FC = () => {
const { context } = useRouteResolver(
'full',
['canGetJobs', 'canGetDataFrameAnalytics', 'canGetTrainedModels'],
basicResolvers()
);
return (

View file

@ -6,14 +6,11 @@
*/
import React, { FC } from 'react';
import { Redirect } from 'react-router-dom';
import { parse } from 'query-string';
import { ML_PAGES } from '../../../../locator';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { useRouteResolver } from '../../use_resolver';
import { resolver } from '../../../jobs/new_job/job_from_lens';
export const fromLensRouteFactory = (): MlRoute => ({
@ -22,7 +19,7 @@ export const fromLensRouteFactory = (): MlRoute => ({
breadcrumbs: [],
});
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const PageWrapper: FC<PageProps> = ({ location }) => {
const { lensId, vis, from, to, query, filters, layerIndex }: Record<string, any> = parse(
location.search,
{
@ -30,9 +27,10 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
}
);
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
const { context } = useRouteResolver('full', ['canCreateJob'], {
redirect: () => resolver(lensId, vis, from, to, query, filters, layerIndex),
});
return (
<PageLoader context={context}>
{<Redirect to={createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB)} />}

View file

@ -6,14 +6,11 @@
*/
import React, { FC } from 'react';
import { Redirect } from 'react-router-dom';
import { parse } from 'query-string';
import { ML_PAGES } from '../../../../locator';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { useRouteResolver } from '../../use_resolver';
import { resolver } from '../../../jobs/new_job/job_from_map';
export const fromMapRouteFactory = (): MlRoute => ({
@ -22,7 +19,7 @@ export const fromMapRouteFactory = (): MlRoute => ({
breadcrumbs: [],
});
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const PageWrapper: FC<PageProps> = ({ location }) => {
const {
dashboard,
dataViewId,
@ -36,10 +33,11 @@ const PageWrapper: FC<PageProps> = ({ location, deps }) => {
sort: false,
});
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
const { context } = useRouteResolver('full', ['canCreateJob'], {
redirect: () =>
resolver(dashboard, dataViewId, embeddable, geoField, splitField, from, to, layer),
});
return (
<PageLoader context={context}>
{<Redirect to={createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB)} />}

View file

@ -6,20 +6,14 @@
*/
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath, useMlKibana } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { useRouteResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { Page, preConfiguredJobRedirect } from '../../../jobs/new_job/pages/index_or_search';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
import { checkBasicLicense } from '../../../license';
import { cacheDataViewsContract } from '../../../util/index_utils';
import { checkGetJobsCapabilitiesResolver } from '../../../capabilities/check_capabilities';
enum MODE {
NEW_JOB,
@ -188,25 +182,16 @@ const PageWrapper: FC<IndexOrSearchPageProps> = ({ nextStepPath, deps, mode }) =
},
} = useMlKibana();
const { redirectToMlAccessDeniedPage } = deps;
const newJobResolvers = {
...basicResolvers(deps),
...basicResolvers(),
preConfiguredJobRedirect: () =>
preConfiguredJobRedirect(deps.dataViewsContract, basePath.get(), navigateToUrl),
};
const dataVizResolvers = {
checkBasicLicense,
cacheDataViewsContract: () => cacheDataViewsContract(deps.dataViewsContract),
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
};
const { context } = useResolver(
undefined,
undefined,
deps.config,
deps.dataViewsContract,
mode === MODE.NEW_JOB ? newJobResolvers : dataVizResolvers
const { context } = useRouteResolver(
mode === MODE.NEW_JOB ? 'full' : 'basic',
mode === MODE.NEW_JOB ? ['canCreateJob'] : [],
mode === MODE.NEW_JOB ? newJobResolvers : {}
);
return (
<PageLoader context={context}>

View file

@ -6,22 +6,19 @@
*/
import React, { FC } from 'react';
import { parse } from 'query-string';
import { i18n } from '@kbn/i18n';
import { basicResolvers } from '../../resolvers';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { createPath, MlRoute, 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';
export const jobTypeRouteFactory = (navigateToPath: NavigateToPath, basePath: string): MlRoute => ({
path: createPath(ML_PAGES.ANOMALY_DETECTION_CREATE_JOB_SELECT_TYPE),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
@ -34,18 +31,14 @@ export const jobTypeRouteFactory = (navigateToPath: NavigateToPath, basePath: st
],
});
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(
index,
savedSearchId,
deps.config,
deps.dataViewsContract,
basicResolvers(deps)
);
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canGetJobs'], basicResolvers());
return (
<PageLoader context={context}>
<Page />
<DataSourceContextProvider>
<Page />
</DataSourceContextProvider>
</PageLoader>
);
};

View file

@ -7,20 +7,17 @@
import { parse } from 'query-string';
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath, useNavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { basicResolvers } from '../../resolvers';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath, useMlKibana, useNavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useRouteResolver } from '../../use_resolver';
import { Page } from '../../../jobs/new_job/recognize';
import { checkViewOrCreateJobs } from '../../../jobs/new_job/recognize/resolvers';
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';
export const recognizeRouteFactory = (
navigateToPath: NavigateToPath,
@ -49,38 +46,87 @@ export const checkViewOrCreateRouteFactory = (): MlRoute => ({
});
const PageWrapper: FC<PageProps> = ({ location, deps }) => {
const { id, index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context, results } = useResolver(
index,
savedSearchId,
deps.config,
deps.dataViewsContract,
const { id } = parse(location.search, { sort: false });
{
...basicResolvers(deps),
existingJobsAndGroups: mlJobService.getJobAndGroupIds,
}
);
const { context, results } = useRouteResolver('full', ['canGetJobs'], {
...basicResolvers(),
existingJobsAndGroups: mlJobService.getJobAndGroupIds,
});
return (
<PageLoader context={context}>
<Page moduleId={id} existingGroupIds={results.existingJobsAndGroups.groupIds} />
<DataSourceContextProvider>
{results ? (
<Page moduleId={id as string} existingGroupIds={results.existingJobsAndGroups.groupIds} />
) : null}
</DataSourceContextProvider>
</PageLoader>
);
};
const CheckViewOrCreateWrapper: FC<PageProps> = ({ location, deps }) => {
const {
services: {
notifications: { toasts },
mlServices: { mlApiServices },
},
} = useMlKibana();
const { id: moduleId, index: dataViewId }: Record<string, any> = parse(location.search, {
sort: false,
});
const { createLinkWithUserDefaults } = useCreateADLinks();
const navigateToPath = useNavigateToPath();
// the single resolver checkViewOrCreateJobs redirects only. so will always reject
useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkViewOrCreateJobs: () =>
checkViewOrCreateJobs(moduleId, dataViewId, createLinkWithUserDefaults, navigateToPath),
});
/**
* Checks whether the jobs in a data recognizer module have been created.
* Redirects to the Anomaly Explorer to view the jobs if they have been created,
* or the recognizer job wizard for the module if not.
*/
function checkViewOrCreateJobs(): Promise<unknown> {
return new Promise((resolve, reject) => {
// Load the module, and check if the job(s) in the module have been created.
// If so, load the jobs in the Anomaly Explorer.
// Otherwise open the data recognizer wizard for the module.
// Always want to call reject() so as not to load original page.
mlApiServices
.dataRecognizerModuleJobsExist({ moduleId })
.then(async (resp: any) => {
if (resp.jobsExist === true) {
// also honor user's time filter setting in Advanced Settings
const url = createLinkWithUserDefaults('explorer', resp.jobs);
await navigateToPath(url);
reject();
} else {
await navigateToPath(`/jobs/new_job/recognize?id=${moduleId}&index=${dataViewId}`);
reject();
}
})
.catch(async (err: Error) => {
// eslint-disable-next-line no-console
console.error(`Error checking whether jobs in module ${moduleId} exists`, err);
toasts.addWarning({
title: i18n.translate('xpack.ml.newJob.recognize.moduleCheckJobsExistWarningTitle', {
defaultMessage: 'Error checking module {moduleId}',
values: { moduleId },
}),
text: i18n.translate(
'xpack.ml.newJob.recognize.moduleCheckJobsExistWarningDescription',
{
defaultMessage:
'An error occurred trying to check whether the jobs in the module have been created.',
}
),
});
await navigateToPath(`/jobs`);
reject();
});
});
}
useRouteResolver('full', ['canCreateJob'], { checkViewOrCreateJobs });
return null;
};

View file

@ -9,11 +9,11 @@ import { parse } from 'query-string';
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { Redirect } from 'react-router-dom';
import { DataSourceContextProvider } from '../../../contexts/ml/data_source_context';
import { NavigateToPath } from '../../../contexts/kibana';
import { basicResolvers } from '../../resolvers';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
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';
@ -194,23 +194,23 @@ const PageWrapper: FC<WizardPageProps> = ({ location, jobType, deps }) => {
);
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context, results } = useResolver(
index,
savedSearchId,
deps.config,
deps.dataViewsContract,
{
...basicResolvers(deps),
privileges: () => checkCreateJobsCapabilitiesResolver(redirectToJobsManagementPage),
jobCaps: () =>
loadNewJobCapabilities(index, savedSearchId, deps.dataViewsContract, ANOMALY_DETECTOR),
existingJobsAndGroups: mlJobService.getJobAndGroupIds,
}
);
const { context, results } = useRouteResolver('full', ['canGetJobs', 'canCreateJob'], {
...basicResolvers(),
// TODO useRouteResolver should be responsible for the redirect
privileges: () => checkCreateJobsCapabilitiesResolver(redirectToJobsManagementPage),
jobCaps: () =>
loadNewJobCapabilities(index, savedSearchId, deps.dataViewsContract, ANOMALY_DETECTOR),
existingJobsAndGroups: mlJobService.getJobAndGroupIds,
});
return (
<PageLoader context={context}>
<Page jobType={jobType} existingJobsAndGroups={results.existingJobsAndGroups} />
<DataSourceContextProvider>
{results ? (
<Page jobType={jobType} existingJobsAndGroups={results.existingJobsAndGroups} />
) : null}
</DataSourceContextProvider>
</PageLoader>
);
};

View file

@ -9,10 +9,8 @@ import React, { FC, Suspense } from 'react';
import { i18n } from '@kbn/i18n';
import { useTimefilter } from '@kbn/ml-date-picker';
import { ML_PAGES } from '../../../locator';
import { createPath, PageLoader, PageProps } from '../router';
import { useResolver } from '../use_resolver';
import { checkFullLicense } from '../../license';
import { checkGetJobsCapabilitiesResolver } from '../../capabilities/check_capabilities';
import { createPath, PageLoader } from '../router';
import { useRouteResolver } from '../use_resolver';
import { getMlNodeCount } from '../../ml_nodes_check';
import { loadMlServerInfo } from '../../services/ml_server_info';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
@ -31,7 +29,7 @@ export const notificationsRouteFactory = (
defaultMessage: 'Notifications',
}),
enableDatePicker: true,
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
{
@ -43,15 +41,12 @@ export const notificationsRouteFactory = (
'data-test-subj': 'mlPageNotifications',
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkFullLicense,
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canGetJobs'], {
getMlNodeCount,
loadMlServerInfo,
});
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
return (

View file

@ -5,23 +5,17 @@
* 2.0.
*/
import React, { FC, Suspense } from 'react';
import { i18n } from '@kbn/i18n';
import { Redirect } from 'react-router-dom';
import { useTimefilter } from '@kbn/ml-date-picker';
import React, { FC, Suspense } from 'react';
import { Redirect } from 'react-router-dom';
import { ML_PAGES } from '../../../locator';
import type { NavigateToPath } from '../../contexts/kibana';
import { checkFullLicense } from '../../license';
import { checkGetJobsCapabilitiesResolver } from '../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../ml_nodes_check';
import { loadMlServerInfo } from '../../services/ml_server_info';
import { createPath, MlRoute, PageLoader, PageProps } from '../router';
import { useResolver } from '../use_resolver';
import { getBreadcrumbWithUrlForApp } from '../breadcrumbs';
import { createPath, MlRoute, PageLoader, PageProps } from '../router';
import { useRouteResolver } from '../use_resolver';
const OverviewPage = React.lazy(() => import('../../overview/overview_page'));
@ -47,15 +41,12 @@ export const overviewRouteFactory = (
'data-test-subj': 'mlPageOverview',
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkFullLicense,
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
const PageWrapper: FC<PageProps> = () => {
const { context } = useRouteResolver('full', ['canGetJobs'], {
getMlNodeCount,
loadMlServerInfo,
});
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
return (

View file

@ -11,12 +11,8 @@ import { useTimefilter } from '@kbn/ml-date-picker';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { checkFullLicense } from '../../../license';
import {
checkGetJobsCapabilitiesResolver,
checkPermission,
} from '../../../capabilities/check_capabilities';
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';
@ -39,18 +35,14 @@ export const calendarListRouteFactory = (
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkFullLicense,
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
});
const { context } = useRouteResolver('full', ['canGetCalendars'], { getMlNodeCount });
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
const canCreateCalendar = checkPermission('canCreateCalendar');
const canDeleteCalendar = checkPermission('canDeleteCalendar');
const [canCreateCalendar, canDeleteCalendar] = usePermissionCheck([
'canCreateCalendar',
'canDeleteCalendar',
]);
return (
<PageLoader context={context}>

View file

@ -10,12 +10,8 @@ import { i18n } from '@kbn/i18n';
import { useTimefilter } from '@kbn/ml-date-picker';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { checkFullLicense } from '../../../license';
import {
checkGetJobsCapabilitiesResolver,
checkPermission,
} from '../../../capabilities/check_capabilities';
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';
@ -79,18 +75,15 @@ const PageWrapper: FC<NewCalendarPageProps> = ({ location, mode, deps }) => {
const pathMatch: string[] | null = location.pathname.match(/.+\/(.+)$/);
calendarId = pathMatch && pathMatch.length > 1 ? pathMatch[1] : undefined;
}
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkFullLicense,
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
});
const { context } = useRouteResolver('full', ['canGetJobs'], { getMlNodeCount });
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
const canCreateCalendar = checkPermission('canCreateCalendar');
const canDeleteCalendar = checkPermission('canDeleteCalendar');
const [canCreateCalendar, canDeleteCalendar] = usePermissionCheck([
'canCreateCalendar',
'canDeleteCalendar',
]);
return (
<PageLoader context={context}>

View file

@ -10,13 +10,9 @@ import { i18n } from '@kbn/i18n';
import { useTimefilter } from '@kbn/ml-date-picker';
import { ML_PAGES } from '../../../../locator';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { checkFullLicense } from '../../../license';
import {
checkGetJobsCapabilitiesResolver,
checkPermission,
} from '../../../capabilities/check_capabilities';
import { createPath, MlRoute, 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';
@ -29,7 +25,7 @@ export const filterListRouteFactory = (
title: i18n.translate('xpack.ml.settings.filterList.docTitle', {
defaultMessage: 'Filters',
}),
render: (props, deps) => <PageWrapper {...props} deps={deps} />,
render: () => <PageWrapper />,
breadcrumbs: [
getBreadcrumbWithUrlForApp('ML_BREADCRUMB', navigateToPath, basePath),
getBreadcrumbWithUrlForApp('ANOMALY_DETECTION_BREADCRUMB', navigateToPath, basePath),
@ -38,19 +34,15 @@ export const filterListRouteFactory = (
],
});
const PageWrapper: FC<PageProps> = ({ deps }) => {
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkFullLicense,
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
getMlNodeCount,
});
const PageWrapper: FC = () => {
const { context } = useRouteResolver('full', ['canGetFilters'], { getMlNodeCount });
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
const canCreateFilter = checkPermission('canCreateFilter');
const canDeleteFilter = checkPermission('canDeleteFilter');
const [canCreateFilter, canDeleteFilter] = usePermissionCheck([
'canCreateFilter',
'canDeleteFilter',
]);
return (
<PageLoader context={context}>

View file

@ -7,22 +7,14 @@
import React, { FC } from 'react';
import { i18n } from '@kbn/i18n';
import { useTimefilter } from '@kbn/ml-date-picker';
import { ML_PAGES } from '../../../../../common/constants/locator';
import { checkFullLicense } from '../../../license';
import {
checkGetJobsCapabilitiesResolver,
checkPermission,
} from '../../../capabilities/check_capabilities';
import { usePermissionCheck } from '../../../capabilities/check_capabilities';
import { getMlNodeCount } from '../../../ml_nodes_check/check_ml_nodes';
import { EditFilterList } from '../../../settings/filter_lists';
import { NavigateToPath } from '../../../contexts/kibana';
import { createPath, MlRoute, PageLoader, PageProps } from '../../router';
import { useResolver } from '../../use_resolver';
import { useRouteResolver } from '../../use_resolver';
import { getBreadcrumbWithUrlForApp } from '../../breadcrumbs';
enum MODE {
@ -77,24 +69,23 @@ export const editFilterListRouteFactory = (
],
});
const PageWrapper: FC<NewFilterPageProps> = ({ location, mode, deps }) => {
const PageWrapper: FC<NewFilterPageProps> = ({ location, mode }) => {
let filterId: string | undefined;
if (mode === MODE.EDIT) {
const pathMatch: string[] | null = location.pathname.match(/.+\/(.+)$/);
filterId = pathMatch && pathMatch.length > 1 ? pathMatch[1] : undefined;
}
const { redirectToMlAccessDeniedPage } = deps;
const { context } = useResolver(undefined, undefined, deps.config, deps.dataViewsContract, {
checkFullLicense,
checkGetJobsCapabilities: () => checkGetJobsCapabilitiesResolver(redirectToMlAccessDeniedPage),
const { context } = useRouteResolver('full', ['canGetFilters', 'canCreateFilter'], {
getMlNodeCount,
});
useTimefilter({ timeRangeSelector: false, autoRefreshSelector: false });
const canCreateFilter = checkPermission('canCreateFilter');
const canDeleteFilter = checkPermission('canDeleteFilter');
const [canCreateFilter, canDeleteFilter] = usePermissionCheck([
'canCreateFilter',
'canDeleteFilter',
]);
return (
<PageLoader context={context}>

Some files were not shown because too many files have changed in this diff Show more