From 4b07e6c5e10029193001d59acdd45afff1576d4d Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 31 Aug 2020 11:45:57 +0200 Subject: [PATCH] [ILM] Add support for frozen phase in UI (#75968) * ILM public side changes to support frozen phase This is a very faithful duplication of the cold phase. We are also excluding the snapshot action as well as the unfollow action as these are features that require higher than basic license privilege. * added "frozen" to the server side schema * add frozen phases component * fix i18n and update jest tests * Slight restructuring to phase types and fix copy paste issues. - also fixed TS error introduced after TS v4 upgrade (delete) * fix hot phase type and remove type constraint from error messages * update validation logic Co-authored-by: Elastic Machine --- .../extend_index_management.test.js.snap | 4 + .../public/application/constants/policy.ts | 11 + .../edit_policy/components/min_age_input.tsx | 11 +- .../components/node_allocation.tsx | 6 +- .../components/set_priority_input.tsx | 6 +- .../sections/edit_policy/edit_policy.tsx | 16 +- .../edit_policy/phases/cold_phase.tsx | 7 +- .../edit_policy/phases/delete_phase.tsx | 7 +- .../edit_policy/phases/frozen_phase.tsx | 210 ++++++++++++++++++ .../sections/edit_policy/phases/hot_phase.tsx | 7 +- .../sections/edit_policy/phases/index.ts | 1 + .../edit_policy/phases/warm_phase.tsx | 7 +- .../services/policies/cold_phase.ts | 4 +- .../services/policies/frozen_phase.ts | 161 ++++++++++++++ .../services/policies/policy_serialization.ts | 11 + .../services/policies/policy_validation.ts | 18 +- .../application/services/policies/types.ts | 66 ++++-- .../public/extend_index_management/index.js | 6 + .../api/policies/register_create_route.ts | 18 ++ 19 files changed, 527 insertions(+), 50 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/frozen_phase.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/services/policies/frozen_phase.ts diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap index 38dd49a286b58..39eb54b941ac4 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap +++ b/x-pack/plugins/index_lifecycle_management/__jest__/__snapshots__/extend_index_management.test.js.snap @@ -76,6 +76,10 @@ Array [ "value": "warm", "view": "Warm", }, + Object { + "value": "frozen", + "view": "Frozen", + }, Object { "value": "cold", "view": "Cold", diff --git a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts index 3a19f03547b5b..fb626e7d7fe76 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts @@ -10,6 +10,7 @@ import { DeletePhase, HotPhase, WarmPhase, + FrozenPhase, } from '../services/policies/types'; export const defaultNewHotPhase: HotPhase = { @@ -47,6 +48,16 @@ export const defaultNewColdPhase: ColdPhase = { phaseIndexPriority: '0', }; +export const defaultNewFrozenPhase: FrozenPhase = { + phaseEnabled: false, + selectedMinimumAge: '0', + selectedMinimumAgeUnits: 'd', + selectedNodeAttrs: '', + selectedReplicaCount: '', + freezeEnabled: false, + phaseIndexPriority: '0', +}; + export const defaultNewDeletePhase: DeletePhase = { phaseEnabled: false, selectedMinimumAge: '0', diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx index 11b743ecc4bb6..5128ba1c881a0 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx @@ -12,7 +12,7 @@ import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiSelect } from import { LearnMoreLink } from './learn_more_link'; import { ErrableFormRow } from './form_errors'; import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; -import { ColdPhase, DeletePhase, Phase, Phases, WarmPhase } from '../../../services/policies/types'; +import { PhaseWithMinAge, Phases } from '../../../services/policies/types'; function getTimingLabelForPhase(phase: keyof Phases) { // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. @@ -27,6 +27,11 @@ function getTimingLabelForPhase(phase: keyof Phases) { defaultMessage: 'Timing for cold phase', }); + case 'frozen': + return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseFrozen.minimumAgeLabel', { + defaultMessage: 'Timing for frozen phase', + }); + case 'delete': return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeLabel', { defaultMessage: 'Timing for delete phase', @@ -63,7 +68,7 @@ function getUnitsAriaLabelForPhase(phase: keyof Phases) { } } -interface Props { +interface Props { rolloverEnabled: boolean; errors?: PhaseValidationErrors; phase: keyof Phases & string; @@ -72,7 +77,7 @@ interface Props { isShowingErrors: boolean; } -export const MinAgeInput = ({ +export const MinAgeInput = ({ rolloverEnabled, errors, phaseData, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx index 0ce2c0d7ea566..b4ff62bfb03dc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/node_allocation.tsx @@ -20,7 +20,7 @@ import { LearnMoreLink } from './learn_more_link'; import { ErrableFormRow } from './form_errors'; import { useLoadNodes } from '../../../services/api'; import { NodeAttrsDetails } from './node_attrs_details'; -import { ColdPhase, Phase, Phases, WarmPhase } from '../../../services/policies/types'; +import { PhaseWithAllocationAction, Phases } from '../../../services/policies/types'; import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; const learnMoreLink = ( @@ -38,14 +38,14 @@ const learnMoreLink = ( ); -interface Props { +interface Props { phase: keyof Phases & string; errors?: PhaseValidationErrors; phaseData: T; setPhaseData: (dataKey: keyof T & string, value: string) => void; isShowingErrors: boolean; } -export const NodeAllocation = ({ +export const NodeAllocation = ({ phase, setPhaseData, errors, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx index 1da7508049f24..1505532a2b16e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/set_priority_input.tsx @@ -10,17 +10,17 @@ import { EuiFieldNumber, EuiTextColor, EuiDescribedFormGroup } from '@elastic/eu import { LearnMoreLink } from './'; import { OptionalLabel } from './'; import { ErrableFormRow } from './'; -import { ColdPhase, HotPhase, Phase, Phases, WarmPhase } from '../../../services/policies/types'; +import { PhaseWithIndexPriority, Phases } from '../../../services/policies/types'; import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; -interface Props { +interface Props { errors?: PhaseValidationErrors; phase: keyof Phases & string; phaseData: T; setPhaseData: (dataKey: keyof T & string, value: any) => void; isShowingErrors: boolean; } -export const SetPriorityInput = ({ +export const SetPriorityInput = ({ errors, phaseData, phase, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx index c99d01b546679..db58c64a8ae8c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx @@ -28,7 +28,7 @@ import { import { toasts } from '../../services/notification'; -import { Policy, PolicyFromES } from '../../services/policies/types'; +import { Phases, Policy, PolicyFromES } from '../../services/policies/types'; import { validatePolicy, ValidationErrors, @@ -42,7 +42,7 @@ import { } from '../../services/policies/policy_serialization'; import { ErrableFormRow, LearnMoreLink, PolicyJsonFlyout } from './components'; -import { ColdPhase, DeletePhase, HotPhase, WarmPhase } from './phases'; +import { ColdPhase, DeletePhase, FrozenPhase, HotPhase, WarmPhase } from './phases'; interface Props { policies: PolicyFromES[]; @@ -118,7 +118,7 @@ export const EditPolicy: React.FunctionComponent = ({ setIsShowingPolicyJsonFlyout(!isShowingPolicyJsonFlyout); }; - const setPhaseData = (phase: 'hot' | 'warm' | 'cold' | 'delete', key: string, value: any) => { + const setPhaseData = (phase: keyof Phases, key: string, value: any) => { setPolicy({ ...policy, phases: { @@ -303,6 +303,16 @@ export const EditPolicy: React.FunctionComponent = ({ + 0} + setPhaseData={(key, value) => setPhaseData('frozen', key, value)} + phaseData={policy.phases.frozen} + hotPhaseRolloverEnabled={policy.phases.hot.rolloverEnabled} + /> + + + 0} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx index fb32752fe24ea..9df6da7a88b2f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/cold_phase.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { ColdPhase as ColdPhaseInterface, Phases } from '../../../services/policies/types'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; +import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; import { LearnMoreLink, @@ -36,9 +36,8 @@ const freezeLabel = i18n.translate('xpack.indexLifecycleMgmt.coldPhase.freezeInd defaultMessage: 'Freeze index', }); -const coldProperty = propertyof('cold'); -const phaseProperty = (propertyName: keyof ColdPhaseInterface) => - propertyof(propertyName); +const coldProperty: keyof Phases = 'cold'; +const phaseProperty = (propertyName: keyof ColdPhaseInterface) => propertyName; interface Props { setPhaseData: (key: keyof ColdPhaseInterface & string, value: string | boolean) => void; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx index d3c73090f25f2..eab93777a72bd 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/delete_phase.tsx @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescribedFormGroup, EuiSwitch, EuiTextColor, EuiFormRow } from '@elastic/eui'; import { DeletePhase as DeletePhaseInterface, Phases } from '../../../services/policies/types'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; +import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; import { ActiveBadge, @@ -20,9 +20,8 @@ import { SnapshotPolicies, } from '../components'; -const deleteProperty = propertyof('delete'); -const phaseProperty = (propertyName: keyof DeletePhaseInterface) => - propertyof(propertyName); +const deleteProperty: keyof Phases = 'delete'; +const phaseProperty = (propertyName: keyof DeletePhaseInterface) => propertyName; interface Props { setPhaseData: (key: keyof DeletePhaseInterface & string, value: string | boolean) => void; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/frozen_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/frozen_phase.tsx new file mode 100644 index 0000000000000..782906a56a9ba --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/frozen_phase.tsx @@ -0,0 +1,210 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { PureComponent, Fragment } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiFieldNumber, + EuiDescribedFormGroup, + EuiSwitch, + EuiTextColor, +} from '@elastic/eui'; + +import { FrozenPhase as FrozenPhaseInterface, Phases } from '../../../services/policies/types'; +import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; + +import { + LearnMoreLink, + ActiveBadge, + PhaseErrorMessage, + OptionalLabel, + ErrableFormRow, + MinAgeInput, + NodeAllocation, + SetPriorityInput, +} from '../components'; + +const freezeLabel = i18n.translate('xpack.indexLifecycleMgmt.frozenPhase.freezeIndexLabel', { + defaultMessage: 'Freeze index', +}); + +const frozenProperty: keyof Phases = 'frozen'; +const phaseProperty = (propertyName: keyof FrozenPhaseInterface) => propertyName; + +interface Props { + setPhaseData: (key: keyof FrozenPhaseInterface & string, value: string | boolean) => void; + phaseData: FrozenPhaseInterface; + isShowingErrors: boolean; + errors?: PhaseValidationErrors; + hotPhaseRolloverEnabled: boolean; +} +export class FrozenPhase extends PureComponent { + render() { + const { + setPhaseData, + phaseData, + errors, + isShowingErrors, + hotPhaseRolloverEnabled, + } = this.props; + + return ( +
+ +

+ +

{' '} + {phaseData.phaseEnabled && !isShowingErrors ? : null} + +
+ } + titleSize="s" + description={ + +

+ +

+ + } + id={`${frozenProperty}-${phaseProperty('phaseEnabled')}`} + checked={phaseData.phaseEnabled} + onChange={(e) => { + setPhaseData(phaseProperty('phaseEnabled'), e.target.checked); + }} + aria-controls="frozenPhaseContent" + /> +
+ } + fullWidth + > + + {phaseData.phaseEnabled ? ( + + + errors={errors} + phaseData={phaseData} + phase={frozenProperty} + isShowingErrors={isShowingErrors} + setPhaseData={setPhaseData} + rolloverEnabled={hotPhaseRolloverEnabled} + /> + + + + phase={frozenProperty} + setPhaseData={setPhaseData} + errors={errors} + phaseData={phaseData} + isShowingErrors={isShowingErrors} + /> + + + + + + + + } + isShowingErrors={isShowingErrors} + errors={errors?.freezeEnabled} + helpText={i18n.translate( + 'xpack.indexLifecycleMgmt.frozenPhase.replicaCountHelpText', + { + defaultMessage: 'By default, the number of replicas remains the same.', + } + )} + > + { + setPhaseData(phaseProperty('selectedReplicaCount'), e.target.value); + }} + min={0} + /> + + + + + ) : ( +
+ )} + + + {phaseData.phaseEnabled ? ( + + + + + } + description={ + + {' '} + + + } + fullWidth + titleSize="xs" + > + { + setPhaseData(phaseProperty('freezeEnabled'), e.target.checked); + }} + label={freezeLabel} + aria-label={freezeLabel} + /> + + + errors={errors} + phaseData={phaseData} + phase={frozenProperty} + isShowingErrors={isShowingErrors} + setPhaseData={setPhaseData} + /> + + ) : null} +
+ ); + } +} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx index 22f0114d16afe..106e3b9139a9b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/hot_phase.tsx @@ -19,7 +19,7 @@ import { } from '@elastic/eui'; import { HotPhase as HotPhaseInterface, Phases } from '../../../services/policies/types'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; +import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; import { LearnMoreLink, @@ -112,9 +112,8 @@ const maxAgeUnits = [ }), }, ]; -const hotProperty = propertyof('hot'); -const phaseProperty = (propertyName: keyof HotPhaseInterface) => - propertyof(propertyName); +const hotProperty: keyof Phases = 'hot'; +const phaseProperty = (propertyName: keyof HotPhaseInterface) => propertyName; interface Props { errors?: PhaseValidationErrors; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts index 8d1ace5950497..d59f2ff6413fd 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/index.ts @@ -7,4 +7,5 @@ export { HotPhase } from './hot_phase'; export { WarmPhase } from './warm_phase'; export { ColdPhase } from './cold_phase'; +export { FrozenPhase } from './frozen_phase'; export { DeletePhase } from './delete_phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx index f7b8c60a5c71f..2733d01ac222d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/phases/warm_phase.tsx @@ -30,7 +30,7 @@ import { } from '../components'; import { Phases, WarmPhase as WarmPhaseInterface } from '../../../services/policies/types'; -import { PhaseValidationErrors, propertyof } from '../../../services/policies/policy_validation'; +import { PhaseValidationErrors } from '../../../services/policies/policy_validation'; const shrinkLabel = i18n.translate('xpack.indexLifecycleMgmt.warmPhase.shrinkIndexLabel', { defaultMessage: 'Shrink index', @@ -47,9 +47,8 @@ const forcemergeLabel = i18n.translate('xpack.indexLifecycleMgmt.warmPhase.force defaultMessage: 'Force merge data', }); -const warmProperty = propertyof('warm'); -const phaseProperty = (propertyName: keyof WarmPhaseInterface) => - propertyof(propertyName); +const warmProperty: keyof Phases = 'warm'; +const phaseProperty = (propertyName: keyof WarmPhaseInterface) => propertyName; interface Props { setPhaseData: (key: keyof WarmPhaseInterface & string, value: boolean | string) => void; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts index 6cc43042ed4ff..7fa82a004b872 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/cold_phase.ts @@ -152,9 +152,9 @@ export const validateColdPhase = (phase: ColdPhase): PhaseValidationErrors { + const phase = { ...frozenPhaseInitialization }; + if (phaseSerialized === undefined || phaseSerialized === null) { + return phase; + } + + phase.phaseEnabled = true; + + if (phaseSerialized.min_age) { + const { size: minAge, units: minAgeUnits } = splitSizeAndUnits(phaseSerialized.min_age); + phase.selectedMinimumAge = minAge; + phase.selectedMinimumAgeUnits = minAgeUnits; + } + + if (phaseSerialized.actions) { + const actions = phaseSerialized.actions; + if (actions.allocate) { + const allocate = actions.allocate; + if (allocate.require) { + Object.entries(allocate.require).forEach((entry) => { + phase.selectedNodeAttrs = entry.join(':'); + }); + if (allocate.number_of_replicas) { + phase.selectedReplicaCount = allocate.number_of_replicas.toString(); + } + } + } + + if (actions.freeze) { + phase.freezeEnabled = true; + } + + if (actions.set_priority) { + phase.phaseIndexPriority = actions.set_priority.priority + ? actions.set_priority.priority.toString() + : ''; + } + } + + return phase; +}; + +export const frozenPhaseToES = ( + phase: FrozenPhase, + originalPhase?: SerializedFrozenPhase +): SerializedFrozenPhase => { + if (!originalPhase) { + originalPhase = { ...serializedPhaseInitialization }; + } + + const esPhase = { ...originalPhase }; + + if (isNumber(phase.selectedMinimumAge)) { + esPhase.min_age = `${phase.selectedMinimumAge}${phase.selectedMinimumAgeUnits}`; + } + + esPhase.actions = esPhase.actions ? { ...esPhase.actions } : {}; + + if (phase.selectedNodeAttrs) { + const [name, value] = phase.selectedNodeAttrs.split(':'); + esPhase.actions.allocate = esPhase.actions.allocate || ({} as AllocateAction); + esPhase.actions.allocate.require = { + [name]: value, + }; + } else { + if (esPhase.actions.allocate) { + // @ts-expect-error + delete esPhase.actions.allocate.require; + } + } + + if (isNumber(phase.selectedReplicaCount)) { + esPhase.actions.allocate = esPhase.actions.allocate || ({} as AllocateAction); + esPhase.actions.allocate.number_of_replicas = parseInt(phase.selectedReplicaCount, 10); + } else { + if (esPhase.actions.allocate) { + // @ts-expect-error + delete esPhase.actions.allocate.number_of_replicas; + } + } + + if ( + esPhase.actions.allocate && + !esPhase.actions.allocate.require && + !isNumber(esPhase.actions.allocate.number_of_replicas) && + isEmpty(esPhase.actions.allocate.include) && + isEmpty(esPhase.actions.allocate.exclude) + ) { + // remove allocate action if it does not define require or number of nodes + // and both include and exclude are empty objects (ES will fail to parse if we don't) + delete esPhase.actions.allocate; + } + + if (phase.freezeEnabled) { + esPhase.actions.freeze = {}; + } else { + delete esPhase.actions.freeze; + } + + if (isNumber(phase.phaseIndexPriority)) { + esPhase.actions.set_priority = { + priority: parseInt(phase.phaseIndexPriority, 10), + }; + } else { + delete esPhase.actions.set_priority; + } + + return esPhase; +}; + +export const validateFrozenPhase = (phase: FrozenPhase): PhaseValidationErrors => { + if (!phase.phaseEnabled) { + return {}; + } + + const phaseErrors = {} as PhaseValidationErrors; + + // index priority is optional, but if it's set, it needs to be a positive number + if (phase.phaseIndexPriority) { + if (!isNumber(phase.phaseIndexPriority)) { + phaseErrors.phaseIndexPriority = [numberRequiredMessage]; + } else if (parseInt(phase.phaseIndexPriority, 10) < 0) { + phaseErrors.phaseIndexPriority = [positiveNumberRequiredMessage]; + } + } + + // min age needs to be a positive number + if (!isNumber(phase.selectedMinimumAge)) { + phaseErrors.selectedMinimumAge = [numberRequiredMessage]; + } else if (parseInt(phase.selectedMinimumAge, 10) < 0) { + phaseErrors.selectedMinimumAge = [positiveNumberRequiredMessage]; + } + + return { ...phaseErrors }; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts index 3953521df1817..807a6fe8ec395 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.ts @@ -9,6 +9,7 @@ import { defaultNewDeletePhase, defaultNewHotPhase, defaultNewWarmPhase, + defaultNewFrozenPhase, serializedPhaseInitialization, } from '../../constants'; @@ -17,6 +18,7 @@ import { Policy, PolicyFromES, SerializedPolicy } from './types'; import { hotPhaseFromES, hotPhaseToES } from './hot_phase'; import { warmPhaseFromES, warmPhaseToES } from './warm_phase'; import { coldPhaseFromES, coldPhaseToES } from './cold_phase'; +import { frozenPhaseFromES, frozenPhaseToES } from './frozen_phase'; import { deletePhaseFromES, deletePhaseToES } from './delete_phase'; export const splitSizeAndUnits = (field: string): { size: string; units: string } => { @@ -53,6 +55,7 @@ export const initializeNewPolicy = (newPolicyName: string = ''): Policy => { hot: { ...defaultNewHotPhase }, warm: { ...defaultNewWarmPhase }, cold: { ...defaultNewColdPhase }, + frozen: { ...defaultNewFrozenPhase }, delete: { ...defaultNewDeletePhase }, }, }; @@ -70,6 +73,7 @@ export const deserializePolicy = (policy: PolicyFromES): Policy => { hot: hotPhaseFromES(phases.hot), warm: warmPhaseFromES(phases.warm), cold: coldPhaseFromES(phases.cold), + frozen: frozenPhaseFromES(phases.frozen), delete: deletePhaseFromES(phases.delete), }, }; @@ -94,6 +98,13 @@ export const serializePolicy = ( serializedPolicy.phases.cold = coldPhaseToES(policy.phases.cold, originalEsPolicy.phases.cold); } + if (policy.phases.frozen.phaseEnabled) { + serializedPolicy.phases.frozen = frozenPhaseToES( + policy.phases.frozen, + originalEsPolicy.phases.frozen + ); + } + if (policy.phases.delete.phaseEnabled) { serializedPolicy.phases.delete = deletePhaseToES( policy.phases.delete, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts index 545488be2cd5e..6fdbc4babd3f3 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_validation.ts @@ -9,7 +9,17 @@ import { validateHotPhase } from './hot_phase'; import { validateWarmPhase } from './warm_phase'; import { validateColdPhase } from './cold_phase'; import { validateDeletePhase } from './delete_phase'; -import { ColdPhase, DeletePhase, HotPhase, Phase, Policy, PolicyFromES, WarmPhase } from './types'; +import { validateFrozenPhase } from './frozen_phase'; + +import { + ColdPhase, + DeletePhase, + FrozenPhase, + HotPhase, + Policy, + PolicyFromES, + WarmPhase, +} from './types'; export const propertyof = (propertyName: keyof T & string) => propertyName; @@ -100,7 +110,7 @@ export const policyNameAlreadyUsedErrorMessage = i18n.translate( defaultMessage: 'That policy name is already used.', } ); -export type PhaseValidationErrors = { +export type PhaseValidationErrors = { [P in keyof Partial]: string[]; }; @@ -108,6 +118,7 @@ export interface ValidationErrors { hot: PhaseValidationErrors; warm: PhaseValidationErrors; cold: PhaseValidationErrors; + frozen: PhaseValidationErrors; delete: PhaseValidationErrors; policyName: string[]; } @@ -148,12 +159,14 @@ export const validatePolicy = ( const hotPhaseErrors = validateHotPhase(policy.phases.hot); const warmPhaseErrors = validateWarmPhase(policy.phases.warm); const coldPhaseErrors = validateColdPhase(policy.phases.cold); + const frozenPhaseErrors = validateFrozenPhase(policy.phases.frozen); const deletePhaseErrors = validateDeletePhase(policy.phases.delete); const isValid = policyNameErrors.length === 0 && Object.keys(hotPhaseErrors).length === 0 && Object.keys(warmPhaseErrors).length === 0 && Object.keys(coldPhaseErrors).length === 0 && + Object.keys(frozenPhaseErrors).length === 0 && Object.keys(deletePhaseErrors).length === 0; return [ isValid, @@ -162,6 +175,7 @@ export const validatePolicy = ( hot: hotPhaseErrors, warm: warmPhaseErrors, cold: coldPhaseErrors, + frozen: frozenPhaseErrors, delete: deletePhaseErrors, }, ]; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/types.ts index 2e2ed5b38bb87..3d4c73cf4a82c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/types.ts @@ -13,6 +13,7 @@ export interface Phases { hot?: SerializedHotPhase; warm?: SerializedWarmPhase; cold?: SerializedColdPhase; + frozen?: SerializedFrozenPhase; delete?: SerializedDeletePhase; } @@ -68,6 +69,16 @@ export interface SerializedColdPhase extends SerializedPhase { }; } +export interface SerializedFrozenPhase extends SerializedPhase { + actions: { + freeze?: {}; + allocate?: AllocateAction; + set_priority?: { + priority: number | null; + }; + }; +} + export interface SerializedDeletePhase extends SerializedPhase { actions: { wait_for_snapshot?: { @@ -94,47 +105,66 @@ export interface Policy { hot: HotPhase; warm: WarmPhase; cold: ColdPhase; + frozen: FrozenPhase; delete: DeletePhase; }; } -export interface Phase { +export interface CommonPhaseSettings { phaseEnabled: boolean; } -export interface HotPhase extends Phase { + +export interface PhaseWithMinAge { + selectedMinimumAge: string; + selectedMinimumAgeUnits: string; +} + +export interface PhaseWithAllocationAction { + selectedNodeAttrs: string; + selectedReplicaCount: string; +} + +export interface PhaseWithIndexPriority { + phaseIndexPriority: string; +} + +export interface HotPhase extends CommonPhaseSettings, PhaseWithIndexPriority { rolloverEnabled: boolean; selectedMaxSizeStored: string; selectedMaxSizeStoredUnits: string; selectedMaxDocuments: string; selectedMaxAge: string; selectedMaxAgeUnits: string; - phaseIndexPriority: string; } -export interface WarmPhase extends Phase { +export interface WarmPhase + extends CommonPhaseSettings, + PhaseWithMinAge, + PhaseWithAllocationAction, + PhaseWithIndexPriority { warmPhaseOnRollover: boolean; - selectedMinimumAge: string; - selectedMinimumAgeUnits: string; - selectedNodeAttrs: string; - selectedReplicaCount: string; shrinkEnabled: boolean; selectedPrimaryShardCount: string; forceMergeEnabled: boolean; selectedForceMergeSegments: string; - phaseIndexPriority: string; } -export interface ColdPhase extends Phase { - selectedMinimumAge: string; - selectedMinimumAgeUnits: string; - selectedNodeAttrs: string; - selectedReplicaCount: string; +export interface ColdPhase + extends CommonPhaseSettings, + PhaseWithMinAge, + PhaseWithAllocationAction, + PhaseWithIndexPriority { freezeEnabled: boolean; - phaseIndexPriority: string; } -export interface DeletePhase extends Phase { - selectedMinimumAge: string; - selectedMinimumAgeUnits: string; +export interface FrozenPhase + extends CommonPhaseSettings, + PhaseWithMinAge, + PhaseWithAllocationAction, + PhaseWithIndexPriority { + freezeEnabled: boolean; +} + +export interface DeletePhase extends CommonPhaseSettings, PhaseWithMinAge { waitForSnapshotPolicy: string; } diff --git a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js index a1eac5264bb6a..8d01f4a4c200e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js +++ b/x-pack/plugins/index_lifecycle_management/public/extend_index_management/index.js @@ -176,6 +176,12 @@ export const ilmFilterExtension = (indices) => { defaultMessage: 'Warm', }), }, + { + value: 'frozen', + view: i18n.translate('xpack.indexLifecycleMgmt.indexMgmtFilter.frozenLabel', { + defaultMessage: 'Frozen', + }), + }, { value: 'cold', view: i18n.translate('xpack.indexLifecycleMgmt.indexMgmtFilter.coldLabel', { diff --git a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts index 2d02802119e47..9b51164fd4c28 100644 --- a/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts +++ b/x-pack/plugins/index_lifecycle_management/server/routes/api/policies/register_create_route.ts @@ -104,6 +104,23 @@ const coldPhaseSchema = schema.maybe( }) ); +const frozenPhaseSchema = schema.maybe( + schema.object({ + min_age: minAgeSchema, + actions: schema.object({ + set_priority: setPrioritySchema, + unfollow: unfollowSchema, + allocate: allocateSchema, + freeze: schema.maybe(schema.object({})), // Freeze has no options + searchable_snapshot: schema.maybe( + schema.object({ + snapshot_repository: schema.string(), + }) + ), + }), + }) +); + const deletePhaseSchema = schema.maybe( schema.object({ min_age: minAgeSchema, @@ -129,6 +146,7 @@ const bodySchema = schema.object({ hot: hotPhaseSchema, warm: warmPhaseSchema, cold: coldPhaseSchema, + frozen: frozenPhaseSchema, delete: deletePhaseSchema, }), });