Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 0 additions & 19 deletions x-pack/platform/plugins/shared/ml/common/license/ml_license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import { BehaviorSubject } from 'rxjs';
import type { ILicense } from '@kbn/licensing-types';
import { distinctUntilChanged, map } from 'rxjs';
import { isEqual } from 'lodash';
import type { MlCapabilities } from '@kbn/ml-common-types/capabilities';
import type { MlCoreSetup } from '../../public/plugin';
import { PLUGIN_ID } from '../constants/app';

export const MINIMUM_LICENSE = 'basic';
Expand Down Expand Up @@ -143,20 +141,3 @@ export function isMinimumLicense(license: ILicense) {
export function isMlEnabled(license: ILicense) {
return license.getFeature(PLUGIN_ID).isEnabled;
}

export async function isMlAvailable(getStartServices: MlCoreSetup['getStartServices']) {
const [coreStart, pluginStart] = await getStartServices();
const license = await pluginStart.licensing.getLicense();
return (
isFullLicense(license) &&
isMlEnabled(license) &&
(coreStart.application.capabilities.ml as MlCapabilities).canGetMlInfo
);
}

export async function ensureMlAvailable(getStartServices: MlCoreSetup['getStartServices']) {
const isAvailable = await isMlAvailable(getStartServices);
if (!isAvailable) {
throw new Error('ML is not available');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { hasLicenseExpired } from '../license';
import { getCapabilities } from './get_capabilities';
import type { MlApi } from '../services/ml_api_service';
import type { MlGlobalServices } from '../app';
import type { MlCoreSetup } from '../../plugin';

let _capabilities: MlCapabilities = getDefaultMlCapabilities();

Expand Down Expand Up @@ -232,6 +233,20 @@ export function checkPermission(capability: keyof MlCapabilities) {
return _capabilities[capability] === true && licenseHasExpired !== true;
}

export async function checkPermissionAsync(
getStartServices: MlCoreSetup['getStartServices'],
capability: keyof MlCapabilities,
throwError: boolean = false
) {
const [coreStart] = await getStartServices();
const hasPermission =
(coreStart.application.capabilities.ml as MlCapabilities)[capability] === true;
if (!hasPermission && throwError) {
throw new Error('You do not have permission to access this feature');
}
return hasPermission;
}

// create the text for the button's tooltips if the user's license has
// expired or if they don't have the privilege to press that button
export function createPermissionFailureMessage(privilegeType: keyof MlCapabilities) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import { LazyAnomalyChartsContainer } from './lazy_anomaly_charts_container';
import { getAnomalyChartsServiceDependencies } from './get_anomaly_charts_services_dependencies';
import { buildDataViewPublishingApi } from '../common/build_data_view_publishing_api';
import { EmbeddableAnomalyChartsUserInput } from './anomaly_charts_setup_flyout';
import { ensureMlAvailable } from '../../../common/license/ml_license';
import { checkPermissionAsync } from '../../application/capabilities/check_capabilities';

export const getAnomalyChartsReactEmbeddableFactory = (
getStartServices: StartServicesAccessor<MlStartDependencies, MlPluginStart>,
Expand All @@ -50,7 +50,7 @@ export const getAnomalyChartsReactEmbeddableFactory = (
const factory: EmbeddableFactory<AnomalyChartsEmbeddableState, AnomalyChartsEmbeddableApi> = {
type: ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE,
buildEmbeddable: async ({ initialState, finalizeApi, uuid, parentApi }) => {
await ensureMlAvailable(getStartServices);
await checkPermissionAsync(getStartServices, 'canGetJobs', true);
if (!apiHasExecutionContext(parentApi)) {
throw new Error('Parent API does not have execution context');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ const mockResponse = of([
},
]);

jest.mock('../../../common/license/ml_license', () => {
jest.mock('../../application/capabilities/check_capabilities', () => {
return {
ensureMlAvailable: jest.fn(),
checkPermissionAsync: jest.fn().mockResolvedValue(true),
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ import { initializeSwimLaneControls, swimLaneComparators } from './initialize_sw
import { initializeSwimLaneDataFetcher } from './initialize_swim_lane_data_fetcher';
import type { AnomalySwimLaneEmbeddableApi } from './types';
import { AnomalySwimlaneUserInput } from './anomaly_swimlane_setup_flyout';
import { ensureMlAvailable } from '../../../common/license/ml_license';
import { checkPermissionAsync } from '../../application/capabilities/check_capabilities';

/**
* Provides the services required by the Anomaly Swimlane Embeddable.
Expand Down Expand Up @@ -91,7 +91,7 @@ export const getAnomalySwimLaneEmbeddableFactory = (
const factory: EmbeddableFactory<AnomalySwimLaneEmbeddableState, AnomalySwimLaneEmbeddableApi> = {
type: ANOMALY_SWIMLANE_EMBEDDABLE_TYPE,
buildEmbeddable: async ({ initialState, finalizeApi, uuid, parentApi }) => {
await ensureMlAvailable(getStartServices);
await checkPermissionAsync(getStartServices, 'canGetJobs', true);
if (!apiHasExecutionContext(parentApi)) {
throw new Error('Parent API does not have execution context');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { getServices } from './get_services';
import { useReactEmbeddableExecutionContext } from '../common/use_embeddable_execution_context';
import { getSingleMetricViewerComponent } from '../../shared_components/single_metric_viewer';
import { EmbeddableSingleMetricViewerUserInput } from './single_metric_viewer_setup_flyout';
import { ensureMlAvailable } from '../../../common/license/ml_license';
import { checkPermissionAsync } from '../../application/capabilities/check_capabilities';

export const getSingleMetricViewerEmbeddableFactory = (
getStartServices: StartServicesAccessor<MlStartDependencies, MlPluginStart>,
Expand All @@ -48,7 +48,7 @@ export const getSingleMetricViewerEmbeddableFactory = (
> = {
type: ANOMALY_SINGLE_METRIC_VIEWER_EMBEDDABLE_TYPE,
buildEmbeddable: async ({ initialState, finalizeApi, uuid, parentApi }) => {
await ensureMlAvailable(getStartServices);
await checkPermissionAsync(getStartServices, 'canGetJobs', true);
const services = await getServices(getStartServices, usageCollection);
const subscriptions = new Subscription();
const titleManager = initializeTitleManager(initialState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
ANOMALY_SINGLE_METRIC_VIEWER_EMBEDDABLE_TYPE,
} from '../embeddables';
import { CONTROLLED_BY_ANOMALY_CHARTS_FILTER } from './constants';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export const APPLY_ENTITY_FIELD_FILTERS_ACTION = 'applyEntityFieldFiltersAction';

Expand Down Expand Up @@ -81,7 +81,7 @@ export function createApplyEntityFieldFiltersAction(
);
},
async isCompatible({ embeddable, data }) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canGetJobs'))) return false;
return (
(embeddable.type === ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE ||
embeddable.type === ANOMALY_SINGLE_METRIC_VIEWER_EMBEDDABLE_TYPE) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { VIEW_BY_JOB_LABEL } from '../application/explorer/explorer_constants';
import type { SwimLaneDrilldownContext } from '../embeddables';
import type { MlCoreSetup } from '../plugin';
import { CONTROLLED_BY_SWIM_LANE_FILTER } from './constants';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export const APPLY_INFLUENCER_FILTERS_ACTION = 'applyInfluencerFiltersAction';

Expand Down Expand Up @@ -77,7 +77,7 @@ export function createApplyInfluencerFiltersAction(
);
},
async isCompatible(context: EmbeddableApiContext) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canGetJobs'))) return false;
const [{ application }] = await getStartServices();
const appId = await firstValueFrom(application.currentAppId$);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { isAnomalySwimlaneSelectionTriggerContext } from './triggers';
import type { AppStateSelectedCells } from '../application/explorer/explorer_utils';
import type { AnomalySwimLaneEmbeddableApi } from '../embeddables/anomaly_swimlane/types';
import type { MlCoreSetup } from '../plugin';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export const APPLY_TIME_RANGE_SELECTION_ACTION = 'applyTimeRangeSelectionAction';

Expand Down Expand Up @@ -65,7 +65,7 @@ export function createApplyTimeRangeSelectionAction(
});
},
async isCompatible(context) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canGetJobs'))) return false;
const [{ application }] = await getStartServices();
const appId = await firstValueFrom(application.currentAppId$);
return isAnomalySwimlaneSelectionTriggerContext(context) && supportedApps.includes(appId!);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import { i18n } from '@kbn/i18n';
import type { UiActionsActionDefinition } from '@kbn/ui-actions-plugin/public';
import type { MlCoreSetup } from '../plugin';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export const CLEAR_SELECTION_ACTION = 'clearSelectionAction';

Expand All @@ -34,7 +34,7 @@ export function createClearSelectionAction(
updateCallback();
},
async isCompatible({ updateCallback }) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canGetJobs'))) return false;
return typeof updateCallback === 'function';
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type { AnomalyChartsEmbeddableApi } from '../embeddables';
import { ANOMALY_EXPLORER_CHARTS_EMBEDDABLE_TYPE } from '../embeddables';
import type { MlCoreSetup } from '../plugin';
import { EmbeddableAnomalyChartsUserInput } from '../embeddables/anomaly_charts/anomaly_charts_setup_flyout';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export const EDIT_ANOMALY_CHARTS_PANEL_ACTION = 'editAnomalyChartsPanelAction';

Expand Down Expand Up @@ -56,7 +56,7 @@ export function createAddAnomalyChartsPanelAction(
defaultMessage: 'View anomaly detection results in a chart.',
}),
async isCompatible(context: EmbeddableApiContext) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canGetJobs'))) return false;
return Boolean(await parentApiIsCompatible(context.embeddable));
},
async execute(context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ANOMALY_SINGLE_METRIC_VIEWER_EMBEDDABLE_TYPE } from '../embeddables';
import type { SingleMetricViewerEmbeddableApi } from '../embeddables/types';
import type { MlCoreSetup } from '../plugin';
import { EmbeddableSingleMetricViewerUserInput } from '../embeddables/single_metric_viewer/single_metric_viewer_setup_flyout';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export type CreateSingleMetricViewerPanelActionContext = EmbeddableApiContext & {
embeddable: SingleMetricViewerEmbeddableApi;
Expand Down Expand Up @@ -57,7 +57,7 @@ export function createAddSingleMetricViewerPanelAction(
'View anomaly detection results for a single metric on a focused date range.',
}),
async isCompatible(context: EmbeddableApiContext) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canGetJobs'))) return false;
return Boolean(await parentApiIsCompatible(context.embeddable));
},
async execute(context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { ANOMALY_SWIMLANE_EMBEDDABLE_TYPE } from '../embeddables';
import type { AnomalySwimLaneEmbeddableApi } from '../embeddables/anomaly_swimlane/types';
import type { MlCoreSetup } from '../plugin';
import { AnomalySwimlaneUserInput } from '../embeddables/anomaly_swimlane/anomaly_swimlane_setup_flyout';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export const EDIT_SWIMLANE_PANEL_ACTION = 'editSwimlanePanelAction';

Expand Down Expand Up @@ -56,7 +56,7 @@ export function createAddSwimlanePanelAction(
defaultMessage: 'View anomaly detection results in a timeline.',
}),
async isCompatible(context: EmbeddableApiContext) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canGetJobs'))) return false;
return Boolean(await parentApiIsCompatible(context.embeddable));
},
async execute(context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
type CreateCategorizationADJobContext,
} from '@kbn/ml-ui-actions';
import type { MlCoreSetup } from '../plugin';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export function createCategorizationADJobAction(
getStartServices: MlCoreSetup['getStartServices']
Expand Down Expand Up @@ -51,7 +51,7 @@ export function createCategorizationADJobAction(
}
},
async isCompatible({ dataView, field }: CreateCategorizationADJobContext) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canCreateJob'))) return false;
return (
dataView.timeFieldName !== undefined &&
dataView.fields.find((f) => f.name === field.name) !== undefined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import type { AnomalySwimLaneEmbeddableApi } from '../embeddables/anomaly_swimla
import { isSwimLaneEmbeddableContext } from '../embeddables/anomaly_swimlane/types';
import type { MlCoreSetup } from '../plugin';
import { getEmbeddableTimeRange } from './get_embeddable_time_range';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export interface OpenInAnomalyExplorerSwimLaneActionContext extends EmbeddableApiContext {
embeddable: AnomalySwimLaneEmbeddableApi;
Expand Down Expand Up @@ -155,7 +155,7 @@ export function createOpenInExplorerAction(
}
},
async isCompatible(context: EmbeddableApiContext) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canGetJobs'))) return false;
return isSwimLaneEmbeddableContext(context) || isAnomalyChartsEmbeddableContext(context);
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { ANOMALY_SINGLE_METRIC_VIEWER_EMBEDDABLE_TYPE } from '../embeddables';

import type { MlCoreSetup } from '../plugin';
import { getEmbeddableTimeRange } from './get_embeddable_time_range';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export interface OpenInSingleMetricViewerActionContext extends EmbeddableApiContext {
embeddable: SingleMetricViewerEmbeddableApi;
Expand Down Expand Up @@ -92,7 +92,7 @@ export function createOpenInSingleMetricViewerAction(
}
},
async isCompatible(context: EmbeddableApiContext) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canGetJobs'))) return false;
return isSingleMetricViewerEmbeddableContext(context);
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { isOfAggregateQueryType } from '@kbn/es-query';
import { LENS_EMBEDDABLE_TYPE } from '@kbn/lens-common';
import type { ActionApi } from './types';
import type { MlCoreSetup } from '../plugin';
import { isMlAvailable } from '../../common/license/ml_license';
import { checkPermissionAsync } from '../application/capabilities/check_capabilities';

export const CREATE_LENS_VIS_TO_ML_AD_JOB_ACTION = 'createMLADJobAction';

Expand Down Expand Up @@ -58,7 +58,7 @@ export function createVisToADJobAction(
}
},
async isCompatible({ embeddable }: EmbeddableApiContext) {
if (!(await isMlAvailable(getStartServices))) return false;
if (!(await checkPermissionAsync(getStartServices, 'canGetJobs'))) return false;
if (
!isApiCompatible(embeddable) ||
!(apiIsOfType(embeddable, LENS_EMBEDDABLE_TYPE) || apiIsOfType(embeddable, 'map'))
Expand Down
Loading