[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:
parent
b5ceb94fb2
commit
2bbaaed3ea
5 changed files with 37 additions and 22 deletions
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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 ?? "",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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")}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue