Skip to content

Conversation

@AkdM
Copy link
Contributor

@AkdM AkdM commented Nov 21, 2025

📝 Description

This PR adds a new Object Lock feature options to storage buckets, allowing users to configure retention policies with granular control over mode and duration.

✨ Features

Object Lock

  • Retention Toggle: Enable/disable object retention
  • Retention Mode Selection:
    • Governance mode: Allows users with specific IAM permissions to override retention
    • Compliance mode: No user can override retention (includes warning alert)
  • Duration Configuration:
    • Number input with unit selector (Days/Years)
    • Smart validation with max limits (100 years or 36,500 days)
    • ISO 8601 duration format conversion (e.g., P1Y, P30D)
    • (WIP) for each object, UI should display a "locked until" date (similar to Cold archive) and locked until date = object version creation date + retention period

Comment on lines 17 to 45
<>
<div className="space-y-2">
<div className="flex flex-row justify-between">
<Badge
variant={
s3.objectLock.status === storages.ObjectLockStatusEnum.enabled
? 'success'
: 'warning'
}
>
{t(`${prefix}Label`)}
</Badge>
{isObjectLockEnabled && (
<Button
data-testid="label-object-lock-options-button"
mode="outline"
size="sm"
className="h-6"
onClick={() => navigate('./object-lock-options')}
>
<Settings className="size-4" />
<span className="font-semibold">
{t('objectLockOptionsButton')}
</span>
</Button>
)}
</div>
</div>
</div>
</>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need a fragment here as you already have a single element (your div)

Suggested change
<>
<div className="space-y-2">
<div className="flex flex-row justify-between">
<Badge
variant={
s3.objectLock.status === storages.ObjectLockStatusEnum.enabled
? 'success'
: 'warning'
}
>
{t(`${prefix}Label`)}
</Badge>
{isObjectLockEnabled && (
<Button
data-testid="label-object-lock-options-button"
mode="outline"
size="sm"
className="h-6"
onClick={() => navigate('./object-lock-options')}
>
<Settings className="size-4" />
<span className="font-semibold">
{t('objectLockOptionsButton')}
</span>
</Button>
)}
</div>
</div>
</div>
</>
<div className="space-y-2">
<div className="flex flex-row justify-between">
<Badge
variant={
s3.objectLock.status === storages.ObjectLockStatusEnum.enabled
? 'success'
: 'warning'
}
>
{t(`${prefix}Label`)}
</Badge>
{isObjectLockEnabled && (
<Button
data-testid="label-object-lock-options-button"
mode="outline"
size="sm"
className="h-6"
onClick={() => navigate('./object-lock-options')}
>
<Settings className="size-4" />
<span className="font-semibold">
{t('objectLockOptionsButton')}
</span>
</Button>
)}
</div>
</div>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use zod and react-hook-forms to handle every form in the app. Could you use it here also ? It would ease validation and complex rules, as they can be handled by the schema:

const objectLockSchema = z.object({
    status: z.nativeEnum(ObjectLockStatusEnum),
    retentionEnabled: z.boolean(),
    rule: z
      .object({
        mode: z.nativeEnum(ObjectLockModeEnum),
        durationValue: z
          .number()
          .min(1, t("errorDurationMin")),
        durationUnit: z.enum(["D", "Y"]),
      })
      .optional(),
  })
  .superRefine((data, ctx) => {
    if (data.retentionEnabled && !data.rule) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: t("errorRuleRequired"),
        path: ["rule"],
      });
      return; 
    }

    if (!data.retentionEnabled && data.rule) {
      ctx.addIssue({
        code: z.ZodIssueCode.custom,
        message: t("errorRuleMustBeEmpty"),
        path: ["rule"],
      });
      return;
    }

    if (data.rule) {
      const { durationValue, durationUnit } = data.rule;
      const max = durationUnit === "D" ? durationLimits.D  : durationLimits.Y;

      if (durationValue > max) {
        ctx.addIssue({
        code: z.ZodIssueCode.custom,
          message: t("errorDurationMax"),
          path: ["rule", "durationValue"],
        });
      }
    }
  })
const form = useForm({
  resolver: zodResolver(objectLockSchema),
  defaultValues: {
    status: s3Query.data?.objectLock?.status,
    rule: s3Query.data?.objectLock?.rule ?? undefined,
  },
});

const onSubmit = form.handleSubmit((values) => {
...
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants