[WEB-914]: fix: Exception due to cycles and modules for undefined fields (#4127)

* fix cycle types

* fix module types
This commit is contained in:
rahulramesha 2024-04-05 20:05:55 +05:30 committed by GitHub
parent 62dac421dc
commit 90609b306f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 68 additions and 63 deletions

View File

@ -6,8 +6,8 @@ export interface ICycle {
backlog_issues: number;
cancelled_issues: number;
completed_issues: number;
created_at: Date;
created_by: string;
created_at?: string;
created_by?: string;
description: string;
distribution?: {
assignees: TAssigneesDistribution[];
@ -16,23 +16,22 @@ export interface ICycle {
};
end_date: string | null;
id: string;
is_favorite: boolean;
issue: string;
is_favorite?: boolean;
name: string;
owned_by_id: string;
progress_snapshot: TProgressSnapshot;
project_id: string;
status: TCycleGroups;
status?: TCycleGroups;
sort_order: number;
start_date: string | null;
started_issues: number;
sub_issues: number;
sub_issues?: number;
total_issues: number;
unstarted_issues: number;
updated_at: Date;
updated_by: string;
updated_at?: string;
updated_by?: string;
archived_at: string | null;
assignee_ids: string[];
assignee_ids?: string[];
view_props: {
filters: IIssueFilterOptions;
};

View File

@ -12,33 +12,33 @@ export interface IModule {
backlog_issues: number;
cancelled_issues: number;
completed_issues: number;
created_at: Date;
created_by: string;
created_at: string;
created_by?: string;
description: string;
description_text: any;
description_html: any;
distribution: {
distribution?: {
assignees: TAssigneesDistribution[];
completion_chart: TCompletionChartDistribution;
labels: TLabelsDistribution[];
};
id: string;
lead_id: string | null;
link_module: ILinkDetails[];
link_module?: ILinkDetails[];
member_ids: string[];
is_favorite: boolean;
name: string;
project_id: string;
sort_order: number;
sub_issues: number;
sub_issues?: number;
start_date: string | null;
started_issues: number;
status: TModuleStatus;
status?: TModuleStatus;
target_date: string | null;
total_issues: number;
unstarted_issues: number;
updated_at: Date;
updated_by: string;
updated_at: string;
updated_by?: string;
archived_at: string | null;
view_props: {
filters: IIssueFilterOptions;

View File

@ -22,11 +22,13 @@ import emptyMembers from "public/empty-state/empty_members.svg";
// types
type Props = {
distribution: {
distribution:
| {
assignees: TAssigneesDistribution[];
completion_chart: TCompletionChartDistribution;
labels: TLabelsDistribution[];
};
}
| undefined;
groupedIssues: {
[key: string]: number;
};
@ -129,7 +131,7 @@ export const SidebarProgressStats: React.FC<Props> = ({
as="div"
className="flex w-full flex-col gap-1.5 overflow-y-auto pt-3.5 vertical-scrollbar scrollbar-sm"
>
{distribution?.assignees.length > 0 ? (
{distribution && distribution?.assignees.length > 0 ? (
distribution.assignees.map((assignee, index) => {
if (assignee.assignee_id)
return (
@ -189,7 +191,7 @@ export const SidebarProgressStats: React.FC<Props> = ({
as="div"
className="flex w-full flex-col gap-1.5 overflow-y-auto pt-3.5 vertical-scrollbar scrollbar-sm"
>
{distribution?.labels.length > 0 ? (
{distribution && distribution?.labels.length > 0 ? (
distribution.labels.map((label, index) => (
<SingleProgressStats
key={label.label_id ?? `no-label-${index}`}

View File

@ -23,14 +23,14 @@ export const ActiveCycleHeader: FC<ActiveCycleHeaderProps> = (props) => {
const cycleOwnerDetails = cycle && cycle.owned_by_id ? getUserDetails(cycle.owned_by_id) : undefined;
const daysLeft = findHowManyDaysLeft(cycle.end_date) ?? 0;
const currentCycleStatus = cycle.status.toLocaleLowerCase() as TCycleGroups;
const currentCycleStatus = cycle.status?.toLocaleLowerCase() as TCycleGroups | undefined;
const cycleAssignee = (cycle.distribution?.assignees ?? []).filter((assignee) => assignee.display_name);
return (
<div className="flex items-center justify-between px-3 py-1.5 rounded border-[0.5px] border-custom-border-100 bg-custom-background-90">
<div className="flex items-center gap-2 cursor-default">
<CycleGroupIcon cycleGroup={currentCycleStatus} className="h-4 w-4" />
<CycleGroupIcon cycleGroup={currentCycleStatus ?? "draft"} className="h-4 w-4" />
<Tooltip tooltipContent={cycle.name} position="top-left">
<h3 className="break-words text-lg font-medium">{truncateText(cycle.name, 70)}</h3>
</Tooltip>

View File

@ -99,7 +99,7 @@ export const UpcomingCycleListItem: React.FC<Props> = observer((props) => {
{renderFormattedDate(cycle.start_date)} - {renderFormattedDate(cycle.end_date)}
</div>
)}
{cycle.assignee_ids?.length > 0 ? (
{cycle.assignee_ids && cycle.assignee_ids?.length > 0 ? (
<AvatarGroup showTooltip={false}>
{cycle.assignee_ids?.map((assigneeId) => {
const member = getUserDetails(assigneeId);

View File

@ -46,7 +46,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
if (!cycleDetails) return null;
const cycleStatus = cycleDetails.status.toLocaleLowerCase();
const cycleStatus = cycleDetails.status?.toLocaleLowerCase();
// const isCompleted = cycleStatus === "completed";
const endDate = getDate(cycleDetails.end_date);
const startDate = getDate(cycleDetails.start_date);
@ -189,7 +189,7 @@ export const CyclesBoardCard: FC<ICyclesBoardCard> = observer((props) => {
<LayersIcon className="h-4 w-4 text-custom-text-300" />
<span className="text-xs text-custom-text-300">{issueCount}</span>
</div>
{cycleDetails.assignee_ids.length > 0 && (
{cycleDetails.assignee_ids && cycleDetails.assignee_ids.length > 0 && (
<Tooltip tooltipContent={`${cycleDetails.assignee_ids.length} Members`} isMobile={isMobile}>
<div className="flex cursor-default items-center gap-1">
<AvatarGroup showTooltip={false}>

View File

@ -25,7 +25,7 @@ export const CycleGanttBlock: React.FC<Props> = observer((props) => {
// derived values
const cycleDetails = getCycleById(cycleId);
const { isMobile } = usePlatformOS();
const cycleStatus = cycleDetails?.status.toLocaleLowerCase();
const cycleStatus = cycleDetails?.status?.toLocaleLowerCase();
return (
<div
@ -74,7 +74,7 @@ export const CycleGanttSidebarBlock: React.FC<Props> = observer((props) => {
// derived values
const cycleDetails = getCycleById(cycleId);
const cycleStatus = cycleDetails?.status.toLocaleLowerCase();
const cycleStatus = cycleDetails?.status?.toLocaleLowerCase();
return (
<Link

View File

@ -216,7 +216,7 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
<div className="relative flex flex-shrink-0 items-center gap-3">
<Tooltip tooltipContent={`${cycleDetails.assignee_ids?.length} Members`} isMobile={isMobile}>
<div className="flex w-10 cursor-default items-center justify-center">
{cycleDetails.assignee_ids?.length > 0 ? (
{cycleDetails.assignee_ids && cycleDetails.assignee_ids?.length > 0 ? (
<AvatarGroup showTooltip={false}>
{cycleDetails.assignee_ids?.map((assignee_id) => {
const member = getUserDetails(assignee_id);

View File

@ -37,7 +37,7 @@ export const CycleQuickActions: React.FC<Props> = observer((props) => {
const { getCycleById, restoreCycle } = useCycle();
// derived values
const cycleDetails = getCycleById(cycleId);
const isCompleted = cycleDetails?.status.toLowerCase() === "completed";
const isCompleted = cycleDetails?.status?.toLowerCase() === "completed";
// auth
const isEditingAllowed =
!!currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId] >= EUserProjectRoles.MEMBER;

View File

@ -211,7 +211,7 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
// [workspaceSlug, projectId, cycleId, issueFilters, updateFilters]
// );
const cycleStatus = cycleDetails?.status.toLocaleLowerCase();
const cycleStatus = cycleDetails?.status?.toLocaleLowerCase();
const isCompleted = cycleStatus === "completed";
const startDate = getDate(cycleDetails?.start_date);

View File

@ -134,9 +134,11 @@ export const TransferIssuesModal: React.FC<Props> = observer((props) => {
<ContrastIcon className="h-5 w-5" />
<div className="flex w-full justify-between">
<span>{cycleDetails?.name}</span>
{cycleDetails.status && (
<span className=" flex items-center rounded-full bg-custom-background-80 px-2 capitalize">
{cycleDetails.status.toLocaleLowerCase()}
</span>
)}
</div>
</button>
);

View File

@ -146,7 +146,7 @@ export const CycleIssuesHeader: React.FC = observer(() => {
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
const issueCount = cycleDetails
? issueFilters?.displayFilters?.sub_issue
? issueFilters?.displayFilters?.sub_issue && cycleDetails?.sub_issues
? cycleDetails.total_issues + cycleDetails?.sub_issues
: cycleDetails.total_issues
: undefined;

View File

@ -147,7 +147,7 @@ export const ModuleIssuesHeader: React.FC = observer(() => {
currentProjectRole && [EUserProjectRoles.ADMIN, EUserProjectRoles.MEMBER].includes(currentProjectRole);
const issueCount = moduleDetails
? issueFilters?.displayFilters?.sub_issue
? issueFilters?.displayFilters?.sub_issue && moduleDetails.sub_issues
? moduleDetails.total_issues + moduleDetails.sub_issues
: moduleDetails.total_issues
: undefined;

View File

@ -62,7 +62,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
const isCompletedCycleSnapshotAvailable = !isEmpty(cycleDetails?.progress_snapshot ?? {});
const isCompletedAndEmpty = isCompletedCycleSnapshotAvailable || cycleDetails?.status.toLowerCase() === "completed";
const isCompletedAndEmpty = isCompletedCycleSnapshotAvailable || cycleDetails?.status?.toLowerCase() === "completed";
const emptyStateType = isCompletedAndEmpty
? EmptyStateType.PROJECT_CYCLE_COMPLETED_NO_ISSUES

View File

@ -51,7 +51,8 @@ export const FilterCycle: React.FC<Props> = observer((props) => {
else setItemsToRender(sortedOptions.length);
};
const cycleStatus = (status: TCycleGroups) => (status ? status.toLocaleLowerCase() : "draft") as TCycleGroups;
const cycleStatus = (status: TCycleGroups | undefined) =>
(status ? status.toLocaleLowerCase() : "draft") as TCycleGroups;
return (
<>

View File

@ -41,7 +41,7 @@ export const ModuleQuickActions: React.FC<Props> = observer((props) => {
const isEditingAllowed =
!!currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId] >= EUserProjectRoles.MEMBER;
const moduleState = moduleDetails?.status.toLocaleLowerCase();
const moduleState = moduleDetails?.status?.toLocaleLowerCase();
const isInArchivableGroup = !!moduleState && ["completed", "cancelled"].includes(moduleState);
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {

View File

@ -84,7 +84,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
const { setTrackElement, captureModuleEvent, captureEvent } = useEventTracker();
const moduleDetails = getModuleById(moduleId);
const moduleState = moduleDetails?.status.toLocaleLowerCase();
const moduleState = moduleDetails?.status?.toLocaleLowerCase();
const isInArchivableGroup = !!moduleState && ["completed", "cancelled"].includes(moduleState);
const { reset, control } = useForm({

View File

@ -13,6 +13,7 @@ import { ICycle, TCycleFilters } from "@plane/types";
export const orderCycles = (cycles: ICycle[]): ICycle[] => {
if (cycles.length === 0) return [];
const acceptedStatuses = ["current", "upcoming", "draft"];
const STATUS_ORDER: {
[key: string]: number;
} = {
@ -21,10 +22,10 @@ export const orderCycles = (cycles: ICycle[]): ICycle[] => {
draft: 3,
};
let filteredCycles = cycles.filter((c) => c.status.toLowerCase() !== "completed");
let filteredCycles = cycles.filter((c) => acceptedStatuses.includes(c.status?.toLowerCase() ?? ""));
filteredCycles = sortBy(filteredCycles, [
(c) => STATUS_ORDER[c.status.toLowerCase()],
(c) => (c.status.toLowerCase() === "upcoming" ? c.start_date : c.name.toLowerCase()),
(c) => STATUS_ORDER[c.status?.toLowerCase() ?? ""],
(c) => (c.status?.toLowerCase() === "upcoming" ? c.start_date : c.name.toLowerCase()),
]);
return filteredCycles;
@ -41,7 +42,7 @@ export const shouldFilterCycle = (cycle: ICycle, filter: TCycleFilters): boolean
Object.keys(filter).forEach((key) => {
const filterKey = key as keyof TCycleFilters;
if (filterKey === "status" && filter.status && filter.status.length > 0)
fallsInFilters = fallsInFilters && filter.status.includes(cycle.status.toLowerCase());
fallsInFilters = fallsInFilters && filter.status.includes(cycle.status?.toLowerCase() ?? "");
if (filterKey === "start_date" && filter.start_date && filter.start_date.length > 0) {
const startDate = getDate(cycle.start_date);
filter.start_date.forEach((dateFilter) => {

View File

@ -55,7 +55,7 @@ export const shouldFilterModule = (
Object.keys(filters).forEach((key) => {
const filterKey = key as keyof TModuleFilters;
if (filterKey === "status" && filters.status && filters.status.length > 0)
fallsInFilters = fallsInFilters && filters.status.includes(module.status.toLowerCase());
fallsInFilters = fallsInFilters && filters.status.includes(module.status?.toLowerCase() ?? "");
if (filterKey === "lead" && filters.lead && filters.lead.length > 0)
fallsInFilters = fallsInFilters && filters.lead.includes(`${module.lead_id}`);
if (filterKey === "members" && filters.members && filters.members.length > 0) {

View File

@ -256,7 +256,7 @@ export class CycleStore implements ICycleStore {
(c) =>
c.project_id === projectId &&
!c.archived_at &&
c.status.toLowerCase() === "completed" &&
c.status?.toLowerCase() === "completed" &&
c.name.toLowerCase().includes(searchQuery.toLowerCase()) &&
shouldFilterCycle(c, filters ?? {})
);

View File

@ -380,7 +380,7 @@ export class ModulesStore implements IModuleStore {
) => {
const originalModuleDetails = this.getModuleById(moduleId);
try {
const linkModules = originalModuleDetails?.link_module.map((link) =>
const linkModules = originalModuleDetails?.link_module?.map((link) =>
link.id === linkId ? { ...link, ...data } : link
);
runInAction(() => {
@ -407,7 +407,7 @@ export class ModulesStore implements IModuleStore {
deleteModuleLink = async (workspaceSlug: string, projectId: string, moduleId: string, linkId: string) =>
await this.moduleService.deleteModuleLink(workspaceSlug, projectId, moduleId, linkId).then(() => {
const moduleDetails = this.getModuleById(moduleId);
const linkModules = moduleDetails?.link_module.filter((link) => link.id !== linkId);
const linkModules = moduleDetails?.link_module?.filter((link) => link.id !== linkId);
runInAction(() => {
set(this.moduleMap, [moduleId, "link_module"], linkModules);
});