in x-pack/platform/plugins/shared/ml/public/application/model_management/model_actions.tsx [42:523]
export function useModelActions({
onDfaTestAction,
onTestAction,
onModelsDeleteRequest,
onModelDeployRequest,
onModelDownloadRequest,
modelAndDeploymentIds,
}: {
onDfaTestAction: (model: DFAModelItem) => void;
onTestAction: (model: TrainedModelItem) => void;
onModelsDeleteRequest: (models: TrainedModelUIItem[]) => void;
onModelDeployRequest: (model: DFAModelItem) => void;
onModelDownloadRequest: (modelId: string) => void;
modelAndDeploymentIds: string[];
}): Array<Action<TrainedModelUIItem>> {
const isMobileLayout = useIsWithinMaxBreakpoint('l');
const isMounted = useMountedState();
const {
services: {
application: { navigateToUrl },
overlays,
docLinks,
mlServices: { mlApi, httpService, trainedModelsService, mlCapabilities },
...startServices
},
} = useMlKibana();
const { showNodeInfo } = useEnabledFeatures();
const { nlpSettings } = useMlServerInfo();
const cloudInfo = useCloudCheck();
const isLoading = useObservable(trainedModelsService.isLoading$, trainedModelsService.isLoading);
const scheduledDeployments = useObservable(
trainedModelsService.scheduledDeployments$,
trainedModelsService.scheduledDeployments
);
const [
canCreateTrainedModels,
canStartStopTrainedModels,
canTestTrainedModels,
canDeleteTrainedModels,
] = usePermissionCheck([
'canCreateTrainedModels',
'canStartStopTrainedModels',
'canTestTrainedModels',
'canDeleteTrainedModels',
]);
const [canManageIngestPipelines, setCanManageIngestPipelines] = useState<boolean>(false);
const startModelDeploymentDocUrl = docLinks.links.ml.startTrainedModelsDeployment;
const navigateToPath = useNavigateToPath();
const urlLocator = useMlLocator()!;
useEffect(() => {
mlApi
.hasPrivileges({
cluster: ['manage_ingest_pipelines'],
})
.then((result) => {
if (isMounted()) {
setCanManageIngestPipelines(
result.hasPrivileges === undefined ||
result.hasPrivileges.cluster?.manage_ingest_pipelines === true
);
}
});
}, [mlApi, isMounted]);
const getUserConfirmation = useMemo(
() => getUserConfirmationProvider(overlays, startServices),
[overlays, startServices]
);
const getUserInputModelDeploymentParams = useMemo(
() =>
getUserInputModelDeploymentParamsProvider(
overlays,
startServices,
startModelDeploymentDocUrl,
cloudInfo,
showNodeInfo,
nlpSettings,
httpService,
trainedModelsService,
mlCapabilities
),
[
overlays,
startServices,
startModelDeploymentDocUrl,
cloudInfo,
showNodeInfo,
nlpSettings,
httpService,
trainedModelsService,
mlCapabilities,
]
);
return useMemo<Array<Action<TrainedModelUIItem>>>(
() => [
{
name: i18n.translate('xpack.ml.trainedModels.modelsList.viewTrainingDataNameActionLabel', {
defaultMessage: 'View training data',
}),
description: i18n.translate(
'xpack.ml.trainedModels.modelsList.viewTrainingDataActionLabel',
{
defaultMessage: 'Training data can be viewed when data frame analytics job exists.',
}
),
icon: 'visTable',
type: 'icon',
available: (item) => isDFAModelItem(item) && !!item.metadata?.analytics_config?.id,
enabled: (item) => isDFAModelItem(item) && item.origin_job_exists === true,
onClick: async (item) => {
if (!isDFAModelItem(item) || item.metadata?.analytics_config === undefined) return;
const analysisType = getAnalysisType(
item.metadata?.analytics_config.analysis
) as DataFrameAnalysisConfigType;
const url = await urlLocator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_EXPLORATION,
pageState: {
jobId: item.metadata?.analytics_config.id as string,
analysisType,
...(analysisType === 'classification' || analysisType === 'regression'
? {
queryText: `${item.metadata?.analytics_config.dest.results_field}.is_training : true`,
}
: {}),
},
});
await navigateToUrl(url);
},
},
{
name: i18n.translate('xpack.ml.inference.modelsList.analyticsMapActionLabel', {
defaultMessage: 'Analytics map',
}),
description: i18n.translate('xpack.ml.inference.modelsList.analyticsMapActionLabel', {
defaultMessage: 'Analytics map',
}),
icon: 'graphApp',
type: 'icon',
isPrimary: true,
available: (item) => isDFAModelItem(item) && !!item.metadata?.analytics_config?.id,
onClick: async (item) => {
const path = await urlLocator.getUrl({
page: ML_PAGES.DATA_FRAME_ANALYTICS_MAP,
pageState: { modelId: item.model_id },
});
await navigateToPath(path, false);
},
},
{
name: i18n.translate('xpack.ml.inference.modelsList.startModelDeploymentActionLabel', {
defaultMessage: 'Start deployment',
}),
description: i18n.translate(
'xpack.ml.inference.modelsList.startModelDeploymentActionDescription',
{
defaultMessage: 'Start deployment',
}
),
'data-test-subj': 'mlModelsTableRowStartDeploymentAction',
icon: 'play',
// @ts-ignore
type: isMobileLayout ? 'icon' : 'button',
isPrimary: true,
color: 'success',
enabled: (item) => {
const isModelBeingDeployed = scheduledDeployments.some(
(deployment) => deployment.modelId === item.model_id
);
if (
isModelDownloadItem(item) &&
item.state === MODEL_STATE.DOWNLOADED_IN_DIFFERENT_SPACE
) {
return false;
}
return canStartStopTrainedModels && !isModelBeingDeployed;
},
available: (item) => {
return (
isNLPModelItem(item) ||
(canCreateTrainedModels &&
isModelDownloadItem(item) &&
(item.state === MODEL_STATE.NOT_DOWNLOADED ||
item.state === MODEL_STATE.DOWNLOADED_IN_DIFFERENT_SPACE))
);
},
onClick: async (item) => {
if (isModelDownloadItem(item) && item.state === MODEL_STATE.NOT_DOWNLOADED) {
onModelDownloadRequest(item.model_id);
}
const modelDeploymentParams = await getUserInputModelDeploymentParams(
item.model_id,
undefined,
modelAndDeploymentIds
);
if (!modelDeploymentParams) return;
trainedModelsService.startModelDeployment(item.model_id, modelDeploymentParams);
},
},
{
name: i18n.translate('xpack.ml.inference.modelsList.updateModelDeploymentActionLabel', {
defaultMessage: 'Update deployment',
}),
description: i18n.translate(
'xpack.ml.inference.modelsList.updateModelDeploymentActionLabel',
{
defaultMessage: 'Update deployment',
}
),
'data-test-subj': 'mlModelsTableRowUpdateDeploymentAction',
icon: 'documentEdit',
type: 'icon',
isPrimary: false,
available: (item) =>
isNLPModelItem(item) &&
canStartStopTrainedModels &&
!isLoading &&
!!item.stats?.deployment_stats?.some((v) => v.state === DEPLOYMENT_STATE.STARTED),
onClick: async (item) => {
if (!isNLPModelItem(item)) return;
const deploymentIdToUpdate = item.deployment_ids[0];
const targetDeployment = item.stats!.deployment_stats.find(
(v) => v.deployment_id === deploymentIdToUpdate
)!;
const deploymentParams = await getUserInputModelDeploymentParams(
item.model_id,
targetDeployment
);
if (!deploymentParams) return;
trainedModelsService.updateModelDeployment(item.model_id, deploymentParams);
},
},
{
name: i18n.translate('xpack.ml.inference.modelsList.stopModelDeploymentActionLabel', {
defaultMessage: 'Stop deployment',
}),
description: i18n.translate(
'xpack.ml.inference.modelsList.stopModelDeploymentActionLabel',
{
defaultMessage: 'Stop deployment',
}
),
'data-test-subj': 'mlModelsTableRowStopDeploymentAction',
icon: 'stop',
type: 'icon',
isPrimary: false,
available: (item) =>
isNLPModelItem(item) &&
canStartStopTrainedModels &&
// Deployment can be either started, starting, or exist in a failed state
(item.state === MODEL_STATE.STARTED || item.state === MODEL_STATE.STARTING) &&
// Only show the action if there is at least one deployment that is not used by the inference service
(!Array.isArray(item.inference_apis) ||
item.deployment_ids.some(
(dId) =>
Array.isArray(item.inference_apis) &&
!item.inference_apis.some((inference) => inference.inference_id === dId)
)),
enabled: (item) =>
!isLoading && !scheduledDeployments.some((d) => d.modelId === item.model_id),
onClick: async (item) => {
if (!isNLPModelItem(item)) return;
const requireForceStop = isPopulatedObject(item.pipelines);
const hasMultipleDeployments = item.deployment_ids.length > 1;
let deploymentIds: string[] = item.deployment_ids;
if (requireForceStop || hasMultipleDeployments) {
try {
deploymentIds = await getUserConfirmation(item);
} catch (error) {
return;
}
}
trainedModelsService.stopModelDeployment(item.model_id, deploymentIds, {
force: requireForceStop,
});
},
},
{
name: (model) => {
return i18n.translate('xpack.ml.trainedModels.modelsList.deployModelActionLabel', {
defaultMessage: 'Deploy model',
});
},
description: i18n.translate('xpack.ml.trainedModels.modelsList.deployModelActionLabel', {
defaultMessage: 'Deploy model',
}),
'data-test-subj': 'mlModelsTableRowDeployAction',
icon: 'continuityAbove',
type: 'icon',
isPrimary: false,
onClick: (model) => {
onModelDeployRequest(model as DFAModelItem);
},
available: (item) => {
return isDFAModelItem(item) && canManageIngestPipelines;
},
enabled: (item) => {
return canStartStopTrainedModels;
},
},
{
name: i18n.translate('xpack.ml.inference.modelsList.testModelActionLabel', {
defaultMessage: 'Test',
}),
description: i18n.translate('xpack.ml.inference.modelsList.testModelActionLabel', {
defaultMessage: 'Test model',
}),
'data-test-subj': 'mlModelsTableRowTestAction',
icon: 'inputOutput',
// @ts-ignore
type: isMobileLayout ? 'icon' : 'button',
isPrimary: true,
available: (item) => isTestable(item, true),
onClick: (item) => {
if (isDFAModelItem(item)) {
onDfaTestAction(item);
} else if (isExistingModel(item)) {
onTestAction(item);
}
},
enabled: (item) => {
return canTestTrainedModels && !isLoading;
},
},
{
name: i18n.translate('xpack.ml.inference.modelsList.analyzeDataDriftLabel', {
defaultMessage: 'Analyze data drift',
}),
description: i18n.translate('xpack.ml.inference.modelsList.analyzeDataDriftLabel', {
defaultMessage: 'Analyze data drift',
}),
'data-test-subj': 'mlModelsAnalyzeDataDriftAction',
icon: 'visTagCloud',
type: 'icon',
isPrimary: true,
available: (item) => {
return (
isDFAModelItem(item) ||
(isExistingModel(item) && Array.isArray(item.indices) && item.indices.length > 0)
);
},
onClick: async (item) => {
if (!isDFAModelItem(item) || !isExistingModel(item)) return;
let indexPatterns: string[] | undefined = item.indices;
if (isDFAModelItem(item) && item?.metadata?.analytics_config?.dest?.index !== undefined) {
const destIndex = item.metadata.analytics_config.dest?.index;
indexPatterns = [destIndex];
}
const path = await urlLocator.getUrl({
page: ML_PAGES.DATA_DRIFT_CUSTOM,
pageState: indexPatterns ? { comparison: indexPatterns.join(',') } : {},
});
await navigateToPath(path, false);
},
},
{
name: (model) => {
return isModelDownloadItem(model) && model.state === MODEL_STATE.DOWNLOADING ? (
<>
{i18n.translate('xpack.ml.trainedModels.modelsList.deleteModelActionLabel', {
defaultMessage: 'Cancel',
})}
</>
) : (
<>
{i18n.translate('xpack.ml.trainedModels.modelsList.deleteModelActionLabel', {
defaultMessage: 'Delete model',
})}
</>
);
},
description: (model: TrainedModelUIItem) => {
if (isModelDownloadItem(model) && model.state === MODEL_STATE.DOWNLOADING) {
return i18n.translate('xpack.ml.trainedModels.modelsList.cancelDownloadActionLabel', {
defaultMessage: 'Cancel download',
});
} else if (isNLPModelItem(model)) {
const hasDeployments = model.deployment_ids?.length ?? 0 > 0;
const { hasInferenceServices } = model;
if (hasInferenceServices) {
return i18n.translate(
'xpack.ml.trainedModels.modelsList.deleteDisabledWithInferenceServicesTooltip',
{
defaultMessage: 'Model is used by the _inference API',
}
);
} else if (hasDeployments) {
return i18n.translate(
'xpack.ml.trainedModels.modelsList.deleteDisabledWithDeploymentsTooltip',
{
defaultMessage: 'Model has started deployments',
}
);
}
}
return i18n.translate('xpack.ml.trainedModels.modelsList.deleteModelActionLabel', {
defaultMessage: 'Delete model',
});
},
'data-test-subj': 'mlModelsTableRowDeleteAction',
icon: 'trash',
// @ts-ignore
type: isMobileLayout ? 'icon' : 'button',
color: 'danger',
isPrimary: false,
onClick: (model) => {
onModelsDeleteRequest([model]);
},
available: (item) => {
if (!canDeleteTrainedModels || isBuiltInModel(item)) return false;
if (isModelDownloadItem(item)) {
return !!item.downloadState;
} else {
const hasZeroPipelines = Object.keys(item.pipelines ?? {}).length === 0;
return hasZeroPipelines || canManageIngestPipelines;
}
},
enabled: (item) => {
return (
!isNLPModelItem(item) ||
(item.state !== MODEL_STATE.STARTED && item.state !== MODEL_STATE.STARTING)
);
},
},
],
[
isMobileLayout,
urlLocator,
navigateToUrl,
navigateToPath,
scheduledDeployments,
canStartStopTrainedModels,
canCreateTrainedModels,
getUserInputModelDeploymentParams,
modelAndDeploymentIds,
trainedModelsService,
onModelDownloadRequest,
isLoading,
getUserConfirmation,
onModelDeployRequest,
canManageIngestPipelines,
onDfaTestAction,
onTestAction,
canTestTrainedModels,
onModelsDeleteRequest,
canDeleteTrainedModels,
]
);
}