From 1e7bb1ea37e7aa8b1ebb77f22fa34e2f1e3302a0 Mon Sep 17 00:00:00 2001 From: Jusheng Huang <117657272+viajes7@users.noreply.github.com> Date: Thu, 31 Oct 2024 21:52:48 +0800 Subject: [PATCH] [Index Management] Fix globalMaxRetention display error in data retention modal (#198113) ## Summary Fixes #197314 **After fixup:** ![image](https://github.com/user-attachments/assets/9fa66906-fa06-4e12-81d8-7bcdaaff3ff3) ![image](https://github.com/user-attachments/assets/a7b9ee72-4ce3-4d2b-8c14-483ea2028597) --------- Co-authored-by: Elastic Machine --- .../application/lib/data_streams.test.tsx | 34 ++++++++++++++- .../public/application/lib/data_streams.tsx | 16 +++++++ .../edit_data_retention_modal.tsx | 10 +++-- .../validations.test.ts | 20 +++++++++ .../edit_data_retention_modal/validations.ts | 43 +++++++++++++------ 5 files changed, 105 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/index_management/public/application/lib/data_streams.test.tsx b/x-pack/plugins/index_management/public/application/lib/data_streams.test.tsx index 5cb0c461f46ba..da5e7b6591e56 100644 --- a/x-pack/plugins/index_management/public/application/lib/data_streams.test.tsx +++ b/x-pack/plugins/index_management/public/application/lib/data_streams.test.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { getLifecycleValue } from './data_streams'; +import { deserializeGlobalMaxRetention, getLifecycleValue } from './data_streams'; describe('Data stream helpers', () => { describe('getLifecycleValue', () => { @@ -45,4 +45,36 @@ describe('Data stream helpers', () => { ).toBe('5 days'); }); }); + + describe('deserializeGlobalMaxRetention', () => { + it('if globalMaxRetention is undefined', () => { + expect(deserializeGlobalMaxRetention(undefined)).toEqual({}); + }); + + it('split globalMaxRetention size and units', () => { + expect(deserializeGlobalMaxRetention('1000h')).toEqual({ + size: '1000', + unit: 'h', + unitText: 'hours', + }); + }); + + it('support all of the units that are accepted by es', () => { + expect(deserializeGlobalMaxRetention('1000ms')).toEqual({ + size: '1000', + unit: 'ms', + unitText: 'milliseconds', + }); + expect(deserializeGlobalMaxRetention('1000micros')).toEqual({ + size: '1000', + unit: 'micros', + unitText: 'microseconds', + }); + expect(deserializeGlobalMaxRetention('1000nanos')).toEqual({ + size: '1000', + unit: 'nanos', + unitText: 'nanoseconds', + }); + }); + }); }); diff --git a/x-pack/plugins/index_management/public/application/lib/data_streams.tsx b/x-pack/plugins/index_management/public/application/lib/data_streams.tsx index 71c8bb0f61177..6747e84df751d 100644 --- a/x-pack/plugins/index_management/public/application/lib/data_streams.tsx +++ b/x-pack/plugins/index_management/public/application/lib/data_streams.tsx @@ -121,3 +121,19 @@ export const isDSLWithILMIndices = (dataStream?: DataStream | null) => { return; }; + +export const deserializeGlobalMaxRetention = (globalMaxRetention?: string) => { + if (!globalMaxRetention) { + return {}; + } + + const { size, unit } = splitSizeAndUnits(globalMaxRetention); + const availableTimeUnits = [...timeUnits, ...extraTimeUnits]; + const match = availableTimeUnits.find((timeUnit) => timeUnit.value === unit); + + return { + size, + unit, + unitText: match?.text ?? unit, + }; +}; diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx index f5eee4671481a..2c60e04be31a6 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/edit_data_retention_modal.tsx @@ -43,7 +43,7 @@ import { getIndexListUri } from '../../../../services/routing'; import { documentationService } from '../../../../services/documentation'; import { splitSizeAndUnits, DataStream } from '../../../../../../common'; import { timeUnits } from '../../../../constants/time_units'; -import { isDSLWithILMIndices } from '../../../../lib/data_streams'; +import { deserializeGlobalMaxRetention, isDSLWithILMIndices } from '../../../../lib/data_streams'; import { useAppContext } from '../../../../app_context'; import { UnitField } from '../../../../components/shared'; import { updateDataRetention } from '../../../../services/api'; @@ -214,6 +214,7 @@ export const EditDataRetentionModal: React.FunctionComponent = ({ const { history } = useAppContext(); const dslWithIlmIndices = isDSLWithILMIndices(dataStream); const { size, unit } = splitSizeAndUnits(lifecycle?.data_retention as string); + const globalMaxRetention = deserializeGlobalMaxRetention(lifecycle?.globalMaxRetention); const { services: { notificationService }, config: { enableTogglingDataRetention, enableProjectLevelRetentionChecks }, @@ -331,8 +332,11 @@ export const EditDataRetentionModal: React.FunctionComponent = ({ <> diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/validations.test.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/validations.test.ts index 0768d0990cdc0..87cc1d36526fb 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/validations.test.ts +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/validations.test.ts @@ -47,6 +47,26 @@ describe('isBiggerThanGlobalMaxRetention', () => { }); }); + it('should correctly compare retention in all of the units that are accepted by es', () => { + // 1000 milliseconds = 1 seconds + expect(isBiggerThanGlobalMaxRetention(1, 's', '1000ms')).toBeUndefined(); + expect(isBiggerThanGlobalMaxRetention(2, 's', '1000ms')).toEqual({ + message: 'Maximum data retention period on this project is 1000 milliseconds.', + }); + + // 1000000 microseconds = 1 seconds + expect(isBiggerThanGlobalMaxRetention(1, 's', '1000000micros')).toBeUndefined(); + expect(isBiggerThanGlobalMaxRetention(2, 'm', '1000000micros')).toEqual({ + message: 'Maximum data retention period on this project is 1000000 microseconds.', + }); + + // 1000000000 microseconds = 1 seconds + expect(isBiggerThanGlobalMaxRetention(2, 's', '1000000000nanos')); + expect(isBiggerThanGlobalMaxRetention(2, 'h', '1000000000nanos')).toEqual({ + message: 'Maximum data retention period on this project is 1000000000 nanoseconds.', + }); + }); + it('should throw an error for unknown time units', () => { expect(() => isBiggerThanGlobalMaxRetention(10, 'x', '30d')).toThrow('Unknown unit: x'); }); diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/validations.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/validations.ts index 831ac2f4c26b9..8486f01fb5b44 100644 --- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/validations.ts +++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/edit_data_retention_modal/validations.ts @@ -7,34 +7,44 @@ import { i18n } from '@kbn/i18n'; import { splitSizeAndUnits } from '../../../../../../common'; +import { deserializeGlobalMaxRetention } from '../../../../lib/data_streams'; -const convertToMinutes = (value: string) => { +const convertToSeconds = (value: string) => { const { size, unit } = splitSizeAndUnits(value); const sizeNum = parseInt(size, 10); switch (unit) { case 'd': - // days to minutes - return sizeNum * 24 * 60; + // days to seconds + return sizeNum * 24 * 60 * 60; case 'h': - // hours to minutes - return sizeNum * 60; + // hours to seconds + return sizeNum * 60 * 60; case 'm': - // minutes to minutes - return sizeNum; + // minutes to seconds + return sizeNum * 60; case 's': - // seconds to minutes (round up if any remainder) - return Math.ceil(sizeNum / 60); + // seconds to seconds + return sizeNum; + case 'ms': + // milliseconds to seconds + return sizeNum / 1000; + case 'micros': + // microseconds to seconds + return sizeNum / 1000 / 1000; + case 'nanos': + // nanoseconds to seconds + return sizeNum / 1000 / 1000 / 1000; default: throw new Error(`Unknown unit: ${unit}`); } }; const isRetentionBiggerThan = (valueA: string, valueB: string) => { - const minutesA = convertToMinutes(valueA); - const minutesB = convertToMinutes(valueB); + const secondsA = convertToSeconds(valueA); + const secondsB = convertToSeconds(valueB); - return minutesA > minutesB; + return secondsA > secondsB; }; export const isBiggerThanGlobalMaxRetention = ( @@ -46,14 +56,19 @@ export const isBiggerThanGlobalMaxRetention = ( return undefined; } + const { size, unitText } = deserializeGlobalMaxRetention(globalMaxRetention); return isRetentionBiggerThan(`${retentionValue}${retentionTimeUnit}`, globalMaxRetention) ? { message: i18n.translate( 'xpack.idxMgmt.dataStreamsDetailsPanel.editDataRetentionModal.dataRetentionFieldMaxError', { - defaultMessage: 'Maximum data retention period on this project is {maxRetention} days.', + defaultMessage: + 'Maximum data retention period on this project is {maxRetention} {unitText}.', // Remove the unit from the globalMaxRetention value - values: { maxRetention: globalMaxRetention.slice(0, -1) }, + values: { + maxRetention: size, + unitText, + }, } ), }