Merge pull request #1018 from makeplane/uat

release: v1.3.0-rc45
This commit is contained in:
sriram veeraghanta
2024-09-03 15:08:29 +05:30
committed by GitHub
21 changed files with 311 additions and 135 deletions

View File

@@ -1178,8 +1178,8 @@ class CycleAnalyticsEndpoint(BaseAPIView):
estimate__type="points",
).exists()
assignee_distribution = {}
label_distribution = {}
assignee_distribution = []
label_distribution = []
completion_chart = {}
if analytic_type == "points" and estimate_type:

View File

@@ -0,0 +1,67 @@
# Django imports
from django.core.management import BaseCommand, CommandError
# Module imports
from plane.db.models import User, Workspace, WorkspaceMember
class Command(BaseCommand):
help = "Change the ownership of the workspace"
def add_arguments(self, parser):
# Positional argument
parser.add_argument(
"--email", type=str, help="user email", required=True
)
parser.add_argument(
"--workspace_slug", type=str, help="workspace slug", required=True
)
def handle(self, *args, **options):
# get the user email from console
email = options.get("email", False)
workspace_slug = options.get("workspace_slug", False)
# raise error if email is not present
if not email or not workspace_slug:
raise CommandError("Error: Email and workspace slug is required")
# Fetch the user
user = User.objects.filter(email=email).first()
# Raise error if the user is not present
if not user:
raise CommandError(f"Error: User with {email} does not exists")
# Fetch the workspace
workspace = Workspace.objects.filter(slug=workspace_slug).first()
# Raise error if the workspace is not present
if not workspace:
raise CommandError(
f"Error: Workspace with {workspace_slug} does not exists"
)
# Change the ownership of the workspace
workspace.owner = user
workspace.save(update_fields=["owner"])
# Add the user to the workspace
workspace_member = WorkspaceMember.objects.filter(
workspace=workspace, member=user
).first()
# If the user is not present in the workspace, create the user
if not workspace_member:
workspace_member = WorkspaceMember.objects.create(
workspace=workspace, member=user, role=20
)
else:
workspace_member.role = 20
workspace_member.save(update_fields=["role"])
# Print the success message
self.stdout.write(
self.style.SUCCESS("Workspace ownership changed successfully")
)

View File

@@ -0,0 +1,65 @@
# Django imports
from django.core.management import BaseCommand, CommandError
from django.db import connection
# Module imports
from plane.db.models import User
class Command(BaseCommand):
help = "Delete the user"
def add_arguments(self, parser):
# Positional argument
parser.add_argument(
"--email", type=str, help="user email", required=True
)
def handle_lingering_constraints(self, user):
"""Delete the related objects from the blacklistedtoken and outstandingtoken tables"""
with connection.cursor() as cursor:
# First, delete entries from the blacklistedtoken table
cursor.execute(
"""
DELETE FROM token_blacklist_blacklistedtoken
WHERE token_id IN (
SELECT id FROM token_blacklist_outstandingtoken
WHERE user_id = %s
)
""",
[user.id],
)
# Then, delete entries from the outstandingtoken table
cursor.execute(
"""
DELETE FROM token_blacklist_outstandingtoken
WHERE user_id = %s
""",
[user.id],
)
def handle(self, *args, **options):
# get the user email from console
email = options.get("email", False)
# raise error if email is not present
if not email:
raise CommandError("Error: Email is required")
# Fetch the user
user = User.objects.filter(email=email).first()
# Raise error if the user is not present
if not user:
raise CommandError(f"Error: User with {email} does not exists")
# Delete the related objects
self.handle_lingering_constraints(user)
# Delete the user
user.delete()
# Print the success message
self.stdout.write(self.style.SUCCESS("User deleted successfully"))
return

View File

@@ -182,7 +182,7 @@ class IssueTypeEndpoint(BaseAPIView):
)
# Default cannot be made in active
if request.data.get("is_default") and not request.data.get(
if issue_type.is_default and not request.data.get(
"is_active"
):
return Response(

View File

@@ -142,16 +142,20 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
};
updateParentStats = (prevIssueState?: TIssue, nextIssueState?: TIssue, id?: string | undefined) => {
const distributionUpdates = getDistributionPathsPostUpdate(
prevIssueState,
nextIssueState,
this.rootIssueStore.rootStore.state.stateMap,
this.rootIssueStore.rootStore.projectEstimate?.currentActiveEstimate?.estimatePointById
);
try {
const distributionUpdates = getDistributionPathsPostUpdate(
prevIssueState,
nextIssueState,
this.rootIssueStore.rootStore.state.stateMap,
this.rootIssueStore.rootStore.projectEstimate?.currentActiveEstimate?.estimatePointById
);
const cycleId = id ?? this.cycleId;
const cycleId = id ?? this.cycleId;
cycleId && this.rootIssueStore.rootStore.cycle.updateCycleDistribution(distributionUpdates, cycleId);
cycleId && this.rootIssueStore.rootStore.cycle.updateCycleDistribution(distributionUpdates, cycleId);
} catch (e) {
console.warn("could not update cycle statistics");
}
};
/**

View File

@@ -98,17 +98,21 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
* @param id
*/
updateParentStats = (prevIssueState?: TIssue, nextIssueState?: TIssue, id?: string | undefined) => {
// get distribution updates
const distributionUpdates = getDistributionPathsPostUpdate(
prevIssueState,
nextIssueState,
this.rootIssueStore.rootStore.state.stateMap,
this.rootIssueStore.rootStore.projectEstimate?.currentActiveEstimate?.estimatePointById
);
try {
// get distribution updates
const distributionUpdates = getDistributionPathsPostUpdate(
prevIssueState,
nextIssueState,
this.rootIssueStore.rootStore.state.stateMap,
this.rootIssueStore.rootStore.projectEstimate?.currentActiveEstimate?.estimatePointById
);
const moduleId = id ?? this.moduleId;
const moduleId = id ?? this.moduleId;
moduleId && this.rootIssueStore.rootStore.module.updateModuleDistribution(distributionUpdates, moduleId);
moduleId && this.rootIssueStore.rootStore.module.updateModuleDistribution(distributionUpdates, moduleId);
} catch (e) {
console.warn("could not update module statistics");
}
};
/**

View File

@@ -1,5 +1,6 @@
import React, { useRef, useState } from "react";
import { Placement } from "@popperjs/core";
import { observer } from "mobx-react";
import { usePopper } from "react-popper";
import { Popover } from "@headlessui/react";
// types
@@ -21,12 +22,13 @@ export type TIssueTypeIconPicker = {
disabled?: boolean;
dropdownClassName?: string;
icon_props: TLogoProps["icon"];
isDefaultIssueType?: boolean;
onChange: (value: TLogoProps["icon"]) => void;
placement?: Placement;
size?: TIssueTypeLogoSize;
};
export const IssueTypeIconPicker: React.FC<TIssueTypeIconPicker> = (props) => {
export const IssueTypeIconPicker: React.FC<TIssueTypeIconPicker> = observer((props) => {
const {
isOpen,
handleToggle,
@@ -36,6 +38,7 @@ export const IssueTypeIconPicker: React.FC<TIssueTypeIconPicker> = (props) => {
disabled = false,
dropdownClassName,
icon_props,
isDefaultIssueType = false,
onChange,
size,
placement = "bottom-start",
@@ -71,7 +74,12 @@ export const IssueTypeIconPicker: React.FC<TIssueTypeIconPicker> = (props) => {
disabled={disabled}
onClick={() => handleToggle(!isOpen)}
>
<IssueTypeLogo icon_props={icon_props} size={size} containerClassName={iconContainerClassName} />
<IssueTypeLogo
icon_props={icon_props}
isDefault={isDefaultIssueType}
size={size}
containerClassName={iconContainerClassName}
/>
</button>
</Popover.Button>
{isOpen && (
@@ -104,4 +112,4 @@ export const IssueTypeIconPicker: React.FC<TIssueTypeIconPicker> = (props) => {
</>
</Popover>
);
};
});

View File

@@ -33,6 +33,7 @@ export const IssueTypeLogo: FC<Props> = (props) => {
const { icon_props, size = "sm", containerClassName, isDefault = false } = props;
// derived values
const LucideIcon = LUCIDE_ICONS_LIST.find((item) => item.name === icon_props?.name);
const renderDefaultIcon = isDefault && (!icon_props?.name || !icon_props?.background_color);
// if no value, return empty fragment
if (!icon_props?.name && !isDefault) return <></>;
@@ -47,7 +48,7 @@ export const IssueTypeLogo: FC<Props> = (props) => {
}}
className={cn("flex-shrink-0 grid place-items-center rounded bg-custom-background-80", containerClassName)} // fallback background color
>
{isDefault ? (
{renderDefaultIcon ? (
<LayersIcon
width={iconSizeMap[size]}
height={iconSizeMap[size]}

View File

@@ -1,6 +1,7 @@
"use client";
import { FormEvent, useState } from "react";
import { observer } from "mobx-react";
// ui
import { Button, Input, TextArea } from "@plane/ui";
// helpers
@@ -18,7 +19,7 @@ type Props = {
handleFormSubmit: () => Promise<void>;
};
export const CreateOrUpdateIssueTypeForm: React.FC<Props> = (props) => {
export const CreateOrUpdateIssueTypeForm: React.FC<Props> = observer((props) => {
const { formData, isSubmitting, handleFormDataChange, handleModalClose, handleFormSubmit } = props;
// state
const [isEmojiPickerOpen, setIsEmojiPickerOpen] = useState(false);
@@ -54,7 +55,8 @@ export const CreateOrUpdateIssueTypeForm: React.FC<Props> = (props) => {
isOpen={isEmojiPickerOpen}
handleToggle={(val: boolean) => setIsEmojiPickerOpen(val)}
icon_props={formData?.logo_props?.icon}
className="flex items-center justify-center flex-shrink0"
isDefaultIssueType={!!formData?.is_default}
className="flex items-center justify-center flex-shrink-0"
iconContainerClassName="flex items-center justify-center"
onChange={(value) => {
handleFormDataChange("logo_props", {
@@ -103,4 +105,4 @@ export const CreateOrUpdateIssueTypeForm: React.FC<Props> = (props) => {
</div>
</form>
);
};
});

View File

@@ -1,4 +1,5 @@
import { FC, useEffect, useState } from "react";
import { observer } from "mobx-react";
// ui
import { EModalPosition, EModalWidth, ModalCore, setToast, TOAST_TYPE } from "@plane/ui";
// helpers
@@ -24,7 +25,7 @@ const defaultIssueTypeData: Partial<TIssueType> = {
description: "",
};
export const CreateOrUpdateIssueTypeModal: FC<Props> = (props) => {
export const CreateOrUpdateIssueTypeModal: FC<Props> = observer((props) => {
const { issueTypeId, isModalOpen, handleModalClose } = props;
// states
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
@@ -131,4 +132,4 @@ export const CreateOrUpdateIssueTypeModal: FC<Props> = (props) => {
/>
</ModalCore>
);
};
});

View File

@@ -3,9 +3,7 @@ import { observer } from "mobx-react";
import Image from "next/image";
import { useTheme } from "next-themes";
// ui
import { Button, setToast, TOAST_TYPE } from "@plane/ui";
// components
import { ComicBoxButton } from "@/components/empty-state";
import { AlertModalCore, Button, setToast, TOAST_TYPE } from "@plane/ui";
// helpers
import { cn } from "@/helpers/common.helper";
// plane web hooks
@@ -26,6 +24,7 @@ export const IssueTypeEmptyState: FC<TIssueTypeEmptyState> = observer((props) =>
const { toggleProPlanModal } = useWorkspaceSubscription();
// states
const [isLoading, setIsLoading] = useState<boolean>(false);
const [enableIssueTypeConfirmation, setEnableIssueTypeConfirmation] = useState<boolean>(false);
// derived values
const isIssueTypeSettingsEnabled = useFlag(workspaceSlug, "ISSUE_TYPE_SETTINGS");
const resolvedEmptyStatePath = `/empty-state/issue-types/issue-type-${resolvedTheme === "light" ? "light" : "dark"}.svg`;
@@ -53,41 +52,65 @@ export const IssueTypeEmptyState: FC<TIssueTypeEmptyState> = observer((props) =>
};
return (
<div className="flex justify-center min-h-full overflow-y-auto py-10 px-5">
<div className={cn("flex flex-col gap-5 md:min-w-[24rem] max-w-[45rem]")}>
<div className="flex flex-col gap-1.5 flex-shrink">
<h3 className="text-xl font-semibold">
{isIssueTypeSettingsEnabled ? "Enable Issue Types" : "Upgrade to enable Issue Types."}
</h3>
<p className="text-sm text-custom-text-200">
Shape issues to your work with Issue Types. Customize with icons, backgrounds, and properties and configure
them for this project.
</p>
</div>
<Image
src={resolvedEmptyStatePath}
alt="issue type empty state"
width={384}
height={250}
layout="responsive"
lazyBoundary="100%"
/>
<div className="relative flex items-center justify-center gap-2 flex-shrink-0 w-full">
{isIssueTypeSettingsEnabled ? (
<ComicBoxButton
label={isLoading ? "Setting up" : "Enable"}
title="Once enabled, Issue Types can't be disabled."
description="Plane's Issues will become the default issue type for this project and will show up with its icon and background in this project."
disabled={isLoading}
onClick={() => handleEnableIssueTypes()}
/>
) : (
<Button disabled={isLoading} onClick={() => toggleProPlanModal(true)}>
Upgrade
</Button>
)}
<>
<AlertModalCore
variant="primary"
isOpen={enableIssueTypeConfirmation}
handleClose={() => setEnableIssueTypeConfirmation(false)}
handleSubmit={() => handleEnableIssueTypes()}
isSubmitting={isLoading}
title="Once enabled, Issue Types can't be disabled."
content={
<>
Plane&apos;s Issues will become the default issue type for this project and will show up with its icon and
background in this project.{" "}
<a
href="https://docs.plane.so/core-concepts/issues/issue-types"
target="_blank"
className="font-medium hover:underline"
>
Read the docs
</a>
.
</>
}
primaryButtonText={{
loading: "Setting up",
default: "Enable",
}}
/>
<div className="flex justify-center min-h-full overflow-y-auto py-10 px-5">
<div className={cn("flex flex-col gap-5 md:min-w-[24rem] max-w-[45rem]")}>
<div className="flex flex-col gap-1.5 flex-shrink">
<h3 className="text-xl font-semibold">
{isIssueTypeSettingsEnabled ? "Enable Issue Types" : "Upgrade to enable Issue Types."}
</h3>
<p className="text-sm text-custom-text-200">
Shape issues to your work with Issue Types. Customize with icons, backgrounds, and properties and
configure them for this project.
</p>
</div>
<Image
src={resolvedEmptyStatePath}
alt="issue type empty state"
width={384}
height={250}
layout="responsive"
lazyBoundary="100%"
/>
<div className="relative flex items-center justify-center gap-2 flex-shrink-0 w-full">
{isIssueTypeSettingsEnabled ? (
<Button disabled={isLoading} onClick={() => setEnableIssueTypeConfirmation(true)}>
Enable
</Button>
) : (
<Button disabled={isLoading} onClick={() => toggleProPlanModal(true)}>
Upgrade
</Button>
)}
</div>
</div>
</div>
</div>
</>
);
});

View File

@@ -74,24 +74,17 @@ export const IssueTypeListItem = observer((props: TIssueTypeListItem) => {
</div>
</div>
<div className="flex-shrink-0 flex">
{issueTypeDetail?.is_default && (
<div
className={cn(
"py-0.5 px-2 text-xs rounded text-custom-primary-100 bg-transparent border border-custom-primary-100 cursor-default font-medium"
)}
>
Default
</div>
)}
<div>
{!issueTypeDetail?.is_default && (
<IssueTypeQuickActions
issueTypeId={issueTypeId}
onEditIssueTypeIdChange={onEditIssueTypeIdChange}
/>
)}
</div>
<IssueTypeQuickActions issueTypeId={issueTypeId} onEditIssueTypeIdChange={onEditIssueTypeIdChange} />
</div>
{issueTypeDetail?.is_default && (
<div
className={cn(
"flex-shrink-0 py-0.5 px-2 text-xs rounded text-custom-primary-100 bg-transparent border border-custom-primary-100 cursor-default font-medium"
)}
>
Default
</div>
)}
</div>
}
className={cn("p-2")}

View File

@@ -1,3 +1,4 @@
import { observer } from "mobx-react";
// plane web components
import { RadioInput } from "@/components/estimates";
// plane web constants
@@ -12,7 +13,7 @@ type TPropertyMultiSelectProps = {
onChange: (value: boolean) => void;
};
export const PropertyMultiSelect = (props: TPropertyMultiSelectProps) => {
export const PropertyMultiSelect = observer((props: TPropertyMultiSelectProps) => {
const { value, variant = "RELATION_ISSUE", isDisabled = false, onChange } = props;
// derived values
const MULTI_SELECT_ATTRIBUTES = DROPDOWN_ATTRIBUTES[variant] ?? [];
@@ -42,4 +43,4 @@ export const PropertyMultiSelect = (props: TPropertyMultiSelectProps) => {
vertical
/>
);
};
});

View File

@@ -1,4 +1,5 @@
import { get, set } from "lodash";
import { observer } from "mobx-react";
// components
import { RadioInput } from "@/components/estimates";
// plane web constants
@@ -11,7 +12,7 @@ type TPropertySettingsConfigurationProps<T extends EIssuePropertyType> = {
onChange: (value: TIssuePropertySettingsMap[T]) => void;
};
export const PropertyRadioInputSelect = <T extends EIssuePropertyType>(
export const PropertyRadioInputSelect = observer(<T extends EIssuePropertyType>(
props: TPropertySettingsConfigurationProps<T>
) => {
const { settings, settingsConfigurations, isDisabled, onChange } = props;
@@ -44,9 +45,9 @@ export const PropertyRadioInputSelect = <T extends EIssuePropertyType>(
vertical
/>
);
};
});
export const PropertySettingsConfiguration = <T extends EIssuePropertyType>(
export const PropertySettingsConfiguration = observer(<T extends EIssuePropertyType>(
props: TPropertySettingsConfigurationProps<T>
) => {
const { settings, settingsConfigurations, isDisabled, onChange } = props;
@@ -64,4 +65,4 @@ export const PropertySettingsConfiguration = <T extends EIssuePropertyType>(
default:
return null;
}
};
});

View File

@@ -1,3 +1,4 @@
import { observer } from "mobx-react";
// ui
import { CustomSearchSelect } from "@plane/ui";
// helpers
@@ -25,7 +26,7 @@ type TPropertyTypeDropdownProps = {
error?: string;
};
export const PropertyTypeDropdown = (props: TPropertyTypeDropdownProps) => {
export const PropertyTypeDropdown = observer((props: TPropertyTypeDropdownProps) => {
const {
issueTypeId,
propertyType,
@@ -76,4 +77,4 @@ export const PropertyTypeDropdown = (props: TPropertyTypeDropdownProps) => {
) : (
<span className="px-2">{getIssuePropertyTypeDisplayName(propertyType, propertyRelationType)}</span>
);
};
});

View File

@@ -1,4 +1,5 @@
import { useState } from "react";
import { observer } from "mobx-react";
// ui
import { AlertModalCore, Checkbox, Tooltip } from "@plane/ui";
@@ -9,7 +10,7 @@ type TPropertyMandatoryFieldToggleProps = {
onMandatoryFieldChange: (value: boolean) => void;
};
export const PropertyMandatoryFieldToggle = (props: TPropertyMandatoryFieldToggleProps) => {
export const PropertyMandatoryFieldToggle = observer((props: TPropertyMandatoryFieldToggleProps) => {
const { value, defaultValue, isDisabled = false, onMandatoryFieldChange } = props;
// states
const [isDefaultResetConfirmationOpen, setIsDefaultResetConfirmationOpen] = useState<boolean>(false);
@@ -69,4 +70,4 @@ export const PropertyMandatoryFieldToggle = (props: TPropertyMandatoryFieldToggl
</div>
</>
);
};
});

View File

@@ -74,15 +74,17 @@ export const IssueTypeQuickActions: React.FC<Props> = observer((props) => {
</button>
</Tooltip>
</div>
<Tooltip
className="shadow"
tooltipContent={!!isIssueTypeEnabled ? "Click to disable" : "Click to enable"}
position="bottom"
>
<div className="w-12">
<ToggleSwitch value={!!isIssueTypeEnabled} onChange={handleEnableDisable} disabled={isLoading} />
</div>
</Tooltip>
{!issueTypeDetail?.is_default && (
<Tooltip
className="shadow"
tooltipContent={!!isIssueTypeEnabled ? "Click to disable" : "Click to enable"}
position="bottom"
>
<div className="w-12">
<ToggleSwitch value={!!isIssueTypeEnabled} onChange={handleEnableDisable} disabled={isLoading} />
</div>
</Tooltip>
)}
</div>
</>
);

View File

@@ -115,7 +115,7 @@ export const CloudEditionBadge = observer(() => {
<Button
tabIndex={-1}
variant="accent-primary"
className="w-full cursor-pointer rounded-2xl px-4 py-1.5 text-center text-sm font-medium outline-none"
className="w-fit min-w-24 cursor-pointer rounded-2xl px-4 py-1 text-center text-sm font-medium outline-none"
onClick={handleProPlanPurchaseModalOpen}
>
{renderButtonText()}
@@ -126,10 +126,10 @@ export const CloudEditionBadge = observer(() => {
<Button
tabIndex={-1}
variant="accent-primary"
className="w-full cursor-pointer rounded-2xl px-3 py-1.5 text-center text-sm font-medium outline-none"
className="w-fit cursor-pointer rounded-2xl px-4 py-1 text-center text-sm font-medium outline-none"
onClick={handlePaidPlanSuccessModalOpen}
>
<Image src={PlaneLogo} alt="Plane Pro" width={14} height={14} />
<Image src={PlaneLogo} alt="Plane Pro" width={12} height={12} />
Plane Pro
</Button>
)}

View File

@@ -13,12 +13,8 @@ export const useIssueProperty = <T extends EIssuePropertyType>(
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useIssueProperty must be used within StoreProvider");
if (!propertyId) {
console.warn("useIssueProperty: propertyId is not provided");
return undefined;
}
const issueProperty = context.issueTypes.data?.[typeId]?.getPropertyById<T>(propertyId);
if (!issueProperty) {
console.warn("useIssueProperty: issueProperty is not found");
}
return issueProperty;
};

View File

@@ -8,12 +8,8 @@ export const useIssueType = (typeId: string | null | undefined): IIssueType | un
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useIssueType must be used within StoreProvider");
if (!typeId) {
console.warn("useIssueType: typeId is not provided");
return undefined;
}
const issueType = context.issueTypes.data?.[typeId];
if (!issueType) {
console.warn("useIssueType: issueType is not found");
}
return issueType;
};

View File

@@ -242,21 +242,27 @@ export const updateDistribution = (distributionObject: ICycle | IModule, distrib
const { id } = assigneeUpdate;
// find and update the assignee issue counts
const issuesAssignee = distributionObject.distribution?.assignees?.find((assignee) => assignee.assignee_id === id);
if (issuesAssignee) {
issuesAssignee.completed_issues += assigneeUpdate.completed_issues ?? 0;
issuesAssignee.pending_issues += assigneeUpdate.pending_issues ?? 0;
issuesAssignee.total_issues += assigneeUpdate.total_issues;
if (Array.isArray(distributionObject.distribution?.assignees)) {
const issuesAssignee = distributionObject.distribution?.assignees?.find(
(assignee) => assignee.assignee_id === id
);
if (issuesAssignee) {
issuesAssignee.completed_issues += assigneeUpdate.completed_issues ?? 0;
issuesAssignee.pending_issues += assigneeUpdate.pending_issues ?? 0;
issuesAssignee.total_issues += assigneeUpdate.total_issues;
}
}
// find and update the assignee points
const pointsAssignee = distributionObject.estimate_distribution?.assignees?.find(
(assignee) => assignee.assignee_id === id
);
if (pointsAssignee) {
pointsAssignee.completed_estimates += assigneeUpdate.completed_estimates ?? 0;
pointsAssignee.pending_estimates += assigneeUpdate.pending_estimates ?? 0;
pointsAssignee.total_estimates += assigneeUpdate.total_estimates;
if (Array.isArray(distributionObject.estimate_distribution?.assignees)) {
const pointsAssignee = distributionObject.estimate_distribution?.assignees?.find(
(assignee) => assignee.assignee_id === id
);
if (pointsAssignee) {
pointsAssignee.completed_estimates += assigneeUpdate.completed_estimates ?? 0;
pointsAssignee.pending_estimates += assigneeUpdate.pending_estimates ?? 0;
pointsAssignee.total_estimates += assigneeUpdate.total_estimates;
}
}
}
@@ -264,19 +270,23 @@ export const updateDistribution = (distributionObject: ICycle | IModule, distrib
const { id } = labelUpdate;
// find and update the label issue counts
const issuesLabel = distributionObject.distribution?.labels?.find((label) => label.label_id === id);
if (issuesLabel) {
issuesLabel.completed_issues += labelUpdate.completed_issues ?? 0;
issuesLabel.pending_issues += labelUpdate.pending_issues ?? 0;
issuesLabel.total_issues += labelUpdate.total_issues;
if (Array.isArray(distributionObject.distribution?.labels)) {
const issuesLabel = distributionObject.distribution?.labels?.find((label) => label.label_id === id);
if (issuesLabel) {
issuesLabel.completed_issues += labelUpdate.completed_issues ?? 0;
issuesLabel.pending_issues += labelUpdate.pending_issues ?? 0;
issuesLabel.total_issues += labelUpdate.total_issues;
}
}
// find and update the label points
const pointsLabel = distributionObject.estimate_distribution?.labels?.find((label) => label.label_id === id);
if (pointsLabel) {
pointsLabel.completed_estimates += labelUpdate.completed_estimates ?? 0;
pointsLabel.pending_estimates += labelUpdate.pending_estimates ?? 0;
pointsLabel.total_estimates += labelUpdate.total_estimates;
if (Array.isArray(distributionObject.estimate_distribution?.labels)) {
const pointsLabel = distributionObject.estimate_distribution?.labels?.find((label) => label.label_id === id);
if (pointsLabel) {
pointsLabel.completed_estimates += labelUpdate.completed_estimates ?? 0;
pointsLabel.pending_estimates += labelUpdate.pending_estimates ?? 0;
pointsLabel.total_estimates += labelUpdate.total_estimates;
}
}
}
};