Skip to content

Commit

Permalink
stop multiple deployments
Browse files Browse the repository at this point in the history
  • Loading branch information
darnautov committed Apr 20, 2023
1 parent 728a07e commit 8ad8e57
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,63 @@
* 2.0.
*/

import React, { FC } from 'react';
import { EuiConfirmModal } from '@elastic/eui';
import React, { type FC, useState } from 'react';
import { EuiCheckboxGroup, EuiCheckboxGroupOption, EuiConfirmModal } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
import type { OverlayStart, ThemeServiceStart } from '@kbn/core/public';
import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import type { ModelItem } from './models_list';

interface ForceStopModelConfirmDialogProps {
model: ModelItem;
onCancel: () => void;
onConfirm: () => void;
onConfirm: (deploymentIds: string[]) => void;
}

/**
* Confirmation is required when there are multiple model deployments
* or associated pipelines.
*/
export const StopModelDeploymentsConfirmDialog: FC<ForceStopModelConfirmDialogProps> = ({
model,
onConfirm,
onCancel,
}) => {
const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState<Record<string, boolean>>(
{}
);

const options: EuiCheckboxGroupOption[] = model.deployment_ids.map((deploymentId) => {
return {
id: deploymentId,
label: deploymentId,
};
});

const onChange = (id: string) => {
setCheckboxIdToSelectedMap((prev) => {
return {
...prev,
[id]: !prev[id],
};
});
};

const selectedDeploymentIds = Object.keys(checkboxIdToSelectedMap).filter(
(id) => checkboxIdToSelectedMap[id]
);

return (
<EuiConfirmModal
title={i18n.translate('xpack.ml.trainedModels.modelsList.forceStopDialog.title', {
defaultMessage: 'Stop model {modelId}?',
values: { modelId: model.model_id },
defaultMessage:
'Stop {deploymentCount, plural, one {deployment} other {deployments}} of model {modelId}?',
values: { modelId: model.model_id, deploymentCount: model.deployment_ids.length },
})}
onCancel={onCancel}
onConfirm={onConfirm}
onConfirm={onConfirm.bind(null, selectedDeploymentIds)}
cancelButtonText={i18n.translate(
'xpack.ml.trainedModels.modelsList.forceStopDialog.cancelText',
{ defaultMessage: 'Cancel' }
Expand All @@ -41,24 +71,47 @@ export const StopModelDeploymentsConfirmDialog: FC<ForceStopModelConfirmDialogPr
{ defaultMessage: 'Stop' }
)}
buttonColor="danger"
confirmButtonDisabled={model.deployment_ids.length > 1 && selectedDeploymentIds.length === 0}
>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.forceStopDialog.pipelinesWarning"
defaultMessage="You can't use these ingest pipelines until you restart the model:"
/>
<ul>
{Object.keys(model.pipelines!)
.sort()
.map((pipelineName) => {
return <li key={pipelineName}>{pipelineName}</li>;
})}
</ul>
{model.deployment_ids.length > 1 ? (
<EuiCheckboxGroup
legend={{
display: 'visible',
children: (
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.forceStopDialog.selectDeploymentsLegend"
defaultMessage="Select deployments to stop"
/>
),
}}
options={options}
idToSelectedMap={checkboxIdToSelectedMap}
onChange={onChange}
/>
) : null}

{isPopulatedObject(model.pipelines) ? (
<>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.forceStopDialog.pipelinesWarning"
defaultMessage="You can't use these ingest pipelines until you restart the model:"
/>
<ul>
{Object.keys(model.pipelines!)
.sort()
.map((pipelineName) => {
return <li key={pipelineName}>{pipelineName}</li>;
})}
</ul>
</>
) : null}
</EuiConfirmModal>
);
};

export const getUserConfirmationProvider =
(overlays: OverlayStart, theme: ThemeServiceStart) => async (forceStopModel: ModelItem) => {
(overlays: OverlayStart, theme: ThemeServiceStart) =>
async (forceStopModel: ModelItem): Promise<string[]> => {
return new Promise(async (resolve, reject) => {
try {
const modalSession = overlays.openModal(
Expand All @@ -68,19 +121,19 @@ export const getUserConfirmationProvider =
model={forceStopModel}
onCancel={() => {
modalSession.close();
resolve(false);
reject();
}}
onConfirm={() => {
onConfirm={(deploymentIds: string[]) => {
modalSession.close();
resolve(true);
resolve(deploymentIds);
}}
/>,
theme.theme$
)
)
);
} catch (e) {
resolve(false);
reject();
}
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -275,14 +275,18 @@ export function useModelActions({
const requireForceStop = isPopulatedObject(item.pipelines);
const hasMultipleDeployments = item.deployment_ids.length > 1;

let deploymentIds: string[] = [item.model_id];
if (requireForceStop || hasMultipleDeployments) {
const hasUserApproved = await getUserConfirmation(item);
if (!hasUserApproved) return;
try {
deploymentIds = await getUserConfirmation(item);
} catch (error) {
return;
}
}

try {
onLoading(true);
await trainedModelsApiService.stopModelAllocation(item.model_id, {
await trainedModelsApiService.stopModelAllocation(deploymentIds, {
force: requireForceStop,
});
displaySuccessToast(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ export function trainedModelsApiProvider(httpService: HttpService) {
});
},

stopModelAllocation(modelId: string, options: { force: boolean } = { force: false }) {
stopModelAllocation(deploymentsIds: string[], options: { force: boolean } = { force: false }) {
const force = options?.force;

return httpService.http<{ acknowledge: boolean }>({
path: `${apiBasePath}/trained_models/${modelId}/deployment/_stop`,
path: `${apiBasePath}/trained_models/${deploymentsIds.join(',')}/deployment/_stop`,
method: 'POST',
query: { force },
});
Expand Down

0 comments on commit 8ad8e57

Please sign in to comment.