From da53daff37668eaa9c6615f26b11ea58a02a46d2 Mon Sep 17 00:00:00 2001 From: jinapurapu <65002498+jinapurapu@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:34:04 -0800 Subject: [PATCH] Add UI to select number of non-current versions subject to ILM expiry rule (#3088) --- models/add_bucket_lifecycle.go | 3 + models/expiration_response.go | 3 + portal-ui/src/api/consoleApi.ts | 8 +++ .../BucketDetails/AddLifecycleModal.tsx | 65 ++++++++++++++++--- .../BucketDetails/BucketLifecyclePanel.tsx | 31 ++++++--- .../src/screens/Console/Buckets/types.tsx | 1 + restapi/embedded_spec.go | 20 ++++++ restapi/user_buckets_lifecycle.go | 24 ++++--- swagger.yml | 8 +++ 9 files changed, 135 insertions(+), 28 deletions(-) diff --git a/models/add_bucket_lifecycle.go b/models/add_bucket_lifecycle.go index ff9e93372..6af45297d 100644 --- a/models/add_bucket_lifecycle.go +++ b/models/add_bucket_lifecycle.go @@ -46,6 +46,9 @@ type AddBucketLifecycle struct { // Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM ExpiryDays int32 `json:"expiry_days,omitempty"` + // Non required, can be set in case of expiration is enabled + NewerNoncurrentversionExpirationVersions int32 `json:"newer_noncurrentversion_expiration_versions,omitempty"` + // Non required, can be set in case of expiration is enabled NoncurrentversionExpirationDays int32 `json:"noncurrentversion_expiration_days,omitempty"` diff --git a/models/expiration_response.go b/models/expiration_response.go index e4c42a87c..961ad3def 100644 --- a/models/expiration_response.go +++ b/models/expiration_response.go @@ -43,6 +43,9 @@ type ExpirationResponse struct { // delete marker DeleteMarker bool `json:"delete_marker,omitempty"` + // newer noncurrent expiration versions + NewerNoncurrentExpirationVersions int64 `json:"newer_noncurrent_expiration_versions,omitempty"` + // noncurrent expiration days NoncurrentExpirationDays int64 `json:"noncurrent_expiration_days,omitempty"` } diff --git a/portal-ui/src/api/consoleApi.ts b/portal-ui/src/api/consoleApi.ts index 8a1514552..ce0fa3a4e 100644 --- a/portal-ui/src/api/consoleApi.ts +++ b/portal-ui/src/api/consoleApi.ts @@ -880,6 +880,8 @@ export interface ExpirationResponse { delete_marker?: boolean; /** @format int64 */ noncurrent_expiration_days?: number; + /** @format int64 */ + newer_noncurrent_expiration_versions?: number; } export interface TransitionResponse { @@ -943,6 +945,12 @@ export interface AddBucketLifecycle { * @default 0 */ noncurrentversion_transition_days?: number; + /** + * Non required, can be set in case of expiration is enabled + * @format int32 + * @default 0 + */ + newer_noncurrentversion_expiration_versions?: number; /** Non required, can be set in case of transition is enabled */ noncurrentversion_transition_storage_class?: string; } diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/AddLifecycleModal.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/AddLifecycleModal.tsx index ac4070237..ba1e90d7c 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/AddLifecycleModal.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/AddLifecycleModal.tsx @@ -19,9 +19,11 @@ import React, { Fragment, useEffect, useState } from "react"; import get from "lodash/get"; import { Accordion, + AlertIcon, Button, FormLayout, Grid, + HelpTip, InputBox, LifecycleConfigIcon, ProgressBar, @@ -74,6 +76,7 @@ const AddLifecycleModal = ({ const [loadingVersioning, setLoadingVersioning] = useState(true); const [expandedAdv, setExpandedAdv] = useState(false); const [expanded, setExpanded] = useState(false); + const [expiryUnit, setExpiryUnit] = useState("days"); /*To be removed on component replacement*/ const formFieldRowFilter = { @@ -116,6 +119,13 @@ const AddLifecycleModal = ({ valid = false; } } + if (!lifecycleDays || parseInt(lifecycleDays) === 0) { + valid = false; + } + if (parseInt(lifecycleDays) > 2147483647) { + //values over int32 cannot be parsed + valid = false; + } setIsFormValid(valid); }, [ilmType, lifecycleDays, storageClass]); @@ -142,8 +152,11 @@ const AddLifecycleModal = ({ if (targetVersion === "current") { expiry["expiry_days"] = parseInt(lifecycleDays); - } else { + } else if (expiryUnit === "days") { expiry["noncurrentversion_expiration_days"] = parseInt(lifecycleDays); + } else { + expiry["newer_noncurrentversion_expiration_versions"] = + parseInt(lifecycleDays); } rules = { @@ -154,7 +167,7 @@ const AddLifecycleModal = ({ if (targetVersion === "current") { transition["transition_days"] = parseInt(lifecycleDays); transition["storage_class"] = storageClass; - } else { + } else if (expiryUnit === "days") { transition["noncurrentversion_transition_days"] = parseInt(lifecycleDays); transition["noncurrentversion_transition_storage_class"] = storageClass; @@ -184,7 +197,6 @@ const AddLifecycleModal = ({ dispatch(setModalErrorSnackMessage(errorToHandler(err))); }); }; - return ( 2147483647 + ? `Number of ${expiryUnit} must be less than or equal to 2147483647` + : "" + : "" + } id="expiry_days" name="expiry_days" onChange={(e: React.ChangeEvent) => { @@ -295,12 +316,38 @@ const AddLifecycleModal = ({ label="After" value={lifecycleDays} overlayObject={ - + + + { + setExpiryUnit(newValue); + }} + /> + {ilmType === "expiry" && targetVersion === "noncurrent" && ( + + Select to set expiry by days or newer noncurrent + versions + + } + placement="right" + > + {" "} + + + )} + + } /> diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx index a7652b924..3b931f6de 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/BucketLifecyclePanel.tsx @@ -86,7 +86,6 @@ const BucketLifecyclePanel = () => { .getBucketLifecycle(bucketName) .then((res) => { const records = get(res.data, "lifecycle", []); - setLifecycleRecords(records || []); setLoadingLifecycle(false); }) @@ -147,7 +146,10 @@ const BucketLifecyclePanel = () => { } if ( el.expiration && - (el.expiration.days > 0 || el.expiration.noncurrent_expiration_days) + (el.expiration.days > 0 || + el.expiration.noncurrent_expiration_days || + (el.expiration.newer_noncurrent_expiration_versions && + el.expiration.newer_noncurrent_expiration_versions > 0)) ) { return Expiry; } @@ -170,7 +172,10 @@ const BucketLifecyclePanel = () => { if (el.expiration) { if (el.expiration.days > 0) { return Current; - } else if (el.expiration.noncurrent_expiration_days) { + } else if ( + el.expiration.noncurrent_expiration_days || + el.expiration.newer_noncurrent_expiration_versions + ) { return Non-Current; } } @@ -200,13 +205,6 @@ const BucketLifecyclePanel = () => { if (!el) { return ; } - if (el.expiration) { - if (el.expiration.days > 0) { - return {el.expiration.days} days; - } else if (el.expiration.noncurrent_expiration_days) { - return {el.expiration.noncurrent_expiration_days} days; - } - } if (el.transition) { if (el.transition.days > 0) { return {el.transition.days} days; @@ -214,6 +212,19 @@ const BucketLifecyclePanel = () => { return {el.transition.noncurrent_transition_days} days; } } + if (el.expiration) { + if (el.expiration.days > 0) { + return {el.expiration.days} days; + } else if (el.expiration.noncurrent_expiration_days) { + return {el.expiration.noncurrent_expiration_days} days; + } else { + return ( + + {el.expiration.newer_noncurrent_expiration_versions} versions + + ); + } + } }, }, { diff --git a/portal-ui/src/screens/Console/Buckets/types.tsx b/portal-ui/src/screens/Console/Buckets/types.tsx index 977e01e2d..0a2584602 100644 --- a/portal-ui/src/screens/Console/Buckets/types.tsx +++ b/portal-ui/src/screens/Console/Buckets/types.tsx @@ -42,6 +42,7 @@ interface IExpirationLifecycle { date: string; delete_marker?: boolean; noncurrent_expiration_days?: number; + newer_noncurrent_expiration_versions?: number; } interface ITransitionLifecycle { diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 487ea7ef7..86928ec80 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -5385,6 +5385,12 @@ func init() { "format": "int32", "default": 0 }, + "newer_noncurrentversion_expiration_versions": { + "description": "Non required, can be set in case of expiration is enabled", + "type": "integer", + "format": "int32", + "default": 0 + }, "noncurrentversion_expiration_days": { "description": "Non required, can be set in case of expiration is enabled", "type": "integer", @@ -6190,6 +6196,10 @@ func init() { "delete_marker": { "type": "boolean" }, + "newer_noncurrent_expiration_versions": { + "type": "integer", + "format": "int64" + }, "noncurrent_expiration_days": { "type": "integer", "format": "int64" @@ -14656,6 +14666,12 @@ func init() { "format": "int32", "default": 0 }, + "newer_noncurrentversion_expiration_versions": { + "description": "Non required, can be set in case of expiration is enabled", + "type": "integer", + "format": "int32", + "default": 0 + }, "noncurrentversion_expiration_days": { "description": "Non required, can be set in case of expiration is enabled", "type": "integer", @@ -15456,6 +15472,10 @@ func init() { "delete_marker": { "type": "boolean" }, + "newer_noncurrent_expiration_versions": { + "type": "integer", + "format": "int64" + }, "noncurrent_expiration_days": { "type": "integer", "format": "int64" diff --git a/restapi/user_buckets_lifecycle.go b/restapi/user_buckets_lifecycle.go index 66f399efc..e6737e631 100644 --- a/restapi/user_buckets_lifecycle.go +++ b/restapi/user_buckets_lifecycle.go @@ -90,7 +90,6 @@ func getBucketLifecycle(ctx context.Context, client MinioClient, bucketName stri if err != nil { return nil, err } - var rules []*models.ObjectBucketLifecycle for _, rule := range lifecycleList.Rules { @@ -115,10 +114,11 @@ func getBucketLifecycle(ctx context.Context, client MinioClient, bucketName stri Status: rule.Status, Prefix: rulePrefix, Expiration: &models.ExpirationResponse{ - Date: rule.Expiration.Date.Format(time.RFC3339), - Days: int64(rule.Expiration.Days), - DeleteMarker: rule.Expiration.DeleteMarker.IsEnabled(), - NoncurrentExpirationDays: int64(rule.NoncurrentVersionExpiration.NoncurrentDays), + Date: rule.Expiration.Date.Format(time.RFC3339), + Days: int64(rule.Expiration.Days), + DeleteMarker: rule.Expiration.DeleteMarker.IsEnabled(), + NoncurrentExpirationDays: int64(rule.NoncurrentVersionExpiration.NoncurrentDays), + NewerNoncurrentExpirationVersions: int64(rule.NoncurrentVersionExpiration.NewerNoncurrentVersions), }, Transition: &models.TransitionResponse{ Date: rule.Transition.Date.Format(time.RFC3339), @@ -178,7 +178,7 @@ func addBucketLifecycle(ctx context.Context, client MinioClient, params bucketAp switch params.Body.Type { case models.AddBucketLifecycleTypeTransition: if params.Body.TransitionDays == 0 && params.Body.NoncurrentversionTransitionDays == 0 { - return errors.New("only one expiry configuration can be set (days or date)") + return errors.New("you must provide a value for transition days or date") } status := !params.Body.Disable @@ -195,12 +195,13 @@ func addBucketLifecycle(ctx context.Context, client MinioClient, params bucketAp noncurrentVersionTransitionStorageClass := strings.ToUpper(params.Body.NoncurrentversionTransitionStorageClass) opts.NoncurrentVersionTransitionDays = &noncurrentVersionTransitionDays opts.NoncurrentVersionTransitionStorageClass = &noncurrentVersionTransitionStorageClass - } else { + } else if params.Body.TransitionDays > 0 { tdays := strconv.Itoa(int(params.Body.TransitionDays)) sclass := strings.ToUpper(params.Body.StorageClass) opts.TransitionDays = &tdays opts.StorageClass = &sclass } + case models.AddBucketLifecycleTypeExpiry: // Verify if expiry items are set if params.Body.NoncurrentversionTransitionDays != 0 { @@ -220,10 +221,15 @@ func addBucketLifecycle(ctx context.Context, client MinioClient, params bucketAp ExpiredObjectDeleteMarker: ¶ms.Body.ExpiredObjectDeleteMarker, } - if params.Body.NoncurrentversionExpirationDays > 0 { + if params.Body.NewerNoncurrentversionExpirationVersions > 0 { + versions := int(params.Body.NewerNoncurrentversionExpirationVersions) + opts.NewerNoncurrentExpirationVersions = &versions + } + switch { + case params.Body.NoncurrentversionExpirationDays > 0: days := int(params.Body.NoncurrentversionExpirationDays) opts.NoncurrentVersionExpirationDays = &days - } else { + case params.Body.ExpiryDays > 0: days := strconv.Itoa(int(params.Body.ExpiryDays)) opts.ExpiryDays = &days } diff --git a/swagger.yml b/swagger.yml index ee5786fe7..0922f7da3 100644 --- a/swagger.yml +++ b/swagger.yml @@ -5067,6 +5067,9 @@ definitions: noncurrent_expiration_days: type: integer format: int64 + newer_noncurrent_expiration_versions: + type: integer + format: int64 transitionResponse: type: object @@ -5154,6 +5157,11 @@ definitions: type: integer format: int32 default: 0 + newer_noncurrentversion_expiration_versions: + description: Non required, can be set in case of expiration is enabled + type: integer + format: int32 + default: 0 noncurrentversion_transition_storage_class: description: Non required, can be set in case of transition is enabled type: string