[8.8] [ML] Disable delete action for deployed models (#158533) (#158572)

# Backport

This will backport the following commits from `main` to `8.8`:
- [[ML] Disable delete action for deployed models
(#158533)](https://github.com/elastic/kibana/pull/158533)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Dima
Arnautov","email":"dmitrii.arnautov@elastic.co"},"sourceCommit":{"committedDate":"2023-05-26T15:17:38Z","message":"[ML]
Disable delete action for deployed models (#158533)\n\n##
Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/157692.\r\n\r\nDisables delete
action for deployed models.\r\n\r\n<img width=\"1506\"
alt=\"image\"\r\nsrc=\"8e44dcca-789e-41c3-9dca-87034f84390b\">\r\n\r\n\r\n###
Checklist\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"59b1ba976720e69588ef2abad92b65fa523f6b9a","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix",":ml","Feature:3rd
Party
Models","Team:ML","v8.9.0","v8.8.1"],"number":158533,"url":"https://github.com/elastic/kibana/pull/158533","mergeCommit":{"message":"[ML]
Disable delete action for deployed models (#158533)\n\n##
Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/157692.\r\n\r\nDisables delete
action for deployed models.\r\n\r\n<img width=\"1506\"
alt=\"image\"\r\nsrc=\"8e44dcca-789e-41c3-9dca-87034f84390b\">\r\n\r\n\r\n###
Checklist\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"59b1ba976720e69588ef2abad92b65fa523f6b9a"}},"sourceBranch":"main","suggestedTargetBranches":["8.8"],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/158533","number":158533,"mergeCommit":{"message":"[ML]
Disable delete action for deployed models (#158533)\n\n##
Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/157692.\r\n\r\nDisables delete
action for deployed models.\r\n\r\n<img width=\"1506\"
alt=\"image\"\r\nsrc=\"8e44dcca-789e-41c3-9dca-87034f84390b\">\r\n\r\n\r\n###
Checklist\r\n\r\n- [x] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n-
[x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"59b1ba976720e69588ef2abad92b65fa523f6b9a"}},{"branch":"8.8","label":"v8.8.1","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Dima Arnautov <dmitrii.arnautov@elastic.co>
This commit is contained in:
Kibana Machine 2023-05-26 12:34:28 -04:00 committed by GitHub
parent eabb7daf21
commit 2940ec9f31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 54 additions and 13 deletions

View file

@ -68,3 +68,5 @@ export const MODEL_STATE = {
defaultMessage: 'downloaded',
}),
} as const;
export type ModelState = typeof MODEL_STATE[keyof typeof MODEL_STATE];

View file

@ -392,16 +392,24 @@ export function useModelActions({
},
{
name: (model) => {
const enabled = !isPopulatedObject(model.pipelines);
const hasPipelines = isPopulatedObject(model.pipelines);
const hasDeployments = model.state === MODEL_STATE.STARTED;
return (
<EuiToolTip
position="left"
content={
enabled
? null
: i18n.translate('xpack.ml.trainedModels.modelsList.deleteDisabledTooltip', {
hasPipelines
? i18n.translate('xpack.ml.trainedModels.modelsList.deleteDisabledTooltip', {
defaultMessage: 'Model has associated pipelines',
})
: hasDeployments
? i18n.translate(
'xpack.ml.trainedModels.modelsList.deleteDisabledWithDeploymentsTooltip',
{
defaultMessage: 'Model has started deployments',
}
)
: null
}
>
<>
@ -428,7 +436,7 @@ export function useModelActions({
enabled: (item) => {
// TODO check for permissions to delete ingest pipelines.
// ATM undefined means pipelines fetch failed server-side.
return !isPopulatedObject(item.pipelines);
return item.state !== MODEL_STATE.STARTED && !isPopulatedObject(item.pipelines);
},
},
{

View file

@ -38,6 +38,7 @@ import {
ELASTIC_MODEL_TAG,
ELASTIC_MODEL_TYPE,
MODEL_STATE,
ModelState,
} from '@kbn/ml-trained-models-utils/src/constants/trained_models';
import { TechnicalPreviewBadge } from '../components/technical_preview_badge';
import { useModelActions } from './model_actions';
@ -70,7 +71,7 @@ export type ModelItem = TrainedModelConfigResponse & {
pipelines?: ModelPipelines['pipelines'] | null;
deployment_ids: string[];
putModelConfig?: object;
state: string;
state: ModelState;
};
export type ModelItemFull = Required<ModelItem>;

View file

@ -210,10 +210,12 @@ export default function ({ getService }: FtrProviderContext) {
numOfAllocations: 1,
threadsPerAllocation: 2,
});
await ml.trainedModelsTable.assertModelDeleteActionButtonEnabled(model.id, false);
});
it(`stops deployment of the imported model ${model.id}`, async () => {
await ml.trainedModelsTable.stopDeployment(model.id);
await ml.trainedModelsTable.assertModelDeleteActionButtonEnabled(model.id, true);
});
it(`deletes the imported model ${model.id}`, async () => {

View file

@ -29,6 +29,8 @@ export function TrainedModelsTableProvider(
) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
const find = getService('find');
const browser = getService('browser');
return new (class ModelsTable {
public async parseModelsTable() {
@ -161,9 +163,7 @@ export function TrainedModelsTableProvider(
}
public async assertModelCollapsedActionsButtonExists(modelId: string, expectedValue: boolean) {
const actionsExists = await testSubjects.exists(
this.rowSelector(modelId, 'euiCollapsedItemActionsButton')
);
const actionsExists = await this.doesModelCollapsedActionsButtonExist(modelId);
expect(actionsExists).to.eql(
expectedValue,
`Expected row collapsed actions menu button for trained model '${modelId}' to be ${
@ -172,6 +172,20 @@ export function TrainedModelsTableProvider(
);
}
public async doesModelCollapsedActionsButtonExist(modelId: string): Promise<boolean> {
return await testSubjects.exists(this.rowSelector(modelId, 'euiCollapsedItemActionsButton'));
}
public async toggleActionsContextMenu(modelId: string, expectOpen = true) {
await testSubjects.click(this.rowSelector(modelId, 'euiCollapsedItemActionsButton'));
const panelElement = await find.byCssSelector('.euiContextMenuPanel');
const isDisplayed = await panelElement.isDisplayed();
expect(isDisplayed).to.eql(
expectOpen,
`Expected the action context menu for '${modelId}' to be ${expectOpen ? 'open' : 'closed'}`
);
}
public async assertModelDeleteActionButtonExists(modelId: string, expectedValue: boolean) {
const actionsExists = await testSubjects.exists(
this.rowSelector(modelId, 'mlModelsTableRowDeleteAction')
@ -224,10 +238,24 @@ export function TrainedModelsTableProvider(
}
public async assertModelDeleteActionButtonEnabled(modelId: string, expectedValue: boolean) {
await this.assertModelDeleteActionButtonExists(modelId, true);
const isEnabled = await testSubjects.isEnabled(
this.rowSelector(modelId, 'mlModelsTableRowDeleteAction')
);
const actionsButtonExists = await this.doesModelCollapsedActionsButtonExist(modelId);
let isEnabled = null;
if (actionsButtonExists) {
await this.toggleActionsContextMenu(modelId, true);
const panelElement = await find.byCssSelector('.euiContextMenuPanel');
const actionButton = await panelElement.findByTestSubject('mlModelsTableRowDeleteAction');
isEnabled = await actionButton.isEnabled();
// escape popover
await browser.pressKeys(browser.keys.ESCAPE);
} else {
await this.assertModelDeleteActionButtonExists(modelId, true);
isEnabled = await testSubjects.isEnabled(
this.rowSelector(modelId, 'mlModelsTableRowDeleteAction')
);
}
expect(isEnabled).to.eql(
expectedValue,
`Expected row delete action button for trained model '${modelId}' to be '${