From fd5326dec6b42ba49ab962f247c830934501d775 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 22 Jan 2024 20:42:09 +0530 Subject: [PATCH] chore: project cycle bug fixes and improvement (#3427) * chore: burndown chart's completed at changes * chore: project cycle bug fixes and improvement * chore: cycle state constant updated --------- Co-authored-by: NarayanBavisetti --- apiserver/plane/db/models/issue.py | 12 + apiserver/plane/utils/analytics_plot.py | 6 +- packages/types/src/cycles.d.ts | 19 +- .../progress/linear-progress-indicator.tsx | 6 +- .../core/sidebar/single-progress-stats.tsx | 2 +- .../cycles/active-cycle-details.tsx | 280 +++++------------- web/components/cycles/active-cycle-stats.tsx | 2 +- web/components/cycles/form.tsx | 1 + web/components/issues/label.tsx | 2 +- web/components/modules/form.tsx | 3 +- web/constants/cycle.ts | 28 ++ 11 files changed, 149 insertions(+), 212 deletions(-) diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 43274ea13..d5ed4247a 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -9,6 +9,7 @@ from django.db.models.signals import post_save from django.dispatch import receiver from django.core.validators import MinValueValidator, MaxValueValidator from django.core.exceptions import ValidationError +from django.utils import timezone # Module imports from . import ProjectBaseModel @@ -183,6 +184,17 @@ class Issue(ProjectBaseModel): self.state = default_state except ImportError: pass + else: + try: + from plane.db.models import State + + # Check if the current issue state group is completed or not + if self.state.group == "completed": + self.completed_at = timezone.now() + else: + self.completed_at = None + except ImportError: + pass if self._state.adding: # Get the maximum display_id value from the database diff --git a/apiserver/plane/utils/analytics_plot.py b/apiserver/plane/utils/analytics_plot.py index 07d456a1d..948eb1b91 100644 --- a/apiserver/plane/utils/analytics_plot.py +++ b/apiserver/plane/utils/analytics_plot.py @@ -4,6 +4,7 @@ from datetime import timedelta # Django import from django.db import models +from django.utils import timezone from django.db.models.functions import TruncDate from django.db.models import Count, F, Sum, Value, Case, When, CharField from django.db.models.functions import ( @@ -168,6 +169,9 @@ def burndown_plot(queryset, slug, project_id, cycle_id=None, module_id=None): if item["date"] is not None and item["date"] <= date ) cumulative_pending_issues -= total_completed - chart_data[str(date)] = cumulative_pending_issues + if date > timezone.now().date(): + chart_data[str(date)] = None + else: + chart_data[str(date)] = cumulative_pending_issues return chart_data diff --git a/packages/types/src/cycles.d.ts b/packages/types/src/cycles.d.ts index 6723b3946..91c6ef1d5 100644 --- a/packages/types/src/cycles.d.ts +++ b/packages/types/src/cycles.d.ts @@ -1,4 +1,11 @@ -import type { IUser, TIssue, IProjectLite, IWorkspaceLite, IIssueFilterOptions, IUserLite } from "@plane/types"; +import type { + IUser, + TIssue, + IProjectLite, + IWorkspaceLite, + IIssueFilterOptions, + IUserLite, +} from "@plane/types"; export type TCycleView = "all" | "active" | "upcoming" | "completed" | "draft"; @@ -54,7 +61,7 @@ export type TAssigneesDistribution = { }; export type TCompletionChartDistribution = { - [key: string]: number; + [key: string]: number | null; }; export type TLabelsDistribution = { @@ -80,9 +87,13 @@ export interface CycleIssueResponse { sub_issues_count: number; } -export type SelectCycleType = (ICycle & { actionType: "edit" | "delete" | "create-issue" }) | undefined; +export type SelectCycleType = + | (ICycle & { actionType: "edit" | "delete" | "create-issue" }) + | undefined; -export type SelectIssue = (TIssue & { actionType: "edit" | "delete" | "create" }) | null; +export type SelectIssue = + | (TIssue & { actionType: "edit" | "delete" | "create" }) + | null; export type CycleDateCheckData = { start_date: string; diff --git a/packages/ui/src/progress/linear-progress-indicator.tsx b/packages/ui/src/progress/linear-progress-indicator.tsx index 471015406..467285024 100644 --- a/packages/ui/src/progress/linear-progress-indicator.tsx +++ b/packages/ui/src/progress/linear-progress-indicator.tsx @@ -4,15 +4,17 @@ import { Tooltip } from "../tooltip"; type Props = { data: any; noTooltip?: boolean; + inPercentage?: boolean; }; -export const LinearProgressIndicator: React.FC = ({ data, noTooltip = false }) => { +export const LinearProgressIndicator: React.FC = ({ data, noTooltip = false, inPercentage = false }) => { const total = data.reduce((acc: any, cur: any) => acc + cur.value, 0); // eslint-disable-next-line @typescript-eslint/no-unused-vars let progress = 0; const bars = data.map((item: any) => { const width = `${(item.value / total) * 100}%`; + if (width === "0%") return <>; const style = { width, backgroundColor: item.color, @@ -22,7 +24,7 @@ export const LinearProgressIndicator: React.FC = ({ data, noTooltip = fal else return ( -
+
); }); diff --git a/web/components/core/sidebar/single-progress-stats.tsx b/web/components/core/sidebar/single-progress-stats.tsx index f58bbc2c3..4d926285b 100644 --- a/web/components/core/sidebar/single-progress-stats.tsx +++ b/web/components/core/sidebar/single-progress-stats.tsx @@ -30,7 +30,7 @@ export const SingleProgressStats: React.FC = ({ - {isNaN(Math.floor((completed / total) * 100)) ? "0" : Math.floor((completed / total) * 100)}% + {isNaN(Math.round((completed / total) * 100)) ? "0" : Math.round((completed / total) * 100)}%
of {total} diff --git a/web/components/cycles/active-cycle-details.tsx b/web/components/cycles/active-cycle-details.tsx index 56c5e1bc9..e5cd91e25 100644 --- a/web/components/cycles/active-cycle-details.tsx +++ b/web/components/cycles/active-cycle-details.tsx @@ -3,7 +3,7 @@ import Link from "next/link"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; // hooks -import { useApplication, useCycle, useIssues, useProjectState } from "hooks/store"; +import { useApplication, useCycle, useIssues, useProject } from "hooks/store"; import useToast from "hooks/use-toast"; // ui import { SingleProgressStats } from "components/core"; @@ -12,55 +12,27 @@ import { Loader, Tooltip, LinearProgressIndicator, - ContrastIcon, - RunningIcon, LayersIcon, StateGroupIcon, PriorityIcon, Avatar, + CycleGroupIcon, } from "@plane/ui"; // components import ProgressChart from "components/core/sidebar/progress-chart"; import { ActiveCycleProgressStats } from "components/cycles"; -import { ViewIssueLabel } from "components/issues"; +import { StateDropdown } from "components/dropdowns"; // icons -import { AlarmClock, AlertTriangle, ArrowRight, CalendarDays, Star, Target } from "lucide-react"; +import { ArrowRight, CalendarCheck, CalendarDays, Star, Target } from "lucide-react"; // helpers -import { renderFormattedDate, findHowManyDaysLeft } from "helpers/date-time.helper"; +import { renderFormattedDate, findHowManyDaysLeft, renderFormattedDateWithoutYear } from "helpers/date-time.helper"; import { truncateText } from "helpers/string.helper"; // types -import { ICycle } from "@plane/types"; +import { ICycle, TCycleGroups } from "@plane/types"; +// constants import { EIssuesStoreType } from "constants/issue"; -import { ACTIVE_CYCLE_ISSUES } from "store/issue/cycle"; import { CYCLE_ISSUES_WITH_PARAMS } from "constants/fetch-keys"; - -const stateGroups = [ - { - key: "backlog_issues", - title: "Backlog", - color: "#dee2e6", - }, - { - key: "unstarted_issues", - title: "Unstarted", - color: "#26b5ce", - }, - { - key: "started_issues", - title: "Started", - color: "#f7ae59", - }, - { - key: "cancelled_issues", - title: "Cancelled", - color: "#d687ff", - }, - { - key: "completed_issues", - title: "Completed", - color: "#09a953", - }, -]; +import { CYCLE_STATE_GROUPS_DETAILS } from "constants/cycle"; interface IActiveCycleDetails { workspaceSlug: string; @@ -72,8 +44,7 @@ export const ActiveCycleDetails: React.FC = observer((props const { workspaceSlug, projectId } = props; // store hooks const { - issues: { issues, fetchActiveCycleIssues }, - issueMap, + issues: { fetchActiveCycleIssues }, } = useIssues(EIssuesStoreType.CYCLE); const { commandPalette: { toggleCreateCycleModal }, @@ -85,7 +56,7 @@ export const ActiveCycleDetails: React.FC = observer((props addCycleToFavorites, removeCycleFromFavorites, } = useCycle(); - const { getProjectStates } = useProjectState(); + const { currentProjectDetails } = useProject(); // toast alert const { setToastAlert } = useToast(); @@ -95,9 +66,8 @@ export const ActiveCycleDetails: React.FC = observer((props ); const activeCycle = currentProjectActiveCycleId ? getActiveCycleById(currentProjectActiveCycleId) : null; - const issueIds = issues?.[ACTIVE_CYCLE_ISSUES]; - useSWR( + const { data: activeCycleIssues } = useSWR( workspaceSlug && projectId && currentProjectActiveCycleId ? CYCLE_ISSUES_WITH_PARAMS(currentProjectActiveCycleId, { priority: "urgent,high" }) : null, @@ -149,7 +119,7 @@ export const ActiveCycleDetails: React.FC = observer((props cancelled: activeCycle.cancelled_issues, }; - const cycleStatus = activeCycle.status.toLocaleLowerCase(); + const cycleStatus = activeCycle.status.toLowerCase() as TCycleGroups; const handleAddToFavorites = (e: MouseEvent) => { e.preventDefault(); @@ -177,7 +147,7 @@ export const ActiveCycleDetails: React.FC = observer((props }); }; - const progressIndicatorData = stateGroups.map((group, index) => ({ + const progressIndicatorData = CYCLE_STATE_GROUPS_DETAILS.map((group, index) => ({ id: index, name: group.title, value: @@ -187,6 +157,8 @@ export const ActiveCycleDetails: React.FC = observer((props color: group.color, })); + const daysLeft = findHowManyDaysLeft(activeCycle.end_date ?? new Date()); + return (
@@ -196,68 +168,15 @@ export const ActiveCycleDetails: React.FC = observer((props
- +

{truncateText(activeCycle.name, 70)}

- - {cycleStatus === "current" ? ( - - - {findHowManyDaysLeft(activeCycle.end_date ?? new Date())} Days Left - - ) : cycleStatus === "upcoming" ? ( - - - {findHowManyDaysLeft(activeCycle.start_date ?? new Date())} Days Left - - ) : cycleStatus === "completed" ? ( - - {activeCycle.total_issues - activeCycle.completed_issues > 0 && ( - - - - - - )}{" "} - Completed - - ) : ( - cycleStatus - )} + + {`${daysLeft} ${daysLeft > 1 ? "days" : "day"} left`} {activeCycle.is_favorite ? (