From 90609b306f36909a71a63c288e91aef7f1eeb175 Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Fri, 5 Apr 2024 20:05:55 +0530 Subject: [PATCH] [WEB-914]: fix: Exception due to cycles and modules for undefined fields (#4127) * fix cycle types * fix module types --- packages/types/src/cycle/cycle.d.ts | 17 ++++++----- packages/types/src/module/modules.d.ts | 16 +++++------ .../core/sidebar/sidebar-progress-stats.tsx | 16 ++++++----- web/components/cycles/active-cycle/header.tsx | 4 +-- .../upcoming-cycles-list-item.tsx | 2 +- .../cycles/board/cycles-board-card.tsx | 4 +-- web/components/cycles/gantt-chart/blocks.tsx | 28 +++++++++---------- .../cycles/list/cycles-list-item.tsx | 2 +- web/components/cycles/quick-actions.tsx | 2 +- web/components/cycles/sidebar.tsx | 2 +- .../cycles/transfer-issues-modal.tsx | 8 ++++-- web/components/headers/cycle-issues.tsx | 2 +- web/components/headers/module-issues.tsx | 2 +- .../issue-layouts/empty-states/cycle.tsx | 2 +- .../filters/header/filters/cycle.tsx | 3 +- web/components/modules/quick-actions.tsx | 2 +- web/components/modules/sidebar.tsx | 2 +- web/helpers/cycle.helper.ts | 9 +++--- web/helpers/module.helper.ts | 2 +- web/store/cycle.store.ts | 2 +- web/store/module.store.ts | 4 +-- 21 files changed, 68 insertions(+), 63 deletions(-) diff --git a/packages/types/src/cycle/cycle.d.ts b/packages/types/src/cycle/cycle.d.ts index 30724706b..28be2ec2a 100644 --- a/packages/types/src/cycle/cycle.d.ts +++ b/packages/types/src/cycle/cycle.d.ts @@ -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; }; diff --git a/packages/types/src/module/modules.d.ts b/packages/types/src/module/modules.d.ts index 7ba2c3b41..95b084f2f 100644 --- a/packages/types/src/module/modules.d.ts +++ b/packages/types/src/module/modules.d.ts @@ -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; diff --git a/web/components/core/sidebar/sidebar-progress-stats.tsx b/web/components/core/sidebar/sidebar-progress-stats.tsx index 21b508e28..528b8aa18 100644 --- a/web/components/core/sidebar/sidebar-progress-stats.tsx +++ b/web/components/core/sidebar/sidebar-progress-stats.tsx @@ -22,11 +22,13 @@ import emptyMembers from "public/empty-state/empty_members.svg"; // types type Props = { - distribution: { - assignees: TAssigneesDistribution[]; - completion_chart: TCompletionChartDistribution; - labels: TLabelsDistribution[]; - }; + distribution: + | { + assignees: TAssigneesDistribution[]; + completion_chart: TCompletionChartDistribution; + labels: TLabelsDistribution[]; + } + | undefined; groupedIssues: { [key: string]: number; }; @@ -129,7 +131,7 @@ export const SidebarProgressStats: React.FC = ({ 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 = ({ 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) => ( = (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 (
- +

{truncateText(cycle.name, 70)}

diff --git a/web/components/cycles/active-cycle/upcoming-cycles-list-item.tsx b/web/components/cycles/active-cycle/upcoming-cycles-list-item.tsx index 2e48a95b4..303d91752 100644 --- a/web/components/cycles/active-cycle/upcoming-cycles-list-item.tsx +++ b/web/components/cycles/active-cycle/upcoming-cycles-list-item.tsx @@ -99,7 +99,7 @@ export const UpcomingCycleListItem: React.FC = observer((props) => { {renderFormattedDate(cycle.start_date)} - {renderFormattedDate(cycle.end_date)}
)} - {cycle.assignee_ids?.length > 0 ? ( + {cycle.assignee_ids && cycle.assignee_ids?.length > 0 ? ( {cycle.assignee_ids?.map((assigneeId) => { const member = getUserDetails(assigneeId); diff --git a/web/components/cycles/board/cycles-board-card.tsx b/web/components/cycles/board/cycles-board-card.tsx index 34d395db4..b528e780b 100644 --- a/web/components/cycles/board/cycles-board-card.tsx +++ b/web/components/cycles/board/cycles-board-card.tsx @@ -46,7 +46,7 @@ export const CyclesBoardCard: FC = 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 = observer((props) => { {issueCount}
- {cycleDetails.assignee_ids.length > 0 && ( + {cycleDetails.assignee_ids && cycleDetails.assignee_ids.length > 0 && (
diff --git a/web/components/cycles/gantt-chart/blocks.tsx b/web/components/cycles/gantt-chart/blocks.tsx index acc081b4d..6cc48aa5d 100644 --- a/web/components/cycles/gantt-chart/blocks.tsx +++ b/web/components/cycles/gantt-chart/blocks.tsx @@ -25,7 +25,7 @@ export const CycleGanttBlock: React.FC = observer((props) => { // derived values const cycleDetails = getCycleById(cycleId); const { isMobile } = usePlatformOS(); - const cycleStatus = cycleDetails?.status.toLocaleLowerCase(); + const cycleStatus = cycleDetails?.status?.toLocaleLowerCase(); return (
= observer((props) => { 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/${cycleDetails?.project_id}/cycles/${cycleDetails?.id}`)} > @@ -74,7 +74,7 @@ export const CycleGanttSidebarBlock: React.FC = observer((props) => { // derived values const cycleDetails = getCycleById(cycleId); - const cycleStatus = cycleDetails?.status.toLocaleLowerCase(); + const cycleStatus = cycleDetails?.status?.toLocaleLowerCase(); return ( = observer((props) => { 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))" + : "" }`} />
{cycleDetails?.name}
diff --git a/web/components/cycles/list/cycles-list-item.tsx b/web/components/cycles/list/cycles-list-item.tsx index 7ee797bb2..827f91ad8 100644 --- a/web/components/cycles/list/cycles-list-item.tsx +++ b/web/components/cycles/list/cycles-list-item.tsx @@ -216,7 +216,7 @@ export const CyclesListItem: FC = observer((props) => {
- {cycleDetails.assignee_ids?.length > 0 ? ( + {cycleDetails.assignee_ids && cycleDetails.assignee_ids?.length > 0 ? ( {cycleDetails.assignee_ids?.map((assignee_id) => { const member = getUserDetails(assignee_id); diff --git a/web/components/cycles/quick-actions.tsx b/web/components/cycles/quick-actions.tsx index bf9aefc87..33a7df836 100644 --- a/web/components/cycles/quick-actions.tsx +++ b/web/components/cycles/quick-actions.tsx @@ -37,7 +37,7 @@ export const CycleQuickActions: React.FC = 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; diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index b72f9fd0d..106f3b3e0 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -211,7 +211,7 @@ export const CycleDetailsSidebar: React.FC = 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); diff --git a/web/components/cycles/transfer-issues-modal.tsx b/web/components/cycles/transfer-issues-modal.tsx index b5e1aa253..2c950002d 100644 --- a/web/components/cycles/transfer-issues-modal.tsx +++ b/web/components/cycles/transfer-issues-modal.tsx @@ -134,9 +134,11 @@ export const TransferIssuesModal: React.FC = observer((props) => {
{cycleDetails?.name} - - {cycleDetails.status.toLocaleLowerCase()} - + {cycleDetails.status && ( + + {cycleDetails.status.toLocaleLowerCase()} + + )}
); diff --git a/web/components/headers/cycle-issues.tsx b/web/components/headers/cycle-issues.tsx index e1113910f..2fd5ffe83 100644 --- a/web/components/headers/cycle-issues.tsx +++ b/web/components/headers/cycle-issues.tsx @@ -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; diff --git a/web/components/headers/module-issues.tsx b/web/components/headers/module-issues.tsx index 56da66d2f..13d81e49b 100644 --- a/web/components/headers/module-issues.tsx +++ b/web/components/headers/module-issues.tsx @@ -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; diff --git a/web/components/issues/issue-layouts/empty-states/cycle.tsx b/web/components/issues/issue-layouts/empty-states/cycle.tsx index 0f0b27c0c..6915600fd 100644 --- a/web/components/issues/issue-layouts/empty-states/cycle.tsx +++ b/web/components/issues/issue-layouts/empty-states/cycle.tsx @@ -62,7 +62,7 @@ export const CycleEmptyState: React.FC = 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 diff --git a/web/components/issues/issue-layouts/filters/header/filters/cycle.tsx b/web/components/issues/issue-layouts/filters/header/filters/cycle.tsx index 4e3fe6fb0..a991bc1f0 100644 --- a/web/components/issues/issue-layouts/filters/header/filters/cycle.tsx +++ b/web/components/issues/issue-layouts/filters/header/filters/cycle.tsx @@ -51,7 +51,8 @@ export const FilterCycle: React.FC = 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 ( <> diff --git a/web/components/modules/quick-actions.tsx b/web/components/modules/quick-actions.tsx index 5decec4ec..a6938c10a 100644 --- a/web/components/modules/quick-actions.tsx +++ b/web/components/modules/quick-actions.tsx @@ -41,7 +41,7 @@ export const ModuleQuickActions: React.FC = 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) => { diff --git a/web/components/modules/sidebar.tsx b/web/components/modules/sidebar.tsx index de6e3d02f..f219aeb95 100644 --- a/web/components/modules/sidebar.tsx +++ b/web/components/modules/sidebar.tsx @@ -84,7 +84,7 @@ export const ModuleDetailsSidebar: React.FC = 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({ diff --git a/web/helpers/cycle.helper.ts b/web/helpers/cycle.helper.ts index 1964ed5e3..8e3506120 100644 --- a/web/helpers/cycle.helper.ts +++ b/web/helpers/cycle.helper.ts @@ -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) => { diff --git a/web/helpers/module.helper.ts b/web/helpers/module.helper.ts index f5cbfe08a..31cfff0ff 100644 --- a/web/helpers/module.helper.ts +++ b/web/helpers/module.helper.ts @@ -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) { diff --git a/web/store/cycle.store.ts b/web/store/cycle.store.ts index ac90f9e5e..18bc58dc7 100644 --- a/web/store/cycle.store.ts +++ b/web/store/cycle.store.ts @@ -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 ?? {}) ); diff --git a/web/store/module.store.ts b/web/store/module.store.ts index 1a2896aa2..fd180545c 100644 --- a/web/store/module.store.ts +++ b/web/store/module.store.ts @@ -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); });