diff --git a/docs/screenshots/lifecycle-rule-desktop.svg b/docs/screenshots/lifecycle-rule-desktop.svg new file mode 100644 index 00000000..721cd049 --- /dev/null +++ b/docs/screenshots/lifecycle-rule-desktop.svg @@ -0,0 +1,10 @@ + + +Lifecycle + +TypeVersionPrefixTime Cycle (Days)Status +ExpireCurrent VersionNon-current Versionpath1/30 days40 daysEnabled + +ExpireCurrent VersionNon-current Version(empty)10 days20 daysEnabled +Rendered from the issue #8036 lifecycle payload + diff --git a/docs/screenshots/lifecycle-rule-mobile.svg b/docs/screenshots/lifecycle-rule-mobile.svg new file mode 100644 index 00000000..2bdaf8d1 --- /dev/null +++ b/docs/screenshots/lifecycle-rule-mobile.svg @@ -0,0 +1,9 @@ + + +lc-rootExpire · Enabled +Prefix(empty) +Time CycleCurrent Version: 30 DaysNon-current Version: 40 Days +lc-path1Expire · Enabled +Prefixpath1/Time CycleCurrent Version: 10 DaysNon-current Version: 20 Days +Rendered from the issue #8036 lifecycle payload + diff --git a/lib/lifecycle-display.ts b/lib/lifecycle-display.ts index ad1f5e6a..85a4c53a 100644 --- a/lib/lifecycle-display.ts +++ b/lib/lifecycle-display.ts @@ -9,15 +9,14 @@ export interface LifecycleRule { Expiration?: { Days?: number Date?: string | Date - StorageClass?: string ExpiredObjectDeleteMarker?: boolean } - NoncurrentVersionExpiration?: { NoncurrentDays?: number } - Transitions?: Array<{ Days?: number; Date?: string | Date; StorageClass?: string }> - NoncurrentVersionTransitions?: Array<{ + NoncurrentVersionExpiration?: { NoncurrentDays?: number - StorageClass?: string - }> + NewerNoncurrentVersions?: number + } + Transitions?: Array<{ Days?: number; Date?: string | Date; StorageClass?: string }> + NoncurrentVersionTransitions?: Array<{ NoncurrentDays?: number; StorageClass?: string }> } export interface LifecycleAction { @@ -27,27 +26,46 @@ export interface LifecycleAction { date?: string | Date tier?: string deleteMarker?: boolean + newerNoncurrentVersions?: number +} + +function hasExpirationAction(expiration?: LifecycleRule["Expiration"]) { + return ( + expiration?.Days !== undefined || + expiration?.Date !== undefined || + expiration?.ExpiredObjectDeleteMarker !== undefined + ) +} + +function hasNoncurrentExpirationAction(expiration?: LifecycleRule["NoncurrentVersionExpiration"]) { + return ( + expiration?.NoncurrentDays !== undefined || + (expiration?.NewerNoncurrentVersions !== undefined && expiration.NewerNoncurrentVersions > 0) + ) } export function getLifecycleActions(rule: LifecycleRule): LifecycleAction[] { const actions: LifecycleAction[] = [] - if (rule.Expiration) { + if (hasExpirationAction(rule.Expiration)) { actions.push({ type: "Expire", version: "Current Version", - days: rule.Expiration.Days, - date: rule.Expiration.Date, - deleteMarker: rule.Expiration.ExpiredObjectDeleteMarker, + days: rule.Expiration?.Days, + date: rule.Expiration?.Date, + deleteMarker: rule.Expiration?.ExpiredObjectDeleteMarker, }) } - if (rule.NoncurrentVersionExpiration) { + if (hasNoncurrentExpirationAction(rule.NoncurrentVersionExpiration)) { actions.push({ type: "Expire", version: "Non-current Version", - days: rule.NoncurrentVersionExpiration.NoncurrentDays, + days: rule.NoncurrentVersionExpiration?.NoncurrentDays, + newerNoncurrentVersions: rule.NoncurrentVersionExpiration?.NewerNoncurrentVersions, }) } for (const transition of rule.Transitions ?? []) { + if (transition.Days === undefined && transition.Date === undefined && transition.StorageClass === undefined) + continue actions.push({ type: "Transition", version: "Current Version", @@ -57,6 +75,7 @@ export function getLifecycleActions(rule: LifecycleRule): LifecycleAction[] { }) } for (const transition of rule.NoncurrentVersionTransitions ?? []) { + if (transition.NoncurrentDays === undefined && transition.StorageClass === undefined) continue actions.push({ type: "Transition", version: "Non-current Version", diff --git a/tests/lib/lifecycle-display.test.ts b/tests/lib/lifecycle-display.test.ts index 83034b06..3e70a42c 100644 --- a/tests/lib/lifecycle-display.test.ts +++ b/tests/lib/lifecycle-display.test.ts @@ -21,6 +21,24 @@ test("mixed expiration rules retain both version labels and their own days (rust } }) +test("empty expiration containers do not create fake actions", () => { + assert.deepEqual(getLifecycleActions({ Expiration: {} }), []) + assert.deepEqual(getLifecycleActions({ NoncurrentVersionExpiration: {} }), []) + assert.deepEqual(getLifecycleActions({ Transitions: [{}], NoncurrentVersionTransitions: [{}] }), []) +}) + +test("explicit delete-marker false remains an explicit action setting", () => { + assert.deepEqual(getLifecycleActions({ Expiration: { ExpiredObjectDeleteMarker: false } }), [ + { + type: "Expire", + version: "Current Version", + days: undefined, + date: undefined, + deleteMarker: false, + }, + ]) +}) + test("each transition keeps its version, tier and zero-day schedule", () => { assert.deepEqual( getLifecycleActions({