From 10ab081a0b80a5549c7e894b4caafe9a89d5861d Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Fri, 29 Dec 2023 15:24:07 +0530 Subject: [PATCH] chore: cycle current status (#3270) * dev: cycle status * chore: cycle status logic updated --------- Co-authored-by: Anmol Singh Bhatia --- apiserver/plane/app/serializers/cycle.py | 1 + apiserver/plane/app/views/cycle.py | 34 +++++++++++++++++-- .../cycles/active-cycle-details.tsx | 4 +-- web/components/cycles/cycles-board-card.tsx | 11 ++---- web/components/cycles/cycles-list-item.tsx | 11 ++---- web/components/cycles/gantt-chart/blocks.tsx | 31 ++++++++--------- web/components/cycles/sidebar.tsx | 6 +--- .../cycles/transfer-issues-modal.tsx | 4 +-- .../issue-layouts/roots/cycle-layout-root.tsx | 7 +--- web/helpers/date-time.helper.ts | 12 ------- web/store/cycle/cycles.store.ts | 5 ++- web/types/cycles.d.ts | 3 ++ 12 files changed, 63 insertions(+), 66 deletions(-) diff --git a/apiserver/plane/app/serializers/cycle.py b/apiserver/plane/app/serializers/cycle.py index 104a3dd06..63abf3a03 100644 --- a/apiserver/plane/app/serializers/cycle.py +++ b/apiserver/plane/app/serializers/cycle.py @@ -40,6 +40,7 @@ class CycleSerializer(BaseSerializer): started_estimates = serializers.IntegerField(read_only=True) workspace_detail = WorkspaceLiteSerializer(read_only=True, source="workspace") project_detail = ProjectLiteSerializer(read_only=True, source="project") + status = serializers.CharField(read_only=True) def validate(self, data): if ( diff --git a/apiserver/plane/app/views/cycle.py b/apiserver/plane/app/views/cycle.py index d2f82d75b..02f259de3 100644 --- a/apiserver/plane/app/views/cycle.py +++ b/apiserver/plane/app/views/cycle.py @@ -11,6 +11,10 @@ from django.db.models import ( Count, Prefetch, Sum, + Case, + When, + Value, + CharField ) from django.core import serializers from django.utils import timezone @@ -157,6 +161,28 @@ class CycleViewSet(WebhookMixin, BaseViewSet): ), ) ) + .annotate( + status=Case( + When( + Q(start_date__lte=timezone.now()) & Q(end_date__gte=timezone.now()), + then=Value("CURRENT") + ), + When( + start_date__gt=timezone.now(), + then=Value("UPCOMING") + ), + When( + end_date__lt=timezone.now(), + then=Value("COMPLETED") + ), + When( + Q(start_date__isnull=True) & Q(end_date__isnull=True), + then=Value("DRAFT") + ), + default=Value("DRAFT"), + output_field=CharField(), + ) + ) .prefetch_related( Prefetch( "issue_cycle__issue__assignees", @@ -177,7 +203,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet): queryset = self.get_queryset() cycle_view = request.GET.get("cycle_view", "all") - queryset = queryset.order_by("-is_favorite","-created_at") + queryset = queryset.order_by("-is_favorite", "-created_at") # Current Cycle if cycle_view == "current": @@ -575,7 +601,9 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet): ) ) - issues = IssueStateSerializer(issues, many=True, fields=fields if fields else None).data + issues = IssueStateSerializer( + issues, many=True, fields=fields if fields else None + ).data issue_dict = {str(issue["id"]): issue for issue in issues} return Response(issue_dict, status=status.HTTP_200_OK) @@ -805,4 +833,4 @@ class TransferCycleIssueEndpoint(BaseAPIView): updated_cycles, ["cycle_id"], batch_size=100 ) - return Response({"message": "Success"}, status=status.HTTP_200_OK) \ No newline at end of file + return Response({"message": "Success"}, status=status.HTTP_200_OK) diff --git a/web/components/cycles/active-cycle-details.tsx b/web/components/cycles/active-cycle-details.tsx index ea982099f..47beaa262 100644 --- a/web/components/cycles/active-cycle-details.tsx +++ b/web/components/cycles/active-cycle-details.tsx @@ -28,7 +28,7 @@ import { ViewIssueLabel } from "components/issues"; // icons import { AlarmClock, AlertTriangle, ArrowRight, CalendarDays, Star, Target } from "lucide-react"; // helpers -import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; +import { renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; import { truncateText } from "helpers/string.helper"; // types import { ICycle } from "types"; @@ -137,7 +137,7 @@ export const ActiveCycleDetails: React.FC = observer((props cancelled: cycle.cancelled_issues, }; - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status.toLocaleLowerCase(); const handleAddToFavorites = (e: MouseEvent) => { e.preventDefault(); diff --git a/web/components/cycles/cycles-board-card.tsx b/web/components/cycles/cycles-board-card.tsx index f020b0998..d43d56872 100644 --- a/web/components/cycles/cycles-board-card.tsx +++ b/web/components/cycles/cycles-board-card.tsx @@ -10,15 +10,10 @@ import { Avatar, AvatarGroup, CustomMenu, Tooltip, LayersIcon, CycleGroupIcon } // icons import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react"; // helpers -import { - getDateRangeStatus, - findHowManyDaysLeft, - renderShortDate, - renderShortMonthDate, -} from "helpers/date-time.helper"; +import { findHowManyDaysLeft, renderShortDate, renderShortMonthDate } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; // types -import { ICycle } from "types"; +import { ICycle, TCycleGroups } from "types"; // store import { useMobxStore } from "lib/mobx/store-provider"; // constants @@ -45,7 +40,7 @@ export const CyclesBoardCard: FC = (props) => { const [updateModal, setUpdateModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false); // computed - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status.toLocaleLowerCase() as TCycleGroups; const isCompleted = cycleStatus === "completed"; const endDate = new Date(cycle.end_date ?? ""); const startDate = new Date(cycle.start_date ?? ""); diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index 86b3bffa9..9ea26ab39 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -13,15 +13,10 @@ import { CustomMenu, Tooltip, CircularProgressIndicator, CycleGroupIcon, AvatarG // icons import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react"; // helpers -import { - getDateRangeStatus, - findHowManyDaysLeft, - renderShortDate, - renderShortMonthDate, -} from "helpers/date-time.helper"; +import { findHowManyDaysLeft, renderShortDate, renderShortMonthDate } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; // types -import { ICycle } from "types"; +import { ICycle, TCycleGroups } from "types"; // constants import { CYCLE_STATUS } from "constants/cycle"; import { EUserWorkspaceRoles } from "constants/workspace"; @@ -50,7 +45,7 @@ export const CyclesListItem: FC = (props) => { const [updateModal, setUpdateModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false); // computed - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status.toLocaleLowerCase() as TCycleGroups; const isCompleted = cycleStatus === "completed"; const endDate = new Date(cycle.end_date ?? ""); const startDate = new Date(cycle.start_date ?? ""); diff --git a/web/components/cycles/gantt-chart/blocks.tsx b/web/components/cycles/gantt-chart/blocks.tsx index 03614592c..76a4d9235 100644 --- a/web/components/cycles/gantt-chart/blocks.tsx +++ b/web/components/cycles/gantt-chart/blocks.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/router"; // ui import { Tooltip, ContrastIcon } from "@plane/ui"; // helpers -import { getDateRangeStatus, renderShortDate } from "helpers/date-time.helper"; +import { renderShortDate } from "helpers/date-time.helper"; // types import { ICycle } from "types"; @@ -11,8 +11,7 @@ export const CycleGanttBlock = ({ data }: { data: ICycle }) => { const router = useRouter(); const { workspaceSlug } = router.query; - const cycleStatus = getDateRangeStatus(data?.start_date, data?.end_date); - + const cycleStatus = data.status.toLocaleLowerCase(); return (
{ cycleStatus === "current" ? "#09a953" : cycleStatus === "upcoming" - ? "#f7ae59" - : cycleStatus === "completed" - ? "#3f76ff" - : cycleStatus === "draft" - ? "rgb(var(--color-text-200))" - : "", + ? "#f7ae59" + : cycleStatus === "completed" + ? "#3f76ff" + : cycleStatus === "draft" + ? "rgb(var(--color-text-200))" + : "", }} onClick={() => router.push(`/${workspaceSlug}/projects/${data?.project}/cycles/${data?.id}`)} > @@ -52,7 +51,7 @@ export const CycleGanttSidebarBlock = ({ data }: { data: ICycle }) => { const router = useRouter(); const { workspaceSlug } = router.query; - const cycleStatus = getDateRangeStatus(data?.start_date, data?.end_date); + const cycleStatus = data.status.toLocaleLowerCase(); return (
{ cycleStatus === "current" ? "#09a953" : cycleStatus === "upcoming" - ? "#f7ae59" - : cycleStatus === "completed" - ? "#3f76ff" - : cycleStatus === "draft" - ? "rgb(var(--color-text-200))" - : "" + ? "#f7ae59" + : cycleStatus === "completed" + ? "#3f76ff" + : cycleStatus === "draft" + ? "rgb(var(--color-text-200))" + : "" }`} />
{data?.name}
diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index dcf86bb46..18c233d6c 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -31,7 +31,6 @@ import { import { copyUrlToClipboard } from "helpers/string.helper"; import { findHowManyDaysLeft, - getDateRangeStatus, isDateGreaterThanToday, renderDateFormat, renderShortDate, @@ -275,10 +274,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { [workspaceSlug, projectId, cycleId, issueFilters, updateFilters] ); - const cycleStatus = - cycleDetails?.start_date && cycleDetails?.end_date - ? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date) - : "draft"; + const cycleStatus = cycleDetails.status.toLocaleLowerCase(); const isCompleted = cycleStatus === "completed"; const isStartValid = new Date(`${cycleDetails?.start_date}`) <= new Date(); diff --git a/web/components/cycles/transfer-issues-modal.tsx b/web/components/cycles/transfer-issues-modal.tsx index 55555e221..dd462e360 100644 --- a/web/components/cycles/transfer-issues-modal.tsx +++ b/web/components/cycles/transfer-issues-modal.tsx @@ -15,8 +15,6 @@ import { AlertCircle, Search, X } from "lucide-react"; import { INCOMPLETE_CYCLES_LIST } from "constants/fetch-keys"; // types import { ICycle } from "types"; -//helper -import { getDateRangeStatus } from "helpers/date-time.helper"; type Props = { isOpen: boolean; @@ -138,7 +136,7 @@ export const TransferIssuesModal: React.FC = observer(({ isOpen, handleCl
{option?.name} - {getDateRangeStatus(option?.start_date, option?.end_date)} + {option.status}
diff --git a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx index f967956f0..f77dfbed4 100644 --- a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx @@ -17,8 +17,6 @@ import { import { TransferIssues, TransferIssuesModal } from "components/cycles"; // ui import { Spinner } from "@plane/ui"; -// helpers -import { getDateRangeStatus } from "helpers/date-time.helper"; export const CycleLayoutRoot: React.FC = observer(() => { const [transferIssuesModal, setTransferIssuesModal] = useState(false); @@ -50,10 +48,7 @@ export const CycleLayoutRoot: React.FC = observer(() => { const activeLayout = issueFilters?.displayFilters?.layout; const cycleDetails = cycleId ? cycleStore.cycle_details[cycleId.toString()] : undefined; - const cycleStatus = - cycleDetails?.start_date && cycleDetails?.end_date - ? getDateRangeStatus(cycleDetails?.start_date, cycleDetails?.end_date) - : "draft"; + const cycleStatus = cycleDetails?.status.toLocaleLowerCase() ?? "draft"; return ( <> diff --git a/web/helpers/date-time.helper.ts b/web/helpers/date-time.helper.ts index fa346d5aa..ab6116d77 100644 --- a/web/helpers/date-time.helper.ts +++ b/web/helpers/date-time.helper.ts @@ -170,18 +170,6 @@ export const formatLongDateDistance = (date: string | Date) => { } }; -export const getDateRangeStatus = (startDate: string | null | undefined, endDate: string | null | undefined) => { - if (!startDate || !endDate) return "draft"; - - const now = new Date(); - const start = new Date(startDate); - const end = new Date(endDate); - - if (start <= now && end >= now) return "current"; - else if (start > now) return "upcoming"; - else return "completed"; -}; - export const renderShortDateWithYearFormat = (date: string | Date, placeholder?: string) => { if (!date || date === "") return null; diff --git a/web/store/cycle/cycles.store.ts b/web/store/cycle/cycles.store.ts index 96122ec14..b6602172d 100644 --- a/web/store/cycle/cycles.store.ts +++ b/web/store/cycle/cycles.store.ts @@ -7,7 +7,6 @@ import { RootStore } from "../root"; import { ProjectService } from "services/project"; import { IssueService } from "services/issue"; import { CycleService } from "services/cycle.service"; -import { getDateRangeStatus } from "helpers/date-time.helper"; export interface ICycleStore { loader: boolean; @@ -318,7 +317,7 @@ export class CycleStore implements ICycleStore { }; addCycleToFavorites = async (workspaceSlug: string, projectId: string, cycle: ICycle) => { - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status; const statusCyclesList = this.cycles[projectId]?.[cycleStatus] ?? []; const allCyclesList = this.projectCycles ?? []; @@ -379,7 +378,7 @@ export class CycleStore implements ICycleStore { }; removeCycleFromFavorites = async (workspaceSlug: string, projectId: string, cycle: ICycle) => { - const cycleStatus = getDateRangeStatus(cycle.start_date, cycle.end_date); + const cycleStatus = cycle.status; const statusCyclesList = this.cycles[projectId]?.[cycleStatus] ?? []; const allCyclesList = this.projectCycles ?? []; diff --git a/web/types/cycles.d.ts b/web/types/cycles.d.ts index c3c5248aa..4f243deeb 100644 --- a/web/types/cycles.d.ts +++ b/web/types/cycles.d.ts @@ -2,6 +2,8 @@ import type { IUser, IIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft"; +export type TCycleGroups = "current" | "upcoming" | "completed" | "draft"; + export type TCycleLayout = "list" | "board" | "gantt"; export interface ICycle { @@ -24,6 +26,7 @@ export interface ICycle { owned_by: IUser; project: string; project_detail: IProjectLite; + status: TCycleGroups; sort_order: number; start_date: string | null; started_issues: number;