From cfbc1a91af7f774bc723ccc98643313a314ebc8c Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Wed, 10 Apr 2024 19:55:16 +0530 Subject: [PATCH] chore: optimised issue loaders and added enum for issue status and current tab (#4164) --- packages/types/src/inbox.d.ts | 17 +++- .../inbox/content/inbox-issue-header.tsx | 13 +-- .../inbox/modals/create-issue-modal.tsx | 3 +- web/components/inbox/root.tsx | 7 +- .../inbox/sidebar/inbox-list-item.tsx | 15 +--- web/components/inbox/sidebar/inbox-list.tsx | 1 - web/components/inbox/sidebar/root.tsx | 9 +- web/constants/inbox.tsx | 11 +-- web/helpers/inbox.helper.ts | 13 +++ .../projects/[projectId]/inbox/index.tsx | 5 +- web/store/inbox/inbox-issue.store.ts | 10 ++- web/store/inbox/project-inbox.store.ts | 83 +++++++++++++++---- 12 files changed, 131 insertions(+), 56 deletions(-) diff --git a/packages/types/src/inbox.d.ts b/packages/types/src/inbox.d.ts index cab9a2f29..68e62f7bf 100644 --- a/packages/types/src/inbox.d.ts +++ b/packages/types/src/inbox.d.ts @@ -2,9 +2,22 @@ import { TPaginationInfo } from "./common"; import { TIssuePriorities } from "./issues"; import { TIssue } from "./issues/base"; -export type TInboxIssueCurrentTab = "open" | "closed"; +enum EInboxIssueCurrentTab { + OPEN = "open", + CLOSED = "closed", +} -export type TInboxIssueStatus = -2 | -1 | 0 | 1 | 2; +enum EInboxIssueStatus { + PENDING = -2, + DECLINED = -1, + SNOOZED = 0, + ACCEPTED = 1, + DUPLICATE = 2, +} + +export type TInboxIssueCurrentTab = EInboxIssueCurrentTab; + +export type TInboxIssueStatus = EInboxIssueStatus; // filters export type TInboxIssueFilterMemberKeys = "assignee" | "created_by"; diff --git a/web/components/inbox/content/inbox-issue-header.tsx b/web/components/inbox/content/inbox-issue-header.tsx index 1e0aaf16f..931041400 100644 --- a/web/components/inbox/content/inbox-issue-header.tsx +++ b/web/components/inbox/content/inbox-issue-header.tsx @@ -16,6 +16,7 @@ import { IssueUpdateStatus } from "@/components/issues"; // constants import { EUserProjectRoles } from "@/constants/project"; // helpers +import { EInboxIssueStatus } from "@/helpers/inbox.helper"; import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useUser, useProjectInbox, useProject } from "@/hooks/store"; @@ -60,27 +61,27 @@ export const InboxIssueActionsHeader: FC = observer((p const issueLink = `${workspaceSlug}/projects/${issue?.project_id}/issues/${currentInboxIssueId}`; const handleInboxIssueAccept = async () => { - inboxIssue?.updateInboxIssueStatus(1); + await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.ACCEPTED); setAcceptIssueModal(false); }; const handleInboxIssueDecline = async () => { - inboxIssue?.updateInboxIssueStatus(-1); + await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.DECLINED); setDeclineIssueModal(false); }; - const handleInboxIssueDuplicate = (issueId: string) => { - inboxIssue?.updateInboxIssueDuplicateTo(issueId); + const handleInboxIssueDuplicate = async (issueId: string) => { + await inboxIssue?.updateInboxIssueDuplicateTo(issueId); }; const handleInboxSIssueSnooze = async (date: Date) => { - inboxIssue?.updateInboxIssueSnoozeTill(date); + await inboxIssue?.updateInboxIssueSnoozeTill(date); setIsSnoozeDateModalOpen(false); }; const handleInboxIssueDelete = async () => { if (!inboxIssue || !currentInboxIssueId) return; - deleteInboxIssue(workspaceSlug, projectId, currentInboxIssueId).finally(() => { + await deleteInboxIssue(workspaceSlug, projectId, currentInboxIssueId).finally(() => { router.push(`/${workspaceSlug}/projects/${projectId}/inbox`); }); }; diff --git a/web/components/inbox/modals/create-issue-modal.tsx b/web/components/inbox/modals/create-issue-modal.tsx index 9ec5cba1e..9336613c6 100644 --- a/web/components/inbox/modals/create-issue-modal.tsx +++ b/web/components/inbox/modals/create-issue-modal.tsx @@ -52,7 +52,7 @@ export const CreateInboxIssueModal: React.FC = observer((props) => { const workspaceStore = useWorkspace(); const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string; // store hooks - const { createInboxIssue, handleCurrentTab } = useProjectInbox(); + const { createInboxIssue } = useProjectInbox(); const { config: { envConfig }, } = useApplication(); @@ -80,7 +80,6 @@ export const CreateInboxIssueModal: React.FC = observer((props) => { .then((res) => { if (!createMore) { router.push(`/${workspaceSlug}/projects/${projectId}/inbox/?currentTab=open&inboxIssueId=${res?.issue?.id}`); - handleCurrentTab("open"); handleClose(); } else { reset(defaultValues); diff --git a/web/components/inbox/root.tsx b/web/components/inbox/root.tsx index 505729b2e..29a59308d 100644 --- a/web/components/inbox/root.tsx +++ b/web/components/inbox/root.tsx @@ -25,8 +25,11 @@ export const InboxIssueRoot: FC = observer((props) => { useSWR( inboxAccessible && workspaceSlug && projectId ? `PROJECT_INBOX_ISSUES_${workspaceSlug}_${projectId}` : null, - () => { - inboxAccessible && workspaceSlug && projectId && fetchInboxIssues(workspaceSlug.toString(), projectId.toString()); + async () => { + inboxAccessible && + workspaceSlug && + projectId && + (await fetchInboxIssues(workspaceSlug.toString(), projectId.toString())); }, { revalidateOnFocus: false, revalidateIfStale: false } ); diff --git a/web/components/inbox/sidebar/inbox-list-item.tsx b/web/components/inbox/sidebar/inbox-list-item.tsx index 837a6992f..e407f0b3c 100644 --- a/web/components/inbox/sidebar/inbox-list-item.tsx +++ b/web/components/inbox/sidebar/inbox-list-item.tsx @@ -1,4 +1,4 @@ -import { FC, MouseEvent, useEffect } from "react"; +import { FC, MouseEvent } from "react"; import { observer } from "mobx-react"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -32,19 +32,6 @@ export const InboxIssueListItem: FC = observer((props) const { isMobile } = usePlatformOS(); const issue = inboxIssue.issue; - useEffect(() => { - if (issue.id === inboxIssueId) { - setTimeout(() => { - const issueItemCard = document.getElementById(`inbox-issue-list-item-${issue.id}`); - if (issueItemCard) - issueItemCard.scrollIntoView({ - behavior: "smooth", - block: "center", - }); - }, 200); - } - }, [inboxIssueId, issue.id]); - const handleIssueRedirection = (event: MouseEvent, currentIssueId: string | undefined) => { if (inboxIssueId === currentIssueId) event.preventDefault(); }; diff --git a/web/components/inbox/sidebar/inbox-list.tsx b/web/components/inbox/sidebar/inbox-list.tsx index e6f536247..4bde1c43e 100644 --- a/web/components/inbox/sidebar/inbox-list.tsx +++ b/web/components/inbox/sidebar/inbox-list.tsx @@ -20,7 +20,6 @@ export const InboxIssueList: FC = observer((props) => { {inboxIssues.map((inboxIssue) => ( = observer((props) => { - {isLoading === "filter-loading" && !inboxIssuePaginationInfo?.next_page_results ? ( + {isLoading != undefined && isLoading === "filter-loading" && !inboxIssuePaginationInfo?.next_page_results ? ( ) : (
= observer((props) => { type={ getAppliedFiltersCount > 0 ? EmptyStateType.INBOX_SIDEBAR_FILTER_EMPTY_STATE - : currentTab === "open" + : currentTab === EInboxIssueCurrentTab.OPEN ? EmptyStateType.INBOX_SIDEBAR_OPEN_TAB : EmptyStateType.INBOX_SIDEBAR_CLOSED_TAB } diff --git a/web/constants/inbox.tsx b/web/constants/inbox.tsx index 6be3ad3fa..4a88b56cf 100644 --- a/web/constants/inbox.tsx +++ b/web/constants/inbox.tsx @@ -4,6 +4,7 @@ import { AlertTriangle, CheckCircle2, Clock, Copy, LucideIcon, XCircle } from "l import { TInboxIssueSortingOrderByKeys, TInboxIssueSortingSortByKeys, TInboxIssueStatus } from "@plane/types"; // helpers import { findHowManyDaysLeft } from "@/helpers/date-time.helper"; +import { EInboxIssueStatus } from "@/helpers/inbox.helper"; export const INBOX_STATUS: { key: string; @@ -16,7 +17,7 @@ export const INBOX_STATUS: { }[] = [ { key: "pending", - status: -2, + status: EInboxIssueStatus.PENDING, icon: AlertTriangle, title: "Pending", description: () => `Pending`, @@ -25,7 +26,7 @@ export const INBOX_STATUS: { }, { key: "declined", - status: -1, + status: EInboxIssueStatus.DECLINED, icon: XCircle, title: "Declined", description: () => `Declined`, @@ -34,7 +35,7 @@ export const INBOX_STATUS: { }, { key: "snoozed", - status: 0, + status: EInboxIssueStatus.SNOOZED, icon: Clock, title: "Snoozed", description: (snoozedTillDate: Date = new Date()) => `${findHowManyDaysLeft(snoozedTillDate)} days to go`, @@ -43,7 +44,7 @@ export const INBOX_STATUS: { }, { key: "accepted", - status: 1, + status: EInboxIssueStatus.ACCEPTED, icon: CheckCircle2, title: "Accepted", description: () => `Accepted`, @@ -52,7 +53,7 @@ export const INBOX_STATUS: { }, { key: "duplicate", - status: 2, + status: EInboxIssueStatus.DUPLICATE, icon: Copy, title: "Duplicate", description: () => `Duplicate`, diff --git a/web/helpers/inbox.helper.ts b/web/helpers/inbox.helper.ts index 9b2ac28e7..8a495cbd8 100644 --- a/web/helpers/inbox.helper.ts +++ b/web/helpers/inbox.helper.ts @@ -1,6 +1,19 @@ import { subDays } from "date-fns"; import { renderFormattedPayloadDate } from "./date-time.helper"; +export enum EInboxIssueCurrentTab { + OPEN = "open", + CLOSED = "closed", +} + +export enum EInboxIssueStatus { + PENDING = -2, + DECLINED = -1, + SNOOZED = 0, + ACCEPTED = 1, + DUPLICATE = 2, +} + export enum EPastDurationFilters { TODAY = "today", YESTERDAY = "yesterday", diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/inbox/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/inbox/index.tsx index 89e5c1022..3bc636ba9 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/inbox/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/inbox/index.tsx @@ -8,6 +8,8 @@ import { ProjectInboxHeader } from "@/components/headers"; import { InboxIssueRoot } from "@/components/inbox"; // constants import { EmptyStateType } from "@/constants/empty-state"; +// helpers +import { EInboxIssueCurrentTab } from "@/helpers/inbox.helper"; // hooks import { useProject, useProjectInbox } from "@/hooks/store"; // layouts @@ -40,7 +42,8 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => { const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Inbox` : "Plane - Inbox"; useEffect(() => { - if (navigationTab && currentTab != navigationTab) handleCurrentTab(navigationTab === "open" ? "open" : "closed"); + if (navigationTab && currentTab != navigationTab) + handleCurrentTab(navigationTab === "open" ? EInboxIssueCurrentTab.OPEN : EInboxIssueCurrentTab.CLOSED); }, [currentTab, navigationTab, handleCurrentTab]); return ( diff --git a/web/store/inbox/inbox-issue.store.ts b/web/store/inbox/inbox-issue.store.ts index c42740cc1..3f8689d6b 100644 --- a/web/store/inbox/inbox-issue.store.ts +++ b/web/store/inbox/inbox-issue.store.ts @@ -1,6 +1,8 @@ import set from "lodash/set"; import { makeObservable, observable, runInAction, action } from "mobx"; import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } from "@plane/types"; +// helpers +import { EInboxIssueStatus } from "@/helpers/inbox.helper"; // services import { InboxIssueService } from "@/services/inbox"; // root store @@ -26,7 +28,7 @@ export class InboxIssueStore implements IInboxIssueStore { // observables isLoading: boolean = false; id: string; - status: TInboxIssueStatus = -2; + status: TInboxIssueStatus = EInboxIssueStatus.PENDING; issue: Partial = {}; snoozed_till: Date | undefined; duplicate_to: string | undefined; @@ -82,7 +84,8 @@ export class InboxIssueStore implements IInboxIssueStore { }; updateInboxIssueDuplicateTo = async (issueId: string) => { - const inboxStatus = 2; + const inboxStatus = EInboxIssueStatus.DUPLICATE; + const previousData: Partial = { status: this.status, duplicate_to: this.duplicate_to, @@ -108,7 +111,8 @@ export class InboxIssueStore implements IInboxIssueStore { }; updateInboxIssueSnoozeTill = async (date: Date) => { - const inboxStatus = 0; + const inboxStatus = EInboxIssueStatus.SNOOZED; + const previousData: Partial = { status: this.status, snoozed_till: this.snoozed_till, diff --git a/web/store/inbox/project-inbox.store.ts b/web/store/inbox/project-inbox.store.ts index 3ac10acd8..e9c55489a 100644 --- a/web/store/inbox/project-inbox.store.ts +++ b/web/store/inbox/project-inbox.store.ts @@ -1,5 +1,7 @@ import isEmpty from "lodash/isEmpty"; import omit from "lodash/omit"; +import orderBy from "lodash/orderBy"; +import reverse from "lodash/reverse"; import set from "lodash/set"; import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; @@ -13,19 +15,26 @@ import { TInboxIssueSortingOrderByQueryParam, } from "@plane/types"; // helpers -import { EPastDurationFilters, getCustomDates } from "@/helpers/inbox.helper"; +import { EInboxIssueCurrentTab, EInboxIssueStatus, EPastDurationFilters, getCustomDates } from "@/helpers/inbox.helper"; // services import { InboxIssueService } from "@/services/inbox"; // root store import { IInboxIssueStore, InboxIssueStore } from "@/store/inbox/inbox-issue.store"; import { RootStore } from "@/store/root.store"; -type TLoader = "init-loading" | "filter-loading" | "pagination-loading" | "issue-loading" | undefined; +type TLoader = + | "init-loading" + | "mutation-loading" + | "filter-loading" + | "pagination-loading" + | "issue-loading" + | undefined; export interface IProjectInboxStore { currentTab: TInboxIssueCurrentTab; isLoading: TLoader; error: { message: string; status: "init-error" | "pagination-error" } | undefined; + currentInboxProjectId: string; inboxFilters: Partial; inboxSorting: Partial; inboxIssuePaginationInfo: TInboxIssuePaginationInfo | undefined; @@ -35,6 +44,7 @@ export interface IProjectInboxStore { inboxIssuesArray: IInboxIssueStore[]; // helper actions getIssueInboxByIssueId: (issueId: string) => IInboxIssueStore | undefined; + inboxIssueSorting: (issues: IInboxIssueStore[]) => IInboxIssueStore[]; inboxIssueQueryParams: ( inboxFilters: Partial, inboxSorting: Partial, @@ -60,11 +70,12 @@ export class ProjectInboxStore implements IProjectInboxStore { // constants PER_PAGE_COUNT = 10; // observables - currentTab: TInboxIssueCurrentTab = "open"; - isLoading: TLoader = undefined; + currentTab: TInboxIssueCurrentTab = EInboxIssueCurrentTab.OPEN; + isLoading: TLoader = "init-loading"; error: { message: string; status: "init-error" | "pagination-error" } | undefined = undefined; + currentInboxProjectId: string = ""; inboxFilters: Partial = { - status: [-2], + status: [EInboxIssueStatus.PENDING], }; inboxSorting: Partial = { order_by: "issue__created_at", @@ -79,6 +90,8 @@ export class ProjectInboxStore implements IProjectInboxStore { makeObservable(this, { currentTab: observable.ref, isLoading: observable.ref, + error: observable, + currentInboxProjectId: observable.ref, inboxFilters: observable, inboxSorting: observable, inboxIssuePaginationInfo: observable, @@ -111,13 +124,40 @@ export class ProjectInboxStore implements IProjectInboxStore { } get inboxIssuesArray() { - return Object.values(this.inboxIssues || {}).filter((inbox) => - (this.currentTab === "open" ? [-2, 0] : [-1, 1, 2]).includes(inbox.status) + return this.inboxIssueSorting( + Object.values(this.inboxIssues || {}).filter((inbox) => + (this.currentTab === EInboxIssueCurrentTab.OPEN + ? [EInboxIssueStatus.PENDING, EInboxIssueStatus.SNOOZED] + : [EInboxIssueStatus.ACCEPTED, EInboxIssueStatus.DECLINED, EInboxIssueStatus.DUPLICATE] + ).includes(inbox.status) + ) ); } getIssueInboxByIssueId = computedFn((issueId: string) => this.inboxIssues?.[issueId] || undefined); + // helpers + inboxIssueSorting = (issues: IInboxIssueStore[]) => { + console.log("issues", issues); + let inboxIssues: IInboxIssueStore[] = []; + if (this.inboxSorting?.order_by && this.inboxSorting?.sort_by) { + switch (this.inboxSorting.order_by) { + case "issue__created_at": + if (this.inboxSorting.sort_by === "desc") inboxIssues = orderBy(issues, ["issue", "created_at"]); + else inboxIssues = reverse(orderBy(issues, ["issue", "created_at"])); + case "issue__updated_at": + if (this.inboxSorting.sort_by === "desc") inboxIssues = orderBy(issues, ["issue", "updated_at"]); + else inboxIssues = reverse(orderBy(issues, ["issue", "updated_at"])); + case "issue__sequence_id": + if (this.inboxSorting.sort_by === "desc") inboxIssues = orderBy(issues, ["issue", "sequence_id"]); + else inboxIssues = reverse(orderBy(issues, ["issue", "sequence_id"])); + default: + inboxIssues = orderBy(issues, ["issue", "created_at"]); + } + } + return inboxIssues; + }; + inboxIssueQueryParams = ( inboxFilters: Partial, inboxSorting: Partial, @@ -177,6 +217,8 @@ export class ProjectInboxStore implements IProjectInboxStore { set(this, "inboxFilters", undefined); set(this, ["inboxSorting", "order_by"], "issue__created_at"); set(this, ["inboxSorting", "sort_by"], "desc"); + set(this, ["inboxIssues"], {}); + set(this, ["inboxIssuePaginationInfo"], undefined); if (tab === "closed") set(this, ["inboxFilters", "status"], [-1, 1, 2]); else set(this, ["inboxFilters", "status"], [-2]); const { workspaceSlug, projectId } = this.store.app.router; @@ -185,12 +227,16 @@ export class ProjectInboxStore implements IProjectInboxStore { handleInboxIssueFilters = (key: T, value: TInboxIssueFilter[T]) => { set(this.inboxFilters, key, value); + set(this, ["inboxIssues"], {}); + set(this, ["inboxIssuePaginationInfo"], undefined); const { workspaceSlug, projectId } = this.store.app.router; if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading"); }; handleInboxIssueSorting = (key: T, value: TInboxIssueSorting[T]) => { set(this.inboxSorting, key, value); + set(this, ["inboxIssues"], {}); + set(this, ["inboxIssuePaginationInfo"], undefined); const { workspaceSlug, projectId } = this.store.app.router; if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading"); }; @@ -202,10 +248,14 @@ export class ProjectInboxStore implements IProjectInboxStore { */ fetchInboxIssues = async (workspaceSlug: string, projectId: string, loadingType: TLoader = undefined) => { try { + if (this.currentInboxProjectId != projectId) { + set(this, ["currentInboxProjectId"], projectId); + set(this, ["inboxIssues"], {}); + set(this, ["inboxIssuePaginationInfo"], undefined); + } + if (Object.keys(this.inboxIssues).length === 0) this.isLoading = "init-loading"; + else this.isLoading = "mutation-loading"; if (loadingType) this.isLoading = loadingType; - else if (Object.keys(this.inboxIssues).length === 0) this.isLoading = "init-loading"; - set(this, ["inboxIssues"], {}); - set(this, ["inboxIssuePaginationInfo"], undefined); const queryParams = this.inboxIssueQueryParams( this.inboxFilters, @@ -247,9 +297,10 @@ export class ProjectInboxStore implements IProjectInboxStore { fetchInboxPaginationIssues = async (workspaceSlug: string, projectId: string) => { try { if ( - !this.inboxIssuePaginationInfo?.total_results || - (this.inboxIssuePaginationInfo?.total_results && - this.inboxIssuesArray.length < this.inboxIssuePaginationInfo?.total_results) + this.inboxIssuePaginationInfo && + (!this.inboxIssuePaginationInfo?.total_results || + (this.inboxIssuePaginationInfo?.total_results && + this.inboxIssuesArray.length < this.inboxIssuePaginationInfo?.total_results)) ) { this.isLoading = "pagination-loading"; @@ -299,15 +350,15 @@ export class ProjectInboxStore implements IProjectInboxStore { const issueId = inboxIssue?.issue?.id || undefined; if (inboxIssue && issueId) { + runInAction(() => { + set(this.inboxIssues, [issueId], new InboxIssueStore(workspaceSlug, projectId, inboxIssue, this.store)); + }); // fetching reactions await this.store.issue.issueDetail.fetchReactions(workspaceSlug, projectId, issueId); // fetching activity await this.store.issue.issueDetail.fetchActivities(workspaceSlug, projectId, issueId); // fetching comments await this.store.issue.issueDetail.fetchComments(workspaceSlug, projectId, issueId); - runInAction(() => { - set(this.inboxIssues, [issueId], new InboxIssueStore(workspaceSlug, projectId, inboxIssue, this.store)); - }); this.isLoading = undefined; } } catch {