forked from github/plane
[WEB-914]: fix: Exception due to cycles and modules for undefined fields (#4127)
* fix cycle types * fix module types
This commit is contained in:
parent
62dac421dc
commit
90609b306f
17
packages/types/src/cycle/cycle.d.ts
vendored
17
packages/types/src/cycle/cycle.d.ts
vendored
@ -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;
|
||||
};
|
||||
|
16
packages/types/src/module/modules.d.ts
vendored
16
packages/types/src/module/modules.d.ts
vendored
@ -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;
|
||||
|
@ -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<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}`}
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
|
@ -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}>
|
||||
|
@ -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
|
||||
@ -35,12 +35,12 @@ export const CycleGanttBlock: React.FC<Props> = 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<Props> = observer((props) => {
|
||||
// derived values
|
||||
const cycleDetails = getCycleById(cycleId);
|
||||
|
||||
const cycleStatus = cycleDetails?.status.toLocaleLowerCase();
|
||||
const cycleStatus = cycleDetails?.status?.toLocaleLowerCase();
|
||||
|
||||
return (
|
||||
<Link
|
||||
@ -87,12 +87,12 @@ export const CycleGanttSidebarBlock: React.FC<Props> = 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))"
|
||||
: ""
|
||||
}`}
|
||||
/>
|
||||
<h6 className="flex-grow truncate text-sm font-medium">{cycleDetails?.name}</h6>
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
<span className=" flex items-center rounded-full bg-custom-background-80 px-2 capitalize">
|
||||
{cycleDetails.status.toLocaleLowerCase()}
|
||||
</span>
|
||||
{cycleDetails.status && (
|
||||
<span className=" flex items-center rounded-full bg-custom-background-80 px-2 capitalize">
|
||||
{cycleDetails.status.toLocaleLowerCase()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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 (
|
||||
<>
|
||||
|
@ -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>) => {
|
||||
|
@ -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({
|
||||
|
@ -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) => {
|
||||
|
@ -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) {
|
||||
|
@ -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 ?? {})
|
||||
);
|
||||
|
@ -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);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user