[UA] ML anomaly job indices special handling (#211407)

Close https://github.com/elastic/kibana-team/issues/1501


<img width="535" alt="Screenshot 2025-02-17 at 11 43 19"
src="https://github.com/user-attachments/assets/648cc007-aef8-4959-add8-6aec943e9e41"
/>
This commit is contained in:
Jean-Louis Leysens 2025-02-18 12:40:27 +01:00 committed by GitHub
parent 9c7c4da7c9
commit 5d3fb74647
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 224 additions and 19 deletions

View file

@ -596,6 +596,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
nlpElser: `${MACHINE_LEARNING_DOCS}ml-nlp-elser.html`,
nlpE5: `${MACHINE_LEARNING_DOCS}ml-nlp-e5.html`,
nlpImportModel: `${MACHINE_LEARNING_DOCS}ml-nlp-import-model.html`,
anomalyMigrationGuide: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/master/migrating-9.0.html#breaking_90_anomaly_detection_results`,
},
transforms: {
guide: isServerless

View file

@ -0,0 +1,98 @@
/*
* 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 { FunctionComponent } from 'react';
import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiCallOut, EuiDescriptionList, EuiLink, EuiText } from '@elastic/eui';
import { useAppContext } from '../../../../../../../app_context';
export const MlAnomalyGuidance: FunctionComponent = () => {
const {
services: {
core: { docLinks },
},
} = useAppContext();
return (
<>
<p>
<EuiCallOut
title={i18n.translate(
'xpack.upgradeAssistant.esDeprecations.indices.indexFlyout.detailsStep.notCompatibleMlAnomalyIndexTitle',
{ defaultMessage: 'ML anomaly index detected' }
)}
>
<FormattedMessage
id="xpack.upgradeAssistant.esDeprecations.indices.indexFlyout.detailsStep.notCompatibleMlAnomalyIndexText"
defaultMessage="Anomaly result indices that were created in 7.x must be either reindexed, marked as read-only, or deleted before upgrading to 9.x. {learnMore}."
values={{
learnMore: (
<EuiLink target="_blank" href={docLinks.links.ml.anomalyMigrationGuide}>
{i18n.translate(
'xpack.upgradeAssistant.esDeprecations.indices.indexFlyout.detailsStep.notCompatibleMlAnomalyIndexText.learnMore',
{ defaultMessage: 'Learn more' }
)}
</EuiLink>
),
}}
/>
</EuiCallOut>
</p>
<EuiDescriptionList
rowGutterSize="m"
listItems={[
{
title: 'Option 1: Reindex data',
description: (
<EuiText size="m">
<FormattedMessage
id="xpack.upgradeAssistant.esDeprecations.indices.indexFlyout.detailsStep.reindexMlAnomalyIndexText"
defaultMessage="While anomaly detection results are being reindexed, jobs continue to run and process new data. However, you cannot completely delete an anomaly detection job that stores results in this index until the reindexing is complete."
/>
</EuiText>
),
},
{
title: 'Option 2: Mark as read-only',
description: (
<EuiText size="m">
<FormattedMessage
id="xpack.upgradeAssistant.esDeprecations.indices.indexFlyout.detailsStep.readOnlyMlAnomalyIndexText"
defaultMessage="This skips reindexing and will mark the result index as read-only. It is useful for large indices that contain the results of only one or a few anomaly detection jobs. If you delete these jobs later, you will not be able to create a new job with the same name. {learnMore} about write blocks."
values={{
learnMore: (
<EuiLink target="_blank" href={docLinks.links.upgradeAssistant.indexBlocks}>
{i18n.translate(
'xpack.upgradeAssistant.esDeprecations.indices.indexFlyout.learnMoreLinkLabel',
{
defaultMessage: 'Learn more',
}
)}
</EuiLink>
),
}}
/>
</EuiText>
),
},
{
title: 'Option 3: Delete this index',
description: (
<EuiText size="m">
<FormattedMessage
id="xpack.upgradeAssistant.esDeprecations.indices.indexFlyout.detailsStep.deleteMlAnomalyIndexText"
defaultMessage="Use the ML UI to delete jobs that are no longer needed. The result index is deleted when all jobs that store results in it have been deleted."
/>
</EuiText>
),
},
]}
/>
</>
);
};

View file

@ -11,7 +11,6 @@ import { ReindexDetailsFlyoutStep } from './reindex_details_step';
import type { ReindexState } from '../../../use_reindex';
import type { UpdateIndexState } from '../../../use_update_index';
import { LoadingState } from '../../../../../../types';
import { cloneDeep } from 'lodash';
import { EnrichedDeprecationInfo } from '../../../../../../../../../common/types';
jest.mock('../../../../../../../app_context', () => {
@ -39,14 +38,14 @@ jest.mock('../../../../../../../app_context', () => {
});
describe('ReindexDetailsFlyoutStep', () => {
const deprecation: EnrichedDeprecationInfo = {
const defaultDeprecation: () => EnrichedDeprecationInfo = () => ({
isCritical: true,
message: 'foo',
resolveDuringUpgrade: false,
type: 'index_settings',
url: 'https://te.st',
};
const defaultReindexState: ReindexState = {
});
const defaultReindexState: () => ReindexState = () => ({
loadingState: LoadingState.Success,
meta: {
indexName: 'some_index',
@ -58,12 +57,12 @@ describe('ReindexDetailsFlyoutStep', () => {
hasRequiredPrivileges: true,
reindexTaskPercComplete: null,
errorMessage: null,
};
});
const defaultUpdateIndexState: UpdateIndexState = {
const defaultUpdateIndexState: () => UpdateIndexState = () => ({
status: 'incomplete',
failedBefore: false,
};
});
it('renders for non-readonly indices', () => {
const wrapper = shallow(
@ -71,9 +70,9 @@ describe('ReindexDetailsFlyoutStep', () => {
closeFlyout={jest.fn()}
startReindex={jest.fn()}
startReadonly={jest.fn()}
reindexState={defaultReindexState}
updateIndexState={defaultUpdateIndexState}
deprecation={deprecation}
reindexState={defaultReindexState()}
updateIndexState={defaultUpdateIndexState()}
deprecation={defaultDeprecation()}
/>
);
@ -221,10 +220,10 @@ describe('ReindexDetailsFlyoutStep', () => {
closeFlyout={jest.fn()}
startReindex={jest.fn()}
startReadonly={jest.fn()}
reindexState={defaultReindexState}
updateIndexState={defaultUpdateIndexState}
reindexState={defaultReindexState()}
updateIndexState={defaultUpdateIndexState()}
deprecation={{
...deprecation,
...defaultDeprecation(),
correctiveAction: { type: 'reindex', transformIds: ['abc', 'def'] },
}}
/>
@ -304,7 +303,7 @@ describe('ReindexDetailsFlyoutStep', () => {
});
it('renders for readonly indices (warning deprecation)', () => {
const props = cloneDeep(defaultReindexState);
const props = defaultReindexState();
props.meta.isReadonly = true;
const wrapper = shallow(
@ -313,8 +312,8 @@ describe('ReindexDetailsFlyoutStep', () => {
startReindex={jest.fn()}
startReadonly={jest.fn()}
reindexState={props}
updateIndexState={defaultUpdateIndexState}
deprecation={deprecation}
updateIndexState={defaultUpdateIndexState()}
deprecation={defaultDeprecation()}
/>
);
@ -385,4 +384,91 @@ describe('ReindexDetailsFlyoutStep', () => {
</Fragment>
`);
});
it('renders ML anomaly index guidance', () => {
const reindexState = defaultReindexState();
reindexState.meta.indexName = '.ml-anomalies-1';
const deprecation = defaultDeprecation();
deprecation.index = '.ml-anomalies-1';
const wrapper = shallow(
<ReindexDetailsFlyoutStep
closeFlyout={jest.fn()}
startReindex={jest.fn()}
startReadonly={jest.fn()}
reindexState={reindexState}
updateIndexState={defaultUpdateIndexState()}
deprecation={deprecation}
/>
);
expect(wrapper).toMatchInlineSnapshot(`
<Fragment>
<EuiFlyoutBody>
<EuiText>
<MlAnomalyGuidance />
</EuiText>
<EuiSpacer />
</EuiFlyoutBody>
<EuiFlyoutFooter>
<EuiFlexGroup
justifyContent="spaceBetween"
>
<EuiFlexItem
grow={false}
>
<EuiButtonEmpty
flush="left"
iconType="cross"
onClick={[MockFunction]}
>
<MemoizedFormattedMessage
defaultMessage="Close"
id="xpack.upgradeAssistant.esDeprecations.indices.indexFlyout.closeButtonLabel"
/>
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiFlexGroup
gutterSize="s"
>
<EuiFlexItem
grow={false}
>
<EuiButton
data-test-subj="startIndexReadonlyButton"
disabled={false}
onClick={[MockFunction]}
>
<MemoizedFormattedMessage
defaultMessage="Mark as read-only"
id="xpack.upgradeAssistant.esDeprecations.indices.indexFlyout.detailsStep.startIndexReadonlyButton"
/>
</EuiButton>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiButton
color="primary"
data-test-subj="startReindexingButton"
disabled={false}
fill={true}
isLoading={false}
onClick={[MockFunction]}
>
<MemoizedFormattedMessage
defaultMessage="Start reindexing"
id="xpack.upgradeAssistant.esDeprecations.indices.indexFlyout.detailsStep.reindexButton.runReindexLabel"
/>
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlyoutFooter>
</Fragment>
`);
});
});

View file

@ -36,8 +36,11 @@ import { FrozenCallOut } from '../frozen_callout';
import type { UpdateIndexState } from '../../../use_update_index';
import { FetchFailedCallOut } from '../fetch_failed_callout';
import { ReindexingFailedCallOut } from '../reindexing_failed_callout';
import { MlAnomalyGuidance } from './ml_anomaly_guidance';
import { ESTransformsTargetGuidance } from './es_transform_target_guidance';
const ML_ANOMALIES_PREFIX = '.ml-anomalies-';
/**
* Displays a flyout that shows the details / corrective action for a "reindex" deprecation for a given index.
*/
@ -72,9 +75,25 @@ export const ReindexDetailsFlyoutStep: React.FunctionComponent<{
const hasReindexingFailed = reindexStatus === ReindexStatus.failed;
const correctiveAction = deprecation.correctiveAction as ReindexAction | undefined;
const isESTransformTarget = !!correctiveAction?.transformIds?.length;
const isMLAnomalyIndex = Boolean(indexName?.startsWith(ML_ANOMALIES_PREFIX));
const { data: nodes } = api.useLoadNodeDiskSpace();
let showEsTransformsGuidance = false;
let showMlAnomalyReindexingGuidance = false;
let showReadOnlyGuidance = false;
let showDefaultGuidance = false;
if (isESTransformTarget) {
showEsTransformsGuidance = true;
} else if (meta.isReadonly) {
showReadOnlyGuidance = true;
} else if (isMLAnomalyIndex) {
showMlAnomalyReindexingGuidance = true;
} else {
showDefaultGuidance = true;
}
return (
<Fragment>
<EuiFlyoutBody>
@ -144,7 +163,9 @@ export const ReindexDetailsFlyoutStep: React.FunctionComponent<{
{meta.isFrozen && <FrozenCallOut />}
<EuiText>
{meta.isReadonly && (
{showEsTransformsGuidance && <ESTransformsTargetGuidance deprecation={deprecation} />}
{showMlAnomalyReindexingGuidance && <MlAnomalyGuidance />}
{showReadOnlyGuidance && (
<Fragment>
<p>
<FormattedMessage
@ -160,8 +181,7 @@ export const ReindexDetailsFlyoutStep: React.FunctionComponent<{
</p>
</Fragment>
)}
{isESTransformTarget && <ESTransformsTargetGuidance deprecation={deprecation} />}
{!meta.isReadonly && !isESTransformTarget && (
{showDefaultGuidance && (
<Fragment>
<p>
<FormattedMessage