[WEB-3918] fix: api tokens is_active (#6941)

* fix: is_active always returning true
chore: formate expired_at to iso date

* Display exact expiration timestamp for API tokens

* chore: remove conversion to iso

* chore: remove unwanted imports

* fix: added timestamp for api token expiry

* fix: handle none value in expired_at

* fix: fix: handle none value in expired_at

* chore: add type hints

* fix: refactor

---------

Co-authored-by: Alaaeddine bousselmi <alaaeddine.bousselmi@medtech.tn>
Co-authored-by: gakshita <akshitagoyal1516@gmail.com>
Co-authored-by: Akshita Goyal <36129505+gakshita@users.noreply.github.com>
This commit is contained in:
Sangeetha 2025-04-24 01:28:29 +05:30 committed by GitHub
parent b5ceb94fb2
commit 2bbaaed3ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 37 additions and 22 deletions

View file

@ -1,5 +1,7 @@
from .base import BaseSerializer from .base import BaseSerializer
from plane.db.models import APIToken, APIActivityLog from plane.db.models import APIToken, APIActivityLog
from rest_framework import serializers
from django.utils import timezone
class APITokenSerializer(BaseSerializer): class APITokenSerializer(BaseSerializer):
@ -17,10 +19,17 @@ class APITokenSerializer(BaseSerializer):
class APITokenReadSerializer(BaseSerializer): class APITokenReadSerializer(BaseSerializer):
is_active = serializers.SerializerMethodField()
class Meta: class Meta:
model = APIToken model = APIToken
exclude = ("token",) exclude = ("token",)
def get_is_active(self, obj: APIToken) -> bool:
if obj.expired_at is None:
return True
return timezone.now() < obj.expired_at
class APIActivityLogSerializer(BaseSerializer): class APIActivityLogSerializer(BaseSerializer):
class Meta: class Meta:

View file

@ -46,7 +46,7 @@ export const CreateApiTokenModal: React.FC<Props> = (props) => {
const csvData = { const csvData = {
Title: data.label, Title: data.label,
Description: data.description, Description: data.description,
Expiry: data.expired_at ? renderFormattedDate(data.expired_at)?.replace(",", " ") ?? "" : "Never expires", Expiry: data.expired_at ? (renderFormattedDate(data.expired_at)?.replace(",", " ") ?? "") : "Never expires",
"Secret key": data.token ?? "", "Secret key": data.token ?? "",
}; };

View file

@ -13,7 +13,7 @@ import { Button, CustomSelect, Input, TextArea, ToggleSwitch, TOAST_TYPE, setToa
import { DateDropdown } from "@/components/dropdowns"; import { DateDropdown } from "@/components/dropdowns";
// helpers // helpers
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
import { renderFormattedDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; import { renderFormattedDate, renderFormattedTime } from "@/helpers/date-time.helper";
type Props = { type Props = {
handleClose: () => void; handleClose: () => void;
@ -51,20 +51,21 @@ const defaultValues: Partial<IApiToken> = {
expired_at: null, expired_at: null,
}; };
const getExpiryDate = (val: string): string | null | undefined => { const getExpiryDate = (val: string): Date | null | undefined => {
const today = new Date(); const today = new Date();
const dateToAdd = EXPIRY_DATE_OPTIONS.find((option) => option.key === val)?.value; const dateToAdd = EXPIRY_DATE_OPTIONS.find((option) => option.key === val)?.value;
if (dateToAdd) return add(today, dateToAdd);
if (dateToAdd) {
const expiryDate = add(today, dateToAdd);
return renderFormattedDate(expiryDate);
}
return null; return null;
}; };
const getFormattedDate = (date: Date): Date => {
const now = new Date();
const hours = now.getHours();
const minutes = now.getMinutes();
const seconds = now.getSeconds();
return add(date, { hours, minutes, seconds });
};
export const CreateApiTokenForm: React.FC<Props> = (props) => { export const CreateApiTokenForm: React.FC<Props> = (props) => {
const { handleClose, neverExpires, toggleNeverExpires, onSubmit } = props; const { handleClose, neverExpires, toggleNeverExpires, onSubmit } = props;
// states // states
@ -97,12 +98,13 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
// if never expires is toggled on, set expired_at to null // if never expires is toggled on, set expired_at to null
if (neverExpires) payload.expired_at = null; if (neverExpires) payload.expired_at = null;
// if never expires is toggled off, and the user has selected a custom date, set expired_at to the custom date // if never expires is toggled off, and the user has selected a custom date, set expired_at to the custom date
else if (data.expired_at === "custom") payload.expired_at = renderFormattedPayloadDate(customDate); else if (data.expired_at === "custom") {
payload.expired_at = customDate && getFormattedDate(customDate).toISOString();
}
// if never expires is toggled off, and the user has selected a predefined date, set expired_at to the predefined date // if never expires is toggled off, and the user has selected a predefined date, set expired_at to the predefined date
else { else {
const expiryDate = getExpiryDate(data.expired_at ?? ""); const expiryDate = getExpiryDate(data.expired_at ?? "");
if (expiryDate) payload.expired_at = expiryDate.toISOString();
if (expiryDate) payload.expired_at = renderFormattedPayloadDate(new Date(expiryDate));
} }
await onSubmit(payload).then(() => { await onSubmit(payload).then(() => {
@ -114,6 +116,8 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
const today = new Date(); const today = new Date();
const tomorrow = add(today, { days: 1 }); const tomorrow = add(today, { days: 1 });
const expiredAt = watch("expired_at"); const expiredAt = watch("expired_at");
const expiryDate = getExpiryDate(expiredAt ?? "");
const customDateFormatted = customDate && getFormattedDate(customDate);
return ( return (
<form onSubmit={handleSubmit(handleFormSubmit)}> <form onSubmit={handleSubmit(handleFormSubmit)}>
@ -219,10 +223,10 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
<span className="text-xs text-custom-text-400"> <span className="text-xs text-custom-text-400">
{expiredAt === "custom" {expiredAt === "custom"
? customDate ? customDate
? `Expires ${renderFormattedDate(customDate)}` ? `Expires ${renderFormattedDate(customDateFormatted ?? "")} at ${renderFormattedTime(customDateFormatted ?? "")}`
: null : null
: expiredAt : expiredAt
? `Expires ${getExpiryDate(expiredAt ?? "")}` ? `Expires ${renderFormattedDate(expiryDate ?? "")} at ${renderFormattedTime(expiryDate ?? "")}`
: null} : null}
</span> </span>
)} )}

View file

@ -6,7 +6,7 @@ import { IApiToken } from "@plane/types";
// ui // ui
import { Button, Tooltip, TOAST_TYPE, setToast } from "@plane/ui"; import { Button, Tooltip, TOAST_TYPE, setToast } from "@plane/ui";
// helpers // helpers
import { renderFormattedDate } from "@/helpers/date-time.helper"; import { renderFormattedDate, renderFormattedTime } from "@/helpers/date-time.helper";
import { copyTextToClipboard } from "@/helpers/string.helper"; import { copyTextToClipboard } from "@/helpers/string.helper";
// types // types
import { usePlatformOS } from "@/hooks/use-platform-os"; import { usePlatformOS } from "@/hooks/use-platform-os";
@ -49,7 +49,9 @@ export const GeneratedTokenDetails: React.FC<Props> = (props) => {
</button> </button>
<div className="mt-6 flex items-center justify-between"> <div className="mt-6 flex items-center justify-between">
<p className="text-xs text-custom-text-400"> <p className="text-xs text-custom-text-400">
{tokenDetails.expired_at ? `Expires ${renderFormattedDate(tokenDetails.expired_at)}` : "Never expires"} {tokenDetails.expired_at
? `Expires ${renderFormattedDate(tokenDetails.expired_at!)} at ${renderFormattedTime(tokenDetails.expired_at!)}`
: "Never expires"}
</p> </p>
<Button variant="neutral-primary" size="sm" onClick={handleClose}> <Button variant="neutral-primary" size="sm" onClick={handleClose}>
{t("close")} {t("close")}

View file

@ -6,7 +6,7 @@ import { IApiToken } from "@plane/types";
// components // components
import { Tooltip } from "@plane/ui"; import { Tooltip } from "@plane/ui";
import { DeleteApiTokenModal } from "@/components/api-token"; import { DeleteApiTokenModal } from "@/components/api-token";
import { renderFormattedDate, calculateTimeAgo } from "@/helpers/date-time.helper"; import { renderFormattedDate, calculateTimeAgo, renderFormattedTime } from "@/helpers/date-time.helper";
import { usePlatformOS } from "@/hooks/use-platform-os"; import { usePlatformOS } from "@/hooks/use-platform-os";
// ui // ui
// helpers // helpers
@ -52,7 +52,7 @@ export const ApiTokenListItem: React.FC<Props> = (props) => {
<p className="mb-1 text-xs leading-6 text-custom-text-400"> <p className="mb-1 text-xs leading-6 text-custom-text-400">
{token.is_active {token.is_active
? token.expired_at ? token.expired_at
? `Expires ${renderFormattedDate(token.expired_at!)}` ? `Expires ${renderFormattedDate(token.expired_at!)} at ${renderFormattedTime(token.expired_at!)}`
: "Never expires" : "Never expires"
: `Expired ${calculateTimeAgo(token.expired_at)}`} : `Expired ${calculateTimeAgo(token.expired_at)}`}
</p> </p>