forked from github/plane
feat: completed cycle snapshot (#3600)
* fix: transfer cycle old distribtion captured * chore: active cycle snapshot * chore: migration file changed * chore: distribution payload changed * chore: labels and assignee structure change * chore: migration changes * chore: cycle snapshot progress payload updated * chore: cycle snapshot progress type added * chore: snapshot progress stats updated in cycle sidebar * chore: empty string validation --------- Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
This commit is contained in:
parent
e2affc3fa6
commit
27037a2177
@ -20,6 +20,7 @@ from django.core import serializers
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.gzip import gzip_page
|
from django.views.decorators.gzip import gzip_page
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@ -312,6 +313,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
"labels": label_distribution,
|
"labels": label_distribution,
|
||||||
"completion_chart": {},
|
"completion_chart": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
if data[0]["start_date"] and data[0]["end_date"]:
|
if data[0]["start_date"] and data[0]["end_date"]:
|
||||||
data[0]["distribution"][
|
data[0]["distribution"][
|
||||||
"completion_chart"
|
"completion_chart"
|
||||||
@ -840,10 +842,230 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
new_cycle = Cycle.objects.get(
|
new_cycle = Cycle.objects.filter(
|
||||||
workspace__slug=slug, project_id=project_id, pk=new_cycle_id
|
workspace__slug=slug, project_id=project_id, pk=new_cycle_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
old_cycle = (
|
||||||
|
Cycle.objects.filter(
|
||||||
|
workspace__slug=slug, project_id=project_id, pk=cycle_id
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
total_issues=Count(
|
||||||
|
"issue_cycle",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
completed_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="completed",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
cancelled_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="cancelled",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
started_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="started",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
unstarted_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="unstarted",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
backlog_issues=Count(
|
||||||
|
"issue_cycle__issue__state__group",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="backlog",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
total_estimates=Sum("issue_cycle__issue__estimate_point")
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
completed_estimates=Sum(
|
||||||
|
"issue_cycle__issue__estimate_point",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="completed",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
started_estimates=Sum(
|
||||||
|
"issue_cycle__issue__estimate_point",
|
||||||
|
filter=Q(
|
||||||
|
issue_cycle__issue__state__group="started",
|
||||||
|
issue_cycle__issue__archived_at__isnull=True,
|
||||||
|
issue_cycle__issue__is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Pass the new_cycle queryset to burndown_plot
|
||||||
|
completion_chart = burndown_plot(
|
||||||
|
queryset=old_cycle.first(),
|
||||||
|
slug=slug,
|
||||||
|
project_id=project_id,
|
||||||
|
cycle_id=cycle_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
assignee_distribution = (
|
||||||
|
Issue.objects.filter(
|
||||||
|
issue_cycle__cycle_id=cycle_id,
|
||||||
|
workspace__slug=slug,
|
||||||
|
project_id=project_id,
|
||||||
|
)
|
||||||
|
.annotate(display_name=F("assignees__display_name"))
|
||||||
|
.annotate(assignee_id=F("assignees__id"))
|
||||||
|
.annotate(avatar=F("assignees__avatar"))
|
||||||
|
.values("display_name", "assignee_id", "avatar")
|
||||||
|
.annotate(
|
||||||
|
total_issues=Count(
|
||||||
|
"id",
|
||||||
|
filter=Q(archived_at__isnull=True, is_draft=False),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
completed_issues=Count(
|
||||||
|
"id",
|
||||||
|
filter=Q(
|
||||||
|
completed_at__isnull=False,
|
||||||
|
archived_at__isnull=True,
|
||||||
|
is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
pending_issues=Count(
|
||||||
|
"id",
|
||||||
|
filter=Q(
|
||||||
|
completed_at__isnull=True,
|
||||||
|
archived_at__isnull=True,
|
||||||
|
is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by("display_name")
|
||||||
|
)
|
||||||
|
|
||||||
|
label_distribution = (
|
||||||
|
Issue.objects.filter(
|
||||||
|
issue_cycle__cycle_id=cycle_id,
|
||||||
|
workspace__slug=slug,
|
||||||
|
project_id=project_id,
|
||||||
|
)
|
||||||
|
.annotate(label_name=F("labels__name"))
|
||||||
|
.annotate(color=F("labels__color"))
|
||||||
|
.annotate(label_id=F("labels__id"))
|
||||||
|
.values("label_name", "color", "label_id")
|
||||||
|
.annotate(
|
||||||
|
total_issues=Count(
|
||||||
|
"id",
|
||||||
|
filter=Q(archived_at__isnull=True, is_draft=False),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
completed_issues=Count(
|
||||||
|
"id",
|
||||||
|
filter=Q(
|
||||||
|
completed_at__isnull=False,
|
||||||
|
archived_at__isnull=True,
|
||||||
|
is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
pending_issues=Count(
|
||||||
|
"id",
|
||||||
|
filter=Q(
|
||||||
|
completed_at__isnull=True,
|
||||||
|
archived_at__isnull=True,
|
||||||
|
is_draft=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.order_by("label_name")
|
||||||
|
)
|
||||||
|
|
||||||
|
assignee_distribution_data = [
|
||||||
|
{
|
||||||
|
"display_name": item["display_name"],
|
||||||
|
"assignee_id": str(item["assignee_id"]) if item["assignee_id"] else None,
|
||||||
|
"avatar": item["avatar"],
|
||||||
|
"total_issues": item["total_issues"],
|
||||||
|
"completed_issues": item["completed_issues"],
|
||||||
|
"pending_issues": item["pending_issues"],
|
||||||
|
}
|
||||||
|
for item in assignee_distribution
|
||||||
|
]
|
||||||
|
|
||||||
|
label_distribution_data = [
|
||||||
|
{
|
||||||
|
"label_name": item["label_name"],
|
||||||
|
"color": item["color"],
|
||||||
|
"label_id": str(item["label_id"]) if item["label_id"] else None,
|
||||||
|
"total_issues": item["total_issues"],
|
||||||
|
"completed_issues": item["completed_issues"],
|
||||||
|
"pending_issues": item["pending_issues"],
|
||||||
|
}
|
||||||
|
for item in label_distribution
|
||||||
|
]
|
||||||
|
|
||||||
|
current_cycle = Cycle.objects.filter(
|
||||||
|
workspace__slug=slug, project_id=project_id, pk=cycle_id
|
||||||
|
).first()
|
||||||
|
|
||||||
|
current_cycle.progress_snapshot = {
|
||||||
|
"total_issues": old_cycle.first().total_issues,
|
||||||
|
"completed_issues": old_cycle.first().completed_issues,
|
||||||
|
"cancelled_issues": old_cycle.first().cancelled_issues,
|
||||||
|
"started_issues": old_cycle.first().started_issues,
|
||||||
|
"unstarted_issues": old_cycle.first().unstarted_issues,
|
||||||
|
"backlog_issues": old_cycle.first().backlog_issues,
|
||||||
|
"total_estimates": old_cycle.first().total_estimates,
|
||||||
|
"completed_estimates": old_cycle.first().completed_estimates,
|
||||||
|
"started_estimates": old_cycle.first().started_estimates,
|
||||||
|
"distribution":{
|
||||||
|
"labels": label_distribution_data,
|
||||||
|
"assignees": assignee_distribution_data,
|
||||||
|
"completion_chart": completion_chart,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
current_cycle.save(update_fields=["progress_snapshot"])
|
||||||
|
|
||||||
if (
|
if (
|
||||||
new_cycle.end_date is not None
|
new_cycle.end_date is not None
|
||||||
and new_cycle.end_date < timezone.now().date()
|
and new_cycle.end_date < timezone.now().date()
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2024-02-08 09:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('db', '0059_auto_20240208_0957'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='cycle',
|
||||||
|
name='progress_snapshot',
|
||||||
|
field=models.JSONField(default=dict),
|
||||||
|
),
|
||||||
|
]
|
@ -68,6 +68,7 @@ class Cycle(ProjectBaseModel):
|
|||||||
sort_order = models.FloatField(default=65535)
|
sort_order = models.FloatField(default=65535)
|
||||||
external_source = models.CharField(max_length=255, null=True, blank=True)
|
external_source = models.CharField(max_length=255, null=True, blank=True)
|
||||||
external_id = models.CharField(max_length=255, blank=True, null=True)
|
external_id = models.CharField(max_length=255, blank=True, null=True)
|
||||||
|
progress_snapshot = models.JSONField(default=dict)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Cycle"
|
verbose_name = "Cycle"
|
||||||
|
18
packages/types/src/cycles.d.ts
vendored
18
packages/types/src/cycles.d.ts
vendored
@ -31,6 +31,7 @@ export interface ICycle {
|
|||||||
issue: string;
|
issue: string;
|
||||||
name: string;
|
name: string;
|
||||||
owned_by: string;
|
owned_by: string;
|
||||||
|
progress_snapshot: TProgressSnapshot;
|
||||||
project: string;
|
project: string;
|
||||||
project_detail: IProjectLite;
|
project_detail: IProjectLite;
|
||||||
status: TCycleGroups;
|
status: TCycleGroups;
|
||||||
@ -49,6 +50,23 @@ export interface ICycle {
|
|||||||
workspace_detail: IWorkspaceLite;
|
workspace_detail: IWorkspaceLite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TProgressSnapshot = {
|
||||||
|
backlog_issues: number;
|
||||||
|
cancelled_issues: number;
|
||||||
|
completed_estimates: number | null;
|
||||||
|
completed_issues: number;
|
||||||
|
distribution?: {
|
||||||
|
assignees: TAssigneesDistribution[];
|
||||||
|
completion_chart: TCompletionChartDistribution;
|
||||||
|
labels: TLabelsDistribution[];
|
||||||
|
};
|
||||||
|
started_estimates: number | null;
|
||||||
|
started_issues: number;
|
||||||
|
total_estimates: number | null;
|
||||||
|
total_issues: number;
|
||||||
|
unstarted_issues: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type TAssigneesDistribution = {
|
export type TAssigneesDistribution = {
|
||||||
assignee_id: string | null;
|
assignee_id: string | null;
|
||||||
avatar: string | null;
|
avatar: string | null;
|
||||||
|
@ -54,10 +54,6 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
|
|||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnChange = () => {
|
|
||||||
if (closeOnSelect) closeDropdown();
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectActiveItem = () => {
|
const selectActiveItem = () => {
|
||||||
const activeItem: HTMLElement | undefined | null = dropdownRef.current?.querySelector(
|
const activeItem: HTMLElement | undefined | null = dropdownRef.current?.querySelector(
|
||||||
`[data-headlessui-state="active"] button`
|
`[data-headlessui-state="active"] button`
|
||||||
|
@ -3,6 +3,7 @@ import { useRouter } from "next/router";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { Disclosure, Popover, Transition } from "@headlessui/react";
|
import { Disclosure, Popover, Transition } from "@headlessui/react";
|
||||||
|
import isEmpty from "lodash/isEmpty";
|
||||||
// services
|
// services
|
||||||
import { CycleService } from "services/cycle.service";
|
import { CycleService } from "services/cycle.service";
|
||||||
// hooks
|
// hooks
|
||||||
@ -293,7 +294,11 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const isEndValid = new Date(`${cycleDetails?.end_date}`) >= new Date(`${cycleDetails?.start_date}`);
|
const isEndValid = new Date(`${cycleDetails?.end_date}`) >= new Date(`${cycleDetails?.start_date}`);
|
||||||
|
|
||||||
const progressPercentage = cycleDetails
|
const progressPercentage = cycleDetails
|
||||||
? Math.round((cycleDetails.completed_issues / cycleDetails.total_issues) * 100)
|
? isCompleted
|
||||||
|
? Math.round(
|
||||||
|
(cycleDetails.progress_snapshot.completed_issues / cycleDetails.progress_snapshot.total_issues) * 100
|
||||||
|
)
|
||||||
|
: Math.round((cycleDetails.completed_issues / cycleDetails.total_issues) * 100)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!cycleDetails)
|
if (!cycleDetails)
|
||||||
@ -317,7 +322,14 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
|
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
|
||||||
|
|
||||||
const issueCount =
|
const issueCount =
|
||||||
cycleDetails.total_issues === 0 ? "0 Issue" : `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
isCompleted && !isEmpty(cycleDetails.progress_snapshot)
|
||||||
|
? cycleDetails.progress_snapshot.total_issues === 0
|
||||||
|
? "0 Issue"
|
||||||
|
: `${cycleDetails.progress_snapshot.completed_issues}/${cycleDetails.progress_snapshot.total_issues}`
|
||||||
|
: cycleDetails.total_issues === 0
|
||||||
|
? "0 Issue"
|
||||||
|
: `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`;
|
||||||
|
|
||||||
const daysLeft = findHowManyDaysLeft(cycleDetails.end_date);
|
const daysLeft = findHowManyDaysLeft(cycleDetails.end_date);
|
||||||
|
|
||||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||||
@ -568,49 +580,105 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
<Transition show={open}>
|
<Transition show={open}>
|
||||||
<Disclosure.Panel>
|
<Disclosure.Panel>
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{cycleDetails.distribution?.completion_chart &&
|
{isCompleted && !isEmpty(cycleDetails.progress_snapshot) ? (
|
||||||
cycleDetails.start_date &&
|
<>
|
||||||
cycleDetails.end_date ? (
|
{cycleDetails.progress_snapshot.distribution?.completion_chart &&
|
||||||
<div className="h-full w-full pt-4">
|
cycleDetails.start_date &&
|
||||||
<div className="flex items-start gap-4 py-2 text-xs">
|
cycleDetails.end_date && (
|
||||||
<div className="flex items-center gap-3 text-custom-text-100">
|
<div className="h-full w-full pt-4">
|
||||||
<div className="flex items-center justify-center gap-1">
|
<div className="flex items-start gap-4 py-2 text-xs">
|
||||||
<span className="h-2.5 w-2.5 rounded-full bg-[#A9BBD0]" />
|
<div className="flex items-center gap-3 text-custom-text-100">
|
||||||
<span>Ideal</span>
|
<div className="flex items-center justify-center gap-1">
|
||||||
|
<span className="h-2.5 w-2.5 rounded-full bg-[#A9BBD0]" />
|
||||||
|
<span>Ideal</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center gap-1">
|
||||||
|
<span className="h-2.5 w-2.5 rounded-full bg-[#4C8FFF]" />
|
||||||
|
<span>Current</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative h-40 w-80">
|
||||||
|
<ProgressChart
|
||||||
|
distribution={cycleDetails.progress_snapshot.distribution?.completion_chart}
|
||||||
|
startDate={cycleDetails.start_date}
|
||||||
|
endDate={cycleDetails.end_date}
|
||||||
|
totalIssues={cycleDetails.progress_snapshot.total_issues}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-center gap-1">
|
)}
|
||||||
<span className="h-2.5 w-2.5 rounded-full bg-[#4C8FFF]" />
|
</>
|
||||||
<span>Current</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="relative h-40 w-80">
|
|
||||||
<ProgressChart
|
|
||||||
distribution={cycleDetails.distribution?.completion_chart}
|
|
||||||
startDate={cycleDetails.start_date}
|
|
||||||
endDate={cycleDetails.end_date}
|
|
||||||
totalIssues={cycleDetails.total_issues}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
""
|
<>
|
||||||
|
{cycleDetails.distribution?.completion_chart &&
|
||||||
|
cycleDetails.start_date &&
|
||||||
|
cycleDetails.end_date && (
|
||||||
|
<div className="h-full w-full pt-4">
|
||||||
|
<div className="flex items-start gap-4 py-2 text-xs">
|
||||||
|
<div className="flex items-center gap-3 text-custom-text-100">
|
||||||
|
<div className="flex items-center justify-center gap-1">
|
||||||
|
<span className="h-2.5 w-2.5 rounded-full bg-[#A9BBD0]" />
|
||||||
|
<span>Ideal</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-center gap-1">
|
||||||
|
<span className="h-2.5 w-2.5 rounded-full bg-[#4C8FFF]" />
|
||||||
|
<span>Current</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="relative h-40 w-80">
|
||||||
|
<ProgressChart
|
||||||
|
distribution={cycleDetails.distribution?.completion_chart}
|
||||||
|
startDate={cycleDetails.start_date}
|
||||||
|
endDate={cycleDetails.end_date}
|
||||||
|
totalIssues={cycleDetails.total_issues}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{cycleDetails.total_issues > 0 && cycleDetails.distribution && (
|
{/* stats */}
|
||||||
<div className="h-full w-full border-t border-custom-border-200 pt-5">
|
{isCompleted && !isEmpty(cycleDetails.progress_snapshot) ? (
|
||||||
<SidebarProgressStats
|
<>
|
||||||
distribution={cycleDetails.distribution}
|
{cycleDetails.progress_snapshot.total_issues > 0 &&
|
||||||
groupedIssues={{
|
cycleDetails.progress_snapshot.distribution && (
|
||||||
backlog: cycleDetails.backlog_issues,
|
<div className="h-full w-full border-t border-custom-border-200 pt-5">
|
||||||
unstarted: cycleDetails.unstarted_issues,
|
<SidebarProgressStats
|
||||||
started: cycleDetails.started_issues,
|
distribution={cycleDetails.progress_snapshot.distribution}
|
||||||
completed: cycleDetails.completed_issues,
|
groupedIssues={{
|
||||||
cancelled: cycleDetails.cancelled_issues,
|
backlog: cycleDetails.progress_snapshot.backlog_issues,
|
||||||
}}
|
unstarted: cycleDetails.progress_snapshot.unstarted_issues,
|
||||||
totalIssues={cycleDetails.total_issues}
|
started: cycleDetails.progress_snapshot.started_issues,
|
||||||
isPeekView={Boolean(peekCycle)}
|
completed: cycleDetails.progress_snapshot.completed_issues,
|
||||||
/>
|
cancelled: cycleDetails.progress_snapshot.cancelled_issues,
|
||||||
</div>
|
}}
|
||||||
|
totalIssues={cycleDetails.progress_snapshot.total_issues}
|
||||||
|
isPeekView={Boolean(peekCycle)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{cycleDetails.total_issues > 0 && cycleDetails.distribution && (
|
||||||
|
<div className="h-full w-full border-t border-custom-border-200 pt-5">
|
||||||
|
<SidebarProgressStats
|
||||||
|
distribution={cycleDetails.distribution}
|
||||||
|
groupedIssues={{
|
||||||
|
backlog: cycleDetails.backlog_issues,
|
||||||
|
unstarted: cycleDetails.unstarted_issues,
|
||||||
|
started: cycleDetails.started_issues,
|
||||||
|
completed: cycleDetails.completed_issues,
|
||||||
|
cancelled: cycleDetails.cancelled_issues,
|
||||||
|
}}
|
||||||
|
totalIssues={cycleDetails.total_issues}
|
||||||
|
isPeekView={Boolean(peekCycle)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
|
Loading…
Reference in New Issue
Block a user