From d844b127773deec3a974d54c69e99b575b9bd2cc Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 20 Oct 2020 15:01:49 +0200 Subject: [PATCH 01/19] migrate all fields on warm phase except, data alloc, replicas and shrink --- .../phases/shared/forcemerge_field.tsx | 7 +- .../components/phases/shared/index.ts | 2 + .../shared/min_age_input_field/index.ts | 7 + .../min_age_input_field.tsx | 211 ++++++++++++++++++ .../phases/shared/min_age_input_field/util.ts | 58 +++++ .../components/phases/warm_phase/index.ts | 7 + .../phases/{ => warm_phase}/warm_phase.tsx | 138 +++++------- .../sections/edit_policy/deserializer.ts | 7 + .../sections/edit_policy/form_schema.ts | 72 +++++- .../sections/edit_policy/i18n_texts.ts | 25 +++ .../application/sections/edit_policy/types.ts | 7 + 11 files changed, 450 insertions(+), 91 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/min_age_input_field/index.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/min_age_input_field/min_age_input_field.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/min_age_input_field/util.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/index.ts rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/{ => warm_phase}/warm_phase.tsx (72%) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx index 987133fd652acf..2a43b99e71452d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx @@ -20,9 +20,8 @@ interface Props { phase: keyof Phases & string; } -const forceMergeEnabledPath = '_meta.hot.forceMergeEnabled'; - export const Forcemerge: React.FunctionComponent = ({ phase }) => { + const forceMergeEnabledPath = `_meta.${phase}.forceMergeEnabled`; const [{ [forceMergeEnabledPath]: forceMergeEnabled }] = useFormData({ watch: [forceMergeEnabledPath], }); @@ -77,8 +76,8 @@ export const Forcemerge: React.FunctionComponent = ({ phase }) => { }} /> = ({ phase }): React.ReactElement => { + const [{ [useRolloverPath]: rolloverEnabled }] = useFormData({ watch: useRolloverPath }); + + let daysOptionLabel; + let hoursOptionLabel; + let minutesOptionLabel; + let secondsOptionLabel; + let millisecondsOptionLabel; + let microsecondsOptionLabel; + let nanosecondsOptionLabel; + + if (rolloverEnabled) { + daysOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel', + { + defaultMessage: 'days from rollover', + } + ); + + hoursOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel', + { + defaultMessage: 'hours from rollover', + } + ); + minutesOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.rolloverMinutesOptionLabel', + { + defaultMessage: 'minutes from rollover', + } + ); + + secondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.rolloverSecondsOptionLabel', + { + defaultMessage: 'seconds from rollover', + } + ); + millisecondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.rolloverMilliSecondsOptionLabel', + { + defaultMessage: 'milliseconds from rollover', + } + ); + + microsecondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel', + { + defaultMessage: 'microseconds from rollover', + } + ); + + nanosecondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.rolloverNanoSecondsOptionLabel', + { + defaultMessage: 'nanoseconds from rollover', + } + ); + } else { + daysOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationDaysOptionLabel', + { + defaultMessage: 'days from index creation', + } + ); + + hoursOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationHoursOptionLabel', + { + defaultMessage: 'hours from index creation', + } + ); + + minutesOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationMinutesOptionLabel', + { + defaultMessage: 'minutes from index creation', + } + ); + + secondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationSecondsOptionLabel', + { + defaultMessage: 'seconds from index creation', + } + ); + + millisecondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationMilliSecondsOptionLabel', + { + defaultMessage: 'milliseconds from index creation', + } + ); + + microsecondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationMicroSecondsOptionLabel', + { + defaultMessage: 'microseconds from index creation', + } + ); + + nanosecondsOptionLabel = i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.creationNanoSecondsOptionLabel', + { + defaultMessage: 'nanoseconds from index creation', + } + ); + } + + return ( + + + + } + /> + ), + euiFieldProps: { + 'data-test-subj': `${phase}-selectedMinimumAge`, + min: 0, + }, + }} + /> + + + + + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/min_age_input_field/util.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/min_age_input_field/util.ts new file mode 100644 index 00000000000000..181894badba7bf --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/min_age_input_field/util.ts @@ -0,0 +1,58 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { Phases } from '../../../../../../../../common/types'; + +type PhaseWithMinAgeAction = 'warm' | 'cold' | 'delete'; + +export function getUnitsAriaLabelForPhase(phase: keyof Phases) { + // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. + switch (phase) { + case 'warm': + return i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeUnitsAriaLabel', + { + defaultMessage: 'Units for timing of warm phase', + } + ); + + case 'cold': + return i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeUnitsAriaLabel', + { + defaultMessage: 'Units for timing of cold phase', + } + ); + + case 'delete': + return i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeUnitsAriaLabel', + { + defaultMessage: 'Units for timing of delete phase', + } + ); + } +} +export function getTimingLabelForPhase(phase: PhaseWithMinAgeAction) { + // NOTE: Hot phase isn't necessary, because indices begin in the hot phase. + switch (phase) { + case 'warm': + return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseWarm.minimumAgeLabel', { + defaultMessage: 'Timing for warm phase', + }); + + case 'cold': + return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseCold.minimumAgeLabel', { + defaultMessage: 'Timing for cold phase', + }); + + case 'delete': + return i18n.translate('xpack.indexLifecycleMgmt.editPolicy.phaseDelete.minimumAgeLabel', { + defaultMessage: 'Timing for delete phase', + }); + } +} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/index.ts new file mode 100644 index 00000000000000..d0686270ba559b --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { WarmPhase } from './warm_phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx similarity index 72% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index bd0b380bdc172c..a4f1500c350f1a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -12,17 +12,22 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer, - EuiFormRow, EuiFieldNumber, EuiSwitch, EuiDescribedFormGroup, } from '@elastic/eui'; -import { useFormData } from '../../../../../shared_imports'; -import { Phases, WarmPhase as WarmPhaseInterface } from '../../../../../../common/types'; -import { PhaseValidationErrors } from '../../../../services/policies/policy_validation'; +import { + useFormData, + UseField, + ToggleField, + useFormContext, +} from '../../../../../../shared_imports'; + +import { Phases, WarmPhase as WarmPhaseInterface } from '../../../../../../../common/types'; +import { PhaseValidationErrors } from '../../../../../services/policies/policy_validation'; -import { useRolloverPath } from './shared'; +import { useRolloverPath, MinAgeInputField, Forcemerge, SetPriorityInput } from '../shared'; import { LearnMoreLink, @@ -30,24 +35,15 @@ import { PhaseErrorMessage, OptionalLabel, ErrableFormRow, - SetPriorityInput, - MinAgeInput, DescribedFormField, - Forcemerge, -} from '../'; +} from '../../index'; -import { DataTierAllocationField } from './shared'; +import { DataTierAllocationField } from '../shared'; const i18nTexts = { shrinkLabel: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.shrinkIndexLabel', { defaultMessage: 'Shrink index', }), - moveToWarmPhaseOnRolloverLabel: i18n.translate( - 'xpack.indexLifecycleMgmt.warmPhase.moveToWarmPhaseOnRolloverLabel', - { - defaultMessage: 'Move to warm phase on rollover', - } - ), dataTierAllocation: { description: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.dataTier.description', { defaultMessage: 'Move data to nodes optimized for less-frequent, read-only access.', @@ -67,15 +63,18 @@ interface Props { isShowingErrors: boolean; errors?: PhaseValidationErrors; } -export const WarmPhase: FunctionComponent = ({ - setPhaseData, - phaseData, - errors, - isShowingErrors, -}) => { - const [{ [useRolloverPath]: hotPhaseRolloverEnabled }] = useFormData({ - watch: [useRolloverPath], +export const WarmPhase: FunctionComponent = ({ setPhaseData, phaseData, errors }) => { + const form = useFormContext(); + const [ + { + [useRolloverPath]: hotPhaseRolloverEnabled, + '_meta.warm.enabled': enabled, + '_meta.warm.warmPhaseOnRollover': warmPhaseOnRollover, + }, + ] = useFormData({ + watch: [useRolloverPath, '_meta.warm.enabled', '_meta.warm.warmPhaseOnRollover'], }); + const isShowingErrors = form.isValid === false; return (
<> @@ -88,7 +87,7 @@ export const WarmPhase: FunctionComponent = ({ defaultMessage="Warm phase" /> {' '} - {phaseData.phaseEnabled && !isShowingErrors ? : null} + {enabled && !isShowingErrors ? : null}
} @@ -103,60 +102,47 @@ export const WarmPhase: FunctionComponent = ({ For faster searches, you can reduce the number of shards and force merge segments." />

- - } - id={`${warmProperty}-${phaseProperty('phaseEnabled')}`} - checked={phaseData.phaseEnabled} - onChange={(e) => { - setPhaseData(phaseProperty('phaseEnabled'), e.target.checked); + } fullWidth > - {phaseData.phaseEnabled ? ( + {enabled && ( - {hotPhaseRolloverEnabled ? ( - - { - setPhaseData(phaseProperty('warmPhaseOnRollover'), e.target.checked); - }} - /> - - ) : null} - {!phaseData.warmPhaseOnRollover || !hotPhaseRolloverEnabled ? ( - + {hotPhaseRolloverEnabled && ( + + )} + {(!warmPhaseOnRollover || !hotPhaseRolloverEnabled) && ( + <> - - errors={errors} - phaseData={phaseData} - phase={warmProperty} - isShowingErrors={isShowingErrors} - setPhaseData={setPhaseData} - rolloverEnabled={hotPhaseRolloverEnabled} - /> - - ) : null} + + + )} - ) : null} + )} - {phaseData.phaseEnabled ? ( + {enabled && ( {/* Data tier allocation section */} = ({ - - - errors={errors} - phaseData={phaseData} - phase={warmProperty} - isShowingErrors={isShowingErrors} - setPhaseData={setPhaseData} - /> + + + + - ) : null} + )} ); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts index bb24eea64ec8cc..8bedcdd1d9f165 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts @@ -23,6 +23,13 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => bestCompression: policy.phases.hot?.actions?.forcemerge?.index_codec === 'best_compression', }, + warm: { + enabled: Boolean(policy.phases.warm), + warmPhaseOnRollover: Boolean(policy.phases.warm?.min_age === '0ms'), + forceMergeEnabled: Boolean(policy.phases.warm?.actions?.forcemerge), + bestCompression: + policy.phases.warm?.actions?.forcemerge?.index_codec === 'best_compression', + }, }, }, (draft) => { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts index 806164c8b0da12..6d62e0aed2d735 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts @@ -46,6 +46,31 @@ export const schema: FormSchema = { ), }, }, + warm: { + enabled: { + defaultValue: false, + label: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.warmPhase.activateWarmPhaseSwitchLabel', + { defaultMessage: 'Activate warm phase' } + ), + }, + warmPhaseOnRollover: { + defaultValue: true, + label: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.moveToWarmPhaseOnRolloverLabel', { + defaultMessage: 'Move to warm phase on rollover', + }), + }, + minAgeUnit: { + defaultValue: 'd', + }, + forceMergeEnabled: { + label: i18nTexts.editPolicy.forceMergeEnabledFieldLabel, + }, + bestCompression: { + label: i18nTexts.editPolicy.bestCompressionFieldLabel, + helpText: i18nTexts.editPolicy.bestCompressionFieldHelpText, + }, + }, }, phases: { hot: { @@ -94,9 +119,7 @@ export const schema: FormSchema = { }, forcemerge: { max_num_segments: { - label: i18n.translate('xpack.indexLifecycleMgmt.forceMerge.numberOfSegmentsLabel', { - defaultMessage: 'Number of segments', - }), + label: i18nTexts.editPolicy.maxNumSegmentsFieldLabel, validations: [ { validator: emptyField( @@ -116,9 +139,46 @@ export const schema: FormSchema = { set_priority: { priority: { defaultValue: defaultSetPriority as any, - label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.indexPriorityLabel', { - defaultMessage: 'Index priority (optional)', - }), + label: i18nTexts.editPolicy.setPriorityFieldLabel, + validations: [{ validator: ifExistsNumberGreaterThanZero }], + serializer: (v: string): any => (v ? parseInt(v, 10) : undefined), + }, + }, + }, + }, + warm: { + min_age: { + defaultValue: '0', + validations: [ + { + validator: ifExistsNumberGreaterThanZero, + }, + ], + }, + actions: { + forcemerge: { + max_num_segments: { + label: i18nTexts.editPolicy.maxNumSegmentsFieldLabel, + validations: [ + { + validator: emptyField( + i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.forcemerge.numberOfSegmentsRequiredError', + { defaultMessage: 'A value for number of segments is required.' } + ) + ), + }, + { + validator: ifExistsNumberGreaterThanZero, + }, + ], + serializer: (v: string): any => (v ? parseInt(v, 10) : undefined), + }, + }, + set_priority: { + priority: { + defaultValue: '50' as any, + label: i18nTexts.editPolicy.setPriorityFieldLabel, validations: [{ validator: ifExistsNumberGreaterThanZero }], serializer: (v: string): any => (v ? parseInt(v, 10) : undefined), }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index 31bb10b402d27c..448df90161c93d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -11,5 +11,30 @@ export const i18nTexts = { forceMergeEnabledFieldLabel: i18n.translate('xpack.indexLifecycleMgmt.forcemerge.enableLabel', { defaultMessage: 'Force merge data', }), + maxNumSegmentsFieldLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.forceMerge.numberOfSegmentsLabel', + { + defaultMessage: 'Number of segments', + } + ), + setPriorityFieldLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.indexPriorityLabel', + { + defaultMessage: 'Index priority (optional)', + } + ), + bestCompressionFieldLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.forcemerge.bestCompressionLabel', + { + defaultMessage: 'Compress stored fields', + } + ), + bestCompressionFieldHelpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.forceMerge.bestCompressionText', + { + defaultMessage: + 'Use higher compression for stored fields at the cost of slower performance.', + } + ), }, }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index dba56eb8ecbf3e..7c8bc7e8080210 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -22,5 +22,12 @@ export interface FormInternal extends SerializedPolicy { maxStorageSizeUnit?: string; maxAgeUnit?: string; }; + warm: { + enabled: boolean; + forceMergeEnabled: boolean; + bestCompression: boolean; + warmPhaseOnRollover: boolean; + minAgeUnit?: string; + }; }; } From e867ffe8e60adea5a5fe88023b30dac60353210a Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 20 Oct 2020 16:15:49 +0200 Subject: [PATCH 02/19] introduce edit policy context to share original policy and migrate shrink and replicas fields --- .../phases/shared/forcemerge_field.tsx | 97 ++-- .../phases/warm_phase/warm_phase.tsx | 140 +++--- .../components/toggleable_field.tsx | 6 +- .../sections/edit_policy/edit_policy.tsx | 439 +++++++++--------- .../edit_policy/edit_policy_context.tsx | 32 ++ .../sections/edit_policy/form_schema.ts | 49 +- .../sections/edit_policy/form_validations.ts | 12 +- .../sections/edit_policy/i18n_texts.ts | 14 + 8 files changed, 413 insertions(+), 376 deletions(-) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx index 2a43b99e71452d..b410bd0e6b3b03 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/forcemerge_field.tsx @@ -4,29 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ +import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiDescribedFormGroup, EuiSpacer, EuiTextColor } from '@elastic/eui'; -import React from 'react'; +import { EuiSpacer, EuiTextColor } from '@elastic/eui'; -import { Phases } from '../../../../../../../common/types'; - -import { UseField, ToggleField, NumericField, useFormData } from '../../../../../../shared_imports'; +import { UseField, ToggleField, NumericField } from '../../../../../../shared_imports'; import { i18nTexts } from '../../../i18n_texts'; -import { LearnMoreLink } from '../../'; +import { useEditPolicyContext } from '../../../edit_policy_context'; + +import { LearnMoreLink, DescribedFormField } from '../../'; interface Props { - phase: keyof Phases & string; + phase: 'hot' | 'warm'; } export const Forcemerge: React.FunctionComponent = ({ phase }) => { - const forceMergeEnabledPath = `_meta.${phase}.forceMergeEnabled`; - const [{ [forceMergeEnabledPath]: forceMergeEnabled }] = useFormData({ - watch: [forceMergeEnabledPath], - }); + const { originalPolicy } = useEditPolicyContext(); + + const initialToggleValue = useMemo(() => { + return Boolean(originalPolicy.phases[phase]?.actions?.forcemerge); + }, [originalPolicy, phase]); + return ( - = ({ phase }) => { } titleSize="xs" fullWidth + switchProps={{ + 'aria-label': i18nTexts.editPolicy.forceMergeEnabledFieldLabel, + 'data-test-subj': `${phase}-forceMergeSwitch`, + 'aria-controls': 'forcemergeContent', + label: i18nTexts.editPolicy.forceMergeEnabledFieldLabel, + initialValue: initialToggleValue, + }} > -
- {forceMergeEnabled && ( - <> - - - - )} + +
-
+ ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index a4f1500c350f1a..3f149827365bfe 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -13,7 +13,6 @@ import { EuiFlexItem, EuiSpacer, EuiFieldNumber, - EuiSwitch, EuiDescribedFormGroup, } from '@elastic/eui'; @@ -22,6 +21,7 @@ import { UseField, ToggleField, useFormContext, + NumericField, } from '../../../../../../shared_imports'; import { Phases, WarmPhase as WarmPhaseInterface } from '../../../../../../../common/types'; @@ -29,14 +29,15 @@ import { PhaseValidationErrors } from '../../../../../services/policies/policy_v import { useRolloverPath, MinAgeInputField, Forcemerge, SetPriorityInput } from '../shared'; +import { useEditPolicyContext } from '../../../edit_policy_context'; + import { LearnMoreLink, ActiveBadge, PhaseErrorMessage, - OptionalLabel, ErrableFormRow, DescribedFormField, -} from '../../index'; +} from '../../'; import { DataTierAllocationField } from '../shared'; @@ -64,6 +65,7 @@ interface Props { errors?: PhaseValidationErrors; } export const WarmPhase: FunctionComponent = ({ setPhaseData, phaseData, errors }) => { + const { originalPolicy } = useEditPolicyContext(); const form = useFormContext(); const [ { @@ -173,40 +175,25 @@ export const WarmPhase: FunctionComponent = ({ setPhaseData, phaseData, e 'xpack.indexLifecycleMgmt.editPolicy.warmPhase.numberOfReplicas.switchLabel', { defaultMessage: 'Set replicas' } ), - initialValue: Boolean(phaseData.selectedReplicaCount), - onChange: (v) => { - if (!v) { - setPhaseData('selectedReplicaCount', ''); - } - }, + initialValue: Boolean( + originalPolicy.phases.warm?.actions?.allocate?.number_of_replicas + ), }} fullWidth > - - - - - } - isShowingErrors={isShowingErrors} - errors={errors?.selectedReplicaCount} - > - { - setPhaseData('selectedReplicaCount', e.target.value); - }} - min={0} - /> - + - = ({ setPhaseData, phaseData, e } - fullWidth titleSize="xs" + switchProps={{ + 'aria-controls': 'shrinkContent', + 'data-test-subj': 'shrinkSwitch', + label: i18nTexts.shrinkLabel, + 'aria-label': i18nTexts.shrinkLabel, + initialValue: Boolean(originalPolicy.phases.warm?.actions?.shrink), + }} + fullWidth > - - { - setPhaseData(phaseProperty('shrinkEnabled'), e.target.checked); - }} - label={i18nTexts.shrinkLabel} - aria-label={i18nTexts.shrinkLabel} - aria-controls="shrinkContent" - /> - -
- {phaseData.shrinkEnabled ? ( - - - - - - { - setPhaseData( - phaseProperty('selectedPrimaryShardCount'), - e.target.value - ); - }} - min={1} - /> - - - - - - ) : null} -
-
-
+
+ + + + + { + setPhaseData(phaseProperty('selectedPrimaryShardCount'), e.target.value); + }} + min={1} + /> + + + + +
+ diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/toggleable_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/toggleable_field.tsx index ff4301808db339..d188a172d746be 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/toggleable_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/toggleable_field.tsx @@ -9,7 +9,7 @@ import { EuiSpacer, EuiSwitch, EuiSwitchProps } from '@elastic/eui'; export interface Props extends Omit { initialValue: boolean; - onChange: (nextValue: boolean) => void; + onChange?: (nextValue: boolean) => void; } export const ToggleableField: FunctionComponent = ({ @@ -28,7 +28,9 @@ export const ToggleableField: FunctionComponent = ({ onChange={(e) => { const nextValue = e.target.checked; setIsContentVisible(nextValue); - onChange(nextValue); + if (onChange) { + onChange(nextValue); + } }} /> 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 8f8b0447f378a4..47b04abd535281 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 @@ -67,6 +67,8 @@ import { schema } from './form_schema'; import { deserializer } from './deserializer'; import { createSerializer } from './serializer'; +import { EditPolicyContextProvider } from './edit_policy_context'; + export interface Props { policies: PolicyFromES[]; policyName: string; @@ -113,9 +115,11 @@ export const EditPolicy: React.FunctionComponent = ({ return createSerializer(existingPolicy?.policy); }, [existingPolicy?.policy]); + const originalPolicy = existingPolicy?.policy ?? defaultPolicy; + const { form } = useForm({ schema, - defaultValue: existingPolicy?.policy ?? defaultPolicy, + defaultValue: originalPolicy, deserializer, serializer, }); @@ -220,234 +224,243 @@ export const EditPolicy: React.FunctionComponent = ({ ); return ( - - - - -

- {isNewPolicy - ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', { - defaultMessage: 'Create an index lifecycle policy', - }) - : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', { - defaultMessage: 'Edit index lifecycle policy {originalPolicyName}', - values: { originalPolicyName }, - })} -

-
- -
-
- - -

- + +

+ {isNewPolicy + ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', { + defaultMessage: 'Create an index lifecycle policy', + }) + : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', { + defaultMessage: 'Edit index lifecycle policy {originalPolicyName}', + values: { originalPolicyName }, + })} +

+ + +
+ + + +

+ {' '} - - } - /> -

-
+ />{' '} + + } + /> +

+ - + - {isNewPolicy ? null : ( - - -

- + {isNewPolicy ? null : ( + + +

+ + + + .{' '} - - .{' '} - +

+
+ + + + { + setSaveAsNew(e.target.checked); + }} + label={ + + + + } /> -

- - - - - { - setSaveAsNew(e.target.checked); - }} - label={ - + +
+ )} + + {saveAsNew || isNewPolicy ? ( + + - } - /> - - - )} - - {saveAsNew || isNewPolicy ? ( - - +
+ } + titleSize="s" + fullWidth + > + - -
- } - titleSize="s" - fullWidth - > - + { + setPolicy({ ...policy, name: e.target.value }); + }} /> - } - > - { - setPolicy({ ...policy, name: e.target.value }); - }} - /> - - - ) : null} - - - - - - - - 0} - setPhaseData={setWarmPhaseData} - phaseData={policy.phases.warm} - /> - - - - 0} - setPhaseData={setColdPhaseData} - phaseData={policy.phases.cold} - /> - - - - 0 - } - getUrlForApp={getUrlForApp} - setPhaseData={setDeletePhaseData} - phaseData={policy.phases.delete} - /> - - - - - - - - - {saveAsNew ? ( - - ) : ( + + + ) : null} + + + + + + + + 0 + } + setPhaseData={setWarmPhaseData} + phaseData={policy.phases.warm} + /> + + + + 0 + } + setPhaseData={setColdPhaseData} + phaseData={policy.phases.cold} + /> + + + + 0 + } + getUrlForApp={getUrlForApp} + setPhaseData={setDeletePhaseData} + phaseData={policy.phases.delete} + /> + + + + + + + + + {saveAsNew ? ( + + ) : ( + + )} + + + + + - )} - - - - - + + + + + + + + {isShowingPolicyJsonFlyout ? ( - - - - - - - - {isShowingPolicyJsonFlyout ? ( - - ) : ( - - )} - - - - - {isShowingPolicyJsonFlyout ? ( - setIsShowingPolicyJsonFlyout(false)} - /> - ) : null} - - -
-
-
+ ) : ( + + )} + + + + + {isShowingPolicyJsonFlyout ? ( + setIsShowingPolicyJsonFlyout(false)} + /> + ) : null} + + + + + + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx new file mode 100644 index 00000000000000..4748c26d6cec1b --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy_context.tsx @@ -0,0 +1,32 @@ +/* + * 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, { createContext, ReactChild, useContext } from 'react'; +import { SerializedPolicy } from '../../../../common/types'; + +interface EditPolicyContextValue { + originalPolicy: SerializedPolicy; +} + +const EditPolicyContext = createContext(null as any); + +export const EditPolicyContextProvider = ({ + value, + children, +}: { + value: EditPolicyContextValue; + children: ReactChild; +}) => { + return {children}; +}; + +export const useEditPolicyContext = () => { + const ctx = useContext(EditPolicyContext); + if (!ctx) { + throw new Error('useEditPolicyContext can only be called inside of EditPolicyContext!'); + } + return ctx; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts index 6d62e0aed2d735..8985ee4e6a68c7 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts @@ -10,10 +10,12 @@ import { FormSchema, fieldValidators } from '../../../shared_imports'; import { defaultSetPriority } from '../../constants'; import { FormInternal } from './types'; + import { ifExistsNumberGreaterThanZero, rolloverThresholdsValidator } from './form_validations'; + import { i18nTexts } from './i18n_texts'; -const { emptyField } = fieldValidators; +const { emptyField, numberGreaterThanField } = fieldValidators; export const schema: FormSchema = { _meta: { @@ -30,20 +32,9 @@ export const schema: FormSchema = { maxAgeUnit: { defaultValue: 'd', }, - forceMergeEnabled: { - label: i18nTexts.editPolicy.forceMergeEnabledFieldLabel, - }, bestCompression: { - label: i18n.translate('xpack.indexLifecycleMgmt.forcemerge.bestCompressionLabel', { - defaultMessage: 'Compress stored fields', - }), - helpText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.forceMerge.bestCompressionText', - { - defaultMessage: - 'Use higher compression for stored fields at the cost of slower performance.', - } - ), + label: i18nTexts.editPolicy.bestCompressionFieldLabel, + helpText: i18nTexts.editPolicy.bestCompressionFieldHelpText, }, }, warm: { @@ -63,9 +54,6 @@ export const schema: FormSchema = { minAgeUnit: { defaultValue: 'd', }, - forceMergeEnabled: { - label: i18nTexts.editPolicy.forceMergeEnabledFieldLabel, - }, bestCompression: { label: i18nTexts.editPolicy.bestCompressionFieldLabel, helpText: i18nTexts.editPolicy.bestCompressionFieldHelpText, @@ -156,6 +144,33 @@ export const schema: FormSchema = { ], }, actions: { + allocate: { + number_of_replicas: { + label: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.numberOfReplicasLabel', { + defaultMessage: 'Number of replicas (optional)', + }), + validations: [ + { + validator: ifExistsNumberGreaterThanZero, + }, + ], + }, + }, + shrink: { + number_of_shards: { + validations: [ + { + validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), + }, + { + validator: numberGreaterThanField({ + message: i18nTexts.editPolicy.errors.numberGreatThan0Required, + than: 0, + }), + }, + ], + }, + }, forcemerge: { max_num_segments: { label: i18nTexts.editPolicy.maxNumSegmentsFieldLabel, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts index b937ea2043138c..18f62eb066c7ed 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts @@ -4,27 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; import { fieldValidators, ValidationFunc } from '../../../shared_imports'; import { i18nTexts } from './components/phases/hot_phase/i18n_texts'; import { ROLLOVER_FORM_PATHS } from './constants'; -const { numberGreaterThanField } = fieldValidators; +import { i18nTexts } from './i18n_texts'; -export const positiveNumberRequiredMessage = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.numberAboveZeroRequiredError', - { - defaultMessage: 'Only numbers above 0 are allowed.', - } -); +const { numberGreaterThanField } = fieldValidators; export const ifExistsNumberGreaterThanZero: ValidationFunc = (arg) => { if (arg.value) { return numberGreaterThanField({ than: 0, - message: positiveNumberRequiredMessage, + message: i18nTexts.editPolicy.errors.numberGreatThan0Required, })({ ...arg, value: parseInt(arg.value, 10), diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index 448df90161c93d..ae442091bd9c82 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -36,5 +36,19 @@ export const i18nTexts = { 'Use higher compression for stored fields at the cost of slower performance.', } ), + errors: { + numberRequired: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.numberRequiredErrorMessage', + { + defaultMessage: 'A number is required.', + } + ), + numberGreatThan0Required: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.numberAboveZeroRequiredError', + { + defaultMessage: 'Only numbers above 0 are allowed.', + } + ), + }, }, }; From d349eee9b1a8804f352cad60d52f773387d311a2 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 20 Oct 2020 18:43:00 +0200 Subject: [PATCH 03/19] Refactored biggest field; data allocation Copied the entire field for now duplicating all of the components --- .../data_tiers/determine_allocation_type.ts | 22 ++- .../application/lib/data_tiers/index.ts | 2 + .../{forcemerge.tsx => forcemerge_legacy.tsx} | 0 .../sections/edit_policy/components/index.ts | 6 +- ...age_input.tsx => min_age_input_legacy.tsx} | 0 .../components/phases/cold_phase.tsx | 4 +- .../components/phases/hot_phase/hot_phase.tsx | 12 +- .../components/phases/hot_phase/i18n_texts.ts | 37 ---- .../components/cloud_data_tier_callout.tsx | 26 +++ .../components/data_tier_allocation.scss | 9 + .../components/data_tier_allocation.tsx | 184 ++++++++++++++++++ .../components/default_allocation_notice.tsx | 106 ++++++++++ .../components/index.ts | 19 ++ .../components/no_node_attributes_warning.tsx | 50 +++++ .../components/node_allocation.tsx | 115 +++++++++++ .../components/node_attrs_details.tsx | 106 ++++++++++ .../components/node_data_provider.tsx | 70 +++++++ .../components/types.ts | 22 +++ .../data_tier_allocation_field.tsx | 43 ++-- .../data_tier_allocation_field/index.ts | 7 + .../data_tier_allocation_legacy_field.tsx | 137 +++++++++++++ .../components/phases/shared/index.ts | 2 + .../phases/warm_phase/warm_phase.tsx | 3 - ...nput.tsx => set_priority_input_legacy.tsx} | 0 .../sections/edit_policy/deserializer.ts | 47 +++-- .../sections/edit_policy/form_schema.ts | 7 + .../sections/edit_policy/form_validations.ts | 14 +- .../sections/edit_policy/i18n_texts.ts | 43 ++++ .../application/sections/edit_policy/types.ts | 4 + .../services/policies/cold_phase.ts | 4 +- .../services/policies/warm_phase.ts | 4 +- .../public/shared_imports.ts | 1 + 32 files changed, 1000 insertions(+), 106 deletions(-) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{forcemerge.tsx => forcemerge_legacy.tsx} (100%) rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{min_age_input.tsx => min_age_input_legacy.tsx} (100%) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/i18n_texts.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/cloud_data_tier_callout.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.scss create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/index.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_attrs_details.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_data_provider.tsx create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/types.ts rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/{ => data_tier_allocation_field}/data_tier_allocation_field.tsx (76%) create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/index.ts create mode 100644 x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_legacy_field.tsx rename x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/{set_priority_input.tsx => set_priority_input_legacy.tsx} (100%) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/determine_allocation_type.ts b/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/determine_allocation_type.ts index 4067ad97fc43bc..099893a9f04011 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/determine_allocation_type.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/determine_allocation_type.ts @@ -11,7 +11,7 @@ import { DataTierAllocationType, AllocateAction } from '../../../../common/types * * See {@DataTierAllocationType} for more information. */ -export const determineDataTierAllocationType = ( +export const determineDataTierAllocationTypeLegacy = ( allocateAction?: AllocateAction ): DataTierAllocationType => { if (!allocateAction) { @@ -32,3 +32,23 @@ export const determineDataTierAllocationType = ( return 'default'; }; + +export const determineDataTierAllocationType = (allocateAction?: AllocateAction) => { + if (!allocateAction) { + return 'node_roles'; + } + + if (allocateAction.migrate?.enabled === false) { + return 'none'; + } + + if ( + (allocateAction.require && Object.keys(allocateAction.require).length) || + (allocateAction.include && Object.keys(allocateAction.include).length) || + (allocateAction.exclude && Object.keys(allocateAction.exclude).length) + ) { + return 'node_attrs'; + } + + return 'node_roles'; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/index.ts index 87f2cbc08ecc0b..ac1aae270628d1 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/index.ts @@ -7,3 +7,5 @@ export * from './determine_allocation_type'; export * from './get_available_node_roles_for_phase'; + +export * from './is_node_role_first_preference'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/forcemerge.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/forcemerge_legacy.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/forcemerge.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/forcemerge_legacy.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts index 04d9a6ef3cf67f..2b774b00b98a9f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts @@ -7,11 +7,11 @@ export { ActiveBadge } from './active_badge'; export { ErrableFormRow } from './form_errors'; export { LearnMoreLink } from './learn_more_link'; -export { MinAgeInput } from './min_age_input'; +export { MinAgeInput } from './min_age_input_legacy'; export { OptionalLabel } from './optional_label'; export { PhaseErrorMessage } from './phase_error_message'; export { PolicyJsonFlyout } from './policy_json_flyout'; -export { SetPriorityInput } from './set_priority_input'; +export { SetPriorityInput } from './set_priority_input_legacy'; export { SnapshotPolicies } from './snapshot_policies'; export { DataTierAllocation, @@ -21,6 +21,6 @@ export { DefaultAllocationNotice, } from './data_tier_allocation'; export { DescribedFormField } from './described_form_field'; -export { Forcemerge } from './forcemerge'; +export { Forcemerge } from './forcemerge_legacy'; export * from './phases'; 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_legacy.tsx similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/min_age_input_legacy.tsx diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase.tsx index 7ed8a94403a9bc..3911925751ceb9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase.tsx @@ -27,7 +27,7 @@ import { DescribedFormField, } from '../'; -import { DataTierAllocationField, useRolloverPath } from './shared'; +import { DataTierAllocationFieldLegacy, useRolloverPath } from './shared'; const i18nTexts = { freezeLabel: i18n.translate('xpack.indexLifecycleMgmt.coldPhase.freezeIndexLabel', { @@ -121,7 +121,7 @@ export const ColdPhase: FunctionComponent = ({ {phaseData.phaseEnabled ? ( {/* Data tier allocation section */} - void }> = ({ @@ -123,11 +121,11 @@ export const HotPhase: FunctionComponent<{ setWarmPhaseOnRollover: (v: boolean) {showEmptyRolloverFieldsError && ( <> -
{i18nTexts.rollOverConfigurationCallout.body}
+
{i18nTexts.editPolicy.errors.rollOverConfigurationCallout.body}
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/i18n_texts.ts deleted file mode 100644 index 6423b12b86dd24..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/i18n_texts.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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 { i18n } from '@kbn/i18n'; - -export const i18nTexts = { - maximumAgeRequiredMessage: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.maximumAgeMissingError', - { - defaultMessage: 'A maximum age is required.', - } - ), - maximumSizeRequiredMessage: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.maximumIndexSizeMissingError', - { - defaultMessage: 'A maximum index size is required.', - } - ), - maximumDocumentsRequiredMessage: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.maximumDocumentsMissingError', - { - defaultMessage: 'Maximum documents is required.', - } - ), - rollOverConfigurationCallout: { - title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.rolloverConfigurationError.title', { - defaultMessage: 'Invalid rollover configuration', - }), - body: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.rolloverConfigurationError.body', { - defaultMessage: - 'A value for one of maximum size, maximum documents, or maximum age is required.', - }), - }, -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/cloud_data_tier_callout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/cloud_data_tier_callout.tsx new file mode 100644 index 00000000000000..2dff55ac10de18 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/cloud_data_tier_callout.tsx @@ -0,0 +1,26 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React, { FunctionComponent } from 'react'; +import { EuiCallOut } from '@elastic/eui'; + +const i18nTexts = { + title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.cloudDataTierCallout.title', { + defaultMessage: 'Create a cold tier', + }), + body: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.cloudDataTierCallout.body', { + defaultMessage: 'Edit your Elastic Cloud deployment to set up a cold tier.', + }), +}; + +export const CloudDataTierCallout: FunctionComponent = () => { + return ( + + {i18nTexts.body} + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.scss b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.scss new file mode 100644 index 00000000000000..62ec3f303e1e8c --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.scss @@ -0,0 +1,9 @@ +.indexLifecycleManagement__phase__dataTierAllocation { + &__controlSection { + background-color: $euiColorLightestShade; + padding-top: $euiSizeM; + padding-left: $euiSizeM; + padding-right: $euiSizeM; + padding-bottom: $euiSizeM; + } +} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx new file mode 100644 index 00000000000000..99fa2abac3abe5 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx @@ -0,0 +1,184 @@ +/* + * 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, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiText, EuiSpacer, EuiSuperSelectOption } from '@elastic/eui'; + +import { UseField, SuperSelectField, useFormData } from '../../../../../../../../shared_imports'; + +import { DataTierAllocationType } from '../../../../../types'; + +import { NodeAllocation } from './node_allocation'; +import { SharedProps } from './types'; + +import './data_tier_allocation.scss'; + +type SelectOptions = EuiSuperSelectOption; + +const i18nTexts = { + allocationOptions: { + warm: { + default: { + input: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.defaultOption.input', + { defaultMessage: 'Use warm nodes (recommended)' } + ), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.defaultOption.helpText', + { defaultMessage: 'Move data to nodes in the warm tier.' } + ), + }, + none: { + inputDisplay: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.noneOption.input', + { defaultMessage: 'Off' } + ), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.noneOption.helpText', + { defaultMessage: 'Do not move data in the warm phase.' } + ), + }, + custom: { + inputDisplay: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.customOption.input', + { defaultMessage: 'Custom' } + ), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.warm.customOption.helpText', + { defaultMessage: 'Move data based on node attributes.' } + ), + }, + }, + cold: { + default: { + input: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.defaultOption.input', + { defaultMessage: 'Use cold nodes (recommended)' } + ), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.defaultOption.helpText', + { defaultMessage: 'Move data to nodes in the cold tier.' } + ), + }, + none: { + inputDisplay: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.noneOption.input', + { defaultMessage: 'Off' } + ), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.noneOption.helpText', + { defaultMessage: 'Do not move data in the cold phase.' } + ), + }, + custom: { + inputDisplay: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.input', + { defaultMessage: 'Custom' } + ), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.helpText', + { defaultMessage: 'Move data based on node attributes.' } + ), + }, + }, + }, +}; + +export const DataTierAllocation: FunctionComponent = (props) => { + const { phase, hasNodeAttributes, disableDataTierOption } = props; + + const dataTierAllocationTypePath = `_meta.${phase}.dataTierAllocationType`; + + const [{ [dataTierAllocationTypePath]: dataTierAllocationType }] = useFormData({ + watch: [dataTierAllocationTypePath], + }); + + return ( +
+ + {(field) => { + /** + * We reset the value to "custom" if we deserialized to "default". + * + * It would be better if we had all the information we needed before deserializing and + * were able to handle this at the deserialization step instead of patching further down + * the component tree - this should be a future refactor. + */ + if (disableDataTierOption && field.value === 'node_roles') { + field.setValue('node_attrs'); + } + return ( + + {i18nTexts.allocationOptions[phase].default.input} + +

+ {i18nTexts.allocationOptions[phase].default.helpText} +

+
+ + ), + }, + { + 'data-test-subj': 'customDataAllocationOption', + value: 'node_attrs', + inputDisplay: i18nTexts.allocationOptions[phase].custom.inputDisplay, + dropdownDisplay: ( + <> + {i18nTexts.allocationOptions[phase].custom.inputDisplay} + +

+ {i18nTexts.allocationOptions[phase].custom.helpText} +

+
+ + ), + }, + { + 'data-test-subj': 'noneDataAllocationOption', + value: 'none', + inputDisplay: i18nTexts.allocationOptions[phase].none.inputDisplay, + dropdownDisplay: ( + <> + {i18nTexts.allocationOptions[phase].none.inputDisplay} + +

+ {i18nTexts.allocationOptions[phase].none.helpText} +

+
+ + ), + }, + ].filter(Boolean) as SelectOptions[], + }} + /> + ); + }} +
+ {dataTierAllocationType === 'node_attrs' && hasNodeAttributes && ( + <> + +
+ +
+ + )} +
+ ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx new file mode 100644 index 00000000000000..9f7b37d49594d1 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx @@ -0,0 +1,106 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React, { FunctionComponent } from 'react'; +import { EuiCallOut } from '@elastic/eui'; + +import { PhaseWithAllocation, NodeDataRole } from '../../../../../../../../../common/types'; + +import { AllocationNodeRole } from '../../../../../../../lib'; + +const i18nTextsNodeRoleToDataTier: Record = { + data_hot: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.dataTierHotLabel', { + defaultMessage: 'hot', + }), + data_warm: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.dataTierWarmLabel', { + defaultMessage: 'warm', + }), + data_cold: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.dataTierColdLabel', { + defaultMessage: 'cold', + }), +}; + +const i18nTexts = { + notice: { + warm: { + title: i18n.translate( + 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm.title', + { defaultMessage: 'No nodes assigned to the warm tier' } + ), + body: (nodeRole: NodeDataRole) => + i18n.translate('xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm', { + defaultMessage: + 'This policy will move data in the warm phase to {tier} tier nodes instead.', + values: { tier: i18nTextsNodeRoleToDataTier[nodeRole] }, + }), + }, + cold: { + title: i18n.translate( + 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.cold.title', + { defaultMessage: 'No nodes assigned to the cold tier' } + ), + body: (nodeRole: NodeDataRole) => + i18n.translate('xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.cold', { + defaultMessage: + 'This policy will move data in the cold phase to {tier} tier nodes instead.', + values: { tier: i18nTextsNodeRoleToDataTier[nodeRole] }, + }), + }, + }, + warning: { + warm: { + title: i18n.translate( + 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotAvailableTitle', + { defaultMessage: 'No nodes assigned to the warm tier' } + ), + body: i18n.translate( + 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotAvailableBody', + { + defaultMessage: + 'Assign at least one node to the warm or hot tier to use role-based allocation. The policy will fail to complete allocation if there are no available nodes.', + } + ), + }, + cold: { + title: i18n.translate( + 'xpack.indexLifecycleMgmt.coldPhase.dataTier.defaultAllocationNotAvailableTitle', + { defaultMessage: 'No nodes assigned to the cold tier' } + ), + body: i18n.translate( + 'xpack.indexLifecycleMgmt.coldPhase.dataTier.defaultAllocationNotAvailableBody', + { + defaultMessage: + 'Assign at least one node to the cold, warm, or hot tier to use role-based allocation. The policy will fail to complete allocation if there are no available nodes.', + } + ), + }, + }, +}; + +interface Props { + phase: PhaseWithAllocation; + targetNodeRole: AllocationNodeRole; +} + +export const DefaultAllocationNotice: FunctionComponent = ({ phase, targetNodeRole }) => { + const content = + targetNodeRole === 'none' ? ( + + {i18nTexts.warning[phase].body} + + ) : ( + + {i18nTexts.notice[phase].body(targetNodeRole)} + + ); + + return content; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/index.ts new file mode 100644 index 00000000000000..0782782e77fadc --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/index.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ + +export { NodesDataProvider } from './node_data_provider'; + +export { NodeAllocation } from './node_allocation'; + +export { NodeAttrsDetails } from './node_attrs_details'; + +export { DataTierAllocation } from './data_tier_allocation'; + +export { DefaultAllocationNotice } from './default_allocation_notice'; + +export { NoNodeAttributesWarning } from './no_node_attributes_warning'; + +export { CloudDataTierCallout } from './cloud_data_tier_callout'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx new file mode 100644 index 00000000000000..69185277f64ce6 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx @@ -0,0 +1,50 @@ +/* + * 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, { FunctionComponent } from 'react'; +import { EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { PhaseWithAllocation } from '../../../../../../common/types'; + +const i18nTexts = { + title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.nodeAttributesMissingLabel', { + defaultMessage: 'No custom node attributes configured', + }), + warm: { + body: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.warm.nodeAttributesMissingDescription', + { + defaultMessage: + 'Define custom node attributes in elasticsearch.yml to use attribute-based allocation. Warm nodes will be used instead.', + } + ), + }, + cold: { + body: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.cold.nodeAttributesMissingDescription', + { + defaultMessage: + 'Define custom node attributes in elasticsearch.yml to use attribute-based allocation. Cold nodes will be used instead.', + } + ), + }, +}; + +export const NoNodeAttributesWarning: FunctionComponent<{ phase: PhaseWithAllocation }> = ({ + phase, +}) => { + return ( + + {i18nTexts[phase].body} + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx new file mode 100644 index 00000000000000..fe74c82d29e7b2 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx @@ -0,0 +1,115 @@ +/* + * 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, { useState, FunctionComponent } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonEmpty, EuiText, EuiSpacer } from '@elastic/eui'; + +import { PhaseWithAllocationAction } from '../../../../../../../../../common/types'; + +import { UseField, SelectField, useFormData } from '../../../../../../../../shared_imports'; + +import { propertyof } from '../../../../../../../services/policies/policy_validation'; + +import { LearnMoreLink } from '../../../../learn_more_link'; + +import { NodeAttrsDetails } from './node_attrs_details'; + +import { SharedProps } from './types'; + +const learnMoreLink = ( + + } + docPath="modules-cluster.html#cluster-shard-allocation-settings" + /> +); + +const i18nTexts = { + doNotModifyAllocationOption: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.nodeAllocation.doNotModifyAllocationOption', + { defaultMessage: 'Do not modify allocation configuration' } + ), +}; + +export const NodeAllocation: FunctionComponent = ({ phase, nodes }) => { + const allocationNodeAttributePath = `_meta.${phase}.allocationNodeAttribute`; + + const [{ [allocationNodeAttributePath]: selectedAllocationNodeAttribute }] = useFormData({ + watch: [allocationNodeAttributePath], + }); + + const [selectedNodeAttrsForDetails, setSelectedNodeAttrsForDetails] = useState( + null + ); + + const nodeOptions = Object.keys(nodes).map((attrs) => ({ + text: `${attrs} (${nodes[attrs].length})`, + value: attrs, + })); + + nodeOptions.sort((a, b) => a.value.localeCompare(b.value)); + + // check that this string is a valid property + const nodeAttrsProperty = propertyof('selectedNodeAttrs'); + + return ( + <> + +

+ +

+
+ + + {/* + TODO: this field component must be revisited to support setting multiple require values and to support + setting `include and exclude values on ILM policies. See https://github.com/elastic/kibana/issues/77344 + */} + setSelectedNodeAttrsForDetails(selectedAllocationNodeAttribute)} + > + + + ) : undefined, + euiFieldProps: { + 'data-test-subj': `${phase}-${nodeAttrsProperty}`, + options: [{ text: i18nTexts.doNotModifyAllocationOption, value: 'none' }].concat( + nodeOptions + ), + }, + }} + /> + {selectedNodeAttrsForDetails ? ( + setSelectedNodeAttrsForDetails(null)} + /> + ) : null} + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_attrs_details.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_attrs_details.tsx new file mode 100644 index 00000000000000..61de977b1cb121 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_attrs_details.tsx @@ -0,0 +1,106 @@ +/* + * 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 from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiFlyoutBody, + EuiFlyout, + EuiTitle, + EuiInMemoryTable, + EuiSpacer, + EuiPortal, + EuiLoadingContent, + EuiCallOut, + EuiButton, +} from '@elastic/eui'; + +import { useLoadNodeDetails } from '../../../../../../../services/api'; + +interface Props { + close: () => void; + selectedNodeAttrs: string; +} + +export const NodeAttrsDetails: React.FunctionComponent = ({ close, selectedNodeAttrs }) => { + const { data, isLoading, error, resendRequest } = useLoadNodeDetails(selectedNodeAttrs); + let content; + if (isLoading) { + content = ; + } else if (error) { + const { statusCode, message } = error; + content = ( + + } + color="danger" + > +

+ {message} ({statusCode}) +

+ + + +
+ ); + } else { + content = ( + + ); + } + return ( + + + + +

+ +

+
+ + {content} +
+
+
+ ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_data_provider.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_data_provider.tsx new file mode 100644 index 00000000000000..bb01a42e378faa --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_data_provider.tsx @@ -0,0 +1,70 @@ +/* + * 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 from 'react'; +import { EuiButton, EuiCallOut, EuiLoadingSpinner, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { ListNodesRouteResponse } from '../../../../../../../../../common/types'; +import { useLoadNodes } from '../../../../../../../services/api'; + +interface Props { + children: (data: ListNodesRouteResponse) => JSX.Element; +} + +export const NodesDataProvider = ({ children }: Props): JSX.Element => { + const { isLoading, data, error, resendRequest } = useLoadNodes(); + + if (isLoading) { + return ( + <> + + + + ); + } + + const renderError = () => { + if (error) { + const { statusCode, message } = error; + return ( + <> + + } + color="danger" + > +

+ {message} ({statusCode}) +

+ + + +
+ + + + ); + } + return null; + }; + + return ( + <> + {renderError()} + {/* `data` will always be defined because we use an initial value when loading */} + {children(data!)} + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/types.ts new file mode 100644 index 00000000000000..7bd620757a8ac5 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/types.ts @@ -0,0 +1,22 @@ +/* + * 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 { + ListNodesRouteResponse, + PhaseWithAllocation, +} from '../../../../../../../../../common/types'; + +export interface SharedProps { + phase: PhaseWithAllocation; + nodes: ListNodesRouteResponse['nodesByAttributes']; + hasNodeAttributes: boolean; + /** + * When on Cloud we want to disable the data tier allocation option when we detect that we are not + * using node roles in our Node config yet. See {@link ListNodesRouteResponse} for information about how this is + * detected. + */ + disableDataTierOption: boolean; +} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx similarity index 76% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx index de7f321e5f15d6..b7b9a1e9f3dad9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx @@ -6,13 +6,16 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; + import { EuiDescribedFormGroup, EuiFormRow, EuiSpacer } from '@elastic/eui'; -import { useKibana } from '../../../../../../shared_imports'; -import { PhaseWithAllocationAction, PhaseWithAllocation } from '../../../../../../../common/types'; -import { PhaseValidationErrors } from '../../../../../services/policies/policy_validation'; -import { getAvailableNodeRoleForPhase } from '../../../../../lib/data_tiers'; -import { isNodeRoleFirstPreference } from '../../../../../lib/data_tiers/is_node_role_first_preference'; +import { useKibana, useFormData } from '../../../../../../../shared_imports'; + +import { PhaseWithAllocation } from '../../../../../../../../common/types'; + +import { getAvailableNodeRoleForPhase } from '../../../../../../lib/data_tiers'; + +import { isNodeRoleFirstPreference } from '../../../../../../lib'; import { DataTierAllocation, @@ -20,7 +23,7 @@ import { NoNodeAttributesWarning, NodesDataProvider, CloudDataTierCallout, -} from '../../data_tier_allocation'; +} from './components'; const i18nTexts = { title: i18n.translate('xpack.indexLifecycleMgmt.common.dataTier.title', { @@ -29,37 +32,29 @@ const i18nTexts = { }; interface Props { - description: React.ReactNode; phase: PhaseWithAllocation; - setPhaseData: (dataKey: keyof PhaseWithAllocationAction, value: string) => void; - isShowingErrors: boolean; - errors?: PhaseValidationErrors; - phaseData: PhaseWithAllocationAction; + description: React.ReactNode; } /** * Top-level layout control for the data tier allocation field. */ -export const DataTierAllocationField: FunctionComponent = ({ - description, - phase, - phaseData, - setPhaseData, - isShowingErrors, - errors, -}) => { +export const DataTierAllocationField: FunctionComponent = ({ phase, description }) => { const { services: { cloud }, } = useKibana(); + const allocationTypePath = `_meta.${phase}.dataTierAllocationType`; + const [{ [allocationTypePath]: allocationType }] = useFormData({ watch: [allocationTypePath] }); + return ( {({ nodesByRoles, nodesByAttributes, isUsingDeprecatedDataRoleConfig }) => { const hasNodeAttrs = Boolean(Object.keys(nodesByAttributes ?? {}).length); const renderNotice = () => { - switch (phaseData.dataTierAllocationType) { - case 'default': + switch (allocationType) { + case 'node_roles': const isCloudEnabled = cloud?.isCloudEnabled ?? false; const isUsingNodeRoles = !isUsingDeprecatedDataRoleConfig; if ( @@ -90,7 +85,7 @@ export const DataTierAllocationField: FunctionComponent = ({ ); } break; - case 'custom': + case 'node_attrs': if (!hasNodeAttrs) { return ( <> @@ -116,10 +111,6 @@ export const DataTierAllocationField: FunctionComponent = ({ void; + isShowingErrors: boolean; + errors?: PhaseValidationErrors; + phaseData: PhaseWithAllocationAction; +} + +/** + * Top-level layout control for the data tier allocation field. + */ +export const DataTierAllocationFieldLegacy: FunctionComponent = ({ + description, + phase, + phaseData, + setPhaseData, + isShowingErrors, + errors, +}) => { + const { + services: { cloud }, + } = useKibana(); + + return ( + + {({ nodesByRoles, nodesByAttributes, isUsingDeprecatedDataRoleConfig }) => { + const hasNodeAttrs = Boolean(Object.keys(nodesByAttributes ?? {}).length); + + const renderNotice = () => { + switch (phaseData.dataTierAllocationType) { + case 'default': + const isCloudEnabled = cloud?.isCloudEnabled ?? false; + const isUsingNodeRoles = !isUsingDeprecatedDataRoleConfig; + if ( + isCloudEnabled && + isUsingNodeRoles && + phase === 'cold' && + !nodesByRoles.data_cold?.length + ) { + // Tell cloud users they can deploy cold tier nodes. + return ( + <> + + + + ); + } + + const allocationNodeRole = getAvailableNodeRoleForPhase(phase, nodesByRoles); + if ( + allocationNodeRole === 'none' || + !isNodeRoleFirstPreference(phase, allocationNodeRole) + ) { + return ( + <> + + + + ); + } + break; + case 'custom': + if (!hasNodeAttrs) { + return ( + <> + + + + ); + } + break; + default: + return null; + } + }; + + return ( + {i18nTexts.title}} + description={description} + fullWidth + > + + <> + + + {/* Data tier related warnings and call-to-action notices */} + {renderNotice()} + + + + ); + }} + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/index.ts index 6355dab89771d7..0cae3eea6316b9 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/index.ts @@ -6,6 +6,8 @@ export { useRolloverPath } from '../../../constants'; +export { DataTierAllocationFieldLegacy } from './data_tier_allocation_legacy_field'; + export { DataTierAllocationField } from './data_tier_allocation_field'; export { Forcemerge } from './forcemerge_field'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index 3f149827365bfe..a3450c0482460b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -150,9 +150,6 @@ export const WarmPhase: FunctionComponent = ({ setPhaseData, phaseData, e - produce( +export const deserializer = (policy: SerializedPolicy): FormInternal => { + const _meta: FormInternal['_meta'] = { + hot: { + useRollover: Boolean(policy.phases.hot?.actions?.rollover), + forceMergeEnabled: Boolean(policy.phases.hot?.actions?.forcemerge), + bestCompression: policy.phases.hot?.actions?.forcemerge?.index_codec === 'best_compression', + }, + warm: { + enabled: Boolean(policy.phases.warm), + warmPhaseOnRollover: Boolean(policy.phases.warm?.min_age === '0ms'), + forceMergeEnabled: Boolean(policy.phases.warm?.actions?.forcemerge), + bestCompression: policy.phases.warm?.actions?.forcemerge?.index_codec === 'best_compression', + dataTierAllocationType: determineDataTierAllocationType(policy.phases.warm?.actions.allocate), + }, + }; + + return produce( { ...policy, - _meta: { - hot: { - useRollover: Boolean(policy.phases.hot?.actions?.rollover), - forceMergeEnabled: Boolean(policy.phases.hot?.actions?.forcemerge), - bestCompression: - policy.phases.hot?.actions?.forcemerge?.index_codec === 'best_compression', - }, - warm: { - enabled: Boolean(policy.phases.warm), - warmPhaseOnRollover: Boolean(policy.phases.warm?.min_age === '0ms'), - forceMergeEnabled: Boolean(policy.phases.warm?.actions?.forcemerge), - bestCompression: - policy.phases.warm?.actions?.forcemerge?.index_codec === 'best_compression', - }, - }, + _meta, }, - (draft) => { + (draft: FormInternal) => { if (draft.phases.hot?.actions?.rollover) { if (draft.phases.hot.actions.rollover.max_size) { const maxSize = splitSizeAndUnits(draft.phases.hot.actions.rollover.max_size); @@ -46,5 +50,12 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => draft._meta.hot.maxAgeUnit = maxAge.units; } } + + if (draft.phases.warm?.actions?.allocate?.require) { + Object.entries(draft.phases.warm.actions.allocate.require).forEach((entry) => { + draft._meta.warm.allocationNodeAttribute = entry.join(':'); + }); + } } ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts index 8985ee4e6a68c7..32897d7be0ed06 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts @@ -58,6 +58,13 @@ export const schema: FormSchema = { label: i18nTexts.editPolicy.bestCompressionFieldLabel, helpText: i18nTexts.editPolicy.bestCompressionFieldHelpText, }, + dataTierAllocationType: { + label: i18nTexts.editPolicy.allocationTypeOptionsFieldLabel, + }, + allocationNodeAttribute: { + defaultValue: 'none', + label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel, + }, }, }, phases: { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts index 18f62eb066c7ed..37ca4e9def340f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_validations.ts @@ -6,8 +6,6 @@ import { fieldValidators, ValidationFunc } from '../../../shared_imports'; -import { i18nTexts } from './components/phases/hot_phase/i18n_texts'; - import { ROLLOVER_FORM_PATHS } from './constants'; import { i18nTexts } from './i18n_texts'; @@ -48,16 +46,22 @@ export const rolloverThresholdsValidator: ValidationFunc = ({ form }) => { ) ) { fields[ROLLOVER_FORM_PATHS.maxAge].setErrors([ - { validationType: ROLLOVER_EMPTY_VALIDATION, message: i18nTexts.maximumAgeRequiredMessage }, + { + validationType: ROLLOVER_EMPTY_VALIDATION, + message: i18nTexts.editPolicy.errors.maximumAgeRequiredMessage, + }, ]); fields[ROLLOVER_FORM_PATHS.maxDocs].setErrors([ { validationType: ROLLOVER_EMPTY_VALIDATION, - message: i18nTexts.maximumDocumentsRequiredMessage, + message: i18nTexts.editPolicy.errors.maximumDocumentsRequiredMessage, }, ]); fields[ROLLOVER_FORM_PATHS.maxSize].setErrors([ - { validationType: ROLLOVER_EMPTY_VALIDATION, message: i18nTexts.maximumSizeRequiredMessage }, + { + validationType: ROLLOVER_EMPTY_VALIDATION, + message: i18nTexts.editPolicy.errors.maximumSizeRequiredMessage, + }, ]); } else { fields[ROLLOVER_FORM_PATHS.maxAge].clearErrors(ROLLOVER_EMPTY_VALIDATION); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index ae442091bd9c82..71031d1801ee6a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -36,6 +36,16 @@ export const i18nTexts = { 'Use higher compression for stored fields at the cost of slower performance.', } ), + allocationTypeOptionsFieldLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.dataTierAllocation.allocationFieldLabel', + { defaultMessage: 'Data tier options' } + ), + allocationNodeAttributeFieldLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.dataTierAllocation.nodeAllocationFieldLabel', + { + defaultMessage: 'Select a node attribute', + } + ), errors: { numberRequired: i18n.translate( 'xpack.indexLifecycleMgmt.editPolicy.numberRequiredErrorMessage', @@ -49,6 +59,39 @@ export const i18nTexts = { defaultMessage: 'Only numbers above 0 are allowed.', } ), + maximumAgeRequiredMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.maximumAgeMissingError', + { + defaultMessage: 'A maximum age is required.', + } + ), + maximumSizeRequiredMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.maximumIndexSizeMissingError', + { + defaultMessage: 'A maximum index size is required.', + } + ), + maximumDocumentsRequiredMessage: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.maximumDocumentsMissingError', + { + defaultMessage: 'Maximum documents is required.', + } + ), + rollOverConfigurationCallout: { + title: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.rolloverConfigurationError.title', + { + defaultMessage: 'Invalid rollover configuration', + } + ), + body: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.rolloverConfigurationError.body', + { + defaultMessage: + 'A value for one of maximum size, maximum documents, or maximum age is required.', + } + ), + }, }, }, }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index 7c8bc7e8080210..98333b5973dc2f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -6,6 +6,8 @@ import { SerializedPolicy } from '../../../../common/types'; +export type DataTierAllocationType = 'node_roles' | 'node_attrs' | 'none'; + /** * Describes the shape of data after deserialization. */ @@ -27,6 +29,8 @@ export interface FormInternal extends SerializedPolicy { forceMergeEnabled: boolean; bestCompression: boolean; warmPhaseOnRollover: boolean; + dataTierAllocationType: DataTierAllocationType; + allocationNodeAttribute?: string; minAgeUnit?: string; }; }; 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 9f5f603fbc5640..89b4743a995694 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 @@ -13,7 +13,7 @@ import { PhaseValidationErrors, positiveNumberRequiredMessage, } from './policy_validation'; -import { determineDataTierAllocationType } from '../../lib'; +import { determineDataTierAllocationTypeLegacy } from '../../lib'; import { serializePhaseWithAllocation } from './shared'; export const coldPhaseInitialization: ColdPhase = { @@ -36,7 +36,7 @@ export const coldPhaseFromES = (phaseSerialized?: SerializedColdPhase): ColdPhas phase.phaseEnabled = true; if (phaseSerialized.actions.allocate) { - phase.dataTierAllocationType = determineDataTierAllocationType( + phase.dataTierAllocationType = determineDataTierAllocationTypeLegacy( phaseSerialized.actions.allocate ); } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/warm_phase.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/warm_phase.ts index 436e5a222f86d4..a5a6fa6abe47b0 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/warm_phase.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/warm_phase.ts @@ -16,7 +16,7 @@ import { positiveNumbersAboveZeroErrorMessage, } from './policy_validation'; -import { determineDataTierAllocationType } from '../../lib'; +import { determineDataTierAllocationTypeLegacy } from '../../lib'; import { serializePhaseWithAllocation } from './shared'; const warmPhaseInitialization: WarmPhase = { @@ -45,7 +45,7 @@ export const warmPhaseFromES = (phaseSerialized?: SerializedWarmPhase): WarmPhas phase.phaseEnabled = true; if (phaseSerialized.actions.allocate) { - phase.dataTierAllocationType = determineDataTierAllocationType( + phase.dataTierAllocationType = determineDataTierAllocationTypeLegacy( phaseSerialized.actions.allocate ); } diff --git a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts index dc3e1b1d1b62da..023aeba57aa7a6 100644 --- a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts +++ b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts @@ -26,6 +26,7 @@ export { ToggleField, NumericField, SelectField, + SuperSelectField, } from '../../../../src/plugins/es_ui_shared/static/forms/components'; export { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; From 779534e7bba5bca93fce2d1b3da76c56327b9c30 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 20 Oct 2020 18:49:47 +0200 Subject: [PATCH 04/19] remove unused import --- .../public/application/sections/edit_policy/deserializer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts index a29971fafcbdb9..996b64b5940fcc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts @@ -11,7 +11,6 @@ import { SerializedPolicy } from '../../../../common/types'; import { splitSizeAndUnits } from '../../services/policies/policy_serialization'; import { determineDataTierAllocationType } from '../../lib'; -import {} from '../../services/policies/policy_serialization'; import { FormInternal } from './types'; From 5bb0be91829b84c45a0c80cbc877ba622bd98d2d Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 21 Oct 2020 12:35:09 +0200 Subject: [PATCH 05/19] complete migration of new serialization --- .../common/types/policies.ts | 4 +- .../components/phases/hot_phase/hot_phase.tsx | 10 +- .../phases/warm_phase/warm_phase.tsx | 54 +++------ .../components/policy_json_flyout.tsx | 4 +- .../sections/edit_policy/deserializer.ts | 16 ++- .../sections/edit_policy/edit_policy.tsx | 32 +---- .../sections/edit_policy/form_schema.ts | 25 +++- .../sections/edit_policy/i18n_texts.ts | 6 + .../sections/edit_policy/serializer.ts | 111 +++++++++++++++--- .../application/sections/edit_policy/types.ts | 39 +++--- 10 files changed, 179 insertions(+), 122 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index 152c5e4e9e0d70..c2d2c22424666d 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -87,8 +87,8 @@ export interface SerializedDeletePhase extends SerializedPhase { export interface AllocateAction { number_of_replicas?: number; - include: {}; - exclude: {}; + include?: {}; + exclude?: {}; require?: { [attribute: string]: string; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx index 232277e9f762d1..d8a95cd8c9c2f2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FunctionComponent, useEffect, useState } from 'react'; +import React, { Fragment, FunctionComponent, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { @@ -40,19 +40,13 @@ import { maxSizeStoredUnits, maxAgeUnits } from './constants'; const hotProperty: keyof Phases = 'hot'; -export const HotPhase: FunctionComponent<{ setWarmPhaseOnRollover: (v: boolean) => void }> = ({ - setWarmPhaseOnRollover, -}) => { +export const HotPhase: FunctionComponent = () => { const [{ [useRolloverPath]: isRolloverEnabled }] = useFormData({ watch: [useRolloverPath] }); const form = useFormContext(); const isShowingErrors = form.isValid === false; const [showEmptyRolloverFieldsError, setShowEmptyRolloverFieldsError] = useState(false); - useEffect(() => { - setWarmPhaseOnRollover(isRolloverEnabled ?? false); - }, [setWarmPhaseOnRollover, isRolloverEnabled]); - return ( <> propertyName; -interface Props { - setPhaseData: ( - key: keyof WarmPhaseInterface & string, - value: boolean | string | undefined - ) => void; - phaseData: WarmPhaseInterface; - isShowingErrors: boolean; - errors?: PhaseValidationErrors; -} -export const WarmPhase: FunctionComponent = ({ setPhaseData, phaseData, errors }) => { +export const WarmPhase: FunctionComponent = () => { const { originalPolicy } = useEditPolicyContext(); const form = useFormContext(); const [ @@ -222,26 +206,18 @@ export const WarmPhase: FunctionComponent = ({ setPhaseData, phaseData, e - - { - setPhaseData(phaseProperty('selectedPrimaryShardCount'), e.target.value); - }} - min={1} - /> - + diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx index e9ce193118b35b..9693733eb61508 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx @@ -25,6 +25,7 @@ import { import { SerializedPolicy } from '../../../../../common/types'; import { useFormContext, useFormData } from '../../../../shared_imports'; +import { FormInternal } from '../types'; interface Props { legacyPolicy: SerializedPolicy; @@ -45,7 +46,7 @@ export const PolicyJsonFlyout: React.FunctionComponent = ({ const [policy, setPolicy] = useState(undefined); const form = useFormContext(); - const [formData, getFormData] = useFormData(); + const [formData, getFormData] = useFormData(); useEffect(() => { (async function checkPolicy() { @@ -57,6 +58,7 @@ export const PolicyJsonFlyout: React.FunctionComponent = ({ phases: { ...legacyPolicy.phases, hot: p.phases.hot, + warm: p.phases.warm, }, }); } else { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts index 996b64b5940fcc..9337b87d995e6d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts @@ -50,10 +50,18 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => { } } - if (draft.phases.warm?.actions?.allocate?.require) { - Object.entries(draft.phases.warm.actions.allocate.require).forEach((entry) => { - draft._meta.warm.allocationNodeAttribute = entry.join(':'); - }); + if (draft.phases.warm) { + if (draft.phases.warm.actions?.allocate?.require) { + Object.entries(draft.phases.warm.actions.allocate.require).forEach((entry) => { + draft._meta.warm.allocationNodeAttribute = entry.join(':'); + }); + } + + if (draft.phases.warm.min_age) { + const minAge = splitSizeAndUnits(draft.phases.warm.min_age); + draft.phases.warm.min_age = minAge.size; + draft._meta.warm.minAgeUnit = minAge.units; + } } } ); 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 47b04abd535281..eecdfb4871a676 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 @@ -91,6 +91,7 @@ const mergeAllSerializedPolicies = ( phases: { ...legacySerializedPolicy.phases, hot: serializedPolicy.phases.hot, + warm: serializedPolicy.phases.warm, }, }; }; @@ -136,22 +137,6 @@ export const EditPolicy: React.FunctionComponent = ({ history.push('/policies'); }; - const setWarmPhaseOnRollover = useCallback( - (value: boolean) => { - setPolicy((p) => ({ - ...p, - phases: { - ...p.phases, - warm: { - ...p.phases.warm, - warmPhaseOnRollover: value, - }, - }, - })); - }, - [setPolicy] - ); - const submit = async () => { setIsShowingErrors(true); const { data: formLibPolicy, isValid: newIsValid } = await form.submit(); @@ -210,10 +195,6 @@ export const EditPolicy: React.FunctionComponent = ({ [setPolicy] ); - const setWarmPhaseData = useCallback( - (key: string, value: any) => setPhaseData('warm', key, value), - [setPhaseData] - ); const setColdPhaseData = useCallback( (key: string, value: any) => setPhaseData('cold', key, value), [setPhaseData] @@ -353,18 +334,11 @@ export const EditPolicy: React.FunctionComponent = ({ - + - 0 - } - setPhaseData={setWarmPhaseData} - phaseData={policy.phases.warm} - /> + diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts index 32897d7be0ed06..440d67ed571ad8 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts @@ -17,6 +17,10 @@ import { i18nTexts } from './i18n_texts'; const { emptyField, numberGreaterThanField } = fieldValidators; +const serializers = { + stringToNumber: (v: string): any => (v ? parseInt(v, 10) : undefined), +}; + export const schema: FormSchema = { _meta: { hot: { @@ -96,7 +100,7 @@ export const schema: FormSchema = { validator: ifExistsNumberGreaterThanZero, }, ], - serializer: (v: string): any => (v ? parseInt(v, 10) : undefined), + serializer: serializers.stringToNumber, }, max_size: { label: i18n.translate('xpack.indexLifecycleMgmt.hotPhase.maximumIndexSizeLabel', { @@ -128,7 +132,7 @@ export const schema: FormSchema = { validator: ifExistsNumberGreaterThanZero, }, ], - serializer: (v: string): any => (v ? parseInt(v, 10) : undefined), + serializer: serializers.stringToNumber, }, }, set_priority: { @@ -136,7 +140,7 @@ export const schema: FormSchema = { defaultValue: defaultSetPriority as any, label: i18nTexts.editPolicy.setPriorityFieldLabel, validations: [{ validator: ifExistsNumberGreaterThanZero }], - serializer: (v: string): any => (v ? parseInt(v, 10) : undefined), + serializer: serializers.stringToNumber, }, }, }, @@ -146,7 +150,11 @@ export const schema: FormSchema = { defaultValue: '0', validations: [ { - validator: ifExistsNumberGreaterThanZero, + validator: numberGreaterThanField({ + than: 0, + allowEquality: true, + message: i18nTexts.editPolicy.errors.positiveNumberRequired, + }), }, ], }, @@ -161,10 +169,14 @@ export const schema: FormSchema = { validator: ifExistsNumberGreaterThanZero, }, ], + serializer: serializers.stringToNumber, }, }, shrink: { number_of_shards: { + label: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.numberOfPrimaryShardsLabel', { + defaultMessage: 'Number of primary shards', + }), validations: [ { validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), @@ -176,6 +188,7 @@ export const schema: FormSchema = { }), }, ], + serializer: serializers.stringToNumber, }, }, forcemerge: { @@ -194,7 +207,7 @@ export const schema: FormSchema = { validator: ifExistsNumberGreaterThanZero, }, ], - serializer: (v: string): any => (v ? parseInt(v, 10) : undefined), + serializer: serializers.stringToNumber, }, }, set_priority: { @@ -202,7 +215,7 @@ export const schema: FormSchema = { defaultValue: '50' as any, label: i18nTexts.editPolicy.setPriorityFieldLabel, validations: [{ validator: ifExistsNumberGreaterThanZero }], - serializer: (v: string): any => (v ? parseInt(v, 10) : undefined), + serializer: serializers.stringToNumber, }, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index 71031d1801ee6a..245c129db47433 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -92,6 +92,12 @@ export const i18nTexts = { } ), }, + positiveNumberRequired: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.errors.positiveNumberRequiredError', + { + defaultMessage: 'Only positive numbers are allowed.', + } + ), }, }, }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts index e0e1ad44f15571..4ebb33be7647ec 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts @@ -4,40 +4,117 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SerializedPolicy } from '../../../../common/types'; +import { isEmpty } from 'lodash'; -import { FormInternal } from './types'; +import { AllocateAction, SerializedPhase, SerializedPolicy } from '../../../../common/types'; + +import { FormInternal, DataAllocationMetaFields } from './types'; +import { isNumber } from '../../services/policies/policy_serialization'; + +const unsafeSerializePhaseWithAllocation = ( + dataAllocationMetaFields: DataAllocationMetaFields, + actions: SerializedPhase['actions'] = {}, + originalAllocation: AllocateAction = {} +) => { + if ( + dataAllocationMetaFields.dataTierAllocationType === 'node_attrs' && + dataAllocationMetaFields.allocationNodeAttribute && + dataAllocationMetaFields.allocationNodeAttribute !== 'none' + ) { + const [name, value] = dataAllocationMetaFields.allocationNodeAttribute.split(':'); + actions.allocate = { + require: { + [name]: value, + }, + // copy over the original include and exclude values until we can set them in the form. + include: !isEmpty(originalAllocation.include) ? { ...originalAllocation.include } : undefined, + exclude: !isEmpty(originalAllocation.exclude) ? { ...originalAllocation.exclude } : undefined, + }; + } else if (dataAllocationMetaFields.dataTierAllocationType === 'none') { + actions.migrate = { enabled: false }; + delete actions.allocate; + } else if (dataAllocationMetaFields.dataTierAllocationType === 'node_roles') { + if (actions.allocate) { + delete actions.allocate.require; + delete actions.allocate.include; + delete actions.allocate.exclude; + } + delete actions.migrate; + } +}; export const createSerializer = (originalPolicy?: SerializedPolicy) => ( data: FormInternal ): SerializedPolicy => { - const { _meta, ...rest } = data; + const { _meta, ...policy } = data; - if (!rest.phases || !rest.phases.hot) { - rest.phases = { hot: { actions: {} } }; + if (!policy.phases || !policy.phases.hot) { + policy.phases = { hot: { actions: {} } }; } - if (rest.phases.hot) { - rest.phases.hot.min_age = originalPolicy?.phases.hot?.min_age ?? '0ms'; + /** + * HOT PHASE SERIALIZATION + */ + if (policy.phases.hot) { + policy.phases.hot.min_age = originalPolicy?.phases.hot?.min_age ?? '0ms'; } - if (rest.phases.hot?.actions) { - if (rest.phases.hot.actions?.rollover && _meta.hot.useRollover) { - if (rest.phases.hot.actions.rollover.max_age) { - rest.phases.hot.actions.rollover.max_age = `${rest.phases.hot.actions.rollover.max_age}${_meta.hot.maxAgeUnit}`; + if (policy.phases.hot?.actions) { + if (policy.phases.hot.actions?.rollover && _meta.hot.useRollover) { + if (policy.phases.hot.actions.rollover.max_age) { + policy.phases.hot.actions.rollover.max_age = `${policy.phases.hot.actions.rollover.max_age}${_meta.hot.maxAgeUnit}`; } - if (rest.phases.hot.actions.rollover.max_size) { - rest.phases.hot.actions.rollover.max_size = `${rest.phases.hot.actions.rollover.max_size}${_meta.hot.maxStorageSizeUnit}`; + if (policy.phases.hot.actions.rollover.max_size) { + policy.phases.hot.actions.rollover.max_size = `${policy.phases.hot.actions.rollover.max_size}${_meta.hot.maxStorageSizeUnit}`; } - if (_meta.hot.bestCompression && rest.phases.hot.actions?.forcemerge) { - rest.phases.hot.actions.forcemerge.index_codec = 'best_compression'; + if (_meta.hot.bestCompression && policy.phases.hot.actions?.forcemerge) { + policy.phases.hot.actions.forcemerge.index_codec = 'best_compression'; } } else { - delete rest.phases.hot.actions?.rollover; + delete policy.phases.hot.actions?.rollover; + } + } + + /** + * WARM PHASE SERIALIZATION + */ + if (policy.phases.warm) { + // If warm phase on rollover is enabled, delete min age field + // An index lifecycle switches to warm phase when rollover occurs, so you cannot specify a warm phase time + // They are mutually exclusive + if (_meta.hot.useRollover && _meta.warm.warmPhaseOnRollover) { + delete policy.phases.warm.min_age; + } else if ( + (!_meta.hot.useRollover || !_meta.warm.warmPhaseOnRollover) && + policy.phases.warm.min_age + ) { + policy.phases.warm.min_age = `${policy.phases.warm.min_age}${_meta.warm.minAgeUnit}`; + } + + unsafeSerializePhaseWithAllocation( + _meta.warm, + policy.phases.warm.actions, + originalPolicy?.phases.warm?.actions.allocate + ); + + if ( + policy.phases.warm.actions.allocate && + !policy.phases.warm.actions.allocate.require && + !isNumber(policy.phases.warm.actions.allocate.number_of_replicas) && + isEmpty(policy.phases.warm.actions.allocate.include) && + isEmpty(policy.phases.warm.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 policy.phases.warm.actions.allocate; + } + + if (_meta.warm.bestCompression && policy.phases.warm.actions?.forcemerge) { + policy.phases.warm.actions.forcemerge.index_codec = 'best_compression'; } } - return rest; + return policy; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts index 98333b5973dc2f..6fcfbd050c69d0 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/types.ts @@ -8,6 +8,27 @@ import { SerializedPolicy } from '../../../../common/types'; export type DataTierAllocationType = 'node_roles' | 'node_attrs' | 'none'; +export interface DataAllocationMetaFields { + dataTierAllocationType: DataTierAllocationType; + allocationNodeAttribute?: string; +} + +interface HotPhaseMetaFields { + useRollover: boolean; + forceMergeEnabled: boolean; + bestCompression: boolean; + maxStorageSizeUnit?: string; + maxAgeUnit?: string; +} + +interface WarmPhaseMetaFields extends DataAllocationMetaFields { + enabled: boolean; + forceMergeEnabled: boolean; + bestCompression: boolean; + warmPhaseOnRollover: boolean; + minAgeUnit?: string; +} + /** * Describes the shape of data after deserialization. */ @@ -17,21 +38,7 @@ export interface FormInternal extends SerializedPolicy { * certain form fields which affects what is ultimately serialized. */ _meta: { - hot: { - useRollover: boolean; - forceMergeEnabled: boolean; - bestCompression: boolean; - maxStorageSizeUnit?: string; - maxAgeUnit?: string; - }; - warm: { - enabled: boolean; - forceMergeEnabled: boolean; - bestCompression: boolean; - warmPhaseOnRollover: boolean; - dataTierAllocationType: DataTierAllocationType; - allocationNodeAttribute?: string; - minAgeUnit?: string; - }; + hot: HotPhaseMetaFields; + warm: WarmPhaseMetaFields; }; } From 98bf14b2790dc155081ac9b06df540e69ec44c3e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 21 Oct 2020 12:45:36 +0200 Subject: [PATCH 06/19] Remove last vestiges of legacy warm phase - also removed policy serialization tests for warm phase --- .../common/types/policies.ts | 12 - .../public/application/constants/policy.ts | 24 +- .../phases/warm_phase/warm_phase.tsx | 11 +- .../policies/policy_serialization.test.ts | 168 +------------ .../services/policies/policy_serialization.ts | 7 - .../services/policies/policy_validation.ts | 16 +- .../services/policies/warm_phase.ts | 230 ------------------ .../public/application/services/ui_metric.ts | 1 - 8 files changed, 10 insertions(+), 459 deletions(-) delete mode 100644 x-pack/plugins/index_lifecycle_management/public/application/services/policies/warm_phase.ts diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index c2d2c22424666d..9e97f982e87b9a 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -110,7 +110,6 @@ export interface ForcemergeAction { export interface LegacyPolicy { name: string; phases: { - warm: WarmPhase; cold: ColdPhase; delete: DeletePhase; }; @@ -154,17 +153,6 @@ export interface PhaseWithForcemergeAction { bestCompressionEnabled: boolean; } -export interface WarmPhase - extends CommonPhaseSettings, - PhaseWithMinAge, - PhaseWithAllocationAction, - PhaseWithIndexPriority, - PhaseWithForcemergeAction { - warmPhaseOnRollover: boolean; - shrinkEnabled: boolean; - selectedPrimaryShardCount: string; -} - export interface ColdPhase extends CommonPhaseSettings, PhaseWithMinAge, 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 c919331082ec34..473e6307f2a633 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 @@ -4,13 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - SerializedPhase, - ColdPhase, - DeletePhase, - WarmPhase, - SerializedPolicy, -} from '../../../common/types'; +import { SerializedPhase, ColdPhase, DeletePhase, SerializedPolicy } from '../../../common/types'; export const defaultSetPriority: string = '100'; @@ -28,22 +22,6 @@ export const defaultPolicy: SerializedPolicy = { }, }; -export const defaultNewWarmPhase: WarmPhase = { - phaseEnabled: false, - forceMergeEnabled: false, - selectedForceMergeSegments: '', - bestCompressionEnabled: false, - selectedMinimumAge: '0', - selectedMinimumAgeUnits: 'd', - selectedNodeAttrs: '', - shrinkEnabled: false, - selectedPrimaryShardCount: '', - selectedReplicaCount: '', - warmPhaseOnRollover: true, - phaseIndexPriority: '50', - dataTierAllocationType: 'default', -}; - export const defaultNewColdPhase: ColdPhase = { phaseEnabled: false, selectedMinimumAge: '0', diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index 3c028c7be87887..b13e867a0b1faf 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -24,7 +24,7 @@ import { NumericField, } from '../../../../../../shared_imports'; -import { Phases, WarmPhase as WarmPhaseInterface } from '../../../../../../../common/types'; +import { Phases } from '../../../../../../../common/types'; import { useRolloverPath, MinAgeInputField, Forcemerge, SetPriorityInput } from '../shared'; @@ -46,7 +46,6 @@ const i18nTexts = { }; const warmProperty: keyof Phases = 'warm'; -const phaseProperty = (propertyName: keyof WarmPhaseInterface) => propertyName; export const WarmPhase: FunctionComponent = () => { const { originalPolicy } = useEditPolicyContext(); @@ -112,7 +111,7 @@ export const WarmPhase: FunctionComponent = () => { componentProps={{ fullWidth: false, euiFieldProps: { - 'data-test-subj': `${warmProperty}-${phaseProperty('warmPhaseOnRollover')}`, + 'data-test-subj': `${warmProperty}-warmPhaseOnRollover`, }, }} /> @@ -168,7 +167,7 @@ export const WarmPhase: FunctionComponent = () => { componentProps={{ fullWidth: false, euiFieldProps: { - 'data-test-subj': `${warmProperty}-${phaseProperty('selectedReplicaCount')}`, + 'data-test-subj': `${warmProperty}-selectedReplicaCount}`, min: 0, }, }} @@ -211,9 +210,7 @@ export const WarmPhase: FunctionComponent = () => { component={NumericField} componentProps={{ euiFieldProps: { - 'data-test-subj': `${warmProperty}-${phaseProperty( - 'selectedPrimaryShardCount' - )}`, + 'data-test-subj': `${warmProperty}-selectedPrimaryShardCount`, min: 1, }, }} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts index 5c7f04986827b4..0be6ab35217360 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/policy_serialization.test.ts @@ -7,7 +7,7 @@ // eslint-disable-next-line no-restricted-imports import cloneDeep from 'lodash/cloneDeep'; import { deserializePolicy, legacySerializePolicy } from './policy_serialization'; -import { defaultNewColdPhase, defaultNewDeletePhase, defaultNewWarmPhase } from '../../constants'; +import { defaultNewColdPhase, defaultNewDeletePhase } from '../../constants'; import { DataTierAllocationType } from '../../../../common/types'; import { coldPhaseInitialization } from './cold_phase'; @@ -18,13 +18,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - warm: { - ...defaultNewWarmPhase, - dataTierAllocationType: 'default', - // These selected attrs should be ignored - selectedNodeAttrs: 'another:thing', - phaseEnabled: true, - }, cold: { ...defaultNewColdPhase, dataTierAllocationType: 'default', @@ -38,9 +31,6 @@ describe('Policy serialization', () => { name: 'test', phases: { hot: { actions: {} }, - warm: { - actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, - }, cold: { actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, }, @@ -50,13 +40,6 @@ describe('Policy serialization', () => { ).toEqual({ name: 'test', phases: { - warm: { - actions: { - set_priority: { - priority: 50, - }, - }, - }, cold: { actions: { set_priority: { @@ -75,12 +58,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - warm: { - ...defaultNewWarmPhase, - dataTierAllocationType: 'custom', - selectedNodeAttrs: 'another:thing', - phaseEnabled: true, - }, cold: { ...defaultNewColdPhase, dataTierAllocationType: 'custom', @@ -94,15 +71,6 @@ describe('Policy serialization', () => { name: 'test', phases: { hot: { actions: {} }, - warm: { - actions: { - allocate: { - include: { keep: 'this' }, - exclude: { keep: 'this' }, - require: { something: 'here' }, - }, - }, - }, cold: { actions: { allocate: { @@ -118,20 +86,6 @@ describe('Policy serialization', () => { ).toEqual({ name: 'test', phases: { - warm: { - actions: { - allocate: { - include: { keep: 'this' }, - exclude: { keep: 'this' }, - require: { - another: 'thing', - }, - }, - set_priority: { - priority: 50, - }, - }, - }, cold: { actions: { allocate: { @@ -157,12 +111,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - warm: { - ...defaultNewWarmPhase, - dataTierAllocationType: 'custom', - selectedNodeAttrs: '', - phaseEnabled: true, - }, cold: { ...defaultNewColdPhase, dataTierAllocationType: 'custom', @@ -176,9 +124,6 @@ describe('Policy serialization', () => { name: 'test', phases: { hot: { actions: {} }, - warm: { - actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, - }, cold: { actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, }, @@ -189,14 +134,6 @@ describe('Policy serialization', () => { // There should be no allocation action in any phases... name: 'test', phases: { - warm: { - actions: { - allocate: { include: {}, exclude: {}, require: { something: 'here' } }, - set_priority: { - priority: 50, - }, - }, - }, cold: { actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } }, @@ -216,12 +153,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - warm: { - ...defaultNewWarmPhase, - dataTierAllocationType: 'none', - selectedNodeAttrs: 'ignore:this', - phaseEnabled: true, - }, cold: { ...defaultNewColdPhase, dataTierAllocationType: 'none', @@ -235,9 +166,6 @@ describe('Policy serialization', () => { name: 'test', phases: { hot: { actions: {} }, - warm: { - actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, - }, cold: { actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, }, @@ -248,16 +176,6 @@ describe('Policy serialization', () => { // There should be no allocation action in any phases... name: 'test', phases: { - warm: { - actions: { - migrate: { - enabled: false, - }, - set_priority: { - priority: 50, - }, - }, - }, cold: { actions: { migrate: { @@ -277,9 +195,6 @@ describe('Policy serialization', () => { const originalPolicy = { name: 'test', phases: { - warm: { - actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, - }, cold: { actions: { allocate: { include: {}, exclude: {}, require: { something: 'here' } } }, }, @@ -291,12 +206,6 @@ describe('Policy serialization', () => { const deserializedPolicy = { name: 'test', phases: { - warm: { - ...defaultNewWarmPhase, - dataTierAllocationType: 'none' as DataTierAllocationType, - selectedNodeAttrs: 'ignore:this', - phaseEnabled: true, - }, cold: { ...defaultNewColdPhase, dataTierAllocationType: 'none' as DataTierAllocationType, @@ -308,10 +217,6 @@ describe('Policy serialization', () => { }, }; - legacySerializePolicy(deserializedPolicy, originalPolicy); - deserializedPolicy.phases.warm.dataTierAllocationType = 'custom'; - legacySerializePolicy(deserializedPolicy, originalPolicy); - deserializedPolicy.phases.warm.dataTierAllocationType = 'default'; legacySerializePolicy(deserializedPolicy, originalPolicy); expect(originalPolicy).toEqual(originalClone); }); @@ -322,13 +227,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - warm: { - ...defaultNewWarmPhase, - phaseEnabled: true, - forceMergeEnabled: true, - selectedForceMergeSegments: '1', - bestCompressionEnabled: true, - }, cold: { ...defaultNewColdPhase, }, @@ -344,19 +242,7 @@ describe('Policy serialization', () => { ) ).toEqual({ name: 'test', - phases: { - warm: { - actions: { - forcemerge: { - max_num_segments: 1, - index_codec: 'best_compression', - }, - set_priority: { - priority: 50, - }, - }, - }, - }, + phases: {}, }); }); @@ -384,31 +270,12 @@ describe('Policy serialization', () => { }, }, }, - warm: { - actions: { - forcemerge: { - max_num_segments: 1, - index_codec: 'best_compression', - }, - set_priority: { - priority: 50, - }, - }, - }, }, }, }) ).toEqual({ name: 'test', phases: { - warm: { - ...defaultNewWarmPhase, - warmPhaseOnRollover: false, - phaseEnabled: true, - forceMergeEnabled: true, - selectedForceMergeSegments: '1', - bestCompressionEnabled: true, - }, cold: { ...coldPhaseInitialization, }, @@ -423,13 +290,6 @@ describe('Policy serialization', () => { { name: 'test', phases: { - warm: { - ...defaultNewWarmPhase, - phaseEnabled: true, - forceMergeEnabled: true, - selectedForceMergeSegments: '1', - bestCompressionEnabled: false, - }, cold: { ...defaultNewColdPhase, }, @@ -438,32 +298,12 @@ describe('Policy serialization', () => { }, { name: 'test', - phases: { - warm: { - actions: { - forcemerge: { - max_num_segments: 1, - index_codec: 'best_compression', - }, - }, - }, - }, + phases: {}, } ) ).toEqual({ name: 'test', - phases: { - warm: { - actions: { - forcemerge: { - max_num_segments: 1, - }, - set_priority: { - priority: 50, - }, - }, - }, - }, + phases: {}, }); }); }); 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 0dce7efce46236..32c7e698b09203 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,11 +9,9 @@ import { LegacyPolicy, PolicyFromES, SerializedPolicy } from '../../../../common import { defaultNewColdPhase, defaultNewDeletePhase, - defaultNewWarmPhase, serializedPhaseInitialization, } from '../../constants'; -import { warmPhaseFromES, warmPhaseToES } from './warm_phase'; import { coldPhaseFromES, coldPhaseToES } from './cold_phase'; import { deletePhaseFromES, deletePhaseToES } from './delete_phase'; @@ -48,7 +46,6 @@ export const initializeNewPolicy = (newPolicyName: string = ''): LegacyPolicy => return { name: newPolicyName, phases: { - warm: { ...defaultNewWarmPhase }, cold: { ...defaultNewColdPhase }, delete: { ...defaultNewDeletePhase }, }, @@ -64,7 +61,6 @@ export const deserializePolicy = (policy: PolicyFromES): LegacyPolicy => { return { name, phases: { - warm: warmPhaseFromES(phases.warm), cold: coldPhaseFromES(phases.cold), delete: deletePhaseFromES(phases.delete), }, @@ -82,9 +78,6 @@ export const legacySerializePolicy = ( name: policy.name, phases: {}, } as SerializedPolicy; - if (policy.phases.warm.phaseEnabled) { - serializedPolicy.phases.warm = warmPhaseToES(policy.phases.warm, originalEsPolicy.phases.warm); - } if (policy.phases.cold.phaseEnabled) { serializedPolicy.phases.cold = coldPhaseToES(policy.phases.cold, originalEsPolicy.phases.cold); 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 eeceb97c409f55..a113cb68a23496 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 @@ -5,14 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { - ColdPhase, - DeletePhase, - LegacyPolicy, - PolicyFromES, - WarmPhase, -} from '../../../../common/types'; -import { validateWarmPhase } from './warm_phase'; +import { ColdPhase, DeletePhase, LegacyPolicy, PolicyFromES } from '../../../../common/types'; import { validateColdPhase } from './cold_phase'; import { validateDeletePhase } from './delete_phase'; @@ -89,7 +82,6 @@ export type PhaseValidationErrors = { }; export interface ValidationErrors { - warm: PhaseValidationErrors; cold: PhaseValidationErrors; delete: PhaseValidationErrors; policyName: string[]; @@ -128,19 +120,16 @@ export const validatePolicy = ( } } - const warmPhaseErrors = validateWarmPhase(policy.phases.warm); const coldPhaseErrors = validateColdPhase(policy.phases.cold); const deletePhaseErrors = validateDeletePhase(policy.phases.delete); const isValid = policyNameErrors.length === 0 && - Object.keys(warmPhaseErrors).length === 0 && Object.keys(coldPhaseErrors).length === 0 && Object.keys(deletePhaseErrors).length === 0; return [ isValid, { policyName: [...policyNameErrors], - warm: warmPhaseErrors, cold: coldPhaseErrors, delete: deletePhaseErrors, }, @@ -156,9 +145,6 @@ export const findFirstError = (errors?: ValidationErrors): string | undefined => return propertyof('policyName'); } - if (Object.keys(errors.warm).length > 0) { - return `${propertyof('warm')}.${Object.keys(errors.warm)[0]}`; - } if (Object.keys(errors.cold).length > 0) { return `${propertyof('cold')}.${Object.keys(errors.cold)[0]}`; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/warm_phase.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/policies/warm_phase.ts deleted file mode 100644 index a5a6fa6abe47b0..00000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/policies/warm_phase.ts +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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 { isEmpty } from 'lodash'; -import { AllocateAction, WarmPhase, SerializedWarmPhase } from '../../../../common/types'; -import { serializedPhaseInitialization } from '../../constants'; -import { isNumber, splitSizeAndUnits } from './policy_serialization'; - -import { - numberRequiredMessage, - PhaseValidationErrors, - positiveNumberRequiredMessage, - positiveNumbersAboveZeroErrorMessage, -} from './policy_validation'; - -import { determineDataTierAllocationTypeLegacy } from '../../lib'; -import { serializePhaseWithAllocation } from './shared'; - -const warmPhaseInitialization: WarmPhase = { - phaseEnabled: false, - warmPhaseOnRollover: false, - selectedMinimumAge: '0', - selectedMinimumAgeUnits: 'd', - selectedNodeAttrs: '', - selectedReplicaCount: '', - shrinkEnabled: false, - selectedPrimaryShardCount: '', - forceMergeEnabled: false, - selectedForceMergeSegments: '', - bestCompressionEnabled: false, - phaseIndexPriority: '', - dataTierAllocationType: 'default', -}; - -export const warmPhaseFromES = (phaseSerialized?: SerializedWarmPhase): WarmPhase => { - const phase: WarmPhase = { ...warmPhaseInitialization }; - - if (phaseSerialized === undefined || phaseSerialized === null) { - return phase; - } - - phase.phaseEnabled = true; - - if (phaseSerialized.actions.allocate) { - phase.dataTierAllocationType = determineDataTierAllocationTypeLegacy( - phaseSerialized.actions.allocate - ); - } - - if (phaseSerialized.min_age) { - if (phaseSerialized.min_age === '0ms') { - phase.warmPhaseOnRollover = true; - } else { - 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.forcemerge) { - const forcemerge = actions.forcemerge; - phase.forceMergeEnabled = true; - phase.selectedForceMergeSegments = forcemerge.max_num_segments.toString(); - // only accepted value for index_codec - phase.bestCompressionEnabled = forcemerge.index_codec === 'best_compression'; - } - - if (actions.shrink) { - phase.shrinkEnabled = true; - phase.selectedPrimaryShardCount = actions.shrink.number_of_shards - ? actions.shrink.number_of_shards.toString() - : ''; - } - - if (actions.set_priority) { - phase.phaseIndexPriority = actions.set_priority.priority - ? actions.set_priority.priority.toString() - : ''; - } - } - return phase; -}; - -export const warmPhaseToES = ( - phase: WarmPhase, - originalEsPhase?: SerializedWarmPhase -): SerializedWarmPhase => { - if (!originalEsPhase) { - originalEsPhase = { ...serializedPhaseInitialization }; - } - - const esPhase = { ...originalEsPhase }; - - if (isNumber(phase.selectedMinimumAge)) { - esPhase.min_age = `${phase.selectedMinimumAge}${phase.selectedMinimumAgeUnits}`; - } - - // If warm phase on rollover is enabled, delete min age field - // An index lifecycle switches to warm phase when rollover occurs, so you cannot specify a warm phase time - // They are mutually exclusive - if (phase.warmPhaseOnRollover) { - delete esPhase.min_age; - } - - esPhase.actions = serializePhaseWithAllocation(phase, esPhase.actions); - - 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) { - 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.forceMergeEnabled) { - esPhase.actions.forcemerge = { - max_num_segments: parseInt(phase.selectedForceMergeSegments, 10), - }; - if (phase.bestCompressionEnabled) { - // only accepted value for index_codec - esPhase.actions.forcemerge.index_codec = 'best_compression'; - } - } else { - delete esPhase.actions.forcemerge; - } - - if (phase.shrinkEnabled && isNumber(phase.selectedPrimaryShardCount)) { - esPhase.actions.shrink = { - number_of_shards: parseInt(phase.selectedPrimaryShardCount, 10), - }; - } else { - delete esPhase.actions.shrink; - } - - if (isNumber(phase.phaseIndexPriority)) { - esPhase.actions.set_priority = { - priority: parseInt(phase.phaseIndexPriority, 10), - }; - } else { - delete esPhase.actions.set_priority; - } - - return esPhase; -}; - -export const validateWarmPhase = (phase: WarmPhase): 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]; - } - } - - // if warm phase on rollover is disabled, min age needs to be a positive number - if (!phase.warmPhaseOnRollover) { - if (!isNumber(phase.selectedMinimumAge)) { - phaseErrors.selectedMinimumAge = [numberRequiredMessage]; - } else if (parseInt(phase.selectedMinimumAge, 10) < 0) { - phaseErrors.selectedMinimumAge = [positiveNumberRequiredMessage]; - } - } - - // if forcemerge is enabled, force merge segments needs to be a number above zero - if (phase.forceMergeEnabled) { - if (!isNumber(phase.selectedForceMergeSegments)) { - phaseErrors.selectedForceMergeSegments = [numberRequiredMessage]; - } else if (parseInt(phase.selectedForceMergeSegments, 10) < 1) { - phaseErrors.selectedForceMergeSegments = [positiveNumbersAboveZeroErrorMessage]; - } - } - - // if shrink is enabled, primary shard count needs to be a number above zero - if (phase.shrinkEnabled) { - if (!isNumber(phase.selectedPrimaryShardCount)) { - phaseErrors.selectedPrimaryShardCount = [numberRequiredMessage]; - } else if (parseInt(phase.selectedPrimaryShardCount, 10) < 1) { - phaseErrors.selectedPrimaryShardCount = [positiveNumbersAboveZeroErrorMessage]; - } - } - - // replica count is optional, but if it's set, it needs to be a positive number - if (phase.selectedReplicaCount) { - if (!isNumber(phase.selectedReplicaCount)) { - phaseErrors.selectedReplicaCount = [numberRequiredMessage]; - } else if (parseInt(phase.selectedReplicaCount, 10) < 0) { - phaseErrors.selectedReplicaCount = [numberRequiredMessage]; - } - } - - return { - ...phaseErrors, - }; -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts index ea5c5619da5899..ec811e29839045 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts @@ -14,7 +14,6 @@ import { UIM_CONFIG_SET_PRIORITY, UIM_CONFIG_WARM_PHASE, defaultNewColdPhase, - defaultNewWarmPhase, defaultSetPriority, } from '../constants'; From 01214116b099557ac921114964ae2b9a63354e71 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 21 Oct 2020 13:52:18 +0200 Subject: [PATCH 07/19] fix existing test coverage and remove use of "none" for node attribute --- .../__jest__/components/edit_policy.test.tsx | 195 ++++++++++-------- .../public/application/constants/policy.ts | 2 + .../components/node_allocation.tsx | 3 +- .../sections/edit_policy/form_schema.ts | 19 +- .../sections/edit_policy/i18n_texts.ts | 20 +- .../sections/edit_policy/serializer.ts | 3 +- .../application/services/ui_metric.test.ts | 6 +- .../public/application/services/ui_metric.ts | 4 +- 8 files changed, 137 insertions(+), 115 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx index d9af20763657b3..9d31480fb42c7c 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx @@ -30,9 +30,7 @@ import { init as initUiMetric } from '../../public/application/services/ui_metri import { init as initNotification } from '../../public/application/services/notification'; import { PolicyFromES } from '../../common/types'; import { - positiveNumbersAboveZeroErrorMessage, positiveNumberRequiredMessage, - numberRequiredMessage, policyNameRequiredMessage, policyNameStartsWithUnderscoreErrorMessage, policyNameContainsCommaErrorMessage, @@ -40,6 +38,8 @@ import { policyNameMustBeDifferentErrorMessage, policyNameAlreadyUsedErrorMessage, } from '../../public/application/services/policies/policy_validation'; + +import { i18nTexts } from '../../public/application/sections/edit_policy/i18n_texts'; import { editPolicyHelpers } from './helpers'; // @ts-ignore @@ -89,13 +89,13 @@ const activatePhase = async (rendered: ReactWrapper, phase: string) => { }); rendered.update(); }; -const openNodeAttributesSection = (rendered: ReactWrapper, phase: string) => { +const openNodeAttributesSection = async (rendered: ReactWrapper, phase: string) => { const getControls = () => findTestSubject(rendered, `${phase}-dataTierAllocationControls`); - act(() => { + await act(async () => { findTestSubject(getControls(), 'dataTierSelect').simulate('click'); }); rendered.update(); - act(() => { + await act(async () => { findTestSubject(getControls(), 'customDataAllocationOption').simulate('click'); }); rendered.update(); @@ -119,19 +119,29 @@ const noRollover = async (rendered: ReactWrapper) => { }); rendered.update(); }; -const getNodeAttributeSelect = (rendered: ReactWrapper, phase: string) => { +const getNodeAttributeSelectLegacy = (rendered: ReactWrapper, phase: string) => { return rendered.find(`select#${phase}-selectedNodeAttrs`); }; +const getNodeAttributeSelect = (rendered: ReactWrapper, phase: string) => { + return findTestSubject(rendered, `${phase}-selectedNodeAttrs`); +}; const setPolicyName = (rendered: ReactWrapper, policyName: string) => { const policyNameField = findTestSubject(rendered, 'policyNameField'); policyNameField.simulate('change', { target: { value: policyName } }); rendered.update(); }; -const setPhaseAfter = (rendered: ReactWrapper, phase: string, after: string | number) => { +const setPhaseAfterLegacy = (rendered: ReactWrapper, phase: string, after: string | number) => { const afterInput = rendered.find(`input#${phase}-selectedMinimumAge`); afterInput.simulate('change', { target: { value: after } }); rendered.update(); }; +const setPhaseAfter = async (rendered: ReactWrapper, phase: string, after: string | number) => { + const afterInput = findTestSubject(rendered, `${phase}-selectedMinimumAge`); + await act(async () => { + afterInput.simulate('change', { target: { value: after } }); + }); + rendered.update(); +}; const setPhaseIndexPriorityLegacy = ( rendered: ReactWrapper, phase: string, @@ -172,10 +182,11 @@ describe('edit policy', () => { * any validation errors. This helper advances timers and can trigger component * state changes. */ - const waitForFormLibValidation = () => { + const waitForFormLibValidation = (rendered: ReactWrapper) => { act(() => { jest.advanceTimersByTime(1000); }); + rendered.update(); }; beforeEach(() => { @@ -288,13 +299,12 @@ describe('edit policy', () => { await act(async () => { maxSizeInput.simulate('change', { target: { value: '' } }); }); - waitForFormLibValidation(); + waitForFormLibValidation(rendered); const maxAgeInput = findTestSubject(rendered, 'hot-selectedMaxAge'); await act(async () => { maxAgeInput.simulate('change', { target: { value: '' } }); }); - waitForFormLibValidation(); - rendered.update(); + waitForFormLibValidation(rendered); await save(rendered); expect(findTestSubject(rendered, 'rolloverSettingsRequired').exists()).toBeTruthy(); }); @@ -305,9 +315,9 @@ describe('edit policy', () => { await act(async () => { maxSizeInput.simulate('change', { target: { value: '-1' } }); }); - waitForFormLibValidation(); + waitForFormLibValidation(rendered); rendered.update(); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); test('should show number above 0 required error when trying to save with 0 for max size', async () => { const rendered = mountWithIntl(component); @@ -316,9 +326,8 @@ describe('edit policy', () => { await act(async () => { maxSizeInput.simulate('change', { target: { value: '-1' } }); }); - waitForFormLibValidation(); - rendered.update(); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); test('should show number above 0 required error when trying to save with -1 for max age', async () => { const rendered = mountWithIntl(component); @@ -327,9 +336,8 @@ describe('edit policy', () => { await act(async () => { maxAgeInput.simulate('change', { target: { value: '-1' } }); }); - waitForFormLibValidation(); - rendered.update(); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); test('should show number above 0 required error when trying to save with 0 for max age', async () => { const rendered = mountWithIntl(component); @@ -338,9 +346,8 @@ describe('edit policy', () => { await act(async () => { maxAgeInput.simulate('change', { target: { value: '0' } }); }); - waitForFormLibValidation(); - rendered.update(); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); test('should show forcemerge input when rollover enabled', () => { const rendered = mountWithIntl(component); @@ -351,8 +358,7 @@ describe('edit policy', () => { const rendered = mountWithIntl(component); setPolicyName(rendered, 'mypolicy'); await noRollover(rendered); - waitForFormLibValidation(); - rendered.update(); + waitForFormLibValidation(rendered); expect(findTestSubject(rendered, 'hot-forceMergeSwitch').exists()).toBeFalsy(); }); test('should show positive number required above zero error when trying to save hot phase with 0 for force merge', async () => { @@ -366,9 +372,8 @@ describe('edit policy', () => { await act(async () => { forcemergeInput.simulate('change', { target: { value: '0' } }); }); - waitForFormLibValidation(); - rendered.update(); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); test('should show positive number above 0 required error when trying to save hot phase with -1 for force merge', async () => { const rendered = mountWithIntl(component); @@ -379,19 +384,17 @@ describe('edit policy', () => { await act(async () => { forcemergeInput.simulate('change', { target: { value: '-1' } }); }); - waitForFormLibValidation(); - rendered.update(); + waitForFormLibValidation(rendered); await save(rendered); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); test('should show positive number required error when trying to save with -1 for index priority', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await setPhaseIndexPriority(rendered, 'hot', '-1'); - waitForFormLibValidation(); - rendered.update(); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); }); describe('warm phase', () => { @@ -408,17 +411,17 @@ describe('edit policy', () => { await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); - setPhaseAfter(rendered, 'warm', ''); - await save(rendered); - expectedErrorMessages(rendered, [numberRequiredMessage]); + await setPhaseAfter(rendered, 'warm', ''); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); }); test('should allow 0 for phase timing', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); - setPhaseAfter(rendered, 'warm', '0'); - await save(rendered); + await setPhaseAfter(rendered, 'warm', '0'); + waitForFormLibValidation(rendered); expectedErrorMessages(rendered, []); }); test('should show positive number required error when trying to save warm phase with -1 for after', async () => { @@ -426,75 +429,87 @@ describe('edit policy', () => { await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); - setPhaseAfter(rendered, 'warm', '-1'); - await save(rendered); - expectedErrorMessages(rendered, [positiveNumberRequiredMessage]); + await setPhaseAfter(rendered, 'warm', '-1'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); }); test('should show positive number required error when trying to save warm phase with -1 for index priority', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); - setPhaseAfter(rendered, 'warm', '1'); - setPhaseIndexPriorityLegacy(rendered, 'warm', '-1'); - await save(rendered); - expectedErrorMessages(rendered, [positiveNumberRequiredMessage]); + await setPhaseAfter(rendered, 'warm', '1'); + await setPhaseAfter(rendered, 'warm', '-1'); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); }); test('should show positive number required above zero error when trying to save warm phase with 0 for shrink', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); - findTestSubject(rendered, 'shrinkSwitch').simulate('click'); - rendered.update(); - setPhaseAfter(rendered, 'warm', '1'); - const shrinkInput = rendered.find('input#warm-selectedPrimaryShardCount'); - shrinkInput.simulate('change', { target: { value: '0' } }); + act(() => { + findTestSubject(rendered, 'shrinkSwitch').simulate('click'); + }); rendered.update(); - await save(rendered); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + await setPhaseAfter(rendered, 'warm', '1'); + const shrinkInput = findTestSubject(rendered, 'warm-selectedPrimaryShardCount'); + await act(async () => { + shrinkInput.simulate('change', { target: { value: '0' } }); + }); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); test('should show positive number above 0 required error when trying to save warm phase with -1 for shrink', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); - setPhaseAfter(rendered, 'warm', '1'); - findTestSubject(rendered, 'shrinkSwitch').simulate('click'); - rendered.update(); - const shrinkInput = rendered.find('input#warm-selectedPrimaryShardCount'); - shrinkInput.simulate('change', { target: { value: '-1' } }); + await setPhaseAfter(rendered, 'warm', '1'); + act(() => { + findTestSubject(rendered, 'shrinkSwitch').simulate('click'); + }); rendered.update(); - await save(rendered); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + const shrinkInput = findTestSubject(rendered, 'warm-selectedPrimaryShardCount'); + await act(async () => { + shrinkInput.simulate('change', { target: { value: '-1' } }); + }); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); test('should show positive number required above zero error when trying to save warm phase with 0 for force merge', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); - setPhaseAfter(rendered, 'warm', '1'); - findTestSubject(rendered, 'warm-forceMergeSwitch').simulate('click'); + await setPhaseAfter(rendered, 'warm', '1'); + act(() => { + findTestSubject(rendered, 'warm-forceMergeSwitch').simulate('click'); + }); rendered.update(); const forcemergeInput = findTestSubject(rendered, 'warm-selectedForceMergeSegments'); - forcemergeInput.simulate('change', { target: { value: '0' } }); - rendered.update(); - await save(rendered); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + await act(async () => { + forcemergeInput.simulate('change', { target: { value: '0' } }); + }); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); test('should show positive number above 0 required error when trying to save warm phase with -1 for force merge', async () => { const rendered = mountWithIntl(component); await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); - setPhaseAfter(rendered, 'warm', '1'); - findTestSubject(rendered, 'warm-forceMergeSwitch').simulate('click'); + await setPhaseAfter(rendered, 'warm', '1'); + await act(async () => { + findTestSubject(rendered, 'warm-forceMergeSwitch').simulate('click'); + }); rendered.update(); const forcemergeInput = findTestSubject(rendered, 'warm-selectedForceMergeSegments'); - forcemergeInput.simulate('change', { target: { value: '-1' } }); - rendered.update(); - await save(rendered); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + await act(async () => { + forcemergeInput.simulate('change', { target: { value: '-1' } }); + }); + waitForFormLibValidation(rendered); + expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.numberGreatThan0Required]); }); test('should show spinner for node attributes input when loading', async () => { server.respondImmediately = false; @@ -504,7 +519,7 @@ describe('edit policy', () => { await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy(); - expect(getNodeAttributeSelect(rendered, 'warm').exists()).toBeFalsy(); + expect(getNodeAttributeSelectLegacy(rendered, 'warm').exists()).toBeFalsy(); }); test('should show warning instead of node attributes input when none exist', async () => { http.setupNodeListResponse({ @@ -517,9 +532,9 @@ describe('edit policy', () => { setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - openNodeAttributesSection(rendered, 'warm'); + await openNodeAttributesSection(rendered, 'warm'); expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeTruthy(); - expect(getNodeAttributeSelect(rendered, 'warm').exists()).toBeFalsy(); + expect(getNodeAttributeSelectLegacy(rendered, 'warm').exists()).toBeFalsy(); }); test('should show node attributes input when attributes exist', async () => { const rendered = mountWithIntl(component); @@ -527,7 +542,7 @@ describe('edit policy', () => { setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - openNodeAttributesSection(rendered, 'warm'); + await openNodeAttributesSection(rendered, 'warm'); expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); const nodeAttributesSelect = getNodeAttributeSelect(rendered, 'warm'); expect(nodeAttributesSelect.exists()).toBeTruthy(); @@ -539,13 +554,15 @@ describe('edit policy', () => { setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - openNodeAttributesSection(rendered, 'warm'); + await openNodeAttributesSection(rendered, 'warm'); expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); const nodeAttributesSelect = getNodeAttributeSelect(rendered, 'warm'); expect(nodeAttributesSelect.exists()).toBeTruthy(); expect(findTestSubject(rendered, 'warm-viewNodeDetailsFlyoutButton').exists()).toBeFalsy(); expect(nodeAttributesSelect.find('option').length).toBe(2); - nodeAttributesSelect.simulate('change', { target: { value: 'attribute:true' } }); + await act(async () => { + nodeAttributesSelect.simulate('change', { target: { value: 'attribute:true' } }); + }); rendered.update(); const flyoutButton = findTestSubject(rendered, 'warm-viewNodeDetailsFlyoutButton'); expect(flyoutButton.exists()).toBeTruthy(); @@ -608,7 +625,7 @@ describe('edit policy', () => { await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); - setPhaseAfter(rendered, 'cold', '0'); + setPhaseAfterLegacy(rendered, 'cold', '0'); await save(rendered); expectedErrorMessages(rendered, []); }); @@ -617,7 +634,7 @@ describe('edit policy', () => { await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); - setPhaseAfter(rendered, 'cold', '-1'); + setPhaseAfterLegacy(rendered, 'cold', '-1'); await save(rendered); expectedErrorMessages(rendered, [positiveNumberRequiredMessage]); }); @@ -629,7 +646,7 @@ describe('edit policy', () => { await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeTruthy(); expect(rendered.find('.euiCallOut--warning').exists()).toBeFalsy(); - expect(getNodeAttributeSelect(rendered, 'cold').exists()).toBeFalsy(); + expect(getNodeAttributeSelectLegacy(rendered, 'cold').exists()).toBeFalsy(); }); test('should show warning instead of node attributes input when none exist', async () => { http.setupNodeListResponse({ @@ -642,9 +659,9 @@ describe('edit policy', () => { setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - openNodeAttributesSection(rendered, 'cold'); + await openNodeAttributesSection(rendered, 'cold'); expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeTruthy(); - expect(getNodeAttributeSelect(rendered, 'cold').exists()).toBeFalsy(); + expect(getNodeAttributeSelectLegacy(rendered, 'cold').exists()).toBeFalsy(); }); test('should show node attributes input when attributes exist', async () => { const rendered = mountWithIntl(component); @@ -652,9 +669,9 @@ describe('edit policy', () => { setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - openNodeAttributesSection(rendered, 'cold'); + await openNodeAttributesSection(rendered, 'cold'); expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); - const nodeAttributesSelect = getNodeAttributeSelect(rendered, 'cold'); + const nodeAttributesSelect = getNodeAttributeSelectLegacy(rendered, 'cold'); expect(nodeAttributesSelect.exists()).toBeTruthy(); expect(nodeAttributesSelect.find('option').length).toBe(2); }); @@ -664,9 +681,9 @@ describe('edit policy', () => { setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - openNodeAttributesSection(rendered, 'cold'); + await openNodeAttributesSection(rendered, 'cold'); expect(findTestSubject(rendered, 'noNodeAttributesWarning').exists()).toBeFalsy(); - const nodeAttributesSelect = getNodeAttributeSelect(rendered, 'cold'); + const nodeAttributesSelect = getNodeAttributeSelectLegacy(rendered, 'cold'); expect(nodeAttributesSelect.exists()).toBeTruthy(); expect(findTestSubject(rendered, 'cold-viewNodeDetailsFlyoutButton').exists()).toBeFalsy(); expect(nodeAttributesSelect.find('option').length).toBe(2); @@ -685,7 +702,7 @@ describe('edit policy', () => { await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'cold'); - setPhaseAfter(rendered, 'cold', '1'); + setPhaseAfterLegacy(rendered, 'cold', '1'); setPhaseIndexPriorityLegacy(rendered, 'cold', '-1'); await save(rendered); expectedErrorMessages(rendered, [positiveNumberRequiredMessage]); @@ -736,7 +753,7 @@ describe('edit policy', () => { await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'delete'); - setPhaseAfter(rendered, 'delete', '0'); + setPhaseAfterLegacy(rendered, 'delete', '0'); await save(rendered); expectedErrorMessages(rendered, []); }); @@ -745,7 +762,7 @@ describe('edit policy', () => { await noRollover(rendered); setPolicyName(rendered, 'mypolicy'); await activatePhase(rendered, 'delete'); - setPhaseAfter(rendered, 'delete', '-1'); + setPhaseAfterLegacy(rendered, 'delete', '-1'); await save(rendered); expectedErrorMessages(rendered, [positiveNumberRequiredMessage]); }); 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 473e6307f2a633..136b68727672aa 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 @@ -8,6 +8,8 @@ import { SerializedPhase, ColdPhase, DeletePhase, SerializedPolicy } from '../.. export const defaultSetPriority: string = '100'; +export const defaultPhaseIndexPriority: string = '50'; + export const defaultPolicy: SerializedPolicy = { name: '', phases: { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx index fe74c82d29e7b2..d866fcd417671b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx @@ -98,9 +98,10 @@ export const NodeAllocation: FunctionComponent = ({ phase, nodes }) ) : undefined, euiFieldProps: { 'data-test-subj': `${phase}-${nodeAttrsProperty}`, - options: [{ text: i18nTexts.doNotModifyAllocationOption, value: 'none' }].concat( + options: [{ text: i18nTexts.doNotModifyAllocationOption, value: '' }].concat( nodeOptions ), + hasNoInitialSelection: false, }, }} /> diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts index 440d67ed571ad8..fc7723598c2a62 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { FormSchema, fieldValidators } from '../../../shared_imports'; -import { defaultSetPriority } from '../../constants'; +import { defaultSetPriority, defaultPhaseIndexPriority } from '../../constants'; import { FormInternal } from './types'; @@ -66,7 +66,6 @@ export const schema: FormSchema = { label: i18nTexts.editPolicy.allocationTypeOptionsFieldLabel, }, allocationNodeAttribute: { - defaultValue: 'none', label: i18nTexts.editPolicy.allocationNodeAttributeFieldLabel, }, }, @@ -150,11 +149,15 @@ export const schema: FormSchema = { defaultValue: '0', validations: [ { - validator: numberGreaterThanField({ - than: 0, - allowEquality: true, - message: i18nTexts.editPolicy.errors.positiveNumberRequired, - }), + validator: (arg) => + numberGreaterThanField({ + than: 0, + allowEquality: true, + message: i18nTexts.editPolicy.errors.nonNegativeNumberRequired, + })({ + ...arg, + value: arg.value === '' ? -Infinity : parseInt(arg.value, 10), + }), }, ], }, @@ -212,7 +215,7 @@ export const schema: FormSchema = { }, set_priority: { priority: { - defaultValue: '50' as any, + defaultValue: defaultPhaseIndexPriority as any, label: i18nTexts.editPolicy.setPriorityFieldLabel, validations: [{ validator: ifExistsNumberGreaterThanZero }], serializer: serializers.stringToNumber, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index 245c129db47433..1fba69b7634aea 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -48,54 +48,54 @@ export const i18nTexts = { ), errors: { numberRequired: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.numberRequiredErrorMessage', + 'xpack.indexLifecycleMgmt.editPolicy.errors.numberRequiredErrorMessage', { defaultMessage: 'A number is required.', } ), numberGreatThan0Required: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.numberAboveZeroRequiredError', + 'xpack.indexLifecycleMgmt.editPolicy.errors.numberAboveZeroRequiredError', { defaultMessage: 'Only numbers above 0 are allowed.', } ), maximumAgeRequiredMessage: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.maximumAgeMissingError', + 'xpack.indexLifecycleMgmt.editPolicy.errors.maximumAgeMissingError', { defaultMessage: 'A maximum age is required.', } ), maximumSizeRequiredMessage: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.maximumIndexSizeMissingError', + 'xpack.indexLifecycleMgmt.editPolicy.errors.maximumIndexSizeMissingError', { defaultMessage: 'A maximum index size is required.', } ), maximumDocumentsRequiredMessage: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.maximumDocumentsMissingError', + 'xpack.indexLifecycleMgmt.editPolicy.errors.maximumDocumentsMissingError', { defaultMessage: 'Maximum documents is required.', } ), rollOverConfigurationCallout: { title: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverConfigurationError.title', + 'xpack.indexLifecycleMgmt.editPolicy.errors.rolloverConfigurationError.title', { defaultMessage: 'Invalid rollover configuration', } ), body: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverConfigurationError.body', + 'xpack.indexLifecycleMgmt.editPolicy.errors.rolloverConfigurationError.body', { defaultMessage: 'A value for one of maximum size, maximum documents, or maximum age is required.', } ), }, - positiveNumberRequired: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.errors.positiveNumberRequiredError', + nonNegativeNumberRequired: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.errors.nonNegativeNumberRequiredError', { - defaultMessage: 'Only positive numbers are allowed.', + defaultMessage: 'Only non-negative numbers are allowed.', } ), }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts index 4ebb33be7647ec..463ed0a201251b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts @@ -18,8 +18,7 @@ const unsafeSerializePhaseWithAllocation = ( ) => { if ( dataAllocationMetaFields.dataTierAllocationType === 'node_attrs' && - dataAllocationMetaFields.allocationNodeAttribute && - dataAllocationMetaFields.allocationNodeAttribute !== 'none' + dataAllocationMetaFields.allocationNodeAttribute ) { const [name, value] = dataAllocationMetaFields.allocationNodeAttribute.split(':'); actions.allocate = { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts index 81eb1c8cad1358..c77e3d22f0e37c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts @@ -9,8 +9,8 @@ import { UIM_CONFIG_WARM_PHASE, UIM_CONFIG_SET_PRIORITY, UIM_CONFIG_FREEZE_INDEX, - defaultNewWarmPhase, defaultNewColdPhase, + defaultPhaseIndexPriority, } from '../constants/'; import { getUiMetricsForPhases } from './ui_metric'; @@ -38,7 +38,7 @@ describe('getUiMetricsForPhases', () => { min_age: '0ms', actions: { set_priority: { - priority: parseInt(defaultNewWarmPhase.phaseIndexPriority, 10), + priority: parseInt(defaultPhaseIndexPriority, 10), }, }, }, @@ -53,7 +53,7 @@ describe('getUiMetricsForPhases', () => { min_age: '0ms', actions: { set_priority: { - priority: parseInt(defaultNewWarmPhase.phaseIndexPriority, 10) + 1, + priority: parseInt(defaultPhaseIndexPriority, 10) + 1, }, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts index ec811e29839045..305b35b23e4d8b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts @@ -15,6 +15,7 @@ import { UIM_CONFIG_WARM_PHASE, defaultNewColdPhase, defaultSetPriority, + defaultPhaseIndexPriority, } from '../constants'; import { Phases } from '../../../common/types'; @@ -49,8 +50,7 @@ export function getUiMetricsForPhases(phases: Phases): string[] { const isWarmPhasePriorityChanged = phases.warm && phases.warm.actions.set_priority && - phases.warm.actions.set_priority.priority !== - parseInt(defaultNewWarmPhase.phaseIndexPriority, 10); + phases.warm.actions.set_priority.priority !== parseInt(defaultPhaseIndexPriority, 10); const isColdPhasePriorityChanged = phases.cold && From d7896d7685fc742c39a3d5ca5ecfa4c98feda891 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 21 Oct 2020 16:18:15 +0200 Subject: [PATCH 08/19] added policy serialization tests --- .../edit_policy/constants.ts | 41 ++- .../edit_policy/edit_policy.helpers.tsx | 103 +++++-- .../edit_policy/edit_policy.test.ts | 253 +++++++++++++++++- .../helpers/http_requests.ts | 10 + .../phases/warm_phase/warm_phase.tsx | 3 +- .../sections/edit_policy/form_schema.ts | 2 +- .../sections/edit_policy/serializer.ts | 45 +++- 7 files changed, 406 insertions(+), 51 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts index bd845b0a7d9a76..bcc5ff53315d72 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts @@ -14,17 +14,52 @@ export const DEFAULT_POLICY: PolicyFromES = { version: 1, modified_date: Date.now().toString(), policy: { - name: '', + name: 'my_policy', + phases: { + hot: { + min_age: '0ms', + actions: { + rollover: { + max_age: '30d', + max_size: '50gb', + }, + }, + }, + }, + }, + name: 'my_policy', +}; + +export const POLICY_WITH_INCLUDE_EXCLUDE: PolicyFromES = { + version: 1, + modified_date: Date.now().toString(), + policy: { + name: 'my_policy', phases: { hot: { min_age: '123ms', actions: { - rollover: {}, + rollover: { + max_age: '30d', + max_size: '50gb', + }, + }, + }, + warm: { + actions: { + allocate: { + include: { + abc: '123', + }, + exclude: { + def: '456', + }, + }, }, }, }, }, - name: '', + name: 'my_policy', }; export const DELETE_PHASE_POLICY: PolicyFromES = { diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx index 0cfccba7613094..37a79d45c432e4 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx @@ -13,6 +13,7 @@ import { POLICY_NAME } from './constants'; import { TestSubjects } from '../helpers'; import { EditPolicy } from '../../../public/application/sections/edit_policy'; +import { DataTierAllocationType } from '../../../public/application/sections/edit_policy/types'; jest.mock('@elastic/eui', () => { const original = jest.requireActual('@elastic/eui'); @@ -54,6 +55,22 @@ export const setup = async () => { const { find, component } = testBed; + const createFormClickAction = (dataTestSubject: string) => async () => { + await act(async () => { + find(dataTestSubject).simulate('click'); + }); + component.update(); + }; + + function createFormSetValueAction(dataTestSubject: string) { + return async (value: V) => { + await act(async () => { + find(dataTestSubject).simulate('change', { target: { value } }); + }); + component.update(); + }; + } + const setWaitForSnapshotPolicy = async (snapshotPolicyName: string) => { act(() => { find('snapshotPolicyCombobox').simulate('change', [{ label: snapshotPolicyName }]); @@ -68,12 +85,7 @@ export const setup = async () => { component.update(); }; - const toggleRollover = async (checked: boolean) => { - await act(async () => { - find('rolloverSwitch').simulate('click', { target: { checked } }); - }); - component.update(); - }; + const toggleRollover = createFormClickAction('rolloverSwitch'); const setMaxSize = async (value: string, units?: string) => { await act(async () => { @@ -87,12 +99,7 @@ export const setup = async () => { component.update(); }; - const setMaxDocs = async (value: string) => { - await act(async () => { - find('hot-selectedMaxDocuments').simulate('change', { target: { value } }); - }); - component.update(); - }; + const setMaxDocs = createFormSetValueAction('hot-selectedMaxDocuments'); const setMaxAge = async (value: string, units?: string) => { await act(async () => { @@ -104,32 +111,56 @@ export const setup = async () => { component.update(); }; - const toggleForceMerge = (phase: string) => async (checked: boolean) => { - await act(async () => { - find(`${phase}-forceMergeSwitch`).simulate('click', { target: { checked } }); + const toggleForceMerge = (phase: string) => createFormClickAction(`${phase}-forceMergeSwitch`); + + const setForcemergeSegmentsCount = (phase: string) => + createFormSetValueAction(`${phase}-selectedForceMergeSegments`); + + const setBestCompression = (phase: string) => createFormClickAction(`${phase}-bestCompression`); + + const setIndexPriority = (phase: string) => + createFormSetValueAction(`${phase}-phaseIndexPriority`); + + const enable = (phase: string) => createFormClickAction(`enablePhaseSwitch-${phase}`); + + const warmPhaseOnRollover = createFormClickAction(`warm-warmPhaseOnRollover`); + + const setMinAgeValue = (phase: string) => createFormSetValueAction(`${phase}-selectedMinimumAge`); + + const setMinAgeUnits = (phase: string) => + createFormSetValueAction(`${phase}-selectedMinimumAgeUnits`); + + const setDataAllocation = (phase: string) => async (value: DataTierAllocationType) => { + act(() => { + find(`${phase}-dataTierAllocationControls.dataTierSelect`).simulate('click'); }); component.update(); - }; - - const setForcemergeSegmentsCount = (phase: string) => async (value: string) => { await act(async () => { - find(`${phase}-selectedForceMergeSegments`).simulate('change', { target: { value } }); + switch (value) { + case 'node_roles': + find(`${phase}-dataTierAllocationControls.defaultDataAllocationOption`).simulate('click'); + break; + case 'node_attrs': + find(`${phase}-dataTierAllocationControls.customDataAllocationOption`).simulate('click'); + break; + default: + find(`${phase}-dataTierAllocationControls.noneDataAllocationOption`).simulate('click'); + } }); component.update(); }; - const setBestCompression = (phase: string) => async (checked: boolean) => { - await act(async () => { - find(`${phase}-bestCompression`).simulate('click', { target: { checked } }); - }); - component.update(); + const setSelectedNodeAttribute = (phase: string) => + createFormSetValueAction(`${phase}-selectedNodeAttrs`); + + const setReplicas = async (value: string) => { + await createFormClickAction('warm-setReplicasSwitch')(); + await createFormSetValueAction('warm-selectedReplicaCount')(value); }; - const setIndexPriority = (phase: string) => async (value: string) => { - await act(async () => { - find(`${phase}-phaseIndexPriority`).simulate('change', { target: { value } }); - }); - component.update(); + const setShrink = async (value: string) => { + await createFormClickAction('shrinkSwitch')(); + await createFormSetValueAction('warm-selectedPrimaryShardCount')(value); }; return { @@ -147,6 +178,20 @@ export const setup = async () => { setBestCompression: setBestCompression('hot'), setIndexPriority: setIndexPriority('hot'), }, + warm: { + enable: enable('warm'), + warmPhaseOnRollover, + setMinAgeValue: setMinAgeValue('warm'), + setMinAgeUnits: setMinAgeUnits('warm'), + setDataAllocation: setDataAllocation('warm'), + setSelectedNodeAttribute: setSelectedNodeAttribute('warm'), + setReplicas, + setShrink, + toggleForceMerge: toggleForceMerge('warm'), + setForcemergeSegments: setForcemergeSegmentsCount('warm'), + setBestCompression: setBestCompression('warm'), + setIndexPriority: setIndexPriority('warm'), + }, }, }; }; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index 3cbc2d982566e6..9f1b990705776c 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -15,6 +15,7 @@ import { NEW_SNAPSHOT_POLICY_NAME, SNAPSHOT_POLICY_NAME, DEFAULT_POLICY, + POLICY_WITH_INCLUDE_EXCLUDE, } from './constants'; window.scrollTo = jest.fn(); @@ -46,9 +47,9 @@ describe('', () => { await actions.hot.setMaxSize('123', 'mb'); await actions.hot.setMaxDocs('123'); await actions.hot.setMaxAge('123', 'h'); - await actions.hot.toggleForceMerge(true); + await actions.hot.toggleForceMerge(); await actions.hot.setForcemergeSegments('123'); - await actions.hot.setBestCompression(true); + await actions.hot.setBestCompression(); await actions.hot.setIndexPriority('123'); await actions.savePolicy(); @@ -81,7 +82,7 @@ describe('', () => { test('disabling rollover', async () => { const { actions } = testBed; - await actions.hot.toggleRollover(false); + await actions.hot.toggleRollover(); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` @@ -103,8 +104,254 @@ describe('', () => { }); }); + describe('warm phase', () => { + describe('serialization', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([DEFAULT_POLICY]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: {}, + nodesByAttributes: { test: ['123'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + httpRequestsMockHelpers.setLoadSnapshotPolicies([]); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + + test('default values', async () => { + const { actions } = testBed; + await actions.warm.enable(); + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + Object { + "name": "my_policy", + "phases": Object { + "hot": Object { + "actions": Object { + "rollover": Object { + "max_age": "30d", + "max_size": "50gb", + }, + "set_priority": Object { + "priority": 100, + }, + }, + "min_age": "0ms", + }, + "warm": Object { + "actions": Object { + "set_priority": Object { + "priority": 50, + }, + }, + "min_age": "0ms", + }, + }, + } + `); + }); + + test('setting all values', async () => { + const { actions } = testBed; + await actions.warm.enable(); + await actions.warm.setMinAgeValue('123'); + await actions.warm.setMinAgeUnits('d'); + await actions.warm.setDataAllocation('node_attrs'); + await actions.warm.setSelectedNodeAttribute('test:123'); + await actions.warm.setReplicas('123'); + await actions.warm.setShrink('123'); + await actions.warm.toggleForceMerge(); + await actions.warm.setForcemergeSegments('123'); + await actions.warm.setBestCompression(); + await actions.warm.setIndexPriority('123'); + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + Object { + "name": "my_policy", + "phases": Object { + "hot": Object { + "actions": Object { + "rollover": Object { + "max_age": "30d", + "max_size": "50gb", + }, + "set_priority": Object { + "priority": 100, + }, + }, + "min_age": "0ms", + }, + "warm": Object { + "actions": Object { + "allocate": Object { + "number_of_replicas": 123, + "require": Object { + "test": "123", + }, + }, + "forcemerge": Object { + "index_codec": "best_compression", + "max_num_segments": 123, + }, + "set_priority": Object { + "priority": 123, + }, + "shrink": Object { + "number_of_shards": 123, + }, + }, + "min_age": "123d", + }, + }, + } + `); + }); + + test('default allocation with replicas set', async () => { + const { actions } = testBed; + await actions.warm.enable(); + await actions.warm.setReplicas('123'); + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + Object { + "name": "my_policy", + "phases": Object { + "hot": Object { + "actions": Object { + "rollover": Object { + "max_age": "30d", + "max_size": "50gb", + }, + "set_priority": Object { + "priority": 100, + }, + }, + "min_age": "0ms", + }, + "warm": Object { + "actions": Object { + "allocate": Object { + "number_of_replicas": 123, + }, + "set_priority": Object { + "priority": 50, + }, + }, + "min_age": "0ms", + }, + }, + } + `); + }); + + test('setting warm phase on rollover to "true"', async () => { + const { actions } = testBed; + await actions.warm.enable(); + await actions.warm.warmPhaseOnRollover(); + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + Object { + "name": "my_policy", + "phases": Object { + "hot": Object { + "actions": Object { + "rollover": Object { + "max_age": "30d", + "max_size": "50gb", + }, + "set_priority": Object { + "priority": 100, + }, + }, + "min_age": "0ms", + }, + "warm": Object { + "actions": Object { + "set_priority": Object { + "priority": 50, + }, + }, + }, + }, + } + `); + }); + }); + + describe('policy with include and exclude', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([POLICY_WITH_INCLUDE_EXCLUDE]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: {}, + nodesByAttributes: { test: ['123'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + httpRequestsMockHelpers.setLoadSnapshotPolicies([]); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + + test('preserves include, exclude allocation settings', async () => { + const { actions } = testBed; + await actions.warm.setDataAllocation('node_attrs'); + await actions.savePolicy(); + const latestRequest = server.requests[server.requests.length - 1]; + expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + Object { + "name": "my_policy", + "phases": Object { + "hot": Object { + "actions": Object { + "rollover": Object { + "max_age": "30d", + "max_size": "50gb", + }, + "set_priority": Object { + "priority": 100, + }, + }, + "min_age": "123ms", + }, + "warm": Object { + "actions": Object { + "allocate": Object { + "exclude": Object { + "def": "456", + }, + "include": Object { + "abc": "123", + }, + }, + "set_priority": Object { + "priority": 50, + }, + }, + "min_age": "0ms", + }, + }, + } + `); + }); + }); + }); + describe('delete phase', () => { beforeEach(async () => { + server.respondImmediately = true; httpRequestsMockHelpers.setLoadPolicies([DELETE_PHASE_POLICY]); httpRequestsMockHelpers.setLoadSnapshotPolicies([ SNAPSHOT_POLICY_NAME, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts index 04f58f93939ca3..c7a493ce80d96b 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/http_requests.ts @@ -6,6 +6,7 @@ import { fakeServer, SinonFakeServer } from 'sinon'; import { API_BASE_PATH } from '../../../common/constants'; +import { ListNodesRouteResponse } from '../../../common/types'; export const init = () => { const server = fakeServer.create(); @@ -38,8 +39,17 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ]); }; + const setListNodes = (body: ListNodesRouteResponse) => { + server.respondWith('GET', `${API_BASE_PATH}/nodes/list`, [ + 200, + { 'Content-Type': 'application/json' }, + JSON.stringify(body), + ]); + }; + return { setLoadPolicies, setLoadSnapshotPolicies, + setListNodes, }; }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index b13e867a0b1faf..d03b36fceefdd0 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -151,6 +151,7 @@ export const WarmPhase: FunctionComponent = () => { } )} switchProps={{ + 'data-test-subj': 'warm-setReplicasSwitch', label: i18n.translate( 'xpack.indexLifecycleMgmt.editPolicy.warmPhase.numberOfReplicas.switchLabel', { defaultMessage: 'Set replicas' } @@ -167,7 +168,7 @@ export const WarmPhase: FunctionComponent = () => { componentProps={{ fullWidth: false, euiFieldProps: { - 'data-test-subj': `${warmProperty}-selectedReplicaCount}`, + 'data-test-subj': `${warmProperty}-selectedReplicaCount`, min: 0, }, }} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts index fc7723598c2a62..a80382e87539c2 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form_schema.ts @@ -56,7 +56,7 @@ export const schema: FormSchema = { }), }, minAgeUnit: { - defaultValue: 'd', + defaultValue: 'ms', }, bestCompression: { label: i18nTexts.editPolicy.bestCompressionFieldLabel, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts index 463ed0a201251b..415de1707dd18f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts @@ -16,22 +16,39 @@ const unsafeSerializePhaseWithAllocation = ( actions: SerializedPhase['actions'] = {}, originalAllocation: AllocateAction = {} ) => { - if ( - dataAllocationMetaFields.dataTierAllocationType === 'node_attrs' && - dataAllocationMetaFields.allocationNodeAttribute - ) { - const [name, value] = dataAllocationMetaFields.allocationNodeAttribute.split(':'); - actions.allocate = { - require: { - [name]: value, - }, - // copy over the original include and exclude values until we can set them in the form. - include: !isEmpty(originalAllocation.include) ? { ...originalAllocation.include } : undefined, - exclude: !isEmpty(originalAllocation.exclude) ? { ...originalAllocation.exclude } : undefined, - }; + if (dataAllocationMetaFields.dataTierAllocationType === 'node_attrs') { + if (dataAllocationMetaFields.allocationNodeAttribute) { + const [name, value] = dataAllocationMetaFields.allocationNodeAttribute.split(':'); + actions.allocate = { + // copy over any other allocate details like "number_of_replicas" + ...actions.allocate, + require: { + [name]: value, + }, + }; + } + + // copy over the original include and exclude values until we can set them in the form. + if (!isEmpty(originalAllocation.include)) { + actions.allocate = { + ...actions.allocate, + include: { ...originalAllocation.include }, + }; + } + + if (!isEmpty(originalAllocation.exclude)) { + actions.allocate = { + ...actions.allocate, + exclude: { ...originalAllocation.exclude }, + }; + } } else if (dataAllocationMetaFields.dataTierAllocationType === 'none') { actions.migrate = { enabled: false }; - delete actions.allocate; + if (actions.allocate) { + delete actions.allocate.require; + delete actions.allocate.include; + delete actions.allocate.exclude; + } } else if (dataAllocationMetaFields.dataTierAllocationType === 'node_roles') { if (actions.allocate) { delete actions.allocate.require; From 4f2a615cd52834f26da85dde3e1a182a0e6c3f8f Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 21 Oct 2020 17:22:37 +0200 Subject: [PATCH 09/19] remove unused translations --- x-pack/plugins/translations/translations/ja-JP.json | 3 --- x-pack/plugins/translations/translations/zh-CN.json | 3 --- 2 files changed, 6 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e645ae32abbd18..4d2295091addda 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -8570,9 +8570,6 @@ "xpack.indexLifecycleMgmt.editPolicy.lifecyclePoliciesLoadingFailedTitle": "既存のライフサイクルポリシーを読み込めません", "xpack.indexLifecycleMgmt.editPolicy.lifecyclePoliciesReloadButton": "再試行", "xpack.indexLifecycleMgmt.editPolicy.lifecyclePolicyDescriptionText": "インデックスへのアクティブな書き込みから削除までの、インデックスライフサイクルの 4 つのフェーズを自動化するには、インデックスポリシーを使用します。", - "xpack.indexLifecycleMgmt.editPolicy.maximumAgeMissingError": "最高年齢が必要です。", - "xpack.indexLifecycleMgmt.editPolicy.maximumDocumentsMissingError": "最高ドキュメント数が必要です。", - "xpack.indexLifecycleMgmt.editPolicy.maximumIndexSizeMissingError": "最大インデックスサイズが必要です。", "xpack.indexLifecycleMgmt.editPolicy.nameLabel": "名前", "xpack.indexLifecycleMgmt.editPolicy.nodeAllocation.customOption.description": "ノード属性を使用して、シャード割り当てを制御します。{learnMoreLink}。", "xpack.indexLifecycleMgmt.editPolicy.nodeAllocation.doNotModifyAllocationOption": "割り当て構成を修正しない", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2f701d0cde284b..a9a48363250cc4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -8577,9 +8577,6 @@ "xpack.indexLifecycleMgmt.editPolicy.lifecyclePoliciesLoadingFailedTitle": "无法加载现有生命周期策略", "xpack.indexLifecycleMgmt.editPolicy.lifecyclePoliciesReloadButton": "重试", "xpack.indexLifecycleMgmt.editPolicy.lifecyclePolicyDescriptionText": "使用索引策略自动化索引生命周期的四个阶段,从频繁地写入到索引到删除索引。", - "xpack.indexLifecycleMgmt.editPolicy.maximumAgeMissingError": "最大存在时间必填。", - "xpack.indexLifecycleMgmt.editPolicy.maximumDocumentsMissingError": "最大文档数必填。", - "xpack.indexLifecycleMgmt.editPolicy.maximumIndexSizeMissingError": "最大索引大小必填。", "xpack.indexLifecycleMgmt.editPolicy.nameLabel": "名称", "xpack.indexLifecycleMgmt.editPolicy.nodeAllocation.customOption.description": "使用节点属性控制分片分配。{learnMoreLink}。", "xpack.indexLifecycleMgmt.editPolicy.nodeAllocation.doNotModifyAllocationOption": "不要修改分配配置", From 2ed016f87804a46dab32b3ee15cc289acd555f5d Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 21 Oct 2020 18:05:00 +0200 Subject: [PATCH 10/19] Fix use of useFormData after update - also minor refactor to use useCallback in policy flyout now that getFormData changes when the form data changes. --- .../components/data_tier_allocation.tsx | 7 +++- .../components/node_allocation.tsx | 5 ++- .../data_tier_allocation_field.tsx | 6 ++- .../min_age_input_field.tsx | 5 ++- .../components/policy_json_flyout.tsx | 41 ++++++++++--------- 5 files changed, 37 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx index 99fa2abac3abe5..2aa7e75a48b1b4 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx @@ -5,6 +5,7 @@ */ import React, { FunctionComponent } from 'react'; +import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { EuiText, EuiSpacer, EuiSuperSelectOption } from '@elastic/eui'; @@ -93,10 +94,12 @@ export const DataTierAllocation: FunctionComponent = (props) => { const dataTierAllocationTypePath = `_meta.${phase}.dataTierAllocationType`; - const [{ [dataTierAllocationTypePath]: dataTierAllocationType }] = useFormData({ - watch: [dataTierAllocationTypePath], + const [formData] = useFormData({ + watch: dataTierAllocationTypePath, }); + const dataTierAllocationType = get(formData, dataTierAllocationTypePath); + return (
diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx index d866fcd417671b..407bb9ea92e855 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/node_allocation.tsx @@ -5,6 +5,7 @@ */ import React, { useState, FunctionComponent } from 'react'; +import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiText, EuiSpacer } from '@elastic/eui'; @@ -43,10 +44,12 @@ const i18nTexts = { export const NodeAllocation: FunctionComponent = ({ phase, nodes }) => { const allocationNodeAttributePath = `_meta.${phase}.allocationNodeAttribute`; - const [{ [allocationNodeAttributePath]: selectedAllocationNodeAttribute }] = useFormData({ + const [formData] = useFormData({ watch: [allocationNodeAttributePath], }); + const selectedAllocationNodeAttribute = get(formData, allocationNodeAttributePath); + const [selectedNodeAttrsForDetails, setSelectedNodeAttrsForDetails] = useState( null ); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx index b7b9a1e9f3dad9..bafc48e6e4eeed 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { get } from 'lodash'; import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; @@ -44,8 +45,9 @@ export const DataTierAllocationField: FunctionComponent = ({ phase, descr services: { cloud }, } = useKibana(); - const allocationTypePath = `_meta.${phase}.dataTierAllocationType`; - const [{ [allocationTypePath]: allocationType }] = useFormData({ watch: [allocationTypePath] }); + const dataTierAllocationTypePath = `_meta.${phase}.dataTierAllocationType`; + const [formData] = useFormData({ watch: dataTierAllocationTypePath }); + const allocationType = get(formData, dataTierAllocationTypePath); return ( diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/min_age_input_field/min_age_input_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/min_age_input_field/min_age_input_field.tsx index 181f2e6c2a29e4..f37c3873544182 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/min_age_input_field/min_age_input_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/min_age_input_field/min_age_input_field.tsx @@ -3,8 +3,8 @@ * 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, { FunctionComponent } from 'react'; +import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -29,7 +29,8 @@ interface Props { } export const MinAgeInputField: FunctionComponent = ({ phase }): React.ReactElement => { - const [{ [useRolloverPath]: rolloverEnabled }] = useFormData({ watch: useRolloverPath }); + const [formData] = useFormData({ watch: useRolloverPath }); + const rolloverEnabled = get(formData, useRolloverPath); let daysOptionLabel; let hoursOptionLabel; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx index 9693733eb61508..4852aecf621f76 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -46,27 +46,28 @@ export const PolicyJsonFlyout: React.FunctionComponent = ({ const [policy, setPolicy] = useState(undefined); const form = useFormContext(); - const [formData, getFormData] = useFormData(); + const [, getFormData] = useFormData(); + + const getPolicy = useCallback(async () => { + setPolicy(undefined); + if (await form.validate()) { + const p = getFormData() as SerializedPolicy; + setPolicy({ + ...legacyPolicy, + phases: { + ...legacyPolicy.phases, + hot: p.phases.hot, + warm: p.phases.warm, + }, + }); + } else { + setPolicy(null); + } + }, [setPolicy, getFormData, legacyPolicy, form]); useEffect(() => { - (async function checkPolicy() { - setPolicy(undefined); - if (await form.validate()) { - const p = getFormData() as SerializedPolicy; - setPolicy({ - ...legacyPolicy, - phases: { - ...legacyPolicy.phases, - hot: p.phases.hot, - warm: p.phases.warm, - }, - }); - } else { - setPolicy(null); - } - })(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [form, legacyPolicy, formData]); + getPolicy(); + }, [getPolicy]); let content: React.ReactNode; switch (policy) { From 5aed5cf4058a6a32b90d464e89eb8abfe6623473 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Thu, 22 Oct 2020 09:49:55 +0200 Subject: [PATCH 11/19] fix import path --- .../components/no_node_attributes_warning.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx index 69185277f64ce6..338e5367a1d0df 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/no_node_attributes_warning.tsx @@ -8,7 +8,7 @@ import React, { FunctionComponent } from 'react'; import { EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { PhaseWithAllocation } from '../../../../../../common/types'; +import { PhaseWithAllocation } from '../../../../../../../../../common/types'; const i18nTexts = { title: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.nodeAttributesMissingLabel', { From 1affb8ea2ad8a0ba081f9ad42d466c7811b5c023 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 26 Oct 2020 15:16:00 +0100 Subject: [PATCH 12/19] simplify serialization snapshot tests --- .../edit_policy/edit_policy.test.ts | 155 +++++------------- 1 file changed, 37 insertions(+), 118 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index 9f1b990705776c..43fe008117b906 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -54,7 +54,8 @@ describe('', () => { await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + const entirePolicy = JSON.parse(JSON.parse(latestRequest.requestBody).body); + expect(entirePolicy).toMatchInlineSnapshot(` Object { "name": "my_policy", "phases": Object { @@ -85,18 +86,14 @@ describe('', () => { await actions.hot.toggleRollover(); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + const policy = JSON.parse(JSON.parse(latestRequest.requestBody).body); + const hotActions = policy.phases.hot.actions; + const rolloverAction = hotActions.rollover; + expect(rolloverAction).toBe(undefined); + expect(hotActions).toMatchInlineSnapshot(` Object { - "name": "my_policy", - "phases": Object { - "hot": Object { - "actions": Object { - "set_priority": Object { - "priority": 100, - }, - }, - "min_age": "0ms", - }, + "set_priority": Object { + "priority": 100, }, } `); @@ -128,31 +125,15 @@ describe('', () => { await actions.warm.enable(); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + const warmPhase = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm; + expect(warmPhase).toMatchInlineSnapshot(` Object { - "name": "my_policy", - "phases": Object { - "hot": Object { - "actions": Object { - "rollover": Object { - "max_age": "30d", - "max_size": "50gb", - }, - "set_priority": Object { - "priority": 100, - }, - }, - "min_age": "0ms", - }, - "warm": Object { - "actions": Object { - "set_priority": Object { - "priority": 50, - }, - }, - "min_age": "0ms", + "actions": Object { + "set_priority": Object { + "priority": 50, }, }, + "min_age": "0ms", } `); }); @@ -172,7 +153,9 @@ describe('', () => { await actions.warm.setIndexPriority('123'); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + const entirePolicy = JSON.parse(JSON.parse(latestRequest.requestBody).body); + // Check shape of entire policy + expect(entirePolicy).toMatchInlineSnapshot(` Object { "name": "my_policy", "phases": Object { @@ -220,33 +203,15 @@ describe('', () => { await actions.warm.setReplicas('123'); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + const warmPhaseActions = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm + .actions; + expect(warmPhaseActions).toMatchInlineSnapshot(` Object { - "name": "my_policy", - "phases": Object { - "hot": Object { - "actions": Object { - "rollover": Object { - "max_age": "30d", - "max_size": "50gb", - }, - "set_priority": Object { - "priority": 100, - }, - }, - "min_age": "0ms", - }, - "warm": Object { - "actions": Object { - "allocate": Object { - "number_of_replicas": 123, - }, - "set_priority": Object { - "priority": 50, - }, - }, - "min_age": "0ms", - }, + "allocate": Object { + "number_of_replicas": 123, + }, + "set_priority": Object { + "priority": 50, }, } `); @@ -258,32 +223,9 @@ describe('', () => { await actions.warm.warmPhaseOnRollover(); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` - Object { - "name": "my_policy", - "phases": Object { - "hot": Object { - "actions": Object { - "rollover": Object { - "max_age": "30d", - "max_size": "50gb", - }, - "set_priority": Object { - "priority": 100, - }, - }, - "min_age": "0ms", - }, - "warm": Object { - "actions": Object { - "set_priority": Object { - "priority": 50, - }, - }, - }, - }, - } - `); + const warmPhaseMinAge = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm + .min_age; + expect(warmPhaseMinAge).toBe(undefined); }); }); @@ -310,38 +252,15 @@ describe('', () => { await actions.warm.setDataAllocation('node_attrs'); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; - expect(JSON.parse(JSON.parse(latestRequest.requestBody).body)).toMatchInlineSnapshot(` + const warmPhaseAllocate = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm + .actions.allocate; + expect(warmPhaseAllocate).toMatchInlineSnapshot(` Object { - "name": "my_policy", - "phases": Object { - "hot": Object { - "actions": Object { - "rollover": Object { - "max_age": "30d", - "max_size": "50gb", - }, - "set_priority": Object { - "priority": 100, - }, - }, - "min_age": "123ms", - }, - "warm": Object { - "actions": Object { - "allocate": Object { - "exclude": Object { - "def": "456", - }, - "include": Object { - "abc": "123", - }, - }, - "set_priority": Object { - "priority": 50, - }, - }, - "min_age": "0ms", - }, + "exclude": Object { + "def": "456", + }, + "include": Object { + "abc": "123", }, } `); From 97da20ede660037f527fd8c6cc63f216317289e3 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 26 Oct 2020 15:19:28 +0100 Subject: [PATCH 13/19] type phases: string -> phases: Phases --- .../edit_policy/edit_policy.helpers.tsx | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx index 37a79d45c432e4..d6d79d290c43b6 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx @@ -9,12 +9,16 @@ import { act } from 'react-dom/test-utils'; import { registerTestBed, TestBedConfig } from '../../../../../test_utils'; -import { POLICY_NAME } from './constants'; -import { TestSubjects } from '../helpers'; - import { EditPolicy } from '../../../public/application/sections/edit_policy'; import { DataTierAllocationType } from '../../../public/application/sections/edit_policy/types'; +import { Phases as PolicyPhases } from '../../../common/types'; + +type Phases = keyof PolicyPhases; + +import { POLICY_NAME } from './constants'; +import { TestSubjects } from '../helpers'; + jest.mock('@elastic/eui', () => { const original = jest.requireActual('@elastic/eui'); @@ -111,26 +115,26 @@ export const setup = async () => { component.update(); }; - const toggleForceMerge = (phase: string) => createFormClickAction(`${phase}-forceMergeSwitch`); + const toggleForceMerge = (phase: Phases) => createFormClickAction(`${phase}-forceMergeSwitch`); - const setForcemergeSegmentsCount = (phase: string) => + const setForcemergeSegmentsCount = (phase: Phases) => createFormSetValueAction(`${phase}-selectedForceMergeSegments`); - const setBestCompression = (phase: string) => createFormClickAction(`${phase}-bestCompression`); + const setBestCompression = (phase: Phases) => createFormClickAction(`${phase}-bestCompression`); - const setIndexPriority = (phase: string) => + const setIndexPriority = (phase: Phases) => createFormSetValueAction(`${phase}-phaseIndexPriority`); - const enable = (phase: string) => createFormClickAction(`enablePhaseSwitch-${phase}`); + const enable = (phase: Phases) => createFormClickAction(`enablePhaseSwitch-${phase}`); const warmPhaseOnRollover = createFormClickAction(`warm-warmPhaseOnRollover`); - const setMinAgeValue = (phase: string) => createFormSetValueAction(`${phase}-selectedMinimumAge`); + const setMinAgeValue = (phase: Phases) => createFormSetValueAction(`${phase}-selectedMinimumAge`); - const setMinAgeUnits = (phase: string) => + const setMinAgeUnits = (phase: Phases) => createFormSetValueAction(`${phase}-selectedMinimumAgeUnits`); - const setDataAllocation = (phase: string) => async (value: DataTierAllocationType) => { + const setDataAllocation = (phase: Phases) => async (value: DataTierAllocationType) => { act(() => { find(`${phase}-dataTierAllocationControls.dataTierSelect`).simulate('click'); }); From 2befd672b7b74433d8434f6a09e7e5300a47a772 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 26 Oct 2020 15:33:29 +0100 Subject: [PATCH 14/19] Addressed some PR review items - refactor toggle click to take a boolean arg - refactor selection options in data tier component to use a func to get select options. --- .../edit_policy/edit_policy.helpers.tsx | 24 ++--- .../edit_policy/edit_policy.test.ts | 21 ++-- .../components/data_tier_allocation.tsx | 102 +++++++++--------- 3 files changed, 75 insertions(+), 72 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx index d6d79d290c43b6..1716f124b0c83a 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx @@ -57,19 +57,19 @@ export type EditPolicyTestBed = SetupReturn extends Promise ? U : Setup export const setup = async () => { const testBed = await initTestBed(); - const { find, component } = testBed; + const { find, component, form } = testBed; - const createFormClickAction = (dataTestSubject: string) => async () => { + const createFormToggleAction = (dataTestSubject: string) => async (checked: boolean) => { await act(async () => { - find(dataTestSubject).simulate('click'); + form.toggleEuiSwitch(dataTestSubject, checked); }); component.update(); }; - function createFormSetValueAction(dataTestSubject: string) { + function createFormSetValueAction(dataTestSubject: string) { return async (value: V) => { await act(async () => { - find(dataTestSubject).simulate('change', { target: { value } }); + form.setInputValue(dataTestSubject, value); }); component.update(); }; @@ -89,7 +89,7 @@ export const setup = async () => { component.update(); }; - const toggleRollover = createFormClickAction('rolloverSwitch'); + const toggleRollover = createFormToggleAction('rolloverSwitch'); const setMaxSize = async (value: string, units?: string) => { await act(async () => { @@ -115,19 +115,19 @@ export const setup = async () => { component.update(); }; - const toggleForceMerge = (phase: Phases) => createFormClickAction(`${phase}-forceMergeSwitch`); + const toggleForceMerge = (phase: Phases) => createFormToggleAction(`${phase}-forceMergeSwitch`); const setForcemergeSegmentsCount = (phase: Phases) => createFormSetValueAction(`${phase}-selectedForceMergeSegments`); - const setBestCompression = (phase: Phases) => createFormClickAction(`${phase}-bestCompression`); + const setBestCompression = (phase: Phases) => createFormToggleAction(`${phase}-bestCompression`); const setIndexPriority = (phase: Phases) => createFormSetValueAction(`${phase}-phaseIndexPriority`); - const enable = (phase: Phases) => createFormClickAction(`enablePhaseSwitch-${phase}`); + const enable = (phase: Phases) => createFormToggleAction(`enablePhaseSwitch-${phase}`); - const warmPhaseOnRollover = createFormClickAction(`warm-warmPhaseOnRollover`); + const warmPhaseOnRollover = createFormToggleAction(`warm-warmPhaseOnRollover`); const setMinAgeValue = (phase: Phases) => createFormSetValueAction(`${phase}-selectedMinimumAge`); @@ -158,12 +158,12 @@ export const setup = async () => { createFormSetValueAction(`${phase}-selectedNodeAttrs`); const setReplicas = async (value: string) => { - await createFormClickAction('warm-setReplicasSwitch')(); + await createFormToggleAction('warm-setReplicasSwitch')(true); await createFormSetValueAction('warm-selectedReplicaCount')(value); }; const setShrink = async (value: string) => { - await createFormClickAction('shrinkSwitch')(); + await createFormToggleAction('shrinkSwitch')(true); await createFormSetValueAction('warm-selectedPrimaryShardCount')(value); }; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index 43fe008117b906..d7fcbc6fe1ad21 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -47,9 +47,9 @@ describe('', () => { await actions.hot.setMaxSize('123', 'mb'); await actions.hot.setMaxDocs('123'); await actions.hot.setMaxAge('123', 'h'); - await actions.hot.toggleForceMerge(); + await actions.hot.toggleForceMerge(true); await actions.hot.setForcemergeSegments('123'); - await actions.hot.setBestCompression(); + await actions.hot.setBestCompression(true); await actions.hot.setIndexPriority('123'); await actions.savePolicy(); @@ -83,7 +83,7 @@ describe('', () => { test('disabling rollover', async () => { const { actions } = testBed; - await actions.hot.toggleRollover(); + await actions.hot.toggleRollover(true); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; const policy = JSON.parse(JSON.parse(latestRequest.requestBody).body); @@ -122,7 +122,7 @@ describe('', () => { test('default values', async () => { const { actions } = testBed; - await actions.warm.enable(); + await actions.warm.enable(true); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; const warmPhase = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm; @@ -140,16 +140,16 @@ describe('', () => { test('setting all values', async () => { const { actions } = testBed; - await actions.warm.enable(); + await actions.warm.enable(true); await actions.warm.setMinAgeValue('123'); await actions.warm.setMinAgeUnits('d'); await actions.warm.setDataAllocation('node_attrs'); await actions.warm.setSelectedNodeAttribute('test:123'); await actions.warm.setReplicas('123'); await actions.warm.setShrink('123'); - await actions.warm.toggleForceMerge(); + await actions.warm.toggleForceMerge(true); await actions.warm.setForcemergeSegments('123'); - await actions.warm.setBestCompression(); + await actions.warm.setBestCompression(true); await actions.warm.setIndexPriority('123'); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; @@ -199,7 +199,7 @@ describe('', () => { test('default allocation with replicas set', async () => { const { actions } = testBed; - await actions.warm.enable(); + await actions.warm.enable(true); await actions.warm.setReplicas('123'); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; @@ -219,8 +219,8 @@ describe('', () => { test('setting warm phase on rollover to "true"', async () => { const { actions } = testBed; - await actions.warm.enable(); - await actions.warm.warmPhaseOnRollover(); + await actions.warm.enable(true); + await actions.warm.warmPhaseOnRollover(true); await actions.savePolicy(); const latestRequest = server.requests[server.requests.length - 1]; const warmPhaseMinAge = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm @@ -270,7 +270,6 @@ describe('', () => { describe('delete phase', () => { beforeEach(async () => { - server.respondImmediately = true; httpRequestsMockHelpers.setLoadPolicies([DELETE_PHASE_POLICY]); httpRequestsMockHelpers.setLoadSnapshotPolicies([ SNAPSHOT_POLICY_NAME, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx index 2aa7e75a48b1b4..56af4bbac7a261 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/data_tier_allocation.tsx @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiText, EuiSpacer, EuiSuperSelectOption } from '@elastic/eui'; import { UseField, SuperSelectField, useFormData } from '../../../../../../../../shared_imports'; +import { PhaseWithAllocation } from '../../../../../../../../../common/types'; import { DataTierAllocationType } from '../../../../../types'; @@ -89,6 +90,57 @@ const i18nTexts = { }, }; +const getSelectOptions = (phase: PhaseWithAllocation, disableDataTierOption: boolean) => + [ + disableDataTierOption + ? undefined + : { + 'data-test-subj': 'defaultDataAllocationOption', + value: 'node_roles', + inputDisplay: i18nTexts.allocationOptions[phase].default.input, + dropdownDisplay: ( + <> + {i18nTexts.allocationOptions[phase].default.input} + +

+ {i18nTexts.allocationOptions[phase].default.helpText} +

+
+ + ), + }, + { + 'data-test-subj': 'customDataAllocationOption', + value: 'node_attrs', + inputDisplay: i18nTexts.allocationOptions[phase].custom.inputDisplay, + dropdownDisplay: ( + <> + {i18nTexts.allocationOptions[phase].custom.inputDisplay} + +

+ {i18nTexts.allocationOptions[phase].custom.helpText} +

+
+ + ), + }, + { + 'data-test-subj': 'noneDataAllocationOption', + value: 'none', + inputDisplay: i18nTexts.allocationOptions[phase].none.inputDisplay, + dropdownDisplay: ( + <> + {i18nTexts.allocationOptions[phase].none.inputDisplay} + +

+ {i18nTexts.allocationOptions[phase].none.helpText} +

+
+ + ), + }, + ].filter(Boolean) as SelectOptions[]; + export const DataTierAllocation: FunctionComponent = (props) => { const { phase, hasNodeAttributes, disableDataTierOption } = props; @@ -120,55 +172,7 @@ export const DataTierAllocation: FunctionComponent = (props) => { euiFieldProps={{ hasDividers: true, 'data-test-subj': 'dataTierSelect', - options: [ - disableDataTierOption - ? undefined - : { - 'data-test-subj': 'defaultDataAllocationOption', - value: 'node_roles', - inputDisplay: i18nTexts.allocationOptions[phase].default.input, - dropdownDisplay: ( - <> - {i18nTexts.allocationOptions[phase].default.input} - -

- {i18nTexts.allocationOptions[phase].default.helpText} -

-
- - ), - }, - { - 'data-test-subj': 'customDataAllocationOption', - value: 'node_attrs', - inputDisplay: i18nTexts.allocationOptions[phase].custom.inputDisplay, - dropdownDisplay: ( - <> - {i18nTexts.allocationOptions[phase].custom.inputDisplay} - -

- {i18nTexts.allocationOptions[phase].custom.helpText} -

-
- - ), - }, - { - 'data-test-subj': 'noneDataAllocationOption', - value: 'none', - inputDisplay: i18nTexts.allocationOptions[phase].none.inputDisplay, - dropdownDisplay: ( - <> - {i18nTexts.allocationOptions[phase].none.inputDisplay} - -

- {i18nTexts.allocationOptions[phase].none.helpText} -

-
- - ), - }, - ].filter(Boolean) as SelectOptions[], + options: getSelectOptions(phase, disableDataTierOption), }} /> ); From 96e45ae3acc33ed794a03c8ca99a5940aab1cadf Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 26 Oct 2020 15:45:01 +0100 Subject: [PATCH 15/19] updated data tier callout logic after new changes --- .../data_tier_allocation_field.tsx | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx index bafc48e6e4eeed..73814537ff2764 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/data_tier_allocation_field.tsx @@ -18,6 +18,8 @@ import { getAvailableNodeRoleForPhase } from '../../../../../../lib/data_tiers'; import { isNodeRoleFirstPreference } from '../../../../../../lib'; +import { DataTierAllocationType } from '../../../../types'; + import { DataTierAllocation, DefaultAllocationNotice, @@ -47,31 +49,35 @@ export const DataTierAllocationField: FunctionComponent = ({ phase, descr const dataTierAllocationTypePath = `_meta.${phase}.dataTierAllocationType`; const [formData] = useFormData({ watch: dataTierAllocationTypePath }); - const allocationType = get(formData, dataTierAllocationTypePath); + const allocationType: DataTierAllocationType = get(formData, dataTierAllocationTypePath); return ( {({ nodesByRoles, nodesByAttributes, isUsingDeprecatedDataRoleConfig }) => { + const hasDataNodeRoles = Object.keys(nodesByRoles).some((nodeRole) => + // match any of the "data_" roles, including data_content. + nodeRole.trim().startsWith('data_') + ); const hasNodeAttrs = Boolean(Object.keys(nodesByAttributes ?? {}).length); + const isCloudEnabled = cloud?.isCloudEnabled ?? false; const renderNotice = () => { switch (allocationType) { case 'node_roles': - const isCloudEnabled = cloud?.isCloudEnabled ?? false; - const isUsingNodeRoles = !isUsingDeprecatedDataRoleConfig; - if ( - isCloudEnabled && - isUsingNodeRoles && - phase === 'cold' && - !nodesByRoles.data_cold?.length - ) { - // Tell cloud users they can deploy cold tier nodes. - return ( - <> - - - - ); + if (isCloudEnabled && phase === 'cold') { + const isUsingNodeRolesAllocation = + !isUsingDeprecatedDataRoleConfig && hasDataNodeRoles; + const hasNoNodesWithNodeRole = !nodesByRoles.data_cold?.length; + + if (isUsingNodeRolesAllocation && hasNoNodesWithNodeRole) { + // Tell cloud users they can deploy nodes on cloud. + return ( + <> + + + + ); + } } const allocationNodeRole = getAvailableNodeRoleForPhase(phase, nodesByRoles); @@ -114,9 +120,9 @@ export const DataTierAllocationField: FunctionComponent = ({ phase, descr hasNodeAttributes={hasNodeAttrs} phase={phase} nodes={nodesByAttributes} - disableDataTierOption={ - !!(isUsingDeprecatedDataRoleConfig && cloud?.isCloudEnabled) - } + disableDataTierOption={Boolean( + isCloudEnabled && !hasDataNodeRoles && isUsingDeprecatedDataRoleConfig + )} /> {/* Data tier related warnings and call-to-action notices */} From cb03877d27fc9fb3943cdf9165cc364d6db07105 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 26 Oct 2020 15:47:14 +0100 Subject: [PATCH 16/19] getPolicy -> updatePolicy Also rather deconstruct the validate fn from the form object --- .../edit_policy/components/policy_json_flyout.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx index 4852aecf621f76..9ed24355ce7b36 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/policy_json_flyout.tsx @@ -45,12 +45,12 @@ export const PolicyJsonFlyout: React.FunctionComponent = ({ */ const [policy, setPolicy] = useState(undefined); - const form = useFormContext(); + const { validate: validateForm } = useFormContext(); const [, getFormData] = useFormData(); - const getPolicy = useCallback(async () => { + const updatePolicy = useCallback(async () => { setPolicy(undefined); - if (await form.validate()) { + if (await validateForm()) { const p = getFormData() as SerializedPolicy; setPolicy({ ...legacyPolicy, @@ -63,11 +63,11 @@ export const PolicyJsonFlyout: React.FunctionComponent = ({ } else { setPolicy(null); } - }, [setPolicy, getFormData, legacyPolicy, form]); + }, [setPolicy, getFormData, legacyPolicy, validateForm]); useEffect(() => { - getPolicy(); - }, [getPolicy]); + updatePolicy(); + }, [updatePolicy]); let content: React.ReactNode; switch (policy) { From 6109d3a1707df74ea9dd9e44c2e5f7a3f0b8fe19 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 26 Oct 2020 16:29:07 +0100 Subject: [PATCH 17/19] fix detection of migrate false and refactor serialization to pure function --- .../common/types/policies.ts | 24 +++-- .../data_tiers/determine_allocation_type.ts | 46 ++++++---- .../sections/edit_policy/serializer.ts | 89 +++++++++---------- .../services/policies/cold_phase.ts | 4 +- 4 files changed, 88 insertions(+), 75 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts index 9e97f982e87b9a..813fcd9c253f1f 100644 --- a/x-pack/plugins/index_lifecycle_management/common/types/policies.ts +++ b/x-pack/plugins/index_lifecycle_management/common/types/policies.ts @@ -35,6 +35,19 @@ export interface SerializedPhase { }; } +export interface MigrateAction { + /** + * If enabled is ever set it will probably only be set to `false` because the default value + * for this is `true`. Rather leave unspecified for true when serialising. + */ + enabled: boolean; +} + +export interface SerializedActionWithAllocation { + allocate?: AllocateAction; + migrate?: MigrateAction; +} + export interface SerializedHotPhase extends SerializedPhase { actions: { rollover?: { @@ -59,7 +72,7 @@ export interface SerializedWarmPhase extends SerializedPhase { set_priority?: { priority: number | null; }; - migrate?: { enabled: boolean }; + migrate?: MigrateAction; }; } @@ -70,7 +83,7 @@ export interface SerializedColdPhase extends SerializedPhase { set_priority?: { priority: number | null; }; - migrate?: { enabled: boolean }; + migrate?: MigrateAction; }; } @@ -92,13 +105,6 @@ export interface AllocateAction { require?: { [attribute: string]: string; }; - migrate?: { - /** - * If enabled is ever set it will only be set to `false` because the default value - * for this is `true`. Rather leave unspecified for true. - */ - enabled: false; - }; } export interface ForcemergeAction { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/determine_allocation_type.ts b/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/determine_allocation_type.ts index 099893a9f04011..20ac439e9964f5 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/determine_allocation_type.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/lib/data_tiers/determine_allocation_type.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { DataTierAllocationType, AllocateAction } from '../../../../common/types'; +import { DataTierAllocationType, AllocateAction, MigrateAction } from '../../../../common/types'; /** * Determine what deserialized state the policy config represents. @@ -12,20 +12,25 @@ import { DataTierAllocationType, AllocateAction } from '../../../../common/types * See {@DataTierAllocationType} for more information. */ export const determineDataTierAllocationTypeLegacy = ( - allocateAction?: AllocateAction + actions: { + allocate?: AllocateAction; + migrate?: MigrateAction; + } = {} ): DataTierAllocationType => { - if (!allocateAction) { - return 'default'; - } + const { allocate, migrate } = actions; - if (allocateAction.migrate?.enabled === false) { + if (migrate?.enabled === false) { return 'none'; } + if (!allocate) { + return 'default'; + } + if ( - (allocateAction.require && Object.keys(allocateAction.require).length) || - (allocateAction.include && Object.keys(allocateAction.include).length) || - (allocateAction.exclude && Object.keys(allocateAction.exclude).length) + (allocate.require && Object.keys(allocate.require).length) || + (allocate.include && Object.keys(allocate.include).length) || + (allocate.exclude && Object.keys(allocate.exclude).length) ) { return 'custom'; } @@ -33,19 +38,26 @@ export const determineDataTierAllocationTypeLegacy = ( return 'default'; }; -export const determineDataTierAllocationType = (allocateAction?: AllocateAction) => { - if (!allocateAction) { - return 'node_roles'; - } +export const determineDataTierAllocationType = ( + actions: { + allocate?: AllocateAction; + migrate?: MigrateAction; + } = {} +) => { + const { allocate, migrate } = actions; - if (allocateAction.migrate?.enabled === false) { + if (migrate?.enabled === false) { return 'none'; } + if (!allocate) { + return 'node_roles'; + } + if ( - (allocateAction.require && Object.keys(allocateAction.require).length) || - (allocateAction.include && Object.keys(allocateAction.include).length) || - (allocateAction.exclude && Object.keys(allocateAction.exclude).length) + (allocate.require && Object.keys(allocate.require).length) || + (allocate.include && Object.keys(allocate.include).length) || + (allocate.exclude && Object.keys(allocate.exclude).length) ) { return 'node_attrs'; } diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts index 415de1707dd18f..90e81528f5afe6 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/serializer.ts @@ -6,57 +6,54 @@ import { isEmpty } from 'lodash'; -import { AllocateAction, SerializedPhase, SerializedPolicy } from '../../../../common/types'; +import { SerializedPolicy, SerializedActionWithAllocation } from '../../../../common/types'; import { FormInternal, DataAllocationMetaFields } from './types'; import { isNumber } from '../../services/policies/policy_serialization'; -const unsafeSerializePhaseWithAllocation = ( - dataAllocationMetaFields: DataAllocationMetaFields, - actions: SerializedPhase['actions'] = {}, - originalAllocation: AllocateAction = {} -) => { - if (dataAllocationMetaFields.dataTierAllocationType === 'node_attrs') { - if (dataAllocationMetaFields.allocationNodeAttribute) { - const [name, value] = dataAllocationMetaFields.allocationNodeAttribute.split(':'); - actions.allocate = { - // copy over any other allocate details like "number_of_replicas" - ...actions.allocate, - require: { - [name]: value, - }, - }; - } +const serializeAllocateAction = ( + { dataTierAllocationType, allocationNodeAttribute }: DataAllocationMetaFields, + newActions: SerializedActionWithAllocation = {}, + originalActions: SerializedActionWithAllocation = {} +): SerializedActionWithAllocation => { + const { allocate, migrate, ...rest } = newActions; + // First copy over all non-allocate and migrate actions. + const actions: SerializedActionWithAllocation = { allocate, migrate, ...rest }; + + switch (dataTierAllocationType) { + case 'node_attrs': + if (allocationNodeAttribute) { + const [name, value] = allocationNodeAttribute.split(':'); + actions.allocate = { + // copy over any other allocate details like "number_of_replicas" + ...actions.allocate, + require: { + [name]: value, + }, + }; + } - // copy over the original include and exclude values until we can set them in the form. - if (!isEmpty(originalAllocation.include)) { - actions.allocate = { - ...actions.allocate, - include: { ...originalAllocation.include }, - }; - } + // copy over the original include and exclude values until we can set them in the form. + if (!isEmpty(originalActions?.allocate?.include)) { + actions.allocate = { + ...actions.allocate, + include: { ...originalActions?.allocate?.include }, + }; + } - if (!isEmpty(originalAllocation.exclude)) { - actions.allocate = { - ...actions.allocate, - exclude: { ...originalAllocation.exclude }, - }; - } - } else if (dataAllocationMetaFields.dataTierAllocationType === 'none') { - actions.migrate = { enabled: false }; - if (actions.allocate) { - delete actions.allocate.require; - delete actions.allocate.include; - delete actions.allocate.exclude; - } - } else if (dataAllocationMetaFields.dataTierAllocationType === 'node_roles') { - if (actions.allocate) { - delete actions.allocate.require; - delete actions.allocate.include; - delete actions.allocate.exclude; - } - delete actions.migrate; + if (!isEmpty(originalActions?.allocate?.exclude)) { + actions.allocate = { + ...actions.allocate, + exclude: { ...originalActions?.allocate?.exclude }, + }; + } + break; + case 'none': + actions.migrate = { enabled: false }; + break; + default: } + return actions; }; export const createSerializer = (originalPolicy?: SerializedPolicy) => ( @@ -109,10 +106,10 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( policy.phases.warm.min_age = `${policy.phases.warm.min_age}${_meta.warm.minAgeUnit}`; } - unsafeSerializePhaseWithAllocation( + policy.phases.warm.actions = serializeAllocateAction( _meta.warm, policy.phases.warm.actions, - originalPolicy?.phases.warm?.actions.allocate + originalPolicy?.phases.warm?.actions ); if ( 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 89b4743a995694..e19107e7c9307d 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 @@ -36,9 +36,7 @@ export const coldPhaseFromES = (phaseSerialized?: SerializedColdPhase): ColdPhas phase.phaseEnabled = true; if (phaseSerialized.actions.allocate) { - phase.dataTierAllocationType = determineDataTierAllocationTypeLegacy( - phaseSerialized.actions.allocate - ); + phase.dataTierAllocationType = determineDataTierAllocationTypeLegacy(phaseSerialized.actions); } if (phaseSerialized.min_age) { From 750992e3c4577b3229a9d8f8a43afed9acfbbbee Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 26 Oct 2020 16:32:25 +0100 Subject: [PATCH 18/19] fix type issue --- .../components/default_allocation_notice.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx index 9f7b37d49594d1..f1f8fef62f1f87 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared/data_tier_allocation_field/components/default_allocation_notice.tsx @@ -8,11 +8,11 @@ import { i18n } from '@kbn/i18n'; import React, { FunctionComponent } from 'react'; import { EuiCallOut } from '@elastic/eui'; -import { PhaseWithAllocation, NodeDataRole } from '../../../../../../../../../common/types'; +import { PhaseWithAllocation, DataTierRole } from '../../../../../../../../../common/types'; import { AllocationNodeRole } from '../../../../../../../lib'; -const i18nTextsNodeRoleToDataTier: Record = { +const i18nTextsNodeRoleToDataTier: Record = { data_hot: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.dataTierHotLabel', { defaultMessage: 'hot', }), @@ -31,7 +31,7 @@ const i18nTexts = { 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm.title', { defaultMessage: 'No nodes assigned to the warm tier' } ), - body: (nodeRole: NodeDataRole) => + body: (nodeRole: DataTierRole) => i18n.translate('xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm', { defaultMessage: 'This policy will move data in the warm phase to {tier} tier nodes instead.', @@ -43,7 +43,7 @@ const i18nTexts = { 'xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.cold.title', { defaultMessage: 'No nodes assigned to the cold tier' } ), - body: (nodeRole: NodeDataRole) => + body: (nodeRole: DataTierRole) => i18n.translate('xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.cold', { defaultMessage: 'This policy will move data in the cold phase to {tier} tier nodes instead.', From b85f49234817cda9d008a56fe0c671f3b176874c Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 26 Oct 2020 17:54:35 +0100 Subject: [PATCH 19/19] fix for correctly detecting policy data tier type - with jest tests see origin here: https://github.com/elastic/kibana/pull/81642 --- .../edit_policy/constants.ts | 55 +++++++++++++++++++ .../edit_policy/edit_policy.test.ts | 55 +++++++++++++++++++ .../sections/edit_policy/deserializer.ts | 2 +- .../services/policies/cold_phase.ts | 2 +- 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts index bcc5ff53315d72..0a96146339a58b 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/constants.ts @@ -95,3 +95,58 @@ export const DELETE_PHASE_POLICY: PolicyFromES = { }, name: POLICY_NAME, }; + +export const POLICY_WITH_NODE_ATTR_AND_OFF_ALLOCATION: PolicyFromES = { + version: 1, + modified_date: Date.now().toString(), + policy: { + phases: { + hot: { + min_age: '0ms', + actions: { + rollover: { + max_size: '50gb', + }, + }, + }, + warm: { + actions: { + allocate: { + require: {}, + include: { test: '123' }, + exclude: {}, + }, + }, + }, + cold: { + actions: { + migrate: { enabled: false }, + }, + }, + }, + name: POLICY_NAME, + }, + name: POLICY_NAME, +}; + +export const POLICY_WITH_NODE_ROLE_ALLOCATION: PolicyFromES = { + version: 1, + modified_date: Date.now().toString(), + policy: { + phases: { + hot: { + min_age: '0ms', + actions: { + rollover: { + max_size: '50gb', + }, + }, + }, + warm: { + actions: {}, + }, + }, + name: POLICY_NAME, + }, + name: POLICY_NAME, +}; diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index d7fcbc6fe1ad21..fccffde3f793f2 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -16,6 +16,8 @@ import { SNAPSHOT_POLICY_NAME, DEFAULT_POLICY, POLICY_WITH_INCLUDE_EXCLUDE, + POLICY_WITH_NODE_ATTR_AND_OFF_ALLOCATION, + POLICY_WITH_NODE_ROLE_ALLOCATION, } from './constants'; window.scrollTo = jest.fn(); @@ -381,4 +383,57 @@ describe('', () => { expect(testBed.find('policiesErrorCallout').exists()).toBeTruthy(); }); }); + + describe('data allocation', () => { + describe('node roles', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([POLICY_WITH_NODE_ROLE_ALLOCATION]); + httpRequestsMockHelpers.setListNodes({ + isUsingDeprecatedDataRoleConfig: false, + nodesByAttributes: { test: ['123'] }, + nodesByRoles: { data: ['123'] }, + }); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + test('showing "default" type', () => { + const { find } = testBed; + expect(find('warm-dataTierAllocationControls.dataTierSelect').text()).toContain( + 'recommended' + ); + expect(find('warm-dataTierAllocationControls.dataTierSelect').text()).not.toContain( + 'Custom' + ); + expect(find('warm-dataTierAllocationControls.dataTierSelect').text()).not.toContain('Off'); + }); + }); + describe('node attr and none', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([POLICY_WITH_NODE_ATTR_AND_OFF_ALLOCATION]); + httpRequestsMockHelpers.setListNodes({ + isUsingDeprecatedDataRoleConfig: false, + nodesByAttributes: { test: ['123'] }, + nodesByRoles: { data: ['123'] }, + }); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + + test('showing "custom" and "off" types', () => { + const { find } = testBed; + expect(find('warm-dataTierAllocationControls.dataTierSelect').text()).toContain('Custom'); + expect(find('cold-dataTierAllocationControls.dataTierSelect').text()).toContain('Off'); + }); + }); + }); }); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts index 9337b87d995e6d..760c6ad713ea04 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/deserializer.ts @@ -26,7 +26,7 @@ export const deserializer = (policy: SerializedPolicy): FormInternal => { warmPhaseOnRollover: Boolean(policy.phases.warm?.min_age === '0ms'), forceMergeEnabled: Boolean(policy.phases.warm?.actions?.forcemerge), bestCompression: policy.phases.warm?.actions?.forcemerge?.index_codec === 'best_compression', - dataTierAllocationType: determineDataTierAllocationType(policy.phases.warm?.actions.allocate), + dataTierAllocationType: determineDataTierAllocationType(policy.phases.warm?.actions), }, }; 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 e19107e7c9307d..faf3954f93fd8c 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 @@ -35,7 +35,7 @@ export const coldPhaseFromES = (phaseSerialized?: SerializedColdPhase): ColdPhas phase.phaseEnabled = true; - if (phaseSerialized.actions.allocate) { + if (phaseSerialized.actions) { phase.dataTierAllocationType = determineDataTierAllocationTypeLegacy(phaseSerialized.actions); }