From 45936e9702fa835245ac3e83609a8659b7f18e44 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 29 Jul 2024 18:41:46 +0530 Subject: [PATCH 1/8] use common getIssues from issue service instead of multiple different services for modules and cycles --- web/core/store/issue/cycle/filter.store.ts | 7 ++++++- web/core/store/issue/cycle/issue.store.ts | 4 ++-- web/core/store/issue/module/filter.store.ts | 7 ++++++- web/core/store/issue/module/issue.store.ts | 4 ++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/web/core/store/issue/cycle/filter.store.ts b/web/core/store/issue/cycle/filter.store.ts index b2045704974..954fe3370e4 100644 --- a/web/core/store/issue/cycle/filter.store.ts +++ b/web/core/store/issue/cycle/filter.store.ts @@ -119,7 +119,12 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI groupId: string | undefined, subGroupId: string | undefined ) => { - const filterParams = this.getAppliedFilters(cycleId); + let filterParams = this.getAppliedFilters(cycleId); + + if (!filterParams) { + filterParams = {}; + } + filterParams["cycle"] = cycleId; const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId); return paginationParams; diff --git a/web/core/store/issue/cycle/issue.store.ts b/web/core/store/issue/cycle/issue.store.ts index c8d34ecdbee..39a1d397885 100644 --- a/web/core/store/issue/cycle/issue.store.ts +++ b/web/core/store/issue/cycle/issue.store.ts @@ -185,7 +185,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues { // get params from pagination options const params = this.issueFilterStore?.getFilterParams(options, cycleId, undefined, undefined, undefined); // call the fetch issues API with the params - const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params, { + const response = await this.issueService.getIssues(workspaceSlug, projectId, params, { signal: this.controller.signal, }); @@ -233,7 +233,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues { subGroupId ); // call the fetch issues API with the params for next page in issues - const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params); + const response = await this.issueService.getIssues(workspaceSlug, projectId, cycleId, params); // after the next page of issues are fetched, call the base method to process the response this.onfetchNexIssues(response, groupId, subGroupId); diff --git a/web/core/store/issue/module/filter.store.ts b/web/core/store/issue/module/filter.store.ts index 1e02ba13adf..d64a4c0f015 100644 --- a/web/core/store/issue/module/filter.store.ts +++ b/web/core/store/issue/module/filter.store.ts @@ -119,7 +119,12 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul groupId: string | undefined, subGroupId: string | undefined ) => { - const filterParams = this.getAppliedFilters(moduleId); + let filterParams = this.getAppliedFilters(moduleId); + + if (!filterParams) { + filterParams = {}; + } + filterParams["module"] = moduleId; const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId); return paginationParams; diff --git a/web/core/store/issue/module/issue.store.ts b/web/core/store/issue/module/issue.store.ts index 9131c8833f0..9f08f32b65e 100644 --- a/web/core/store/issue/module/issue.store.ts +++ b/web/core/store/issue/module/issue.store.ts @@ -142,7 +142,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues { // get params from pagination options const params = this.issueFilterStore?.getFilterParams(options, moduleId, undefined, undefined, undefined); // call the fetch issues API with the params - const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params, { + const response = await this.issueService.getIssues(workspaceSlug, projectId, params, { signal: this.controller.signal, }); @@ -190,7 +190,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues { subGroupId ); // call the fetch issues API with the params for next page in issues - const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params); + const response = await this.issueService.getIssues(workspaceSlug, projectId, params); // after the next page of issues are fetched, call the base method to process the response this.onfetchNexIssues(response, groupId, subGroupId); From 33dc9316b157f55e8597ff58ed60acc2f6106538 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 23 Sep 2024 18:36:16 +0530 Subject: [PATCH 2/8] add group by to server constants --- packages/constants/issue.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/constants/issue.ts b/packages/constants/issue.ts index 67f8af56fd6..5db398c7634 100644 --- a/packages/constants/issue.ts +++ b/packages/constants/issue.ts @@ -13,6 +13,19 @@ export enum EIssueGroupByToServerOptions { "created_by" = "created_by", } +export enum EIssueGroupBYServerToProperty { + "state_id" = "state_id", + "priority" = "priority", + "labels__id" = "label_ids", + "state__group" = "state__group", + "assignees__id" = "assignee_ids", + "cycle_id" = "cycle_id", + "issue_module__module_id" = "module_ids", + "target_date" = "target_date", + "project_id" = "project_id", + "created_by" = "created_by", +} + export enum EServerGroupByToFilterOptions { "state_id" = "state", "priority" = "priority", From e0f0a09860f990664b6f4fe08c7e47a9599116a1 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 23 Sep 2024 18:47:38 +0530 Subject: [PATCH 3/8] change issue detail's overview's is loading logic to the loader from the store --- .../(detail)/[archivedIssueId]/page.tsx | 6 +- .../issues/(detail)/[issueId]/page.tsx | 6 +- .../components/issues/peek-overview/root.tsx | 8 +- .../store/issue/issue-details/issue.store.ts | 83 +++++++++++-------- 4 files changed, 56 insertions(+), 47 deletions(-) diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx index f0c41ab85d5..69d24b167b2 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/archives/issues/(detail)/[archivedIssueId]/page.tsx @@ -19,12 +19,12 @@ const ArchivedIssueDetailsPage = observer(() => { // hooks const { fetchIssue, - issue: { getIssueById }, + issue: { getIssueById, isFetchingIssueDetails }, } = useIssueDetail(); const { getProjectById } = useProject(); - const { isLoading } = useSWR( + useSWR( workspaceSlug && projectId && archivedIssueId ? `ARCHIVED_ISSUE_DETAIL_${workspaceSlug}_${projectId}_${archivedIssueId}` : null, @@ -40,7 +40,7 @@ const ArchivedIssueDetailsPage = observer(() => { if (!issue) return <>; - const issueLoader = !issue || isLoading ? true : false; + const issueLoader = !issue || isFetchingIssueDetails ? true : false; return ( <> diff --git a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx index c5f604abc36..7956c96fb86 100644 --- a/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/projects/(detail)/[projectId]/issues/(detail)/[issueId]/page.tsx @@ -27,12 +27,12 @@ const IssueDetailsPage = observer(() => { // store hooks const { fetchIssue, - issue: { getIssueById }, + issue: { getIssueById, isFetchingIssueDetails }, } = useIssueDetail(); const { getProjectById } = useProject(); const { toggleIssueDetailSidebar, issueDetailSidebarCollapsed } = useAppTheme(); // fetching issue details - const { isLoading, error } = useSWR( + const { error } = useSWR( workspaceSlug && projectId && issueId ? `ISSUE_DETAIL_${workspaceSlug}_${projectId}_${issueId}` : null, workspaceSlug && projectId && issueId ? () => fetchIssue(workspaceSlug.toString(), projectId.toString(), issueId.toString()) @@ -41,7 +41,7 @@ const IssueDetailsPage = observer(() => { // derived values const issue = getIssueById(issueId?.toString() || "") || undefined; const project = (issue?.project_id && getProjectById(issue?.project_id)) || undefined; - const issueLoader = !issue || isLoading ? true : false; + const issueLoader = !issue || isFetchingIssueDetails ? true : false; const pageTitle = project && issue ? `${project?.identifier}-${issue?.sequence_id} ${issue?.name}` : undefined; useEffect(() => { diff --git a/web/core/components/issues/peek-overview/root.tsx b/web/core/components/issues/peek-overview/root.tsx index 15dafab66b1..0283585d530 100644 --- a/web/core/components/issues/peek-overview/root.tsx +++ b/web/core/components/issues/peek-overview/root.tsx @@ -35,14 +35,13 @@ export const IssuePeekOverview: FC = observer((props) => { const { peekIssue, setPeekIssue, - issue: { fetchIssue }, + issue: { fetchIssue, isFetchingIssueDetails }, fetchActivities, } = useIssueDetail(); const { issues } = useIssuesStore(); const { captureIssueEvent } = useEventTracker(); // state - const [loader, setLoader] = useState(true); const [error, setError] = useState(false); const removeRoutePeekId = () => { @@ -54,7 +53,6 @@ export const IssuePeekOverview: FC = observer((props) => { () => ({ fetch: async (workspaceSlug: string, projectId: string, issueId: string, loader = true) => { try { - setLoader(loader); setError(false); await fetchIssue( workspaceSlug, @@ -62,10 +60,8 @@ export const IssuePeekOverview: FC = observer((props) => { issueId, is_archived ? "ARCHIVED" : is_draft ? "DRAFT" : "DEFAULT" ); - setLoader(false); setError(false); } catch (error) { - setLoader(false); setError(true); console.error("Error fetching the parent issue"); } @@ -348,7 +344,7 @@ export const IssuePeekOverview: FC = observer((props) => { workspaceSlug={peekIssue.workspaceSlug} projectId={peekIssue.projectId} issueId={peekIssue.issueId} - isLoading={loader} + isLoading={isFetchingIssueDetails} isError={error} is_archived={is_archived} disabled={!isEditable} diff --git a/web/core/store/issue/issue-details/issue.store.ts b/web/core/store/issue/issue-details/issue.store.ts index 871be85e026..120103f2cdd 100644 --- a/web/core/store/issue/issue-details/issue.store.ts +++ b/web/core/store/issue/issue-details/issue.store.ts @@ -1,4 +1,4 @@ -import { makeObservable } from "mobx"; +import { makeObservable, observable } from "mobx"; import { computedFn } from "mobx-utils"; // types import { TIssue } from "@plane/types"; @@ -32,11 +32,13 @@ export interface IIssueStoreActions { } export interface IIssueStore extends IIssueStoreActions { + isFetchingIssueDetails: boolean; // helper methods getIssueById: (issueId: string) => TIssue | undefined; } export class IssueStore implements IIssueStore { + isFetchingIssueDetails: boolean = false; // root store rootIssueDetailStore: IIssueDetail; // services @@ -45,7 +47,9 @@ export class IssueStore implements IIssueStore { issueDraftService; constructor(rootStore: IIssueDetail) { - makeObservable(this, {}); + makeObservable(this, { + isFetchingIssueDetails: observable.ref, + }); // root store this.rootIssueDetailStore = rootStore; // services @@ -66,7 +70,9 @@ export class IssueStore implements IIssueStore { expand: "issue_reactions,issue_attachment,issue_link,parent", }; - let issue: TIssue; + let issue: TIssue | undefined; + + this.isFetchingIssueDetails = true; if (issueType === "ARCHIVED") issue = await this.issueArchiveService.retrieveArchivedIssue(workspaceSlug, projectId, issueId, query); @@ -76,38 +82,7 @@ export class IssueStore implements IIssueStore { if (!issue) throw new Error("Issue not found"); - const issuePayload: TIssue = { - id: issue?.id, - sequence_id: issue?.sequence_id, - name: issue?.name, - description_html: issue?.description_html, - sort_order: issue?.sort_order, - state_id: issue?.state_id, - priority: issue?.priority, - label_ids: issue?.label_ids, - assignee_ids: issue?.assignee_ids, - estimate_point: issue?.estimate_point, - sub_issues_count: issue?.sub_issues_count, - attachment_count: issue?.attachment_count, - link_count: issue?.link_count, - project_id: issue?.project_id, - parent_id: issue?.parent_id, - cycle_id: issue?.cycle_id, - module_ids: issue?.module_ids, - type_id: issue?.type_id, - created_at: issue?.created_at, - updated_at: issue?.updated_at, - start_date: issue?.start_date, - target_date: issue?.target_date, - completed_at: issue?.completed_at, - archived_at: issue?.archived_at, - created_by: issue?.created_by, - updated_by: issue?.updated_by, - is_draft: issue?.is_draft, - is_subscribed: issue?.is_subscribed, - }; - - this.rootIssueDetailStore.rootIssueStore.issues.addIssue([issuePayload]); + this.addIssueToStore(issue); // store handlers from issue detail // parent @@ -150,6 +125,44 @@ export class IssueStore implements IIssueStore { return issue; }; + addIssueToStore = (issue: TIssue) => { + const issuePayload: TIssue = { + id: issue?.id, + sequence_id: issue?.sequence_id, + name: issue?.name, + description_html: issue?.description_html, + sort_order: issue?.sort_order, + state_id: issue?.state_id, + priority: issue?.priority, + label_ids: issue?.label_ids, + assignee_ids: issue?.assignee_ids, + estimate_point: issue?.estimate_point, + sub_issues_count: issue?.sub_issues_count, + attachment_count: issue?.attachment_count, + link_count: issue?.link_count, + project_id: issue?.project_id, + parent_id: issue?.parent_id, + cycle_id: issue?.cycle_id, + module_ids: issue?.module_ids, + type_id: issue?.type_id, + created_at: issue?.created_at, + updated_at: issue?.updated_at, + start_date: issue?.start_date, + target_date: issue?.target_date, + completed_at: issue?.completed_at, + archived_at: issue?.archived_at, + created_by: issue?.created_by, + updated_by: issue?.updated_by, + is_draft: issue?.is_draft, + is_subscribed: issue?.is_subscribed, + }; + + this.rootIssueDetailStore.rootIssueStore.issues.addIssue([issuePayload]); + this.isFetchingIssueDetails = false; + + return issuePayload; + }; + updateIssue = async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => { await this.rootIssueDetailStore.rootIssueStore.projectIssues.updateIssue(workspaceSlug, projectId, issueId, data); await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId); From d6f633396cae0b36adf6431d1094ef8f04a9ceb7 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 23 Sep 2024 18:48:03 +0530 Subject: [PATCH 4/8] add extra method in local storage --- web/core/hooks/use-local-storage.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/web/core/hooks/use-local-storage.tsx b/web/core/hooks/use-local-storage.tsx index e13165bf80e..538b8a93b77 100644 --- a/web/core/hooks/use-local-storage.tsx +++ b/web/core/hooks/use-local-storage.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from "react"; -const getValueFromLocalStorage = (key: string, defaultValue: any) => { +export const getValueFromLocalStorage = (key: string, defaultValue: any) => { if (typeof window === undefined || typeof window === "undefined") return defaultValue; try { const item = window.localStorage.getItem(key); @@ -11,6 +11,16 @@ const getValueFromLocalStorage = (key: string, defaultValue: any) => { } }; +export const setValueIntoLocalStorage = (key: string, value: any) => { + if (typeof window === undefined || typeof window === "undefined") return false; + try { + window.localStorage.setItem(key, JSON.stringify(value)); + return true; + } catch (error) { + return false; + } +}; + const useLocalStorage = (key: string, initialValue: T) => { const [storedValue, setStoredValue] = useState(() => getValueFromLocalStorage(key, initialValue)); From bbecefbe5c8e3a0ec8789e6a5d4cbfbb23405843 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 23 Sep 2024 18:48:38 +0530 Subject: [PATCH 5/8] Kanban render 10 issues by default per column --- web/core/components/issues/issue-layouts/kanban/block.tsx | 3 +++ .../components/issues/issue-layouts/kanban/blocks-list.tsx | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/web/core/components/issues/issue-layouts/kanban/block.tsx b/web/core/components/issues/issue-layouts/kanban/block.tsx index e894398c571..29696b3d4f7 100644 --- a/web/core/components/issues/issue-layouts/kanban/block.tsx +++ b/web/core/components/issues/issue-layouts/kanban/block.tsx @@ -39,6 +39,7 @@ interface IssueBlockProps { quickActions: TRenderQuickActions; canEditProperties: (projectId: string | undefined) => boolean; scrollableContainerRef?: MutableRefObject; + shouldRenderByDefault?: boolean; } interface IssueDetailsBlockProps { @@ -114,6 +115,7 @@ export const KanbanIssueBlock: React.FC = observer((props) => { quickActions, canEditProperties, scrollableContainerRef, + shouldRenderByDefault, } = props; const cardRef = useRef(null); @@ -222,6 +224,7 @@ export const KanbanIssueBlock: React.FC = observer((props) => { defaultHeight="100px" horizontalOffset={100} verticalOffset={200} + defaultValue={shouldRenderByDefault} > = observer((p <> {issueIds && issueIds.length > 0 ? ( <> - {issueIds.map((issueId) => { + {issueIds.map((issueId, index) => { if (!issueId) return null; let draggableId = issueId; @@ -50,6 +50,7 @@ export const KanbanIssueBlocksList: React.FC = observer((p issueId={issueId} groupId={groupId} subGroupId={sub_group_id} + shouldRenderByDefault={index <= 10} issuesMap={issuesMap} displayProperties={displayProperties} updateIssue={updateIssue} From d279013b95a4c45896b41aee2c6b19c39e188fc1 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Mon, 23 Sep 2024 20:16:41 +0530 Subject: [PATCH 6/8] fix height in group virtualization --- web/core/components/issues/issue-layouts/kanban/default.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/core/components/issues/issue-layouts/kanban/default.tsx b/web/core/components/issues/issue-layouts/kanban/default.tsx index ebc98baa229..696ba5d2b84 100644 --- a/web/core/components/issues/issue-layouts/kanban/default.tsx +++ b/web/core/components/issues/issue-layouts/kanban/default.tsx @@ -186,7 +186,7 @@ export const KanBan: React.FC = observer((props) => { verticalOffset={100} horizontalOffset={100} root={scrollableContainerRef} - classNames="relative h-full" + classNames="h-full min-h-[120px]" defaultHeight={`${groupHeight}px`} placeholderChildren={ Date: Tue, 24 Sep 2024 13:57:54 +0530 Subject: [PATCH 7/8] remove debounced code for Kanban fetching more issues per column --- .../issues/issue-layouts/kanban/base-kanban-root.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx index 0578348a4ee..e94e73db79a 100644 --- a/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -93,12 +93,6 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas [fetchNextIssues] ); - const debouncedFetchMoreIssues = debounce( - (groupId?: string, subgroupId?: string) => fetchMoreIssues(groupId, subgroupId), - 300, - { leading: true, trailing: false } - ); - const groupedIssueIds = issues?.groupedIssueIds; const userDisplayFilters = displayFilters || null; @@ -275,7 +269,7 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas addIssuesToView={addIssuesToView} scrollableContainerRef={scrollableContainerRef} handleOnDrop={handleOnDrop} - loadMoreIssues={debouncedFetchMoreIssues} + loadMoreIssues={fetchMoreIssues} /> From abf341bde9d48ddbe738158b496bd87b8fe0e1d7 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Tue, 24 Sep 2024 14:16:01 +0530 Subject: [PATCH 8/8] fix lint errors --- web/core/components/common/logo.tsx | 4 ++-- .../issues/issue-layouts/kanban/base-kanban-root.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/web/core/components/common/logo.tsx b/web/core/components/common/logo.tsx index 52d3dedb9bf..02b26a10be9 100644 --- a/web/core/components/common/logo.tsx +++ b/web/core/components/common/logo.tsx @@ -3,12 +3,12 @@ import { FC } from "react"; // emoji-picker-react import { Emoji } from "emoji-picker-react"; -// import { icons } from "lucide-react"; -import useFontFaceObserver from "use-font-face-observer"; import { TLogoProps } from "@plane/types"; // helpers import { LUCIDE_ICONS_LIST } from "@plane/ui"; import { emojiCodeToUnicode } from "@/helpers/emoji.helper"; +// import { icons } from "lucide-react"; +import useFontFaceObserver from "use-font-face-observer"; type Props = { logo: TLogoProps; diff --git a/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx index e94e73db79a..fe2b2eee1c1 100644 --- a/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/core/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -4,7 +4,6 @@ import { FC, useCallback, useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element"; -import debounce from "lodash/debounce"; import { observer } from "mobx-react"; import { useParams, usePathname } from "next/navigation"; import { DeleteIssueModal } from "@/components/issues";