From 34549c5e740daa1b4e093acfaf7673c70264b51e Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Thu, 14 Dec 2023 12:29:57 +0530 Subject: [PATCH] optimize Kanban for performance --- .../issue-layouts/kanban/base-kanban-root.tsx | 40 +++--- .../issue-layouts/kanban/blocks-list.tsx | 10 +- .../issues/issue-layouts/kanban/default.tsx | 117 +++++++----------- .../issue-layouts/kanban/kanban-group.tsx | 102 +++++++++++++++ .../kanban/roots/project-root.tsx | 26 ++-- .../issues/issue-layouts/kanban/swimlanes.tsx | 47 ++++--- .../issues/issue-layouts/kanban/utils.tsx | 93 ++++++-------- 7 files changed, 253 insertions(+), 182 deletions(-) create mode 100644 web/components/issues/issue-layouts/kanban/kanban-group.tsx diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx index 43b815615..6d620b84f 100644 --- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -125,12 +125,15 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; - const canEditProperties = (projectId: string | undefined) => { - const isEditingAllowedBasedOnProject = - canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed; + const canEditProperties = useCallback( + (projectId: string | undefined) => { + const isEditingAllowedBasedOnProject = + canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed; - return enableInlineEditing && isEditingAllowedBasedOnProject; - }; + return enableInlineEditing && isEditingAllowedBasedOnProject; + }, + [canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed] + ); const onDragStart = (dragStart: DragStart) => { setDragState({ @@ -185,18 +188,21 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas [issueActions] ); - const renderQuickActions = (issue: IIssue, customActionButton?: React.ReactElement) => ( - handleIssues(issue, EIssueActions.DELETE)} - handleUpdate={ - issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined - } - handleRemoveFromView={ - issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined - } - /> + const renderQuickActions = useCallback( + (issue: IIssue, customActionButton?: React.ReactElement) => ( + handleIssues(issue, EIssueActions.DELETE)} + handleUpdate={ + issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined + } + handleRemoveFromView={ + issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined + } + /> + ), + [issueActions, handleIssues] ); const handleDeleteIssue = async () => { diff --git a/web/components/issues/issue-layouts/kanban/blocks-list.tsx b/web/components/issues/issue-layouts/kanban/blocks-list.tsx index c14689fcd..16ae94f9d 100644 --- a/web/components/issues/issue-layouts/kanban/blocks-list.tsx +++ b/web/components/issues/issue-layouts/kanban/blocks-list.tsx @@ -1,8 +1,10 @@ -// components -import { KanbanIssueBlock } from "components/issues"; +import { memo } from "react"; +//types import { IIssueDisplayProperties, IIssue } from "types"; import { EIssueActions } from "../types"; import { IIssueResponse } from "store_legacy/issues/types"; +// components +import { KanbanIssueBlock } from "components/issues"; interface IssueBlocksListProps { sub_group_id: string; @@ -17,7 +19,7 @@ interface IssueBlocksListProps { canEditProperties: (projectId: string | undefined) => boolean; } -export const KanbanIssueBlocksList: React.FC = (props) => { +const KanbanIssueBlocksListMemo: React.FC = (props) => { const { sub_group_id, columnId, @@ -61,3 +63,5 @@ export const KanbanIssueBlocksList: React.FC = (props) => ); }; + +export const KanbanIssueBlocksList = memo(KanbanIssueBlocksListMemo); diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index 7bb7b426b..a64847b4f 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -1,11 +1,10 @@ import React from "react"; import { observer } from "mobx-react-lite"; -import { Droppable } from "@hello-pangea/dnd"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components -import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from "components/issues"; import { HeaderGroupByCard } from "./headers/group-by-card"; +import { KanbanGroup } from "./kanban-group"; // types import { IIssueDisplayProperties, IIssue } from "types"; // constants @@ -76,81 +75,51 @@ const GroupByKanBan: React.FC = observer((props) => {
{list && list.length > 0 && - list.map((_list: IKanbanColumn) => ( -
- {sub_group_by === null && ( -
- -
- )} + list.map((_list: IKanbanColumn) => { + const verticalPosition = verticalAlignPosition(_list); -
- - {(provided: any, snapshot: any) => ( -
- {issues && !verticalAlignPosition(_list) ? ( - - ) : null} - - {provided.placeholder} -
- )} -
- -
- {enableQuickIssueCreate && !disableIssueCreation && ( - + {sub_group_by === null && ( +
+ - )} -
+
+ )} +
-
- ))} + ); + })}
); }); diff --git a/web/components/issues/issue-layouts/kanban/kanban-group.tsx b/web/components/issues/issue-layouts/kanban/kanban-group.tsx new file mode 100644 index 000000000..828ec9a75 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/kanban-group.tsx @@ -0,0 +1,102 @@ +import { Droppable } from "@hello-pangea/dnd"; +//types +import { IIssue, IIssueDisplayProperties, IIssueResponse } from "types"; +import { EIssueActions } from "../types"; +//components +import { KanBanQuickAddIssueForm, KanbanIssueBlocksList } from "."; + +interface IKanbanGroup { + groupId: string; + issues: IIssueResponse; + issueIds: any; + sub_group_by: string | null; + group_by: string | null; + sub_group_id: string; + isDragDisabled: boolean; + handleIssues: (issue: IIssue, action: EIssueActions) => void; + showEmptyGroup: boolean; + quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; + displayProperties: IIssueDisplayProperties | null; + enableQuickIssueCreate?: boolean; + quickAddCallback?: ( + workspaceSlug: string, + projectId: string, + data: IIssue, + viewId?: string + ) => Promise; + viewId?: string; + disableIssueCreation?: boolean; + canEditProperties: (projectId: string | undefined) => boolean; + verticalPosition: any; +} + +export const KanbanGroup = (props: IKanbanGroup) => { + const { + groupId, + sub_group_id, + group_by, + sub_group_by, + issues, + verticalPosition, + issueIds, + isDragDisabled, + showEmptyGroup, + handleIssues, + quickActions, + displayProperties, + canEditProperties, + enableQuickIssueCreate, + disableIssueCreation, + quickAddCallback, + viewId, + } = props; + + return ( +
+ + {(provided: any, snapshot: any) => ( +
+ {issues && !verticalPosition ? ( + + ) : null} + + {provided.placeholder} +
+ )} +
+ +
+ {enableQuickIssueCreate && !disableIssueCreation && ( + + )} +
+
+ ); +}; diff --git a/web/components/issues/issue-layouts/kanban/roots/project-root.tsx b/web/components/issues/issue-layouts/kanban/roots/project-root.tsx index 5950135a3..c33577444 100644 --- a/web/components/issues/issue-layouts/kanban/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/project-root.tsx @@ -1,14 +1,15 @@ import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; +import { useMemo } from "react"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // components import { ProjectIssueQuickActions } from "components/issues"; +import { BaseKanBanRoot } from "../base-kanban-root"; // types import { IIssue } from "types"; // constants import { EIssueActions } from "../../types"; -import { BaseKanBanRoot } from "../base-kanban-root"; import { EProjectStore } from "store_legacy/command-palette.store"; import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types"; @@ -25,18 +26,21 @@ export const KanBanLayout: React.FC = observer(() => { kanBanHelpers: kanBanHelperStore, } = useMobxStore(); - const issueActions = { - [EIssueActions.UPDATE]: async (issue: IIssue) => { - if (!workspaceSlug) return; + const issueActions = useMemo( + () => ({ + [EIssueActions.UPDATE]: async (issue: IIssue) => { + if (!workspaceSlug) return; - await issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue); - }, - [EIssueActions.DELETE]: async (issue: IIssue) => { - if (!workspaceSlug) return; + await issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue); + }, + [EIssueActions.DELETE]: async (issue: IIssue) => { + if (!workspaceSlug) return; - await issueStore.removeIssue(workspaceSlug, issue.project, issue.id); - }, - }; + await issueStore.removeIssue(workspaceSlug, issue.project, issue.id); + }, + }), + [issueStore] + ); const handleDragDrop = async ( source: any, diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index d856ce0ef..f7f70b8b5 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -1,5 +1,7 @@ import React from "react"; import { observer } from "mobx-react-lite"; +//mobx +import { useMobxStore } from "lib/mobx/store-provider"; // components import { KanBan } from "./default"; import { HeaderSubGroupByCard } from "./headers/sub-group-by-card"; @@ -11,7 +13,6 @@ import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } f import { EIssueActions } from "../types"; import { EProjectStore } from "store_legacy/command-palette.store"; import { IKanbanColumn, columnTypes, getKanbanColumns } from "./utils"; -import { useMobxStore } from "lib/mobx/store-provider"; interface ISubGroupSwimlaneHeader { issueIds: any; @@ -28,29 +29,27 @@ const SubGroupSwimlaneHeader: React.FC = ({ list, kanBanToggle, handleKanBanToggle, -}) => { - return ( -
- {list && - list.length > 0 && - list.map((_list: IKanbanColumn) => ( -
- -
- ))} -
- ); -}; +}) => ( +
+ {list && + list.length > 0 && + list.map((_list: IKanbanColumn) => ( +
+ +
+ ))} +
+); interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { issues: IIssueResponse; diff --git a/web/components/issues/issue-layouts/kanban/utils.tsx b/web/components/issues/issue-layouts/kanban/utils.tsx index 97a6b8b55..571015f13 100644 --- a/web/components/issues/issue-layouts/kanban/utils.tsx +++ b/web/components/issues/issue-layouts/kanban/utils.tsx @@ -51,62 +51,54 @@ const getProjectColumns = (project: IProjectStore): IKanbanColumn[] | undefined if (!projects) return; - return projects.map((project) => { - return { - id: project.id, - name: project.name, - Icon:
{renderEmoji(project.emoji || "")}
, - payload: { project: project.id }, - }; - }); + return projects.map((project) => ({ + id: project.id, + name: project.name, + Icon:
{renderEmoji(project.emoji || "")}
, + payload: { project: project.id }, + })); }; const getStateColumns = (projectState: IProjectStateStore): IKanbanColumn[] | undefined => { const { projectStates } = projectState; if (!projectStates) return; - return projectStates.map((state) => { - return { - id: state.id, - name: state.name, - Icon: ( -
- -
- ), - payload: { state: state.id }, - }; - }); + return projectStates.map((state) => ({ + id: state.id, + name: state.name, + Icon: ( +
+ +
+ ), + payload: { state: state.id }, + })); }; const getStateGroupColumns = () => { const stateGroups = ISSUE_STATE_GROUPS; - return stateGroups.map((stateGroup) => { - return { - id: stateGroup.key, - name: stateGroup.title, - Icon: ( -
- -
- ), - payload: {}, - }; - }); + return stateGroups.map((stateGroup) => ({ + id: stateGroup.key, + name: stateGroup.title, + Icon: ( +
+ +
+ ), + payload: {}, + })); }; const getPriorityColumns = () => { const priorities = ISSUE_PRIORITIES; - return priorities.map((priority) => { - return { - id: priority.key, - name: priority.title, - Icon: , - payload: { priority: priority.key }, - }; - }); + return priorities.map((priority) => ({ + id: priority.key, + name: priority.title, + Icon: , + payload: { priority: priority.key }, + })); }; const getLabelsColumns = (projectLabel: IProjectLabelStore) => { @@ -116,19 +108,14 @@ const getLabelsColumns = (projectLabel: IProjectLabelStore) => { const labels = [...projectLabels, { id: "None", name: "None", color: "#666" }]; - return labels.map((label) => { - return { - id: label.id, - name: label.name, - Icon: ( -
- ), - payload: { labels: [label.id] }, - }; - }); + return labels.map((label) => ({ + id: label.id, + name: label.name, + Icon: ( +
+ ), + payload: { labels: [label.id] }, + })); }; const getAssigneeColumns = (projectMember: IProjectMemberStore) => {